Coroutine Schedulers
Prerequisites: Coroutines
Games and servers use coroutines to run many "tasks" that each wait for something — a timer, an animation, a network reply — without callbacks.
local tasks = {}
local tick = 0
local function spawn(name, fn)
tasks[#tasks + 1] = {name = name, co = coroutine.create(fn), wakeAt = 0}
end
local function wait(ticks)
coroutine.yield(ticks)
end
local function run()
while #tasks > 0 do
tick = tick + 1
for i = #tasks, 1, -1 do
local t = tasks[i]
if tick >= t.wakeAt then
local ok, ticks = coroutine.resume(t.co)
if not ok then error(ticks) end
if coroutine.status(t.co) == "dead" then
table.remove(tasks, i)
else
t.wakeAt = tick + ticks
end
end
end
end
end
spawn("blink", function()
for i = 1, 3 do
print(tick, "blink " .. i)
wait(2)
end
end)
spawn("door", function()
print(tick, "door opening")
wait(3)
print(tick, "door open")
end)
run()
1 door opening
1 blink 1
3 blink 2
4 door open
5 blink 3
Each task reads like straight-line code ("blink, wait 2 ticks, blink
again"), but the tasks interleave. In a game, run would advance once per
frame; in OpenResty, the equivalent wait is a network call.
Where you'll see this
- Roblox —
task.wait()andtask.spawnare coroutine-based. - LÖVE — libraries like
hump.timerandknife.coroutinescript sequences with coroutines. - OpenResty — each request runs in a coroutine; I/O calls yield while waiting, so Nginx serves thousands of requests on one thread.
- Neovim — async plugins use coroutines to await jobs and callbacks.