Repository: godotengine/godot
File: core/object/object.cpp (C++)

Code before:
	OBJ_DEBUG_LOCK

	// If this is a ref-counted object, prevent it from being destroyed during signal
	// emission, which is needed in certain edge cases; e.g., GH-73889 and GH-109471.
	// Moreover, since signals can be emitted from constructors (classic example being
	// notify_property_list_changed), we must be careful not to do the ref init ourselves,
	// which would lead to the object being destroyed at the end of this function.
	bool pending_unref = Object::cast_to<RefCounted>(this) ? ((RefCounted *)this)->reference() : false;

	Error err = OK;

	// Temporary used for binding source with CONNECT_APPEND_SOURCE_OBJECT.
	// Fine when combined with CONNECT_DEFERRED as the Callable is copied when enqueued.
	Callable callable_with_source_object_binded;

	for (uint32_t i = 0; i < slot_count; ++i) {
		const Callable *callable_ptr = &slot_callables[i];
		const uint32_t &flags = slot_flags[i];

		if (!callable_ptr->is_valid()) {
			// Target might have been deleted during signal callback, this is expected and OK.
			continue;
		}

		if (flags & CONNECT_APPEND_SOURCE_OBJECT) {
			callable_with_source_object_binded = callable_ptr->bind(this);
			callable_ptr = &callable_with_source_object_binded;
		}
		const Callable &callable = *callable_ptr;

		const Variant **args = p_args;
		int argc = p_argcount;

		if (flags & CONNECT_DEFERRED) {
			MessageQueue::get_singleton()->push_callablep(callable, args, argc, true);
		} else {
			Callable::CallError ce;
			_emitting = true;
			Variant ret;
			callable.callp(args, argc, ret, ce);
			_emitting = false;

			if (ce.error != Callable::CallError::CALL_OK) {
				Object *target = callable.get_object();
#ifdef DEBUG_ENABLED
				if (target && flags & CONNECT_PERSIST && Engine::get_singleton()->is_editor_hint()) {
					Ref<Script> other_scr = target->get_script();
					if (other_scr.is_valid() && !other_scr->is_tool()) {
						// Trying to call not-tool method in editor, just ignore it.
						continue;
					}

Review comment (suggestion):
Using `bind()` again (like a Russian nesting doll) will cancel the `source`'s emission (not the original arguments) if unbinding is enabled (`unbind > 0`).

<img width="837" height="124" alt="1" src="https://github.com/user-attachments/assets/926858e2-f56c-4d7c-b88f-10699113ae0f" />





Code after:
	OBJ_DEBUG_LOCK

	// If this is a ref-counted object, prevent it from being destroyed during signal
	// emission, which is needed in certain edge cases; e.g., GH-73889 and GH-109471.
	// Moreover, since signals can be emitted from constructors (classic example being
	// notify_property_list_changed), we must be careful not to do the ref init ourselves,
	// which would lead to the object being destroyed at the end of this function.
	bool pending_unref = Object::cast_to<RefCounted>(this) ? ((RefCounted *)this)->reference() : false;

	Error err = OK;

	Vector<const Variant *> 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];

		if (!callable.is_valid()) {
			// Target might have been deleted during signal callback, this is expected and OK.
			continue;
		}

		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.
			}
		}