godot-docs icon indicating copy to clipboard operation
godot-docs copied to clipboard

Document the exact order of all lifetime signals/callbacks/notifications

Open KoBeWi opened this issue 3 years ago • 6 comments

Your Godot version: 3.4.1

Issue description: From time to time, I keep seeing people confused about order of lifetime methods. Recently there was a bug report, where it turned out that the user tried to access an onready variable inside a callback from signal emitted in _ready() of a child node. It's convoluted, but the onready variable wasn't initialized in parent, because child nodes will call their _ready() first.

We do have some docs about order of some methods, but there isn't any comprehensive guide that lists all callbacks/notifications etc and order of variable initialization.

I made a small test project. For a single node the order is this:

  • variables are initialized
  • exported variables are initialized
  • _init() callback
  • NOTIFICATION_ENTER_TREE
  • _enter_tree() callback
  • tree_entered signal
  • NOTIFICATION_POST_ENTER_TREE
  • onready variables are initialized
  • _ready() callback
  • NOTIFICATION_READY
  • ready signal

It gets super-complicated when node has children. My test scene was like this: image Tested with this script, that prints about everything for when node is created and destroyed:

extends Node

func _init() -> void:
	prints(self, "_init() callback")
	
	connect("tree_entered", self, "on_tree_entered")
	connect("ready", self, "on_ready")
	connect("tree_exiting", self, "on_tree_exiting")
	connect("tree_exited", self, "on_tree_exited")

func _enter_tree() -> void:
	prints(name, "_enter_tree() callback")

func _ready() -> void:
	prints(name, "_ready() callback")
	
	if get_parent() == get_tree().root:
		queue_free()

func _exit_tree() -> void:
	prints(name, "_exit_tree() callback")

func _notification(what: int) -> void:
	match what:
		NOTIFICATION_ENTER_TREE:
			prints(name, "NOTIFICATION_ENTER_TREE")
		NOTIFICATION_EXIT_TREE:
			prints(name, "NOTIFICATION_EXIT_TREE")
		NOTIFICATION_READY:
			prints(name, "NOTIFICATION_READY")
		NOTIFICATION_PREDELETE:
			prints(name, "NOTIFICATION_PREDELETE")
		NOTIFICATION_POSTINITIALIZE:
			prints(name, "NOTIFICATION_POSTINITIALIZE")
		NOTIFICATION_POST_ENTER_TREE:
			prints(name, "NOTIFICATION_POST_ENTER_TREE")

func on_tree_entered():
	prints(name, "tree_entered signal")

func on_ready():
	prints(name, "ready signal")

func on_tree_exiting():
	prints(name, "tree_exiting signal")

func on_tree_exited():
	prints(name, "tree_exited signal")

And the result:

--- entering

[Node:1254] _init() callback
[Spatial:1255] _init() callback
[Control:1256] _init() callback
[Node2D:1257] _init() callback
[CanvasLayer:1258] _init() callback
[HTTPRequest:1259] _init() callback
[ResourcePreloader:1264] _init() callback
A-- NOTIFICATION_ENTER_TREE
A-- _enter_tree() callback
A-- tree_entered signal
AA- NOTIFICATION_ENTER_TREE
AA- _enter_tree() callback
AA- tree_entered signal
AAA NOTIFICATION_ENTER_TREE
AAA _enter_tree() callback
AAA tree_entered signal
AAB NOTIFICATION_ENTER_TREE
AAB _enter_tree() callback
AAB tree_entered signal
AB- NOTIFICATION_ENTER_TREE
AB- _enter_tree() callback
AB- tree_entered signal
ABA NOTIFICATION_ENTER_TREE
ABA _enter_tree() callback
ABA tree_entered signal
ABB NOTIFICATION_ENTER_TREE
ABB _enter_tree() callback
ABB tree_entered signal
AAA NOTIFICATION_POST_ENTER_TREE
AAA _ready() callback
AAA NOTIFICATION_READY
AAA ready signal
AAB NOTIFICATION_POST_ENTER_TREE
AAB _ready() callback
AAB NOTIFICATION_READY
AAB ready signal
AA- NOTIFICATION_POST_ENTER_TREE
AA- _ready() callback
AA- NOTIFICATION_READY
AA- ready signal
ABA NOTIFICATION_POST_ENTER_TREE
ABA _ready() callback
ABA NOTIFICATION_READY
ABA ready signal
ABB NOTIFICATION_POST_ENTER_TREE
ABB _ready() callback
ABB NOTIFICATION_READY
ABB ready signal
AB- NOTIFICATION_POST_ENTER_TREE
AB- _ready() callback
AB- NOTIFICATION_READY
AB- ready signal
A-- NOTIFICATION_POST_ENTER_TREE
A-- _ready() callback
A-- NOTIFICATION_READY
A-- ready signal

