Optional — the most distinctively Lua thing in the language, and the one with no real equivalent in most others.
Every function you have written runs from top to bottom and then stops. A coroutine is a function that can pause in the middle, hand control back, and later carry on from exactly where it left off — with all its local variables still in place.
local function sequence() print("step 1") coroutine.yield() print("step 2") coroutine.yield() print("step 3")end
local co = coroutine.create(sequence)
print("status:", coroutine.status(co))coroutine.resume(co)coroutine.resume(co)coroutine.resume(co)print("status:", coroutine.status(co))status: suspendedstep 1step 2step 3status: deadThree functions do all the work:
coroutine.create wraps a function up. It does not run it — note that nothing printed until the first resume.coroutine.resume starts it, or continues it from its last pause.coroutine.yield pauses from the inside and returns control to whoever resumed.This is not a thread. Nothing runs at the same time as anything else, there is no scheduler, and you never need a lock. Control moves between two places, and only one of them is ever running. That is why coroutines are easy to reason about and threads are not.
coroutine.wrap hands you a plain function instead of a coroutine object. Call the function to resume it, and whatever it yields comes straight back.
local squares = coroutine.wrap(function() for i = 1, 4 do coroutine.yield(i * i) endend)
print(squares())print(squares())14Look at what that is. A function you can keep calling, which remembers where it got to and produces the next value. That is exactly the iterator contract from Activity 1.8 — so a wrapped coroutine can drive a for ... in loop directly, and when it finishes it returns nil and the loop stops on its own.
The difference from a closure iterator is what you can express. A closure has to reconstruct its position from stored variables every call; a coroutine just keeps standing where it was. For walking a nested structure, that is the difference between a bookkeeping exercise and a recursive function with a yield in it.
wrap has one cost: create plus resume reports errors by returning false, like pcall, whereas a wrapped one raises them at you. Use wrap for iterators and create when you want to check the status.
Here is the problem coroutines solve. A game runs one function per frame, sixty times a second. You want to write: open the door, wait two seconds, sound the alarm, wait one more, cut the lights.
Without coroutines that becomes a state machine — a phase variable, a timer, and a growing chain of elseif — and the sequence you wanted is scattered across it. With coroutines you write the sequence.
local function wait(seconds) local elapsed = 0 while elapsed < seconds do elapsed = elapsed + coroutine.yield() endend
local cutscene = coroutine.create(function() print("Door opens") wait(2) print("Alarm sounds") wait(1) print("Lights out")end)
for frame = 1, 8 do if coroutine.status(cutscene) ~= "dead" then coroutine.resume(cutscene, 0.5) endendDoor opensAlarm soundsLights outThe loop stands in for a game's frames, each one worth half a second. The value passed to resume comes back out of yield inside the coroutine, which is how wait learns how much time went by. Values travel both directions.
In Unit 2 the frame loop is love.update(dt) and you would resume with the real dt. The cutscene function would not change at all.
Two things to remember. A dead coroutine cannot be resumed — check the status or you get a false and a "cannot resume dead coroutine" message. And you cannot yield from inside a normal function called by C code, which in practice means avoid yielding from inside a table.sort comparison or a metamethod.
Start a fresh main.lua.
coroutine.yield() between each.-- main.lua - coroutines
local function sequence() print("step 1") coroutine.yield() print("step 2") coroutine.yield() print("step 3")end
local co = coroutine.create(sequence)
print("status:", coroutine.status(co))coroutine.resume(co)coroutine.resume(co)coroutine.resume(co)print("status:", coroutine.status(co))status: suspendedstep 1step 2step 3status: deadcoroutine.wrap to make something that yields square numbers, and call it twice.for n in gen do.-- main.lua - coroutines
local function sequence() print("step 1") coroutine.yield() print("step 2") coroutine.yield() print("step 3")end
local co = coroutine.create(sequence)
print("status:", coroutine.status(co))coroutine.resume(co)coroutine.resume(co)coroutine.resume(co)print("status:", coroutine.status(co))
local squares = coroutine.wrap(function() for i = 1, 4 do coroutine.yield(i * i) endend)
print(squares())print(squares())
local gen = coroutine.wrap(function() for i = 1, 3 do coroutine.yield(i) endend)
for n in gen do print("got", n)endstatus: suspendedstep 1step 2step 3status: dead14got 1got 2got 3wait(seconds) that keeps yielding until enough time has been handed to it.-- main.lua - coroutines
local function sequence() print("step 1") coroutine.yield() print("step 2") coroutine.yield() print("step 3")end
local co = coroutine.create(sequence)
print("status:", coroutine.status(co))coroutine.resume(co)coroutine.resume(co)coroutine.resume(co)print("status:", coroutine.status(co))
local squares = coroutine.wrap(function() for i = 1, 4 do coroutine.yield(i * i) endend)
print(squares())print(squares())
local gen = coroutine.wrap(function() for i = 1, 3 do coroutine.yield(i) endend)
for n in gen do print("got", n)end
local function wait(seconds) local elapsed = 0 while elapsed < seconds do elapsed = elapsed + coroutine.yield() endend
local cutscene = coroutine.create(function() print("Door opens") wait(2) print("Alarm sounds") wait(1) print("Lights out")end)
for frame = 1, 8 do if coroutine.status(cutscene) ~= "dead" then coroutine.resume(cutscene, 0.5) endendwait return how much it overshot by, and have the caller print it.for v in .... Recursion plus yield makes this about six lines; try it as a closure iterator afterwards and see how much harder it gets.status: suspendedstep 1step 2step 3status: dead14got 1got 2got 3Door opensAlarm soundsLights outThree lines from a function that paused twice, across eight frames it knew nothing about.