Wednesday, August 25, 2010

DisplayObject inheritance in Corona

In Corona SDK, DisplayObjects are actually native C++ objects under the hood. Lua makes it relatively easy to expose native functionality in the Lua language, but then a Lua "userdata" loses the "object-oriented" capabilities of a Lua table.

Corona goes way beyond the simple solution with this problem; a Corona DisplayObject behaves partially as a normal Lua table, giving you the ability to add properties to it. This is a great convenience, but because of the way Lua is implemented, it cannot give you the full ability to "override" methods and properties that Lua can for generic tables.

Here's a more detailed description and example.

Corona Display Objects are actually C++ native objects internally. The way Lua implements binding native pointers is with "userdata", which sets a metatable, used for looking up methods and properties, giving you a very powerful kind of extensibility. However, userdata metatables cannot be replaced by Lua code; this is a measure defined by Lua (see "Programming in Lua" for details).

Normally, native objects bound to Lua in this way do not behave like tables. There are a number of examples in Corona of this. They may have properties and/or methods but are not extensible. The fact that Display Objects behave partly like tables is a convenience feature. The alternative would have been having a DisplayObject field for custom data, allowing you to access your own data associated with a DisplayObject, for example. Raising this functionality directly into the DisplayObject is conceptually simpler (although more work to implement). But the underlying native bridge is constrained by the way Lua implements it, and we view this as a feature, not a bug, since allowing an instance of a native object to selectively override parts of its interface could be profoundly destabilizing and possibly insecure.

Meanwhile, it is possible to "extend" Corona DisplayObjects as tables, by using Lua's built in features. This is not exactly like Lua's normal capabilities, because we can't replace the DisplayObject metatable in this case. However, it is very useful if you want to extend DisplayObjects.

So here's a code example of how to accomplish this. An image is loaded by makeProxy(), but the actual object returned is a table. Any properties/methods not defined by the proxy table are forwarded to the underlying DisplayObject.

The basic method is described in

function makeProxy(params)

local t = display.newImage(params.background)

local _t = t

-- create proxy
local t = {}

-- create metatable
local mt = {
__index = function (t,k)
print("*access to element " .. tostring(k))
return _t[k] -- access the original table

__newindex = function (t,k,v)
print("*update of element " .. tostring(k) ..
" to " .. tostring(v))
_t[k] = v -- update original table


setmetatable(t, mt)

print("bar on you!")

return t

aPuppy = makeProxy({background="puppy.jpeg"})

aPuppy.y = 200

function aPuppy:foo()

print("foo on you!")



Don said...

Does this method actually work? I get an error when I try to access a display object method.

For instance if I create a newCircle and then try to setFillColor on it via the proxy I get a Proxy is nil error.


local function newCircle(_x, _y, _radius)
local c = display.newCircle(_x, _y, _radius)
local proxy = {}
local mt = {
__index = function(_t, _k)
return c[_k]
, __newindex = function(_t, _k, _v)
c[_k] = _v
setmetatable(proxy, mt)
return proxy

local circle = newCircle(0, 0, 100)
circle:setFillColor(255, 0, 0)

-- you get an error because the method setFillColor is returned for the display object but circle is applied as the first parameter which throws the error.

Don said...

Here's how I got it to work with an __index function that looks like this.

function(_t, _k)
if disp[_k] then
if type(disp[_k]) == 'function' then
return function(...) arg[1] = disp; disp[_k](unpack(arg)) end
return disp[_k]
return nil

Greg Hauptmann said...

what about a non-metatable based approach like here?

Would this give the same results and be a bit easier to implement / read.