Lifecycle

the order node hooks fire, made visible.

▶ Run in browser

Tags: basics lifecycle node

Every node runs through the same hooks in the same order:

  • on_enter_tree() when it is attached to a running tree,

  • on_ready() right after, the first time only,

  • on_update(dt) every frame while it is in the tree,

  • on_exit_tree() when it leaves.

Here one child node is added with add_child and taken away with remove_child on a repeating four-second cycle, and each hook writes a line to the log on screen. on_enter_tree fires on every entry and on_ready only on the first: a node that is removed and added back does not ready again.

That gives each hook its job. on_ready builds what the node owns for its whole life, such as child nodes and signal connections, so they are built once. on_enter_tree does per-entry setup, and it must redo whatever on_exit_tree undoes: put that in on_ready instead and the node comes back broken the second time it joins a tree. Here on_enter_tree resets the update count, so it starts from one on every entry.

Source

 1# /// simvx
 2# tags = ["basics", "lifecycle", "node"]
 3# web = { width = 800, height = 600, responsive = true }
 4# ///
 5"""Lifecycle: the order node hooks fire, made visible.
 6
 7Every node runs through the same hooks in the same order:
 8
 9- `on_enter_tree()` when it is attached to a running tree,
10- `on_ready()` right after, the first time only,
11- `on_update(dt)` every frame while it is in the tree,
12- `on_exit_tree()` when it leaves.
13
14Here one child node is added with `add_child` and taken away with `remove_child` on a repeating
15four-second cycle, and each hook writes a line to the log on screen. `on_enter_tree` fires on
16every entry and `on_ready` only on the first: a node that is removed and added back does not
17ready again.
18
19That gives each hook its job. `on_ready` builds what the node owns for its whole life, such as
20child nodes and signal connections, so they are built once. `on_enter_tree` does per-entry setup,
21and it must redo whatever `on_exit_tree` undoes: put that in `on_ready` instead and the node comes
22back broken the second time it joins a tree. Here `on_enter_tree` resets the update count, so it
23starts from one on every entry.
24"""
25
26from simvx.core import Node2D
27from simvx.graphics import App
28
29SPAWN_AT = 0.3  # seconds into a cycle at which the tracked child is added
30REMOVE_AT = 3.0  # seconds into a cycle at which it is removed again
31CYCLE = 4.0  # length of one add and remove pass
32
33
34class Tracked(Node2D):
35    """A node that writes each lifecycle hook it receives to its log."""
36
37    def __init__(self, **kwargs):
38        super().__init__(**kwargs)
39        self.log: list[str] = []
40        self.entries = self.readies = self.updates = 0
41
42    def note(self, event: str):
43        self.log.append(event)
44        del self.log[:-10]  # keep the newest lines, as many as fit on screen
45
46    def on_enter_tree(self):
47        self.updates = 0
48        self.entries += 1
49        self.note(f"on_enter_tree (entry #{self.entries})")
50
51    def on_ready(self):
52        self.readies += 1
53        self.note(f"on_ready (#{self.readies}: once and never again)")
54
55    def on_update(self, dt: float):
56        self.updates += 1
57        if self.updates <= 2:  # on_update recurs every frame, so log only the first two
58            self.note(f"on_update (#{self.updates})")
59
60    def on_exit_tree(self):
61        self.note("on_exit_tree")
62
63
64class LifecycleDemo(Node2D):
65    dynamic = True  # on_draw reads plain attributes that change every frame
66
67    def on_ready(self):
68        self.t = 0.0
69        self.tracked = Tracked()
70
71    def on_update(self, dt: float):
72        self.t += dt
73        in_tree = self.tracked.parent is not None
74        wanted = SPAWN_AT <= self.t % CYCLE < REMOVE_AT
75        if wanted and not in_tree:
76            self.add_child(self.tracked)
77        elif in_tree and not wanted:
78            self.remove_child(self.tracked)
79
80    def on_draw(self, renderer):
81        tracked = self.tracked
82        state = "child in tree" if tracked.parent is not None else "no child"
83        renderer.text("Node lifecycle hooks (in firing order)", (20, 20), fill=(1, 1, 1), scale=2)
84        renderer.text(f"cycle t = {self.t % CYCLE:4.1f}s of {CYCLE:.1f}s   ({state})", (20, 56), fill=(0.7, 0.7, 0.7))
85        tally = f"entries: {tracked.entries}    readies: {tracked.readies}"
86        renderer.text(tally, (20, 84), fill=(0.9, 0.8, 0.4))
87        for i, line in enumerate(tracked.log, 1):
88            renderer.text(f"{i:2d}.  {line}", (40, 100 + i * 28), fill=(0.5, 0.9, 0.6))
89
90
91if __name__ == "__main__":
92    App(title="Lifecycle", width=800, height=600).run(LifecycleDemo())