Skip to Content
InternalsLifecycle and Runtime Loop

Lifecycle and Runtime Loop

One of the easiest mistakes when learning Ansiq is to blur these two things together:

  • the app lifecycle
  • the runtime’s update loop

They are related, but they answer different questions.

First: the app lifecycle

At a high level, a Ansiq app usually goes through:

  1. create the app
  2. enter the terminal session
  3. call mount(...)
  4. perform the first render(...)
  5. enter the runtime loop
  6. repeatedly handle input, messages, and rerender work
  7. exit and restore the terminal
App lifecycle
1create app
2enter terminal session
3mount(...)
4first render(...)
5runtime loop
6exit and restore terminal

This answers:

what phases does an app go through over its lifetime?

Second: the runtime loop

The runtime loop answers a different question:

once some change happens, how does Ansiq turn that change into a stable terminal update?

Ansiq currently coordinates:

  • input events
  • app messages
  • reactive flushes
  • rerender decisions
  • partial relayout
  • invalidated regions
  • framebuffer diff
  • terminal patch emission
Main update path
1input / async messages / reactive flush
2dirty scope collection
3subtree rerender or full rerender
4partial relayout
5invalidated regions
6framebuffer diff
7terminal patch emission

The four important App methods

pub trait App { type Message: Send + 'static; fn mount(&mut self, _handle: &RuntimeHandle<Self::Message>) {} fn render(&mut self, cx: &mut ViewCtx<'_, Self::Message>) -> Element<Self::Message>; fn update(&mut self, message: Self::Message, handle: &RuntimeHandle<Self::Message>); fn on_unhandled_key( &mut self, _key: Key, _handle: &RuntimeHandle<Self::Message>, ) -> bool { false } }

mount(...)

Use mount(...) for work that should exist as soon as the app starts:

  • start a sampler
  • open a long-lived connection
  • emit an initial message

It is not the place where UI gets drawn.

render(...)

render(...) describes the current UI tree and establishes the reactive reads for that render.

It does not emit patches directly.

update(...)

update(...) consumes messages and turns them into app state changes.

It answers:

  • what did the user do?
  • what new fact arrived from background work?
  • how should that fact change app state?

on_unhandled_key(...)

If no widget consumes a key and the runtime does not consume it for global focus navigation, the key can still bubble back to the app.

This is useful for:

  • global shortcuts
  • Escape to cancel
  • mode switches
  • command entry
fn on_unhandled_key( &mut self, key: Key, handle: &RuntimeHandle<Self::Message>, ) -> bool { if key == Key::Esc { let _ = handle.emit(Message::Cancel); return true; } false }

Why this is not “just an event loop”

In simpler UI systems, the loop can feel like “receive an event, redraw once”.

That is not enough for Ansiq because the runtime also has to coordinate:

  • async messages
  • localized rerender
  • localized relayout
  • viewport and history behavior

So the runtime loop is an architectural boundary, not just an implementation detail.

Next

Continue with Reactivity and Reactive System in Depth.
The first page teaches how to use signal, computed, and effect; the second explains why the reactive graph stays separate from the UI tree.

Last updated on