coroutine
The complete coroutine library. Coroutines are the sanctioned way to
write multi-step logic that spans frames — yield inside, resume from a
hook.
Creation
Section titled “Creation”coroutine.create(f)
Section titled “coroutine.create(f)”Wraps function f into a coroutine. Nothing runs until the first resume.
local co = coroutine.create(function(a, b) print("running", a, b)end)print(coroutine.status(co)) --> suspendedcoroutine.wrap(f)
Section titled “coroutine.wrap(f)”Like create, but returns a callable — calling it resumes the coroutine.
Errors propagate to the caller (no ok flag).
local step = coroutine.wrap(function() for i = 1, 3 do print("step", i) coroutine.yield() endend)step() --> step 1step() --> step 2step() --> step 3Running
Section titled “Running”coroutine.resume(co, ...)
Section titled “coroutine.resume(co, ...)”Starts or continues the coroutine; arguments pass to the function (first
resume) or become the yield() return values (later resumes). Returns
ok plus the yield/return values or the error message.
local co = coroutine.create(function(x) local got = coroutine.yield(x * 2) print("resumed with", got)end)print(coroutine.resume(co, 21)) --> true 42print(coroutine.resume(co, "hi")) --> true (prints: resumed with hi)coroutine.yield(...)
Section titled “coroutine.yield(...)”Pauses the coroutine, sending values to the resumer. When resumed, the
arguments of resume come back as yield’s return values.
local co = coroutine.create(function() local answer = coroutine.yield("ask me") print("got", answer)end)local _, question = coroutine.resume(co)coroutine.resume(co, 42) --> got 42coroutine.status(co)
Section titled “coroutine.status(co)”One of "suspended", "running", "normal" (resumed another
coroutine), or "dead".
local co = coroutine.create(function() coroutine.yield() end)coroutine.resume(co)print(coroutine.status(co)) --> suspendedcoroutine.resume(co)print(coroutine.status(co)) --> deadcoroutine.running()
Section titled “coroutine.running()”Returns the running coroutine (or nil on the main thread) and a
boolean: is this the main thread?
local co = coroutine.create(function() local self, isMain = coroutine.running() print(isMain) --> falseend)coroutine.resume(co)print(coroutine.running()) --> nil truecoroutine.isyieldable()
Section titled “coroutine.isyieldable()”True inside a coroutine that can yield right now.
print(coroutine.isyieldable()) --> false (main thread)Cleanup (Luau)
Section titled “Cleanup (Luau)”coroutine.close(co)
Section titled “coroutine.close(co)”Closes a suspended (or never-started) coroutine, running any pending
defer/cleanup, and puts it in the dead state. Closing a running or
dead coroutine errors.
local co = coroutine.create(function() coroutine.yield() end)coroutine.resume(co)print(coroutine.close(co)) --> trueprint(coroutine.status(co)) --> deadThe frame-driven pattern
Section titled “The frame-driven pattern”The bread-and-butter use in mods — stateful sequences without callbacks:
-- blink a warning: 0.5s on, 0.5s off, foreverlocal blink = coroutine.wrap(function() while true do print("ON") coroutine.yield(0.5) -- wait 0.5s print("OFF") coroutine.yield(0.5) endend)
local wait = 0usems.hook("frame", function(dt) wait = wait - dt if wait <= 0 then wait = blink() or 0 -- yield returns the next delay endend)