Skip to content

Commit

Permalink
GH-724 Fix copy-n-paste between two orchestrations
Browse files Browse the repository at this point in the history
  • Loading branch information
Naros committed Oct 5, 2024
1 parent 95d418e commit 92d35be
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 19 deletions.
44 changes: 44 additions & 0 deletions src/common/method_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "dictionary_utils.h"
#include "property_utils.h"
#include "script/script_server.h"
#include "variant_operators.h"
#include "variant_utils.h"

#include <godot_cpp/core/class_db.hpp>

Expand Down Expand Up @@ -117,4 +119,46 @@ namespace MethodUtils
{
return p_method.arguments.size() - p_method.default_arguments.size();
}

bool has_same_signature(const MethodInfo& p_method_a, const MethodInfo& p_method_b)
{
if (p_method_a.arguments.size() != p_method_b.arguments.size())
return false;

if (p_method_a.default_arguments.size() != p_method_b.default_arguments.size())
return false;

if (p_method_a.flags != p_method_b.flags)
return false;

if (p_method_a.name != p_method_b.name)
return false;

if (!PropertyUtils::are_equal(p_method_a.return_val, p_method_b.return_val))
return false;

for (int i = 0; i < p_method_a.arguments.size(); i++)
{
const PropertyInfo& a = p_method_a.arguments[i];
const PropertyInfo& b = p_method_b.arguments[i];

// PropertyUtils::are_equal does not compare names
// But here we want to compare names
if (a.name != b.name)
return false;

if (!PropertyUtils::are_equal(a, b))
return false;
}

for (int i = 0; i < p_method_b.default_arguments.size(); i++)
{
const Variant& a = p_method_a.default_arguments[i];
const Variant& b = p_method_b.default_arguments[i];
if (!VariantUtils::evaluate(Variant::OP_EQUAL, a, b))
return false;
}

return true;
}
}
6 changes: 6 additions & 0 deletions src/common/method_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ namespace MethodUtils
/// @param p_method the method
/// @return the number of arguments that have no default values
size_t get_argument_count_without_defaults(const MethodInfo& p_method);

//// Checks whether two <code>MethodInfo</code> structures have the same structures
/// @param p_method_a the left method structure
/// @param p_method_b the right method structure
/// @return true if the method structures are the same, false otherwise
bool has_same_signature(const MethodInfo& p_method_a, const MethodInfo& p_method_b);
}

#endif // ORCHESTRATOR_METHOD_UTILS_H
168 changes: 152 additions & 16 deletions src/editor/graph/graph_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
#include "common/callable_lambda.h"
#include "common/dictionary_utils.h"
#include "common/logger.h"
#include "common/method_utils.h"
#include "common/name_utils.h"
#include "common/property_utils.h"
#include "common/scene_utils.h"
#include "common/settings.h"
#include "common/string_utils.h"
Expand Down Expand Up @@ -2012,26 +2014,76 @@ void OrchestratorGraphEdit::_on_copy_nodes_request()
{
_clipboard->reset();

for_each_graph_node([&](OrchestratorGraphNode* node) {
if (node->is_selected())
const Vector<Ref<OScriptNode>> selected = get_selected_script_nodes();
if (selected.is_empty())
{
_notify("No nodes selected, nothing copied to clipboard.", "Clipboard error");
return;
}

// Check if any selected nodes cannot be copied, showing message if not.
for (const Ref<OScriptNode>& node : selected)
{
if (!node->can_duplicate())
{
Ref<OScriptNode> script_node = node->get_script_node();
_notify(vformat("Cannot duplicate node '%s' (%d).", node->get_node_title(), node->get_id()), "Clipboard error");
return;
}
}

if (!script_node->can_duplicate())
// Local cache of copied objects
// Prevents creating multiple instances on paste of the same function, variable, or signal
RBSet<Ref<OScriptFunction>> functions_cache;
RBSet<Ref<OScriptVariable>> variables_cache;
RBSet<Ref<OScriptSignal>> signals_cache;

// Perform copy to clipboard
for (const Ref<OScriptNode>& node : selected)
{
const Ref<OScriptNodeCallScriptFunction> call_function_node = node;
if (call_function_node.is_valid())
{
const Ref<OScriptFunction> function = call_function_node->get_function();
if (!functions_cache.has(function))
{
WARN_PRINT_ONCE_ED("There are some nodes that cannot be copied, they were not placed on the clipboard.");
return;
functions_cache.insert(function);
_clipboard->functions.insert(function->duplicate());
}
}

const int id = script_node->get_id();
_clipboard->positions[id] = script_node->get_position();
_clipboard->nodes[id] = _script_graph->copy_node(id, true);
const Ref<OScriptNodeVariable> variable_node = node;
if (variable_node.is_valid())
{
const Ref<OScriptVariable> variable = variable_node->get_variable();
if (!variables_cache.has(variable))
{
variables_cache.insert(variable);
_clipboard->variables.insert(variable->duplicate());
}
}
});

const Ref<OScriptNodeEmitSignal> emit_signal_node = node;
if (emit_signal_node.is_valid())
{
const Ref<OScriptSignal> signal = emit_signal_node->get_signal();
if (!signals_cache.has(signal))
{
signals_cache.insert(signal);
_clipboard->signals.insert(signal->duplicate());
}
}

const int node_id = node->get_id();
_clipboard->positions[node_id] = node->get_position();
_clipboard->nodes[node_id] = _script_graph->copy_node(node_id, true);
}

// Connections between pasted nodes, copy connections
for (const OScriptConnection& E : get_orchestration()->get_connections())
{
if (_clipboard->nodes.has(E.from_node) && _clipboard->nodes.has(E.to_node))
_clipboard->connections.insert(E);
}
}

