Log inGet started

task

The task namespace — the engine's Luau API reference for task.

The task namespace — 16 functions.

task/active

task.active() -> number

Get number of active (running + waiting + deferred) tasks.

task/await

task.await(promise_id) -> any

Yield until a promise is resolved. Used with mcp_call and other promise-returning APIs.

task/batched

task.batched(count, fn, batch_size?, on_complete?) -> number

Run fn(i) for i = 1..count, yielding to the next frame every batch_size iterations (default 100). Spawns a coroutine and returns its task handle immediately. Use for bulk spawn loops or mass mutations that would otherwise block the VM long enough to trip the watchdog.

task/callerInfo

task.callerInfo(level?) -> string?

Return the formatted "<source>:<line>" of a Luau stack frame, or nil if the frame doesn't exist or has no source. level defaults to 1 (the immediate Lua caller); 2 skips one frame above, and so on. Used by task.loop to tag iteration-error warnings with the spawn site, and useful for any custom error-reporting wrapper since the stock debug.info is gated out of user scope. See gh#3416 / gh#3248.

task/cancel

task.cancel(handle)

Cancel a running or waiting task. The coroutine will not resume. No-op on finished tasks.

task/collect

task.collect(handle?) -> boolean

Free a finished task handle (or all finished handles if no argument). Returns true if any were collected. Finished handles are auto-collected after ~5 seconds.

task/defer

task.defer(fn, ...args) -> number

Schedule fn to run at end of current frame. Returns a task handle that can be used with task.status/task.cancel.

task/delay

task.delay(seconds, fn, ...args) -> number

Schedule fn to run after seconds of real time. Returns a task handle.

task/inflight

task.inflight(label) -> number

Number of scheduler tasks with the given label that have been dispatched and not yet reached a terminal state (finished or cancelled). Component lifecycle hooks dispatch under their hook name — task.inflight("awake") == 0 means every awake() coroutine has settled. The join primitive for "loaded means loaded": scene-load completion polls it so 'done' includes the detached lifecycle work the spawn dispatched, not merely the entity rows.

task/loop

task.loop(fn, dt?) -> number

Spawn a self-healing per-frame loop. Wraps task.spawn(function() while true do pcall(fn); task.wait(dt or 0) end end) — an uncaught error inside fn is caught, surfaced to task.onError (if registered), logged via log.warn, and the loop continues on the next iteration. Use this for the common scene pattern of one long-lived coroutine that drives input/sim/UI per frame; a bad nil-deref on one entity no longer kills the entire loop. See gh#3248.

task/onError

task.onError(handler: ((err: string, source: string?, handle: number) -> ())?)

Register a per-VM error handler invoked when a coroutine spawned via task.spawn / task.defer / task.delay errors. The handler receives (err_message, source_name, handle) and is itself called under pcall — its own errors are caught and logged, never re-raised. Passing nil clears the handler. Use this to surface 'silent freeze' coroutine deaths to scene-side code (e.g. show a SCRIPT ERROR overlay, restart the loop with backoff, escalate to telemetry). See gh#3248.

task/result

task.result(handle) -> any?

Get a finished task's return value, decoded from its lossless JSON capture. Returns nil if the task isn't finished (still running, errored, cancelled, or its result wasn't captured). Pairs with task.status to poll a promoted execute/bash by id without building a /runtime/tasks path.

task/spawn

task.spawn(fn, ...args) -> number

Spawn a new coroutine that runs fn with the given args. Returns a numeric task handle (not a thread) that can be passed to task.cancel/task.status.

task/spawnSystem

task.spawnSystem(fn, ...args) -> number

Like task.spawn, but the coroutine is forced system-owned: it is never gameplay-owned, so it keeps running while the editor pauses gameplay — even when started from a component's awake()/onEnable(). Use for system-level loops (input ticking, watchers, editor tooling) that must survive pause regardless of where they were launched. Nested spawns inherit non-ownership normally.

task/status

task.status(handle) -> string

Get task status: 'running' (executing), 'waiting' (task.wait), 'suspended' (await), 'deferred' (queued), 'finished' (completed), 'cancelled', or 'dead' (unknown handle / aged out of terminal history). A finished task that was auto-collected still reports its terminal status from the terminal-history cache (mirrors task.result), so missing the live finished flip doesn't lose the outcome.

task/wait

task.wait(seconds?) -> number

Yield the current coroutine. Resumes after seconds of real time (default: next frame). Returns actual elapsed time.

  • api
  • reference