Need advice on disconnecting signals before freeing a node

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Charlie

Bugs are far harder to understand when they happen rarely. I ran into one of these when I had a node that was responding to a signal after queue_free(). So I learned to disconnect signals before queue_free(). But…

Should I ALWAYS disconnect ALL signals before freeing a node?

I’m thinking specifically about some UI panels that have quite long blocks of connect code to their own descendant nodes (buttons, etc.). These are signals among nodes that will be freed together. It’s not a big deal but adding 20 lines of disconnect seems like code clutter if it isn’t necessary. Is there some engine reason why I should disconnect all of these? Or should I do so anyway for the sake of best practice?

:bust_in_silhouette: Reply From: avencherus

They should be disconnected automatically.

What is probably happening in your case is that the signal is firing before the Node is finished being freed. queue_free() is deferred and usually happens a frame later or at least after everything has processed in the current frame.

This allows it to safely finish some things, but if you have a signal firing before the Node is gone, you’ll have some timing issues.

I’ve had a lot of timing issues with the core Node events, notifications, and signals. There are usually ways to account for them with extra care, but they’re not immediately obvious, and can take a lot of debugging.

That said, I’ve found that code to be too difficult to maintain in practice, and generally use the entry and exit points of Godot in very few things. I’ve resorted to my own methods for initialization and cleaning up nodes. When doing this I can quickly review when and what exactly happens.

With queue_free() I would have the game invoke a custom clean up method to have more control. For an enemy it would play all its death effects, then hide itself and do all the immediate work needed to get the Node in the right state. It may even involve delays or timers. Lastly, it would kick off something like queue_free() when it is truly being disregarded.

In many cases its nothing more than queue_free(), but I have at least the option to go back and expand that when issues like these get introduced if changes are made later.

You may also still have to be dealing with disconnecting a lot of signals. If this is the case, and you have a common set of signals you can usually loop through lists and disconnect things using the information found in get_signal_list() and get_signal_connection_list(). It’s generally a lot less code, and in many cases very re-usable.

Thanks!

The problem was (as you deduced!) due to a signal firing in the same frame after queue_free(). I wonder if I could have fixed my original bug with appropriate use of call_deferred() to delay the offending signal? I.e.,…

signaling_node.connect("signal", responding_node, "bug_function")
responding_node.queue_free()
signaling_node.call_deferred("emit_signal", "signal")

…is it guaranteed in such code that bug_function will never be called?

If not, then I’ll take your advice and make a general solution using signal lists.

Charlie | 2019-01-02 21:04

They should be disconnected automatically.

Is this mentioned in the documentation or elsewhere? I could not find a reference for it.

Senad | 2020-07-07 15:40

@senad It is at least as of the most recent 3.2 docs.

Object — Godot Engine (stable) documentation in English

If the target is destroyed in the game’s lifecycle, the connection
will be lost.

Also the source code shows the connections cleared when Objects are deconstructed.

https://github.com/godotengine/godot/blob/a4028b99ec92183889909cc11e61a29c256abea7/core/object.cpp#L2015-L2036

avencherus | 2020-07-08 21:15