Exploring Lua Coroutines in ComputerCraft

 

A fellow Atom recently introduced me to the Minecraft mod ComputerCraft, which extends the game to include virtual computers that are programmable in Lua and capable of interacting with the game world. Needless to say, I found myself rather preoccupied with Lua that weekend.

Unfortunately, reacting to events in ComputerCraft requires repeatedly polling via a function call. That’s no problem, though, since Lua has first-class functions. I just whipped up a little dispatching code that takes care of the polling and lets you register functions as handlers for the events. Very nice.

-- Delayed rednet echo service
-- Sends the message back after a three second delay

rx.on("rednet_message", function(sender, msg)
  local ourID = os.startTimer(3)
  rx.on {
    event = "timer",
    once = true,
    filter = function(timerID)
      return ourID == timerID
    end,
    f = function()
      rednet.send(sender, msg)
    end
  }
end)

rx.run()

At least, I thought so until I realized what I had done. I’d recreated all the pleasure of Node.js for ComputerCraft. Callback hell! Pyramid of doom!

Fortunately, Lua has coroutines. This was the first time I’d come across the perfect combination of having both a good reason to use them and a language that has good support for them.

A coroutine is a procedure that is able to suspend its execution by yielding back to its caller. When the coroutine is resumed (which does not necessarily need to be done where it was previously invoked), the procedure will continue right where it left off, with all of its state and locals intact.

With this in mind, I wondered, why not wrap event callbacks in coroutines? Then, rather than require event handlers to provide callbacks when they need to wait for future events, I yield back to the event dispatcher with a description of what condition I want met before my handler resumes. At a glance, event handlers would appear to be written synchronously. But behind the scenes, when a handler needs to wait, the dispatcher sets it aside until a relevant future event is received.

rx.on("rednet_message", function(sender, msg)

  rx.wait(3)

  rednet.send(sender, msg)

end)

rx.run()

It turned out to be quite easy and fun to implement. You can check out the source on github. I’ve found Lua to be quite elegant in its minimalism and adherence to its design philosophy, and its support for coroutines gives a relatively unique ability to abstract away some of the pain of asynchronous programming.

Conversation
  • frfe says:

    nice

  • Comments are closed.