diff --git a/core/object/object.cpp b/core/object/object.cpp index 4729eecf87..c49962c7f0 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1340,6 +1340,9 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int Error err = OK; + Vector append_source_mem; + Variant source = this; + for (uint32_t i = 0; i < slot_count; ++i) { const Callable &callable = slot_callables[i]; const uint32_t &flags = slot_flags[i]; @@ -1352,6 +1355,31 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int const Variant **args = p_args; int argc = p_argcount; + if (flags & CONNECT_APPEND_SOURCE_OBJECT) { + // Source is being appended regardless of unbinds. + // Implemented by inserting before the first to-be-unbinded arg. + int source_index = p_argcount - callable.get_unbound_arguments_count(); + if (source_index >= 0) { + append_source_mem.resize(p_argcount + 1); + const Variant **args_mem = append_source_mem.ptrw(); + + for (int j = 0; j < source_index; j++) { + args_mem[j] = p_args[j]; + } + args_mem[source_index] = &source; + for (int j = source_index; j < p_argcount; j++) { + args_mem[j + 1] = p_args[j]; + } + + args = args_mem; + argc = p_argcount + 1; + } else { + // More args unbound than provided, call will fail. + // Since appended source is non-unbindable, the error + // about too many unbinds should be correct as is. + } + } + if (flags & CONNECT_DEFERRED) { MessageQueue::get_singleton()->push_callablep(callable, args, argc, true); } else { diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index f9fc0906cf..c50afa3910 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -1051,7 +1051,17 @@ Reference-counted connections can be assigned to the same [Callable] multiple times. Each disconnection decreases the internal counter. The signal fully disconnects only when the counter reaches 0. - The source object is automatically bound when a [PackedScene] is instantiated. If this flag bit is enabled, the source object will be appended right after the original arguments of the signal. + On signal emission, the source object is automatically appended after the original arguments of the signal, regardless of the connected [Callable]'s unbinds which affect only the original arguments of the signal (see [method Callable.unbind], [method Callable.get_unbound_arguments_count]). + [codeblock] + extends Object + + signal test_signal + + func test(): + print(self) # Prints e.g. <Object#35332818393> + test_signal.connect(prints.unbind(1), CONNECT_APPEND_SOURCE_OBJECT) + test_signal.emit("emit_arg_1", "emit_arg_2") # Prints emit_arg_1 <Object#35332818393> + [/codeblock] diff --git a/editor/scene/connections_dialog.cpp b/editor/scene/connections_dialog.cpp index 44a355c695..d0083bbe11 100644 --- a/editor/scene/connections_dialog.cpp +++ b/editor/scene/connections_dialog.cpp @@ -1668,12 +1668,13 @@ void ConnectionsDock::update_tree() { if (cd.flags & CONNECT_ONE_SHOT) { path += " (one-shot)"; } - if (cd.flags & CONNECT_APPEND_SOURCE_OBJECT) { - path += " (source)"; - } if (cd.unbinds > 0) { path += " unbinds(" + itos(cd.unbinds) + ")"; } + // CONNECT_APPEND_SOURCE_OBJECT is not affected by unbinds, list it between unbinds/binds to better indicate the final order. + if (cd.flags & CONNECT_APPEND_SOURCE_OBJECT) { + path += " (source)"; + } if (!cd.binds.is_empty()) { path += " binds("; for (int i = 0; i < cd.binds.size(); i++) { diff --git a/editor/scene/connections_dialog.h b/editor/scene/connections_dialog.h index 0523743ef3..bbab591edc 100644 --- a/editor/scene/connections_dialog.h +++ b/editor/scene/connections_dialog.h @@ -84,11 +84,6 @@ public: unbinds = ccu->get_unbinds(); base_callable = ccu->get_callable(); } - - // The source object may already be bound, ignore it to prevent display of the source object. - if ((flags & CONNECT_APPEND_SOURCE_OBJECT) && (source == binds[0])) { - binds.remove_at(0); - } } else { base_callable = p_connection.callable; } diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 039dd5eb2c..c25994a3d3 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -664,9 +664,6 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { Callable callable(cto, snames[c.method]); Array binds; - if (c.flags & CONNECT_APPEND_SOURCE_OBJECT) { - binds.push_back(cfrom); - } for (int bind : c.binds) { binds.push_back(props[bind]); @@ -1193,11 +1190,6 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, HashMapget_unbinds(); base_callable = ccu->get_callable(); } - - // The source object may already be bound, ignore it to avoid saving the source object. - if ((c.flags & CONNECT_APPEND_SOURCE_OBJECT) && (p_node == binds[0])) { - binds.remove_at(0); - } } else { base_callable = c.callable; } diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index 34941843e1..e683568a56 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -316,6 +316,29 @@ TEST_CASE("[Object] Absent name getter") { "The returned value should equal nil variant."); } +class SignalReceiver : public Object { + GDCLASS(SignalReceiver, Object); + +public: + Vector received_args; + + void callback0() { + received_args = Vector{}; + } + + void callback1(Variant p_arg1) { + received_args = Vector{ p_arg1 }; + } + + void callback2(Variant p_arg1, Variant p_arg2) { + received_args = Vector{ p_arg1, p_arg2 }; + } + + void callback3(Variant p_arg1, Variant p_arg2, Variant p_arg3) { + received_args = Vector{ p_arg1, p_arg2, p_arg3 }; + } +}; + TEST_CASE("[Object] Signals") { Object object; @@ -455,6 +478,51 @@ TEST_CASE("[Object] Signals") { object.get_all_signal_connections(&signal_connections); CHECK(signal_connections.size() == 0); } + + SUBCASE("Connecting with CONNECT_APPEND_SOURCE_OBJECT flag") { + SignalReceiver target; + + object.connect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback1), Object::CONNECT_APPEND_SOURCE_OBJECT); + object.emit_signal("my_custom_signal"); + CHECK_EQ(target.received_args, Vector{ &object }); + object.disconnect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback1)); + + object.connect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback2), Object::CONNECT_APPEND_SOURCE_OBJECT); + object.emit_signal("my_custom_signal", "emit_arg"); + CHECK_EQ(target.received_args, Vector{ "emit_arg", &object }); + object.disconnect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback2)); + + object.connect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback2).bind("bind_arg"), Object::CONNECT_APPEND_SOURCE_OBJECT); + object.emit_signal("my_custom_signal"); + CHECK_EQ(target.received_args, Vector{ &object, "bind_arg" }); + object.disconnect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback2)); + + object.connect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback3).bind("bind_arg"), Object::CONNECT_APPEND_SOURCE_OBJECT); + object.emit_signal("my_custom_signal", "emit_arg"); + CHECK_EQ(target.received_args, Vector{ "emit_arg", &object, "bind_arg" }); + object.disconnect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback3)); + + object.connect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback3).bind(&object), Object::CONNECT_APPEND_SOURCE_OBJECT); + object.emit_signal("my_custom_signal", &object); + CHECK_EQ(target.received_args, Vector{ &object, &object, &object }); + object.disconnect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback3)); + + // Source should be appended regardless of unbinding. + object.connect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback1).unbind(1), Object::CONNECT_APPEND_SOURCE_OBJECT); + object.emit_signal("my_custom_signal", "emit_arg"); + CHECK_EQ(target.received_args, Vector{ &object }); + object.disconnect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback1)); + + object.connect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback2).bind("bind_arg").unbind(1), Object::CONNECT_APPEND_SOURCE_OBJECT); + object.emit_signal("my_custom_signal", "emit_arg"); + CHECK_EQ(target.received_args, Vector{ &object, "bind_arg" }); + object.disconnect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback2)); + + object.connect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback2).unbind(1).bind("bind_arg"), Object::CONNECT_APPEND_SOURCE_OBJECT); + object.emit_signal("my_custom_signal", "emit_arg"); + CHECK_EQ(target.received_args, Vector{ "emit_arg", &object }); + object.disconnect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback2)); + } } class NotificationObjectSuperclass : public Object {