--- exiting

ABB _exit_tree() callback
ABB tree_exiting signal
ABB NOTIFICATION_EXIT_TREE
ABA _exit_tree() callback
ABA tree_exiting signal
ABA NOTIFICATION_EXIT_TREE
AB- _exit_tree() callback
AB- tree_exiting signal
AB- NOTIFICATION_EXIT_TREE
AAB _exit_tree() callback
AAB tree_exiting signal
AAB NOTIFICATION_EXIT_TREE
AAA _exit_tree() callback
AAA tree_exiting signal
AAA NOTIFICATION_EXIT_TREE
AA- _exit_tree() callback
AA- tree_exiting signal
AA- NOTIFICATION_EXIT_TREE
A-- _exit_tree() callback
A-- tree_exiting signal
A-- NOTIFICATION_EXIT_TREE
AAA tree_exited signal
AAB tree_exited signal
AA- tree_exited signal
ABA tree_exited signal
ABB tree_exited signal
AB- tree_exited signal
A-- tree_exited signal
ABB NOTIFICATION_PREDELETE
ABA NOTIFICATION_PREDELETE
AB- NOTIFICATION_PREDELETE
AAB NOTIFICATION_PREDELETE
AAA NOTIFICATION_PREDELETE
AA- NOTIFICATION_PREDELETE
A-- NOTIFICATION_PREDELETE

Interesting/not expected stuff:

  • _init() is called for every node in scene before any other method and before any is added to tree
  • enter_tree is called in the same order as init, i.e. depth-first
  • NOTIFICATION_POST_ENTER_TREE happens right before _ready() callback in any node, but onready variables aren't initialized yet
  • which means that _ready() is the earliest place where you can use onready variables and initialization of onready variables has the same order as _ready() callback
  • ready and enter tree have reverse order of things. For enter tree, the notification comes first, while it's the opposite for ready
  • tree_exiting is emitted first in children, same as NOTIFICATION_PREDELETE, but tree_exited is the opposite; parents emit it first.
  • thus tree_exiting is "equivalent" of ready and tree_exited is "equivalent" of enter_tree, but they are reversed, i.e. when destroying nodes it's as if "ready" came first instead of last

That's lots of information, not sure how much of it is really useful, but it would be nice to have it documented somewhere. Knowing the exact order of things in tree might come in handy for some complicated scene logic and might save some confusion when things don't get called in the order you'd expect.

KoBeWi avatar Jan 02 '22 12:01 KoBeWi

Related to https://github.com/godotengine/godot-docs/issues/3475 (possible duplicate)? Either way, this issue has more information, so I'd vouch for keeping it open.

Calinou avatar Jan 02 '22 16:01 Calinou

What would be a good section of the documentation to put this in?

skyace65 avatar Jan 07 '22 03:01 skyace65

@skyace65: Scripting, core features, new subpage (like "Groups" is one, or "Nodes and scene instancing" is another).

Zireael07 avatar Feb 03 '22 17:02 Zireael07

but tree_exited is the opposite; parents emit it first.

From the list children exited first as well as exiting and then their parent, or am I missing something, it's late over here so I might be dumb.

viksl avatar Jul 10 '22 23:07 viksl

Ah you seem to be right. The order is still different though.

KoBeWi avatar Jul 10 '22 23:07 KoBeWi

Yeah, it seems _exit_tree(), tree_exiting, notification_exit_tree are "deep bottom up", tree_exited is "deep top to bottom", notification_predelete is "deep bottom up".

tree_exited has the same order as _ready() callback, notification_ready and ready and notification_post_enter_tree.

Well I guess I'd expect all the exit and predelete to follow the same order, it's interesting to see it's not, I have no idea what reason could be behind it. :)

viksl avatar Jul 11 '22 07:07 viksl

I did look up into this a while back (I just found this issue), I'll link it here in case it is useful:

  • old simpler version: https://stackoverflow.com/a/66947013/402022
  • new version: https://gamedev.stackexchange.com/a/194937/10408

theraot avatar Dec 18 '22 12:12 theraot

