diff --git a/src/common/method_utils.cpp b/src/common/method_utils.cpp index 3784ef87..637b0595 100644 --- a/src/common/method_utils.cpp +++ b/src/common/method_utils.cpp @@ -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 @@ -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; + } } \ No newline at end of file diff --git a/src/common/method_utils.h b/src/common/method_utils.h index 69805d7c..4236144c 100644 --- a/src/common/method_utils.h +++ b/src/common/method_utils.h @@ -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 MethodInfo 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 \ No newline at end of file diff --git a/src/editor/graph/graph_edit.cpp b/src/editor/graph/graph_edit.cpp index 03645c87..13501925 100644 --- a/src/editor/graph/graph_edit.cpp +++ b/src/editor/graph/graph_edit.cpp @@ -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" @@ -2012,26 +2014,76 @@ void OrchestratorGraphEdit::_on_copy_nodes_request() { _clipboard->reset(); - for_each_graph_node([&](OrchestratorGraphNode* node) { - if (node->is_selected()) + const Vector> 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& node : selected) + { + if (!node->can_duplicate()) { - Ref 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> functions_cache; + RBSet> variables_cache; + RBSet> signals_cache; + + // Perform copy to clipboard + for (const Ref& node : selected) + { + const Ref call_function_node = node; + if (call_function_node.is_valid()) + { + const Ref 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 variable_node = node; + if (variable_node.is_valid()) + { + const Ref variable = variable_node->get_variable(); + if (!variables_cache.has(variable)) + { + variables_cache.insert(variable); + _clipboard->variables.insert(variable->duplicate()); + } } - }); + const Ref emit_signal_node = node; + if (emit_signal_node.is_valid()) + { + const Ref 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() @@ -2101,15 +2153,97 @@ void OrchestratorGraphEdit::_on_paste_nodes_request() if (_clipboard->is_empty()) return; - Vector duplications; - RBSet existing_positions; - for (const KeyValue& 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& 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 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> variables_to_create; + for (const Ref& 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 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> signals_to_create; + for (const Ref& 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 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>& E : _clipboard->nodes) + { + const Ref 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 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& E : variables_to_create) + { + Ref 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& E : signals_to_create) + { + Ref 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()) { @@ -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() diff --git a/src/editor/graph/graph_edit.h b/src/editor/graph/graph_edit.h index 9a323892..8475b921 100644 --- a/src/editor/graph/graph_edit.h +++ b/src/editor/graph/graph_edit.h @@ -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 @@ -98,6 +101,9 @@ class OrchestratorGraphEdit : public GraphEdit HashMap> nodes; HashMap positions; RBSet connections; + RBSet> functions; + RBSet> variables; + RBSet> signals; /// Returns whether the clipboard is empty bool is_empty() const @@ -111,6 +117,9 @@ class OrchestratorGraphEdit : public GraphEdit nodes.clear(); positions.clear(); connections.clear(); + signals.clear(); + variables.clear(); + functions.clear(); } }; diff --git a/src/script/nodes/signals/emit_signal.cpp b/src/script/nodes/signals/emit_signal.cpp index fd98150a..d8c21323 100644 --- a/src/script/nodes/signals/emit_signal.cpp +++ b/src/script/nodes/signals/emit_signal.cpp @@ -16,6 +16,7 @@ // #include "emit_signal.h" +#include "common/macros.h" #include "common/property_utils.h" #include "common/variant_utils.h" @@ -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() diff --git a/src/script/nodes/variables/variable_set.cpp b/src/script/nodes/variables/variable_set.cpp index fb5b0e6d..272ae856 100644 --- a/src/script/nodes/variables/variable_set.cpp +++ b/src/script/nodes/variables/variable_set.cpp @@ -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>& p_old_pins) diff --git a/src/script/signals.cpp b/src/script/signals.cpp index b4ca21d9..b9a60dac 100644 --- a/src/script/signals.cpp +++ b/src/script/signals.cpp @@ -119,3 +119,10 @@ size_t OScriptSignal::get_argument_count() const return _method.arguments.size(); } +void OScriptSignal::copy_persistent_state(const Ref& p_other) +{ + _method = p_other->_method; + + notify_property_list_changed(); + emit_changed(); +} diff --git a/src/script/signals.h b/src/script/signals.h index 04cfa6f1..4aace08b 100644 --- a/src/script/signals.h +++ b/src/script/signals.h @@ -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& p_other); }; #endif // ORCHESTRATOR_SCRIPT_SIGNALS_H diff --git a/src/script/variable.cpp b/src/script/variable.cpp index e416482a..61a2e26a 100644 --- a/src/script/variable.cpp +++ b/src/script/variable.cpp @@ -424,3 +424,21 @@ void OScriptVariable::set_constant(bool p_constant) emit_changed(); } } + +void OScriptVariable::copy_persistent_state(const Ref& 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(); +} diff --git a/src/script/variable.h b/src/script/variable.h index 54e2649e..e84489c8 100644 --- a/src/script/variable.h +++ b/src/script/variable.h @@ -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& p_other); }; #endif // ORCHESTRATOR_SCRIPT_VARIABLE_H