void OrchestratorGraphEdit::_on_cut_nodes_request()
Expand Down Expand Up @@ -2101,15 +2153,97 @@ void OrchestratorGraphEdit::_on_paste_nodes_request()
if (_clipboard->is_empty())
return;

Vector<int> duplications;
RBSet<Vector2> existing_positions;
for (const KeyValue<int, Vector2>& E : _clipboard->positions)
Orchestration* orchestration = _script_graph->get_orchestration();

// Iterate copied function declarations and assert if paste is invalid
// Functions are unique in that we do not clone their nodes or structure, so the function must exist
// in the target orchestration with the same signature for the paste to be valid.
for (const Ref<OScriptFunction>& E : _clipboard->functions)
{
existing_positions.insert(E.value.snapped(Vector2(2, 2)));
duplications.push_back(E.key);
if (!orchestration->has_function(E->get_function_name()))
{
_notify(vformat("Function '%s' does not exist in this orchestration.", E->get_function_name()), "Clipboard error");
return;
}

// Exists, verify if its identical
const Ref<OScriptFunction> other = orchestration->find_function(E->get_function_name());
if (!MethodUtils::has_same_signature(E->get_method_info(), other->get_method_info()))
{
_notify(vformat("Function '%s' exists with a different definition.", E->get_function_name()), "Clipboard error");
return;
}
}

// Iterate copied variable declarations and assert if paste is invalid
Vector<Ref<OScriptVariable>> variables_to_create;
for (const Ref<OScriptVariable>& E : _clipboard->variables)
{
if (!orchestration->has_variable(E->get_variable_name()))
{
variables_to_create.push_back(E);
continue;
}

// Exists, verify if its identical
const Ref<OScriptVariable> other = orchestration->get_variable(E->get_variable_name());
if (!PropertyUtils::are_equal(E->get_info(), other->get_info()))
{
_notify(vformat("Variable '%s' exists with a different definition.", E->get_variable_name()), "Clipboard error");
return;
}
}

// Iterate copied signal declarations and assert if paste is invalid
Vector<Ref<OScriptSignal>> signals_to_create;
for (const Ref<OScriptSignal>& E : _clipboard->signals)
{
if (!orchestration->has_custom_signal(E->get_signal_name()))
{
signals_to_create.push_back(E);
continue;
}

// When signal exists, verify whether the signal has the same signature and fail if it doesn't.
const Ref<OScriptSignal> other = orchestration->get_custom_signal(E->get_signal_name());
if (!MethodUtils::has_same_signature(E->get_method_info(), other->get_method_info()))
{
_notify(vformat("Signal '%s' exists with a different definition.", E->get_signal_name()), "Clipboard error");
return;
}
}

for (const KeyValue<int, Ref<OScriptNode>>& E : _clipboard->nodes)
{
const Ref<OScriptNodeCallScriptFunction> call_script_function_node = E.value;
if (call_script_function_node.is_valid())
{
const StringName function_name = call_script_function_node->get_function()->get_function_name();
const Ref<OScriptFunction> this_function = get_orchestration()->find_function(function_name);
if (this_function.is_valid())
{
// Since source OScriptFunction matches this OScriptFunction declaration, copy the
// GUID from this orchestrations script function and set it on the node
call_script_function_node->set("guid", this_function->get_guid().to_string());
}
}
}

// Iterate variables to be created
for (const Ref<OScriptVariable>& E : variables_to_create)
{
Ref<OScriptVariable> new_variable = orchestration->create_variable(E->get_variable_name());
new_variable->copy_persistent_state(E);
}

Vector2 mouse_up_position = get_screen_position() + get_local_mouse_position();
// Iterate signals to be created
for (const Ref<OScriptSignal>& E : signals_to_create)
{
Ref<OScriptSignal> new_signal = orchestration->create_custom_signal(E->get_signal_name());
new_signal->copy_persistent_state(E);
}

