Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

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 fn throws, 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.