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 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())