Lifecycle & Rendering
Lifecycle: on_mount and on_unmount
When a component is inserted into the view tree, the compiler calls its on_mount method (if defined). When removed from the tree, on_unmount is called:
component Connection {
var conn: ?net.TcpStream = null;
fn on_mount() {
conn = net.connect_host("localhost", 8080) or { return; };
}
fn on_unmount() {
if (conn != null) {
var s: net.TcpStream = conn!;
s.close();
conn = null;
}
}
}on_mount— called once after the component is added to the active view treeon_unmount— called once before the component is removed from the view tree
Both are optional. Define either, both, or neither.
After on_unmount and the children’s unmounting, the component’s own
strings and other managed vars and props are freed (and left empty),
anything the UI still points at it for (keyboard focus, pointer capture,
hover, an open dialog) is cleared, and the component’s memory is freed:
mounting and unmounting a tree, or switching an if branch and updating a
for list, gives back everything it allocated.
A component made in #code with let c = Timer { ... }; is destroyed the
same way with ui.unmount(c). @drop(c) only frees its managed vars and
props: it does not call on_unmount, and the component itself stays
allocated.
After a hot reload: on_reload
In a hot run (tape run --hot), when a reload
replaces a component’s code, on_reload runs once on each of its
instances, after their vars were carried over to the new code. It re-derives
what depended on the old code: a coroutine that had to restart reads
started() == false, so on_reload can start it again.
component Guard {
var walk: walk_route;
co fn walk_route() { ... }
fn on_reload() {
if (!walk.started()) { walk = walk_route.start(); }
}
}Rendering via @dispatch
Visual rendering is not a special compiler feature — it uses the standard dispatch mechanism. Components opt into painting by calling ui.drawable() in a @tape block:
component Panel {
@tape { ui.drawable(); }
prop bg: color;
fn paint(s: *gfx.Surface, x: i64, y: i64, w: i64, h: i64) {
s.fill_rect(x, y, w, h, bg);
}
}ui.drawable() is a comptime function that emits:
pub @tape fn drawable() {
@emit var bounds_x: i64 = 0;
@emit var bounds_y: i64 = 0;
@emit var bounds_w: i64 = 0;
@emit var bounds_h: i64 = 0;
@emit @dispatch fn paint(s: *gfx.Surface, x: i64, y: i64, w: i64, h: i64);
}Layout dispatch
Components that manage child positioning opt into layout:
component MyLayout {
@tape { ui.drawable(); }
@tape { ui.layoutable(); }
fn layout(x: i64, y: i64, w: i64, h: i64) {
// position children within bounds
}
fn paint(s: *gfx.Surface, x: i64, y: i64, w: i64, h: i64) {
// draw background, then the children: ui.paint_tree(child, s)
}
}ui.layoutable() emits @dispatch fn layout(...). A component that is
both drawable and layoutable owns its subtree: layout places each child
(ui.layout_tree(child, x, y, w, h)) and paint paints them
(ui.paint_tree(child, s)) after its own drawing; the default tree walker
does neither for it (ui: the tree).
Interaction dispatch
Components that handle input opt into the interactive protocol:
component Button {
@tape { ui.drawable(); }
@tape { ui.interactive(); }
event on_click();
fn handle_pointer(ev: *ui.PointerEvent) {
if (ev.kind == ui.PointerKind.Down) { ev.handled = true; } // take the press
if (ev.kind == ui.PointerKind.Up) {
fire on_click();
}
}
fn handle_key(ev: ui.KeyEvent) {
// keyboard handling
}
}ui.interactive() emits @dispatch fn handle_pointer(ev: *PointerEvent) and @dispatch fn handle_key(ev: KeyEvent).
Rendering cadence
- Input event arrives (pointer, key, timer)
- Event handler executes (may mutate
var/statemultiple times) - Handler returns — dirty flags are set
- Frame tick: runtime walks dirty components
- For each dirty component: recalculate layout if needed, repaint dirty region
- Clear dirty flags
A component is repainted at most once per frame, regardless of how many state mutations occurred, and only when something changed: the frame repaints the dirty components’ rectangles (with whatever lies under and over them there) and a window shows just those (ui: what gets repainted).
Dispatch summary
| Capability | Comptime helper | Dispatch methods |
|---|---|---|
| Painting | ui.drawable() | paint(s, x, y, w, h) |
| Layout | ui.layoutable() | layout(x, y, w, h) |
| Input | ui.interactive() | handle_pointer(ev), handle_key(ev) |
Last modified: