A fixed-timestep frame loop on LEV.
One task runs the frame: fixed-step updates, a render, a display update, then a sleep to the next frame boundary. The boundary advances by one step each frame. A sleep measured from the end of the frame's work would add each late wake-up to the period. A loop asked for 60 fps would then settle near 58.
The loop reads no input and opens no screen. The caller reads keys (from
evdev at a console, from the display's keyboard in a window) and hands
in a display (gfx.drm_display or gfx.x11_display).
| Name | Signature |
|---|---|
| ticker | ticker(fps, opts) -> ticker |
| loop | loop(opts) -> handle |
ticker(
fps,opts) ->ticker
Create a frame ticker for a custom loop
For a caller that drives its own loop body; gfx.loop paces its frames on
one. fps defaults to 30. Returns { step = <seconds>, tick = fn }.
ticker:tick() sleeps to the next frame boundary and returns the seconds
since the previous tick and the clock's time; the first tick returns 0 at
once. A tick behind its boundary by less than a step sleeps nought, which
still yields to the tasks sharing the loop; one behind by more starts the
boundary again from now. Call it inside lev.run.
| opts field | description |
|---|---|
clock | what the ticker reads time from, a table with now() and sleep(seconds, token); lev by default. A test hands in one it turns by hand |
token | a cancel token the sleep takes; a tick whose sleep it cancels returns nil and "cancelled", and gfx.loop ends there |
loop(
opts) ->handle
Create a fixed-timestep frame loop
| opts field | description |
|---|---|
fps | frames per second and update rate, default 30 |
update | fn(dt, t): one fixed step of dt = 1/fps; t is the clock's now() |
render | fn(canvas): draws the frame |
canvas | the canvas passed to render |
display | a display as gfx.display names one, its update called after each render; or nil |
setup | fn(handle): runs once before the first frame |
teardown | fn(handle): runs once after the last frame |
max_frame | the longest frame time counted, in seconds, default 0.25 |
clock | what the loop reads time from: now() and sleep(seconds, token); default lev |
A slow frame runs update more than once to catch up, at most 8 times.
max_frame caps the time a stall adds. Debt past the cap is dropped: the
frame after a stall starts even.
The loop never opens or destroys the display; its owner does.
setup runs inside the loop's lev.run, after the cancel token exists.
A task spawned from setup on handle:token() ends when the loop ends,
on stop or on an error in a frame. Use it to read keys or feed audio
beside the frame. Errors in setup and teardown are swallowed.
| handle member | description |
|---|---|
h:run() | run inside its own lev.run; blocks until stopped. Returns nil and the error when update, render or the display raised |
h:start() | spawn the frame task into a running lev.run and return |
h:stop() | stop after the current frame; cancels the token |
h:pause(), h:resume() | stop and restart updates and rendering; fps_actual counts from the resume |
h:is_running(), h:is_paused() | state |
h:token() | the cancel token stop cancels; nil before the loop runs |
h.fps_actual | frames drawn in the last second |
h.canvas, h.display | what opts gave |