Merge pull request #114328 from kleonc/node_duplicating_signal_source_node_fix
`CONNECT_APPEND_SOURCE_OBJECT` on signal emission
This commit is contained in:
@@ -1340,6 +1340,9 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
|
|||||||
|
|
||||||
Error err = OK;
|
Error err = OK;
|
||||||
|
|
||||||
|
Vector<const Variant *> append_source_mem;
|
||||||
|
Variant source = this;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < slot_count; ++i) {
|
for (uint32_t i = 0; i < slot_count; ++i) {
|
||||||
const Callable &callable = slot_callables[i];
|
const Callable &callable = slot_callables[i];
|
||||||
const uint32_t &flags = slot_flags[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;
|
const Variant **args = p_args;
|
||||||
int argc = p_argcount;
|
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) {
|
if (flags & CONNECT_DEFERRED) {
|
||||||
MessageQueue::get_singleton()->push_callablep(callable, args, argc, true);
|
MessageQueue::get_singleton()->push_callablep(callable, args, argc, true);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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.
|
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.
|
||||||
</constant>
|
</constant>
|
||||||
<constant name="CONNECT_APPEND_SOURCE_OBJECT" value="16" enum="ConnectFlags">
|
<constant name="CONNECT_APPEND_SOURCE_OBJECT" value="16" enum="ConnectFlags">
|
||||||
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]
|
||||||
</constant>
|
</constant>
|
||||||
</constants>
|
</constants>
|
||||||
</class>
|
</class>
|
||||||
|
|||||||
@@ -1668,12 +1668,13 @@ void ConnectionsDock::update_tree() {
|
|||||||
if (cd.flags & CONNECT_ONE_SHOT) {
|
if (cd.flags & CONNECT_ONE_SHOT) {
|
||||||
path += " (one-shot)";
|
path += " (one-shot)";
|
||||||
}
|
}
|
||||||
if (cd.flags & CONNECT_APPEND_SOURCE_OBJECT) {
|
|
||||||
path += " (source)";
|
|
||||||
}
|
|
||||||
if (cd.unbinds > 0) {
|
if (cd.unbinds > 0) {
|
||||||
path += " unbinds(" + itos(cd.unbinds) + ")";
|
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()) {
|
if (!cd.binds.is_empty()) {
|
||||||
path += " binds(";
|
path += " binds(";
|
||||||
for (int i = 0; i < cd.binds.size(); i++) {
|
for (int i = 0; i < cd.binds.size(); i++) {
|
||||||
|
|||||||
@@ -84,11 +84,6 @@ public:
|
|||||||
unbinds = ccu->get_unbinds();
|
unbinds = ccu->get_unbinds();
|
||||||
base_callable = ccu->get_callable();
|
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 {
|
} else {
|
||||||
base_callable = p_connection.callable;
|
base_callable = p_connection.callable;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -664,9 +664,6 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
|
|||||||
Callable callable(cto, snames[c.method]);
|
Callable callable(cto, snames[c.method]);
|
||||||
|
|
||||||
Array binds;
|
Array binds;
|
||||||
if (c.flags & CONNECT_APPEND_SOURCE_OBJECT) {
|
|
||||||
binds.push_back(cfrom);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int bind : c.binds) {
|
for (int bind : c.binds) {
|
||||||
binds.push_back(props[bind]);
|
binds.push_back(props[bind]);
|
||||||
@@ -1193,11 +1190,6 @@ Error SceneState::_parse_connections(Node *p_owner, Node *p_node, HashMap<String
|
|||||||
unbinds = ccu->get_unbinds();
|
unbinds = ccu->get_unbinds();
|
||||||
base_callable = ccu->get_callable();
|
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 {
|
} else {
|
||||||
base_callable = c.callable;
|
base_callable = c.callable;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -316,6 +316,29 @@ TEST_CASE("[Object] Absent name getter") {
|
|||||||
"The returned value should equal nil variant.");
|
"The returned value should equal nil variant.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SignalReceiver : public Object {
|
||||||
|
GDCLASS(SignalReceiver, Object);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Vector<Variant> received_args;
|
||||||
|
|
||||||
|
void callback0() {
|
||||||
|
received_args = Vector<Variant>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void callback1(Variant p_arg1) {
|
||||||
|
received_args = Vector<Variant>{ p_arg1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
void callback2(Variant p_arg1, Variant p_arg2) {
|
||||||
|
received_args = Vector<Variant>{ p_arg1, p_arg2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
void callback3(Variant p_arg1, Variant p_arg2, Variant p_arg3) {
|
||||||
|
received_args = Vector<Variant>{ p_arg1, p_arg2, p_arg3 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
TEST_CASE("[Object] Signals") {
|
TEST_CASE("[Object] Signals") {
|
||||||
Object object;
|
Object object;
|
||||||
|
|
||||||
@@ -455,6 +478,51 @@ TEST_CASE("[Object] Signals") {
|
|||||||
object.get_all_signal_connections(&signal_connections);
|
object.get_all_signal_connections(&signal_connections);
|
||||||
CHECK(signal_connections.size() == 0);
|
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<Variant>{ &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<Variant>{ "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<Variant>{ &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<Variant>{ "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<Variant>{ &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<Variant>{ &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<Variant>{ &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<Variant>{ "emit_arg", &object });
|
||||||
|
object.disconnect("my_custom_signal", callable_mp(&target, &SignalReceiver::callback2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotificationObjectSuperclass : public Object {
|
class NotificationObjectSuperclass : public Object {
|
||||||
|
|||||||
Reference in New Issue
Block a user