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:

tape
component Connection {
    var socket: *u8 = null;

    fn on_mount() {
        socket = net.connect("localhost", 8080);
    }

    fn on_unmount() {
        net.close(socket);
        socket = null;
    }
}
  • on_mount — called once after the component is added to the active view tree
  • on_unmount — called once before the component is removed from the view tree

Both are optional. Define either, both, or neither.

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:

tape
component Panel {
    @tape { ui.drawable(); }

    prop bg: color;

    fn paint(ctx: *u8, x: i32, y: i32, w: i32, h: i32) {
        gfx.fill_rect(ctx, x, y, w, h, bg);
    }
}

ui.drawable() is a comptime function that emits:

tape
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(ctx: *u8, x: i32, y: i32, w: i32, h: i32);
}

Layout dispatch

Components that manage child positioning opt into layout:

tape
component MyLayout {
    @tape { ui.drawable(); }
    @tape { ui.layoutable(); }

    fn layout(ctx: *u8, x: i64, y: i64, w: i64, h: i64) {
        // position children within bounds
    }

    fn paint(ctx: *u8, x: i32, y: i32, w: i32, h: i32) {
        // draw background
    }
}

ui.layoutable() emits @dispatch fn layout(...).

Interaction dispatch

Components that handle input opt into the interactive protocol:

tape
component Button {
    @tape { ui.drawable(); }
    @tape { ui.interactive(); }

    fn handle_pointer(ev: *ui.PointerEvent) {
        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

  1. Input event arrives (pointer, key, timer)
  2. Event handler executes (may mutate var/state multiple times)
  3. Handler returns — dirty flags are set
  4. Frame tick: runtime walks dirty components
  5. For each dirty component: recalculate layout if needed, repaint dirty region
  6. Clear dirty flags

A component is repainted at most once per frame, regardless of how many state mutations occurred.

Dispatch summary

CapabilityComptime helperDispatch methods
Paintingui.drawable()paint(ctx, x, y, w, h)
Layoutui.layoutable()layout(ctx, x, y, w, h)
Inputui.interactive()handle_pointer(ev), handle_key(ev)

Last modified: