Lifecycle¶
the order node hooks fire, made visible.
▶ Run in browserTags: 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_treebracket a node’s time in the tree;on_readyfires exactly once.on_update(dt)runs once per frame while the node is in the tree.Removing a child (
remove_child) fires itson_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())