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 the running tree, on_ready once right after, then on_update(dt) every frame, and on_exit_tree when it leaves. Here a child node is spawned and later removed on a timer, and each hook appends to a shared log so the firing order is plainly readable on screen.

What it demonstrates

  • Hook order: on_enter_tree -> on_ready -> on_update(dt) (every frame) -> on_exit_tree.

  • on_enter_tree / on_exit_tree bracket a node’s time in the tree; on_ready fires exactly once.

  • on_update(dt) runs once per frame while the node is in the tree.

  • Removing a child (remove_child) fires its on_exit_tree.

Source

 1"""Lifecycle: the order node hooks fire, made visible.
 2
 3Every node runs through the same hooks in the same order: `on_enter_tree`
 4when it is attached to the running tree, `on_ready` once right after, then
 5`on_update(dt)` every frame, and `on_exit_tree` when it leaves. Here a child
 6node is spawned and later removed on a timer, and each hook appends to a shared
 7log so the firing order is plainly readable on screen.
 8
 9# /// simvx
10# tags = ["basics", "lifecycle", "node"]
11# web = { root = "LifecycleDemo", width = 800, height = 600, responsive = true }
12# ///
13
14## What it demonstrates
15
16- Hook order: `on_enter_tree` -> `on_ready` -> `on_update(dt)` (every frame) -> `on_exit_tree`.
17- `on_enter_tree` / `on_exit_tree` bracket a node's time in the tree; `on_ready` fires exactly once.
18- `on_update(dt)` runs once per frame while the node is in the tree.
19- Removing a child (`remove_child`) fires its `on_exit_tree`.
20"""
21
22from simvx.core import Node2D, Vec2
23from simvx.graphics import App
24
25WIDTH, HEIGHT = 800, 600
26
27
28class Tracked(Node2D):
29    """A node that records each lifecycle hook into the demo's shared log."""
30
31    def __init__(self, log, **kwargs):
32        self._log = log
33        self.updates = 0
34        super().__init__(**kwargs)
35
36    def _note(self, event):
37        # Keep the log short enough to render; newest entries stay visible.
38        self._log.append(event)
39        del self._log[:-12]
40
41    def on_enter_tree(self):
42        self._note("on_enter_tree")
43
44    def on_ready(self):
45        self._note("on_ready")
46
47    def on_update(self, dt: float):
48        self.updates += 1
49        if self.updates <= 3:  # log only the first few; updates recur every frame
50            self._note(f"on_update (#{self.updates})")
51
52    def on_exit_tree(self):
53        self._note("on_exit_tree")
54
55
56class LifecycleDemo(Node2D):
57    def on_ready(self):
58        self.log: list[str] = []
59        self.child: Tracked | None = None
60        self._t = 0.0
61        self._spawn_at = 0.6  # spawn the tracked child shortly after start
62        self._remove_at = 3.0  # then remove it to show on_exit_tree
63
64    def on_update(self, dt: float):
65        self._t += dt
66        if self.child is None and self._t >= self._spawn_at and self._t < self._remove_at:
67            # Attaching to the running tree triggers enter_tree then ready.
68            self.child = self.add_child(Tracked(self.log, position=Vec2(0, 0)))
69        if self.child is not None and self._t >= self._remove_at:
70            # Detaching fires the child's on_exit_tree.
71            self.remove_child(self.child)
72            self.child = None
73
74    def on_draw(self, renderer):
75        renderer.draw_text("Node lifecycle hooks (in firing order)", (20, 20), scale=2, colour=(1, 1, 1))
76        state = "child in tree" if self.child is not None else "no child"
77        renderer.draw_text(f"t = {self._t:4.1f}s   ({state})", (20, 56), scale=1, colour=(0.7, 0.7, 0.7))
78        for i, entry in enumerate(self.log):
79            renderer.draw_text(f"{i + 1:2d}.  {entry}", (40, 100 + i * 28), scale=1, colour=(0.5, 0.9, 0.6))
80
81
82if __name__ == "__main__":
83    App(title="Lifecycle", width=WIDTH, height=HEIGHT).run(LifecycleDemo())