8.18.1.1. Application lifecycle
A long-running dasImgui program needs two compatible owners:
daslang-livecalls the exportedinit/update/shutdownfunctions and collects between update calls.Standalone
daslangcallsmain(), which owns the same lifecycle and must also provide a safe garbage-collection boundary.
The canonical application shape below supports windowed, live-reload, and
headless execution from one source file. It follows the same memory rule as
utils/dasllama-server/main.das: application state that survives a frame is
global, and collection happens only after one update has finished and before
the next frame creates collectable locals.
Source: modules/dasImgui/examples/tutorial/application_lifecycle.das.
1options gen2
2options _comment_hygiene = true
3options gc
4options persistent_heap
5
6require imgui/imgui_harness
7
8// =============================================================================
9// TUTORIAL: application_lifecycle - one source file for standalone and live.
10//
11// The lifecycle exports are owned by daslang-live when hosted. main() owns the
12// same lifecycle in standalone mode. After update() returns, main calls the
13// harness GC helper: frame locals are dead, and the helper is a no-op when the
14// same source is hosted by daslang-live.
15//
16// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/application_lifecycle.das
17// LIVE: daslang-live modules/dasImgui/examples/tutorial/application_lifecycle.das
18// HEADLESS: daslang.exe modules/dasImgui/examples/tutorial/application_lifecycle.das -- --headless --headless-frames=600
19// =============================================================================
20
21var private g_frames = 0
22
23[export]
24def init() {
25 harness_init("dasImgui application lifecycle", 760, 440)
26}
27
28[export]
29def update() {
30 if (!harness_begin_frame()) return
31 harness_new_frame()
32
33 SetNextWindowPos(ImVec2(40.0f, 40.0f), ImGuiCond.Always)
34 SetNextWindowSize(ImVec2(680.0f, 340.0f), ImGuiCond.Always)
35 window(APPLICATION_WIN, (text = "Application lifecycle", closable = false,
36 flags = ImGuiWindowFlags.None)) {
37 text("init -> update* -> shutdown")
38 separator()
39 text("frame {g_frames}")
40 text("The same file runs under daslang and daslang-live.")
41 text("Standalone GC runs only between completed frames.")
42 spacing(APP_SPACING)
43 if (button(QUIT_BUTTON, (text = "Quit"))) {
44 request_exit()
45 }
46 }
47
48 harness_end_frame()
49 g_frames++
50}
51
52[export]
53def shutdown() {
54 harness_shutdown()
55}
56
57[export]
58def main() {
59 init()
60 while (!exit_requested()) {
61 update()
62 // Safe boundary: update returned, so no frame-local collectable value
63 // remains live on the JIT stack. The live host collects for us.
64 harness_maybe_collect_gc()
65 }
66 shutdown()
67}
8.18.1.1.1. Lifecycle ownership
init()Creates long-lived resources. It is called once by standalone
mainand by the live host on load/reload. Harness setup is reload-safe.update()Processes exactly one frame. A
falseresult fromharness_begin_frame()skips rendering while the host is paused, reloading, closing, or a headless frame cap has been reached.shutdown()Releases resources in reverse ownership order. The harness distinguishes a live reload from final process exit and preserves the live ImGui context when appropriate.
main()Is used only by standalone
daslang. It callsinitonce, repeatedly callsupdate, and always finishes withshutdown.
8.18.1.1.2. The two heap options
Long-running applications that allocate transient strings or containers use:
options persistent_heap
options gc
persistent_heap makes the context heap collectable. gc enables
collection in the context. Both are needed before calling heap_collect and
both are entry-program policies, so the tutorial declares them explicitly.
They do not make ordinary JIT stack locals traceable. A collection while a live string, array, table, JSON value, or other collectable object exists only in a JIT local can reclaim that object and crash. This is why the lifecycle shape, rather than merely the two options, is the safety mechanism.
8.18.1.1.3. The safe collection point
GC is the safety net, not the per-frame ownership mechanism. Long-running
daslang applications should release uniquely-owned transient arrays, strings,
tables, JSON trees, and replaced caches with delete as soon as their
ownership ends. Stable derived data should be cached and rebuilt only when its
inputs change. With that discipline, the heap reuses freed storage and a real
collection is needed only occasionally.
Standalone programs do not receive the live host’s between-update collection
pass. Their main loop must call harness_maybe_collect_gc() immediately
after update() returns. The helper delegates to the existing glfw_live
fragmentation heuristic: calling the check every loop does not mean
collecting every loop. It only collects when the heap has enough unused space
to make compaction worthwhile. It is a no-op under daslang-live, because
the host already owns collection there.
This placement matters. Do not call heap_collect from the middle of widget
rendering or while a request/frame-local collectable value must remain alive.
Keep durable state in module globals, keep standalone main free of
collectable locals that span the collection call, and collect only after
update returns completely.
For a non-harness service, use the explicit server form:
[export]
def main() {
init()
while (!exit_requested()) {
update()
maybe_collect_gc() // update returned; request locals are dead
}
shutdown()
}
For non-harness services the equivalent call is their own
maybe_collect_gc. utils/dasllama-server/main.das is the production reference. Its
maybe_collect_gc skips host-owned live mode, rate-limits collections, uses
heap-fragmentation ratios, and can honor a forced diagnostic collection.
Simple GLFW applications can use live/glfw_live’s existing
maybe_collect_gc helper at the same post-update boundary.
8.18.1.1.4. Delete-first example
def update() {
var response <- build_transient_response()
send(response)
delete response // deterministic ownership end; no GC required here
}
For a cached UI layout, keep the cache global. When document text, available
width, font roles, style, or zoom changes, delete the old cached layout and
replace it once. An idle frame should render the retained layout without
creating a new graph for GC to discover later.
8.18.1.1.5. Run modes
# Standalone window.
daslang.exe modules/dasImgui/examples/tutorial/application_lifecycle.das
# Live-reload host owns init/update/shutdown.
daslang-live modules/dasImgui/examples/tutorial/application_lifecycle.das
# CPU-only smoke run with a deterministic exit.
daslang.exe modules/dasImgui/examples/tutorial/application_lifecycle.das -- --headless --headless-frames=600
See also
Live reload for reload state and lifecycle hooks.
Harness + headless mode for the harness backend split.
skills/imgui_application.mdfor the implementation checklist used when creating or reviewing an application.