Builtin Library
README.md
queue.module
queue(fn) -> (ok, err) — defer FFI mutations to be applied across frames.
Why
Inside the body of queue(...), write FFI calls (entity.spawn,
component.add, localPosition.set, etc.) enqueue rather than execute
synchronously. The engine drainer applies them in FIFO order with a per-frame
budget so a 10k-spawn batch resolves in ~10 ticks instead of blowing one
frame's CPU and GPU upload bandwidth.
Usage
local ok, err = queue(function()
for i = 1, 1000 do
local e = entity.spawn("cube_" .. i)
e.component.add("Model", { mesh = "cube" })
end
end)
assert(ok, err)
The callback returns immediately. Entities materialize over the next several
frames. task.wait(0.1) (or longer) lets you observe the drained state.
Semantics
- Write deferral. All write FFI calls (
entity.spawn,component.add,__native.insert,localPosition.set, etc.) push onto a cross-frame ring instead of running their synchronous handlers. - Halt-on-error (default). If
fnthrows, the partial batch is cleared — none of the deferred writes apply. The error is returned as(false, err). - Nested no-op.
queue(function() queue(function() ... end) end)runs the inner body in the already-active scope — depth counter is re-entrant. - Reads are NOT fenced (Stage 1). Reading inside
queue(...)may see stale state because the writes have not drained yet. Stage 2 of the plan adds yieldable read fences with per-key intersection.
Internals
queue is a thin Luau wrapper around __native_queue.{enter, exit, depth, clear_my_pending} (see
crates/zero_scripting/src/ffi/bindings/native_queue.rs). The cross-frame
ring lives in crates/zero_scripting/src/ffi_callbacks.rs as
PENDING_DEFERRED_MUTATIONS. The drainer is wired into
process_script_mutations_router in src/engine/systems/scripting/mutations.rs
at iteration 0 of each tick.
See docs/plans/2026-05-06-luau-queue-deferred-mutations.md for the design.