Assignment Examples
Practical, scenario-based examples for every public API in Assignment. Each section demonstrates realistic usage patterns including argument forwarding, cancellation, and edge cases.
Spawn
Pooled Family. Runs work immediately. Recycled execution and automatic cleanup. Best for short, self-contained tasks.
Scenario A — Fire-and-forget callback
-- Run a non-blocking save without stalling the current frame.
-- Execution continues on the next line immediately.
Assignment.Spawn(function()
DataService:SavePlayerData(player)
end)
Scenario B — Resume an existing thread with arguments
local pendingThread = coroutine.create(function(value: number)
print("Resumed with:", value) --> "Resumed with: 42"
end)
Assignment.Spawn(pendingThread, 42)
Scenario C — Brief yield inside a pooled call
-- A short yield is fine here — the work finishes quickly
-- and the shared worker is free again as soon as it does.
Assignment.Spawn(function()
Assignment.Wait(0.1)
applyStatusEffect(player, "Stunned")
end)
SpawnIsolated
Isolated Family. Runs work immediately at full engine priority in a dedicated thread. Bypasses all throttling. Returns a thread cancellation token. Use when work will sleep for a long time or involve repeated slow yields.
Scenario A — Long-polling loop with multi-second intervals
-- This loop sleeps for 30 seconds each iteration.
-- Running it through Spawn would keep the shared worker occupied
-- for that entire sleep, leaving all other short incoming work without
-- a free worker to reuse. SpawnIsolated gives this task its own
-- dedicated engine thread so the shared worker is never involved.
local serviceThread = Assignment.SpawnIsolated(function()
while gameActive do
Assignment.Wait(30)
syncLeaderboardData()
end
end)
onGameEnd:Connect(function()
Assignment.Cancel(serviceThread)
end)
Scenario B — Multi-step workflow with slow async calls
-- Each DataStore call may take several seconds to complete.
-- Isolating this work keeps the shared worker free the entire time
-- so short tasks elsewhere in your code are never delayed.
Assignment.SpawnIsolated(function()
local profileData = DataService:LoadProfile(player)
Assignment.Wait(0) -- yield one frame between steps; lets other work breathe
local inventoryData = DataService:LoadInventory(player)
Assignment.Wait(0)
applyLoadedData(player, profileData, inventoryData)
end)
Scenario C — Isolated thread with external cancellation
local monitorThread = Assignment.SpawnIsolated(function()
while true do
Assignment.Wait(5)
checkServerHealth()
end
end)
onServerShutdown:Connect(function()
Assignment.Cancel(monitorThread)
end)
Defer
Pooled Family. Queues work for the next available frame. Large backlogs are spread across frames automatically — no single frame is overwhelmed. Full Handle-based cancellation.
Scenario A — Defer heavy setup out of module load
-- Avoid blocking the current frame with expensive setup.
-- This runs on the very next frame after this code executes.
Assignment.Defer(function()
buildLookupTable()
cachePlayerData()
end)
Scenario B — Cancel a deferred call before it fires
local handle = Assignment.Defer(function()
sendWelcomeNotification(player)
end)
-- Player disconnected before the next frame
if not player:IsDescendantOf(game) then
handle:Break()
end
Scenario C — Defer a thread with arguments
local handshakeThread = coroutine.create(function(sessionId: string, token: string)
validateHandshake(sessionId, token)
end)
local handle = Assignment.Defer(handshakeThread, sessionId, authToken)
Scenario D — Spreading a large batch of work across frames
-- Deferring many calls at once lets Assignment spread them across frames
-- rather than processing everything in one spike.
for _, system in ipairs(systemsToInit) do
Assignment.Defer(system.Initialize, system)
end
DeferIsolated
Isolated Family. Queues work for the next available frame at full engine priority. Bypasses frame-spread throttling. Returns a thread cancellation token. Use when the deferred work will then sleep for a long time.
Scenario A — Deferred batch of slow async writes
-- Each DataStore write may yield for several seconds.
-- DeferIsolated queues the batch to the next frame but gives it
-- a dedicated engine thread, so the shared worker is never occupied
-- during any of those writes.
Assignment.DeferIsolated(function()
for _, record in ipairs(pendingRecords) do
DataService:Write(record)
Assignment.Wait(0) -- breathe between writes
end
end)
Scenario B — Deferred isolated call with cancellation
local thread = Assignment.DeferIsolated(function()
local result = HttpService:GetAsync(endpoint)
processResult(result)
end)
-- If the request is no longer needed before the next frame fires
if requestAborted then
Assignment.Cancel(thread)
end
Delay
Pooled Family. Schedules work to run after a set duration. Cancelled work is silently discarded before it ever runs. Zero and negative durations queue on the next available frame, same as Defer.
Scenario A — Ability cooldown reset
setAbilityEnabled(player, "Dash", false)
local cooldownHandle = Assignment.Delay(5, function()
setAbilityEnabled(player, "Dash", true)
end)
playerCooldowns[player] = cooldownHandle
Scenario B — Cancel a delayed effect when conditions change
local explosionHandle = Assignment.Delay(3, triggerExplosion, bombPosition)
onBombDefused:Connect(function()
explosionHandle:Break()
-- triggerExplosion will never run
end)
Scenario C — Resume a thread after a delay with arguments
local resultThread = coroutine.create(function(hitPosition: Vector3)
spawnImpactEffect(hitPosition)
end)
Assignment.Delay(0.2, resultThread, hitPos)
Scenario D — Passing zero or a negative duration
-- Delay(0, ...) runs on the next available frame — same as Defer.
-- Useful when you want "as soon as possible" with a cancellable Handle.
local handle = Assignment.Delay(0, function()
applyImmediateEffect(player)
end)
-- Negative values behave identically.
local handle2 = Assignment.Delay(-99, function()
applyImmediateEffect(player)
end)
-- Both can be cancelled before the next frame fires.
handle:Break()
handle2:Break()
DelayIsolated
Isolated Family. Schedules work after a set duration at full engine priority in a dedicated thread. Bypasses all throttling. Returns a thread cancellation token. Use when the work that fires after the delay will sleep for a long time.
Scenario A — Delayed multi-step async operation
-- Each step involves a slow async call that may take several seconds.
-- Running this through the standard Delay would keep the shared worker
-- occupied for every one of those yields. DelayIsolated gives this
-- work its own dedicated engine thread so the shared worker stays free.
local operationThread = Assignment.DelayIsolated(10, function()
local snapshot = DataService:LoadSnapshot(serverId)
Assignment.Wait(0) -- breathe between steps
ReplicationService:Push(snapshot)
end)
onShutdown:Connect(function()
Assignment.Cancel(operationThread)
end)
Scenario B — Delayed spawn with async setup and cancellation
local spawnThread = Assignment.DelayIsolated(5, function()
local config = ConfigService:Fetch(entityType) -- slow async call
spawnEntity(spawnPoint, config)
end)
onZoneCleared:Connect(function()
Assignment.Cancel(spawnThread)
end)
Wait
Pauses the current thread for a duration and returns the actual time elapsed. Works correctly whether called from a pooled or isolated thread — no special handling needed.
Scenario A — Timed loop using elapsed time for smooth interpolation
local startTime = os.clock()
while true do
Assignment.Wait(0.05)
local t = math.min((os.clock() - startTime) / 2, 1)
setAlpha(lerp(0, 1, t))
if t >= 1 then break end
end
Scenario B — Passing nil — one-frame yield
-- Assignment.Wait() with no argument pauses for exactly one frame.
-- Useful for letting other queued work run before continuing.
Assignment.Spawn(function()
startPhaseOne()
Assignment.Wait() -- yields one frame
startPhaseTwo()
end)
Scenario C — Passing zero — identical to nil
-- Assignment.Wait(0) behaves the same as Assignment.Wait(nil).
-- Both yield for one frame. Use whichever reads more clearly.
Assignment.Spawn(function()
for _, step in ipairs(initSteps) do
step()
Assignment.Wait(0) -- breathe between steps
end
end)
Scenario D — Passing a negative value
-- Negative durations are treated the same as zero — one-frame yield, no error.
Assignment.Spawn(function()
Assignment.Wait(-99) -- same as Wait(0)
continueWork()
end)
Scenario E — Called from inside an isolated thread
-- Assignment.Wait detects the context automatically.
-- Inside an isolated thread it defers to the engine's native wait.
-- No special handling needed at the call site.
Assignment.SpawnIsolated(function()
while active do
Assignment.Wait(60) -- works correctly; no need to think about context
runHourlyMaintenance()
end
end)
Repeat
Calls a function at a fixed interval for a set number of iterations or indefinitely.
Scenario A — Finite countdown with early exit
local countdownHandle = Assignment.Repeat(10, 1, function(iteration: number, handle: Assignment.Handle)
local remaining = 10 - iteration + 1
updateCountdownUI(remaining)
if roundEnded then
handle:Break() -- stop the loop early if the round ends before 10 ticks
end
end)
Scenario B — Infinite loop with external cancellation
local pollHandle = Assignment.Repeat(-1, 30, function(_iteration: number, _handle: Assignment.Handle)
refreshLeaderboard()
end)
onGameModeEnd:Connect(function()
pollHandle:Break()
end)
Scenario C — Forwarding arguments to the callback
Assignment.Repeat(6, 5, function(iteration: number, handle: Assignment.Handle, target: Player, amount: number)
grantResource(target, amount)
if target.Parent == nil then
handle:Break() -- player left; stop awarding
end
end, player, 10)
Scenario D — Zero or nil interval — one-frame yield between iterations
-- Passing nil or 0 as the interval yields one frame between iterations
-- rather than a timed wait. Useful for batch processing that should stay
-- responsive but run as fast as possible.
local batchHandle = Assignment.Repeat(#recordsToProcess, nil, function(iteration: number, _handle: Assignment.Handle)
processRecord(recordsToProcess[iteration])
end)
Cancel
Stops a scheduled task before it runs. Accepts a Handle from any pooled call or a thread from any isolated call.
Scenario A — Cancel a handle when a player disconnects
local respawnHandle = Assignment.Delay(5, respawnPlayer, player)
player.AncestryChanged:Connect(function()
if player.Parent == nil then
Assignment.Cancel(respawnHandle)
end
end)
Scenario B — Cancel an isolated thread by reference
local thread = Assignment.DelayIsolated(3, doLongAsyncWork)
onConditionChanged:Connect(function()
Assignment.Cancel(thread)
end)
Scenario C — Passing nil is safe
-- Assignment.Cancel silently does nothing when passed nil.
-- Safe to call even if the handle was never assigned.
local handle: Assignment.Handle? = nil
if someCondition then
handle = Assignment.Delay(2, doWork)
end
Assignment.Cancel(handle :: any)
Migrate
Hands all pending scheduled work to the engine's native scheduler, preserving remaining time for each task.
Scenario A — Automatic shutdown (no action required)
-- Assignment handles this automatically when the server closes.
-- All pending work is handed off with remaining time intact.
-- No code is needed — shown here for reference only.
Scenario B — Manual migration for a controlled shutdown sequence