Patching Codea objects

I was delighted today to discover that I could add “copy” and “unpack” methods to vector and matrix Codea objects doing:

-- patch copy and unpack methods onto vec2's metatable
local mt = getmetatable(vec2())
mt.copy = function(v) return vec2(v.x, v.y) end
mt.unpack = function(v) return v.x, v.y end

However, I also discovered that you could not do the same with the “color” object. After digging around in the runtime, it looks the reason may be that the vector and matrix objects use luaL_register instead of luaL_openlib (which color uses).

Why is color class treated differently than the other Codea classes? Would it be possible to treat it the same? Or, even better, would it also be possible for you guys to add “copy” and “unpack” methods?

That’s probably an oversight on our part. I am happy to change it, and also add native copy and unpack methods. If you could add it to the issue tracker that would be great.

@Simeon, in addition to, or as an alternative to, a copy function, perhaps (for example) vec4(v) could work if v was itself the result of a vec4 function?

V1 = vec4(); V2 = vec4(V1)

(Update) Also, as the fourth (“w”) co-ordinate defaults to 1, perhaps this could work too:

V1 = vec3(); V2 = vec4(V1)

(2nd update) Thinking back to the redefined math.sin() example in <a = href=“http://www.lua.org/pil/6.1.html”>Programming in Lua, perhaps what I am suggesting is not required, if a coder can do something like this:

do
    local oldvec4 = vec4
    vec4 = function (x, y, z, w)
        -- enhanced vec4 code here, using oldvec4
    end     
end

@mpilgrem: You really want copy and unpack semantics for your native data objects in Lua. For one, what they do is really clear, and for two, they make it much easier to write generic functions for different data types (for example, I’ve written code to synthesize get/set methods for class properties, and it would be great to be able to just specify “copy” or “assign” and have the synthesizer generate methods with the appropriate behavior). I was also thinking that a “set” method would be great, that takes a parameter for each element in the data object (e.g. vec2 takes 2, matrix takes 16). Combined with an unpack method, you could assign the values of one object to another easily without creating unnecessary copies.

I’ve been wondering, @toadkick, why the userdata returned by color() and vec4() behave differently, in the way that you describe - I’m trying to learn more about userdata, metatables and the Codea Runtime Library.

I do not think it is to do with luaL_openlib versus luaL_register - see the latter in lauxlib.c (luaI_openlib means luaL_openlib - see lauxlib.h).

I think it is to do with the default case implemented in the switch in Lget(). (In both cases, __index is set to Lget in the metatable for the userdata).

color defaults to lua_pushnil(L) (put a nil value on the stack).

vec4, on the other hand, defaults to looking up the value for the key in its metatable, and puts that on the stack.

So, you can add additional keys and values to the metatable of the userdata returned by color(), but indexing the userdata with those keys yields nil.

(Update) It appears to me that most of Codea’s other userdata behaves like vec4, but that the touch userdata behaves like color.

(Further update) I added a page to the wiki related to this discussion.

Ah, thanks for the info @mpilgrem. I was just guessing really, I took a cursory glance at the files and that was the first thing I noticed. Your explanation makes sense though.

Thinking further on this, the problem with the userdata returned by color() can be fixed by attaching an enhanced function to the __index key in the userdata’s metatable. For example:

--
-- Enhance the Lget function referenced by __index
--

-- Warning: Codea forum may not display all underscore characters below

do
    local mt = getmetatable(color())
    local oldLget = mt["__index"]    -- preserve old Lget
    mt["__index"] = function (t, k)
        local v = oldLget(t, k)    -- try the old Lget
        if v then return v end    -- if it works, then use it
        return mt[k]    -- otherwise, look in the metatable
    end
end

Once __index is enhanced, other enhancements should work. For example:


do
    local mt = getmetatable(color())
    mt["unpack"] = function(self) return self.r, self.g, self.b, self.a end
end

c1 = color(255, 255, 0, 255)
print(c1:unpack()) -- generates output: 255   255   0   255

nice work @mpilgrem! This is very helpful.

@mpilgrem I should have changed color to look up unknown keys in its metatable. This is something I’d still like to change.

Seems to be fixed for both color and touch in current 1.4.5 version so workaround is no longer required :slight_smile: Also updated wiki (https://bitbucket.org/TwoLivesLeft/codea/wiki/CodeaUserdefinedTypes)