Builtin Library
README.md
autoTick
Opt-in tick loop for zinput. Spawns a task.spawnSystem worker that
invokes a registered tick function (Zin.tick) once per frame while the
input layer has work, and waits for the input layer's next change
(__waitChange("input"): an input event, a moved input snapshot, an
armed observation, a mapping-layer mark) once a tick finds it at rest
(Zin.atRest()), so code-mode snippets and tests without a host script
component still drive the stateful subsystems — live binding dispatch, the touch overlay, the
emulation floor, axes, chords, rebind, action handlers, event subscribers.
task.spawnSystem (not task.spawn) makes the loop system-owned, so it
keeps ticking while the editor pauses gameplay.
The first real input read arms this worker on its own. A host script
component can also call Zin.tick(dt) from its update(dt) hook, and
autoTick.start can be called directly; all three are idempotent
alongside each other.
Exports
M.start() -> boolean— spawn the worker if not already running. Returns true when a new loop was started.M.stop()— flag the loop to exit on its next yield. No-op if not running.M.isRunning() -> boolean— current status.M._setTickFn(fn: (number?) -> ())— internal hook. Called once byzinput/init.luauto registerZin.tick.M._setLastTickFrameIdFn(fn: () -> number)— internal hook. Wires the frame-id accessor so the worker can dedup itself against explicit ticks within the same engine frame.M._setAtRestFn(fn: () -> boolean)— internal hook. WiresZin.atRest, the predicate the worker reads after each tick to decide whether to wait for the input layer's next change.
Usage
local Zin = require("@builtin::modules.zinput")
-- One-shot test / code-mode setup:
Zin.autoTick.start()
-- ... later ...
Zin.autoTick.stop()
Notes
- Single-flight: calling
start()twice is a no-op — there is at most one worker per VM. - Cross-coroutine dedup: when an explicit
Zin.tickran in the same engine frame, the worker skips that frame soonHeldhandlers and event subscribers don't double-fire. - Module-local
_runningsurvives Luau require-cache reload, so a re-requiredautoTick.modulestill reports the loop is live.restart_instanceresets the VM and clears everything. start()raises if_setTickFnhasn't been wired yet — alwaysrequire("@builtin::modules.zinput")first.