Skip to content

The app loop

The app loop is The Elm Architecture: the app owns a Model, the view only reads it and emits messages, update is the only place that changes it.

kiln.run(kiln.App(Model, Msg){init = init, update = update, view = view}, {title = "Meine App"})
procsignaturewhat
initproc() -> Modelthe first model
updateproc(m: ^Model, msg: Msg)the only place the model changes
viewproc(m: ^Model, out: ^[dynamic]Msg)draws, appends messages to out
tickproc(m: ^Model, dt: f32)optional, once per frame before view

The second argument of run is a Config: title, window size, theme, scale. Zero values select the defaults.

Each page of the example is its own package with its own Model, Msg, update and view. The parent has a variant of its Msg union for each child’s Msg and forwards them:

Msg :: union {
Go_To,
tasks.Msg,
people.Msg,
}
update :: proc(m: ^Model, msg: Msg) {
switch v in msg {
case Go_To:
m.page = v.page
case tasks.Msg:
tasks.update(&m.tasks, v)
case people.Msg:
people.update(&m.people, v)
}
}
view :: proc(m: ^Model, out: ^[dynamic]Msg) {
switch m.page {
case .Tasks:
kiln.embed(out, &m.tasks, tasks.view)
case .People:
kiln.embed(out, &m.people, people.view)
}
}

embed runs the child view and converts its messages into the parent’s message type.

Work that would block the frame (network, disk) goes to task. It runs on a worker thread and comes back as a message, which goes through update like one from the view:

kiln.task(proc(url: string) -> Msg {
body, err := http_get(url)
return Loaded{body, err}
}, strings.clone(url))
  • work must not touch the model or kiln’s UI state, only data.
  • It runs with the allocator of the caller of task, so update can free what it allocated.
  • The message type can be a variant of the app’s Msg at any depth, so modules can start their own tasks.
  • tasks_running() counts the ones still out, e.g. for a spinner.
  • In snapshot builds tasks run synchronously, so snapshots are deterministic.

Timers are for the view and keyed by name, like dialogs:

if kiln.every("refresh", 300) {append(out, Refresh{})} // every 5 minutes
if kiln.after("hint", 3) {append(out, Hide_Hint{})} // once, after 3 seconds

They only run while they are called in every frame. reset_timer starts one over.

procwhat
toast(msg, tone)short message, also callable from update
key_pressed(key)a key in this frame, unless a text field has focus
copy_to_clipboard(s), clipboard()the system clipboard
now(), frame_time()time since start, last frame duration
quit()closes the window at the end of the frame