PICO-8 Generic Dispatch with Parameters

PICO-8 has this nice helper function called foreach(table, func) which will call the function for each item. I like to use this in my _draw function to dispatch drawing to game objects. The trouble is that most of the time I want to call a method (table-bound function) instead of a function. Normally this required a helper function per-method to invoke.

Consider the following PICO-8 code (it’s just Lua with some sugar).

-- returns a table with table-bound method 'draw'
function dot(x, y)
 return {x=x, y=y,
  draw=function(self,col)
   local c=col or 7
  	pset(self.x,self.y,c)
  end
 }
end

-- returns a function with captured upvalues
function dot2(x, y)
 return function(col)
  local c=col or 7
  pset(x,y,c)
 end
end

function _init()
 drawable={}
 add(drawable, dot(64, 64))
 add(drawable, dot2(64, 68))
end

In this example, I’m showing two different ways to make “drawable” instances. dot returns its instance as a table with a method named draw. dot2 returns a function which draws itself when invoked. I’ll start with the dot2 case.

function invoke(o)
 o()
end

function _draw()
 cls()
 foreach(drawable, invoke)
end

The nice thing about the approach of making drawable a function closure is that you can make a generic invoke helper function which just calls instance. This means you only need a single helper function. The downside is that you can only return one callable behavior this way which isn’t ideal for more complex objects.

function call_draw(o)
 o:draw()
end

function _draw()
 cls()
 foreach(drawable, call_draw)
end

This approach is more flexible and allows for more methods to be defined, but now you need a call_foo helper for each different method to call. Another problem with both approaches is you can’t pass in arguments to the method calls.

It turns out that Lua gives us a ton of flexibility to build our generic dispatch. Let’s start with the problem of dispatching by name.

-- Call Method
function callm(method)
 return function(o)
  o[method](o)
 end
end

function _draw()
 cls()
 foreach(drawable, callm("draw"))
end

callm is a pretty simple helper. The core of it just does a table lookup by name and invokes the result. callm returns a function because foreach expects a function that takes the object being iterated. There are a few problems with this approach. First this crashes if there isn’t a draw method on the instance. Second, the dot.draw method takes an argument and there’s no way to pass one in.

-- Call Method
function callm(method, ...)
 local params={...}
 return function(o)
  if type(o)=="table" then
   local m=o[method]
   if type(m)=="function" then
    m(o,unpack(params))
   end
  end
 end
end

function _draw()
 cls()
 foreach(drawable, callm("draw", 10))
end

This version adds support for varargs that get forwarded to the method when the returned function is called. This allows parameters to be passed to the method. This also adds some type checking to ensure the instance passed in is a table and has a function named method. You can see the foreach call site looks pretty good.

Finally, if you want to continue to use callable objects instead of tables, we can take this same concept and make it work with those too.

-- Call function 'object'
function call(...)
 local params={...}
 return function(o)
  if type(o)=="function" then
   o(unpack(params))
  end
 end
end

function _draw()
 cls()
 foreach(drawable, call(10))
end

Final thoughts

This adds overhead to the calls, and it’s almost certainly more efficient to use for x in all(XS). That said, it’s a neat little piece of code and I think it’s pretty cool that it can be accomplished with Lua.

Try out the code.
function _init()
 game_items={}
 -- add items
end

function _update()
 -- call 'update' on each instance that implements it
 foreach(game_items, callm('update')
end

function _draw()
 -- call 'draw' on each instance that implements it
 foreach(game_items, callm('draw')
end

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s