Clock
Shared clock for timers, frame callbacks, and deterministic stepping in tests. Delegates to os.clock(), workspace:GetServerTimeNow(), task.*, and RunService in live games, or steps virtual time in manual mode (Clock.manual(true)).
local Clock = Flux.Clock
Clock.manual(true)
Clock.delay(1, function() print("one second later") end)
Clock.step(1) -- advances 1 second in 1/60s framesTypes
Phase type
type Phase = "PreRender" | "RenderStepped" | "Heartbeat"Functions
now
Clock.now(): numberSeconds on a monotonic clock, for measuring intervals: os.clock() in a game, virtual time on a manual clock.
wall
Clock.wall(): numberUnix 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
Clock.delay<A...>(seconds: number?, fn: ((A...) -> ...any) | thread, ...: A...): threadRuns fn(...) (or resumes a thread) after seconds, like task.delay. Returns the thread, for cancel.
wait Yields
Clock.wait(seconds: number?): numberYields 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
Clock.defer<A...>(fn: ((A...) -> ...any) | thread, ...: A...): threadRuns 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
Clock.cancel(thread: thread)Cancels a thread from delay, defer or a wait, like task.cancel.
connect
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
Clock.manual(on: boolean)Toggles manual clock mode for tests (headless Luau is always manual). Disabling manual mode clears pending tasks via Clock.reset().
isManual
Clock.isManual(): booleanTests only: whether the clock is manual.
drain
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
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
Clock.reset()Tests only: drops every pending timer, wait, deferred call and manual subscriber. now keeps its value.