Executing the same script on 4.2.1 now yields different results.

  • tree_exited is now called on nodes in the same order as exit_tree and tree_exiting
  • the timing of NOTIFICATION_PREDELETE seems to have broken and probably needs a fix

Concerning the inconsistency? between enter_tree and ready when it comes to the notification/callback/signal trigger order, it seems to still be there.

Given the behavior seems to have changed over time, I'm not sure how much should be left out as "implementation details" when creating the documentation. IMO we should be as specific as possible and try to keep the implementation in sync with the documentation, because some people will invariably end up relying on the order without necessarily being aware of it.

--- entering

<Node#27917288653> _init() callback
<Node3D#27934065870> _init() callback
<Control#27950843087> _init() callback
<Node2D#27984397524> _init() callback
<CanvasLayer#28001174741> _init() callback
<HTTPRequest#28017951958> _init() callback
<ResourcePreloader#28135392477> _init() callback
A-- NOTIFICATION_ENTER_TREE
A-- _enter_tree() callback
A-- tree_entered signal
AA- NOTIFICATION_ENTER_TREE
AA- _enter_tree() callback
AA- tree_entered signal
AAA NOTIFICATION_ENTER_TREE
AAA _enter_tree() callback
AAA tree_entered signal
AAB NOTIFICATION_ENTER_TREE
AAB _enter_tree() callback
AAB tree_entered signal
AB- NOTIFICATION_ENTER_TREE
AB- _enter_tree() callback
AB- tree_entered signal
ABA NOTIFICATION_ENTER_TREE
ABA _enter_tree() callback
ABA tree_entered signal
ABB NOTIFICATION_ENTER_TREE
ABB _enter_tree() callback
ABB tree_entered signal
AAA NOTIFICATION_POST_ENTER_TREE
AAA _ready() callback
AAA NOTIFICATION_READY
AAA ready signal
AAB NOTIFICATION_POST_ENTER_TREE
AAB _ready() callback
AAB NOTIFICATION_READY
AAB ready signal
AA- NOTIFICATION_POST_ENTER_TREE
AA- _ready() callback
AA- NOTIFICATION_READY
AA- ready signal
ABA NOTIFICATION_POST_ENTER_TREE
ABA _ready() callback
ABA NOTIFICATION_READY
ABA ready signal
ABB NOTIFICATION_POST_ENTER_TREE
ABB _ready() callback
ABB NOTIFICATION_READY
ABB ready signal
AB- NOTIFICATION_POST_ENTER_TREE
AB- _ready() callback
AB- NOTIFICATION_READY
AB- ready signal
A-- NOTIFICATION_POST_ENTER_TREE
A-- _ready() callback
A-- NOTIFICATION_READY
A-- ready signal

--- exiting

A-- NOTIFICATION_PREDELETE
ABB _exit_tree() callback
ABB tree_exiting signal
ABB NOTIFICATION_EXIT_TREE
ABA _exit_tree() callback
ABA tree_exiting signal
ABA NOTIFICATION_EXIT_TREE
AB- _exit_tree() callback
AB- tree_exiting signal
AB- NOTIFICATION_EXIT_TREE
AAB _exit_tree() callback
AAB tree_exiting signal
AAB NOTIFICATION_EXIT_TREE
AAA _exit_tree() callback
AAA tree_exiting signal
AAA NOTIFICATION_EXIT_TREE
AA- _exit_tree() callback
AA- tree_exiting signal
AA- NOTIFICATION_EXIT_TREE
A-- _exit_tree() callback
A-- tree_exiting signal
A-- NOTIFICATION_EXIT_TREE
ABB tree_exited signal
ABA tree_exited signal
AB- tree_exited signal
AAB tree_exited signal
AAA tree_exited signal
AA- tree_exited signal
A-- tree_exited signal
AB- NOTIFICATION_PREDELETE
ABB NOTIFICATION_PREDELETE
ABA NOTIFICATION_PREDELETE
AA- NOTIFICATION_PREDELETE
AAB NOTIFICATION_PREDELETE
AAA NOTIFICATION_PREDELETE

ershn avatar Dec 25 '23 03:12 ershn

Here's the test scene with up-to-date script: OrderOfNodes.tscn.txt Indeed the predelete thing looks like a bug. Other changes might've been caused by Godot 3 to Godot 4 transition.

KoBeWi avatar Dec 25 '23 11:12 KoBeWi