const Vector2 mouse_up_position = get_screen_position() + get_local_mouse_position();
Vector2 position_offset = (get_scroll_offset() + (mouse_up_position - get_screen_position())) / get_zoom();
if (is_snapping_enabled())
{
Expand Down Expand Up @@ -2142,6 +2276,8 @@ void OrchestratorGraphEdit::_on_paste_nodes_request()
if (OrchestratorGraphNode* node = _get_node_by_id(selected_id))
node->set_selected(true);
}

emit_signal("nodes_changed");
}

void OrchestratorGraphEdit::_on_script_changed()
Expand Down
11 changes: 10 additions & 1 deletion src/editor/graph/graph_edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@

#include "actions/action_menu.h"
#include "common/version.h"
#include "graph_node.h"
#include "editor/graph/graph_node.h"
#include "script/function.h"
#include "script/signals.h"
#include "script/variable.h"

#include <functional>

Expand Down Expand Up @@ -98,6 +101,9 @@ class OrchestratorGraphEdit : public GraphEdit
HashMap<int, Ref<OScriptNode>> nodes;
HashMap<int, Vector2> positions;
RBSet<OScriptConnection> connections;
RBSet<Ref<OScriptFunction>> functions;
RBSet<Ref<OScriptVariable>> variables;
RBSet<Ref<OScriptSignal>> signals;

/// Returns whether the clipboard is empty
bool is_empty() const
Expand All @@ -111,6 +117,9 @@ class OrchestratorGraphEdit : public GraphEdit
nodes.clear();
positions.clear();
connections.clear();
signals.clear();
variables.clear();
functions.clear();
}
};

Expand Down
3 changes: 2 additions & 1 deletion src/script/nodes/signals/emit_signal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//
#include "emit_signal.h"

#include "common/macros.h"
#include "common/property_utils.h"
#include "common/variant_utils.h"

Expand Down Expand Up @@ -182,7 +183,7 @@ void OScriptNodeEmitSignal::post_placed_new_node()
super::post_placed_new_node();

if (_signal.is_valid() && _is_in_editor())
_signal->connect("changed", callable_mp(this, &OScriptNodeEmitSignal::_on_signal_changed));
OCONNECT(_signal, "changed", callable_mp(this, &OScriptNodeEmitSignal::_on_signal_changed));
}

void OScriptNodeEmitSignal::allocate_default_pins()
Expand Down
2 changes: 1 addition & 1 deletion src/script/nodes/variables/variable_set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ String OScriptNodeVariableSet::get_tooltip_text() const

String OScriptNodeVariableSet::get_node_title() const
{
return vformat("Set %s", _variable->get_variable_name());
return vformat("Set %s", _variable_name);
}

void OScriptNodeVariableSet::reallocate_pins_during_reconstruction(const Vector<Ref<OScriptNodePin>>& p_old_pins)
Expand Down
7 changes: 7 additions & 0 deletions src/script/signals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,10 @@ size_t OScriptSignal::get_argument_count() const
return _method.arguments.size();
}

void OScriptSignal::copy_persistent_state(const Ref<OScriptSignal>& p_other)
{
_method = p_other->_method;

notify_property_list_changed();
emit_changed();
}
4 changes: 4 additions & 0 deletions src/script/signals.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ class OScriptSignal : public Resource
/// Get the number of function arguments
/// @return the number of arguments
size_t get_argument_count() const;

/// Copy the persistent state from one signal to this signal
/// @param p_other the other signal to source state from
void copy_persistent_state(const Ref<OScriptSignal>& p_other);
};

#endif // ORCHESTRATOR_SCRIPT_SIGNALS_H
18 changes: 18 additions & 0 deletions src/script/variable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,21 @@ void OScriptVariable::set_constant(bool p_constant)
emit_changed();
}
}

void OScriptVariable::copy_persistent_state(const Ref<OScriptVariable>& p_other)
{
_category = p_other->_category;
_classification = p_other->_classification;
_constant = p_other->_constant;
_default_value = p_other->_default_value;
_description = p_other->_description;
_exportable = p_other->_exportable;
_exported = p_other->_exported;
_type_category = p_other->_type_category;
_type_subcategory = p_other->_type_subcategory;
_value_list = p_other->_value_list;
_info = p_other->_info;

notify_property_list_changed();
emit_changed();
}
4 changes: 4 additions & 0 deletions src/script/variable.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ class OScriptVariable : public Resource
/// Sets whether the variable is a constant
/// @param p_constant whether the variable is constant
void set_constant(bool p_constant);

/// Copy the persistent state from one variable to this variable
/// @param p_other the other variable to source state from
void copy_persistent_state(const Ref<OScriptVariable>& p_other);
};

#endif // ORCHESTRATOR_SCRIPT_VARIABLE_H

0 comments on commit 92d35be

Please sign in to comment.