Skip to content

Clock ​

Clock: the one source of time for Flux and the code built on it. In a game it is the engine's own: now is os.clock(), wall is the server's synchronized time, and delay, wait, defer, cancel and connect are task.* and the RunService frame signals. Each call costs one branch over calling those directly.

Tests switch it to a manual clock (manual(true); headless Luau is always manual), where time only moves when the test says so: step(seconds) advances it in frames and runs what came due, in order.

luau
local Clock = Flux.Clock
Clock.manual(true)
Clock.delay(1, function() print("one second later") end)
Clock.step(1) -- prints, in 60 frames of 1/60 s

A manual frame of dt seconds:

  1. advances now by dt and resumes every timer and wait now due, earliest deadline first (ties in the order they were scheduled); a timer scheduled for zero seconds while they run waits for the next frame, like the engine's;
  2. calls the PreRender/RenderStepped subscribers with dt;
  3. steps Flux itself: every spring and tween by dt, then a Flux.flush();
  4. calls the Heartbeat subscribers with dt;
  5. after each of those phases, drain()s the work deferred with defer.

reset() drops every pending timer, wait, deferred call and manual subscriber (between tests); now keeps counting, so time never runs backwards. wall() on a manual clock starts at 2026-01-01T00:00:00Z.

Types ​

Phase type ​

luau
type Phase = "PreRender" | "RenderStepped" | "Heartbeat"

Functions ​

now ​

luau
Clock.now(): number

Seconds on a monotonic clock, for measuring intervals: os.clock() in a game, virtual time on a manual clock.

wall ​

luau
Clock.wall(): number

Unix time in seconds, for dates and countdowns shared between players: workspace:GetServerTimeNow() in a game, 2026-01-01T00:00:00Z + now() on a manual clock.

delay ​

luau
Clock.delay<A...>(seconds: number?, fn: ((A...) -> ...any) | thread, ...: A...): thread

Runs fn(...) (or resumes a thread) after seconds, like task.delay. Returns the thread, for cancel.

wait Yields ​

luau
Clock.wait(seconds: number?): number

Yields the running thread for seconds (default: one frame), like task.wait, and returns the time it waited. On a manual clock it waits for a step to pass the deadline, so it needs a coroutine to yield.

defer ​

luau
Clock.defer<A...>(fn: ((A...) -> ...any) | thread, ...: A...): thread

Runs fn(...) (or resumes a thread) once the current work finishes, like task.defer. On a manual clock it waits for drain() (which every step frame calls). Returns the thread, for cancel.

cancel ​

luau
Clock.cancel(thread: thread)

Cancels a thread from delay, defer or a wait, like task.cancel.

connect ​

luau
Clock.connect(phase: Phase, fn: (dt: number) -> ()): () -> ()

Calls fn(dt) every frame at phase ("PreRender", "RenderStepped" or "Heartbeat"). Returns a function that disconnects it. A manual clock calls it from step instead of the engine's frames.

manual ​

luau
Clock.manual(on: boolean)

Tests only: turns the manual clock on or off. Headless Luau is always manual (manual(false) does nothing there). Turning it on starts virtual time at os.clock(); turning it off drops what was pending, like reset().

isManual ​

luau
Clock.isManual(): boolean

Tests only: whether the clock is manual.

drain ​

luau
Clock.drain()

Tests only: runs deferred work until none is left, including work it defers in turn. Errors after 10000 calls, when deferred work keeps deferring more.

step ​

luau
Clock.step(seconds: number, frameLength: number?)

Tests only: advances a manual clock by seconds, in frames of at most frameLength seconds (default 1/60). step(0) runs one frame of zero length: due timers, subscribers, a Flux step and the deferred work.

reset ​

luau
Clock.reset()

Tests only: drops every pending timer, wait, deferred call and manual subscriber. now keeps its value.