Runtime Loop
In many UI systems, the main loop looks like an implementation detail:
- read events
- render
- flush output
In Ansiq, the runtime loop is not a hidden plumbing layer. It is a real architectural boundary.
Why? Because Ansiq is not just drawing a tree. It has to coordinate:
- terminal events
- app messages
- reactive flushes
- rerender decisions
- layout and patch emission
If those concerns are not gathered into one clear loop, they start to fight each other.
Why the runtime loop matters as a design object
Look at what Ansiq has to coordinate today:
- input events
- async task messages
- dirty scopes produced by the reactive system
- focus and routing
- subtree replacement
- partial relayout
- invalidated region tracking
- terminal patch emission
That means the runtime loop is not:
“render whenever something changes”
It is much closer to:
“collect several sources of change and reduce them into one predictable screen update pipeline”
First, separate lifecycle from the per-update loop
One of the easiest things to blur together in Ansiq is:
- the
Applifecycle - the runtime’s per-update loop
They are related, but they are not the same thing.
App lifecycle
At a high level, a Ansiq app usually goes through:
- create the
App - enter the terminal session
- call
mount(...) - perform the first
render(...) - enter the main loop
- keep reacting to input, messages, and reactive dirtiness
- exit and restore terminal state
As a rough timeline:
create app
-> enter terminal session
-> mount(...)
-> first render(...)
-> main runtime loop
-> exit / restore terminalThe runtime loop per update
The loop itself is more fine-grained. It repeatedly coordinates:
- input events
- async messages
- reactive flushes
- rerender decisions
- relayout
- redraw / diff / patch emission
So lifecycle answers:
what stages does the app go through over its lifetime?
While the runtime loop answers:
when something changes, how do we turn that into one stable terminal update?
What that pipeline looks like
Today, the core Ansiq runtime chain can be summarized as:
input / async messages / reactive flush
-> dirty scope collection
-> subtree rerender or full rerender
-> partial relayout
-> invalidated regions
-> framebuffer diff
-> terminal patch emissionThe value of this chain is not just performance. It is boundary clarity.
What mount, render, and update each mean
mount(...)
mount(...) is for work that should exist as soon as the app starts, for example:
- starting background sampling
- opening long-lived connections
- scheduling initial messages
It does not render UI. It establishes long-lived runtime conditions.
render(...)
render(...) is responsible for:
- describing the current UI tree
- exposing component boundaries
- performing reactive reads for this render pass
It does not draw the terminal directly, and it does not decide how patches are emitted.
update(...)
update(...) is responsible for:
- consuming messages
- mutating app state
- deciding whether more async work should begin
It does not directly:
- patch the terminal
- relayout the tree
- own the low-level focus implementation
The shortest possible summary is:
mountestablishes long-lived background conditions,updateconsumes facts and changes state, andrenderdescribes the current UI.
Why this logic should not be spread around the system
Without a strong runtime loop, the most common failure mode is that different parts of the system start deciding redraw behavior independently:
- input handling decides when to repaint
- widgets mutate global state directly
- surface decides viewport changes in isolation
- app logic quietly triggers extra rerenders
This can still appear to work in a small app. But once you add:
- async work
- multiple panes
- subtree replacement
- history / scrollback
it becomes extremely difficult to debug.
What the Ansiq runtime loop is responsible for
From the perspective of the loop, the runtime has three major jobs:
1. Collect all sources of change
That includes:
- keyboard input
- async messages
- reactive dirty scopes
2. Decide the smallest acceptable update
That includes:
- full rerender vs dirty subtree replacement
- which ancestors must be relaid out
- which regions only need redraw
3. Commit the update into the terminal
That includes:
- full-frame patches
- region diffs
- cursor patches
This is why the runtime loop cannot be treated as “just an event loop detail”. It defines the update model of the whole framework.
What a real update looks like
Suppose the user types one more character into an input while a background task also delivers Message::Chunk.
The runtime loop should not behave like “whoever arrives first triggers an ad-hoc redraw”. It should behave more like this:
- receive the input event and async message
- route the key and see whether a widget consumes it
- forward the message into
update(...) - run
flush_reactivity()and collect dirty scopes - decide between full rerender and subtree replacement
- perform partial relayout
- compute invalidated regions
- diff framebuffers
- emit terminal patches
The most important value here is not only speed. It is:
- the order is explicit
- the ownership is explicit
- bugs can be traced to a specific layer
A key design principle
One of Ansiq’s most important architectural rules is:
- the reactive system decides what became dirty
- the runtime decides how the screen should update
The runtime loop is exactly where those two worlds meet.
It connects the reactive graph on one side and the terminal surface on the other. If that boundary becomes blurry, Ansiq quickly turns into an unstable mix of several UI models at once.
What to read in the source
If you want to map this page to implementation, start here:
ansiq-runtimerun.rsengine.rs
ansiq-surfacesession.rs
When reading, the important question is not “what does every function do?”, but rather:
- where events enter
- where dirty work is collected
- where rerender is decided
- where patches are emitted
The conclusion of this page
In Ansiq, the runtime loop is not hidden plumbing.
It is a real architectural object because it ties together:
- reactive updates
- tree updates
- partial layout
- terminal patch emission
into one unified, debuggable, documentable pipeline.
If you want to understand why the loop does not immediately imply “rerender everything”, the next page to read is Reactive Graph vs UI Tree.