gfx.loop

index ยท gfx

Overview

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).

Functions

NameSignature
tickerticker(fps, opts) -> ticker
looploop(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 fielddescription
clockwhat 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
tokena 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 fielddescription
fpsframes per second and update rate, default 30
updatefn(dt, t): one fixed step of dt = 1/fps; t is the clock's now()
renderfn(canvas): draws the frame
canvasthe canvas passed to render
displaya display as gfx.display names one, its update called after each render; or nil
setupfn(handle): runs once before the first frame
teardownfn(handle): runs once after the last frame
max_framethe longest frame time counted, in seconds, default 0.25
clockwhat 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 memberdescription
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_actualframes drawn in the last second
h.canvas, h.displaywhat opts gave