Uncommon Lua Features: Coroutines
Last week I talked about how to use Cocoa notifications to connect a Lua environment to a Cocoa UI. This is great for things like this:
When the user clicks a space on the map containing a unit, update the status panel with the specs for that unit
But terrible for things like this:
When the user clicks OK, load the next level out of the level file and send it to a Lua function for pre-processing
Things like that are even more common than the first type, but result in terrible code. What to do?
The problem is that the only way data moves from Cocoa to Lua is when Cocoa fires an event. We can’t call Cocoa from Lua and get a value back. The technical name for this problem is inversion of control: the Lua code is controlling execution but its flow-of-control is driven form Cocoa.
Inversion of control results in code that looks like this:
LM.observe_notification("startBtnClicked", function() LM.post_notification("loadLevel",{name="level_1"}) end) LM.observe_notification("levelLoaded", function(data) local lvl = pre_process(data.level) end)
See, we have to split this into two functions, and it gets even worse: suppose we had some data from the first function we needed in the second one. In that case, we’d have to tunnel it through Cocoa to get it there, because the second function doesn’t remember the first one at all. What a pain.
One way to fix this obviously is to just wire up the Objective C functions we want to call, but there are a couple of problems with that: Lua’s C API want to talk to C, and it won’t be easy to connect it to an Objective C message, and part of the goal of this was to do everything through notifications.
What we really want to do is have the first function send a notification, wait for another notification, and pick up where it left off. We want it to remember where it was in the function and return there, instead of to the beginning of another function. Lua has a method of doing this, which is the same thing it uses for (cooperative) multithreading: coroutines.
A coroutine is a function that has access to a special function called yield
. When you call yield
, the coroutine returns to its caller the value you yielded, then stops. The caller (or anyone else) can later call resume
to pick up right at the yield
call (the value passed to resume
is returned by yield
). For example:
c = coroutine.create(function() n = coroutine.yield(1) -- Second resume call resumes here return n+1 end) print(coroutine.resume(c)) -- Prints 1 print(coroutine.resume(c,5)) -- Prints 6
So, all we have to do is tell our Lua script to, whenever it hears a notification of a certain type, pass the userinfo into a coroutine, wait for the coroutine to yield, and then return until the next notification. We can store all the state we need to in local variables in the coroutine, and the code looks very straightforward.
We re-invert the inversion of control. You can write web servers like this, using a similar feature in some languages called continuations.