Frame loop — a fixed-timestep animation/game loop on the LEV runtime.
Generalizes the matrix rain's hand-rolled lev.spawn loop into a reusable
harness: a render task drives a fixed-step clock (decoupling simulation rate
from the variable wall-clock frame time, with a spiral-of-death clamp), while
a separate input task reads keys cooperatively via term.read_key over a
lev.fd_reader — the proven pattern from the shell's input handling. Both
tasks share one cancel token so stop() tears the whole thing down at once.
Two entry points:
handle:run() — owns its own lev.run; blocks until stopped. For a
standalone game/animation.
handle:start() — spawns the tasks into an ALREADY-running lev.run and
returns immediately. For embedding (e.g. the matrix rain
inside the shell's loop).
Terminals have no vsync, so the loop paces with lev.sleep and exposes the
measured fps_actual.
| Name | Signature |
|---|---|
ticker | ticker(fps) -> ticker |
loop | loop(opts) -> handle |
ticker(
fps) ->ticker
Create a frame ticker for a custom loop
Returns { step = <seconds>, tick = fn }.
Each ticker:tick() sleeps until the next frame boundary
and returns the delta time (seconds) since the previous tick.
Use it when you want to drive your own loop body rather than
hand callbacks to loop.
loop(
opts) ->handle
Create a fixed-timestep frame loop
opts:
fps (default 30);
update (fn(dt, t) — called at the fixed step, possibly multiple times per frame to catch up;
render (fn(canvas));
input (fn(key, mods, event, shifted, base));
on_resize (fn(cols, rows));
canvas (the canvas passed to render and displayed);
display (true to auto-create an absolute top-left display over the canvas, a display object to reuse, or nil for none);
alt_screen (default true — enter the alt screen + raw mode + KKBP, restored on stop);
setup/teardown (fn(handle));
max_frame (dt clamp, default 0.25).
Handle:
:run() (own lev.run, blocks until stopped),
:start() (spawn into a running lev.run, returns immediately),
:stop(), :pause(), :resume(), :is_running();
fields handle.canvas, handle.display, handle.fps_actual.