4
votes

I've got a scripting system working well using userdata objects. However, I now want to have a property on my userdata that can take a regular table.

I think what I should do is create a normal table and set the metatable to use my current set of metamethods, however I'm struggling to understand how to do this - I'm sure it's a simple tweak, I just can't see it right now.

My existing code looks like:

void
LuaContext::push(lua_State* state, boost::shared_ptr<LuaWrapped> wrapped) {
    static struct luaL_Reg methods[] = {
        { "__index", LuaWrapped::static_get },
        { "__newindex", LuaWrapped::static_set },
        { "__len", LuaWrapped::static_len },
        { "__ipairs", LuaWrapped::static_ipairs },
        { "__pairs", LuaWrapped::static_pairs },
        { "__gc", LuaWrapped::static_gc },
        { "__eq", LuaWrapped::static_eq },
        { NULL, NULL }
    };

    LuaWrapped::Ptr **ptr = (LuaWrapped::Ptr **)lua_newuserdata(state, sizeof(LuaWrapped::Ptr *));
    *ptr = new LuaWrapped::Ptr(wrapped);

    if (luaL_newmetatable(state, "LuaWrapped")) {
        lua_pushstring(state, "__index");
        lua_pushvalue(state, -2);
        lua_settable(state, -3);
        luaL_openlib(state, NULL, methods, 0);
    }
    lua_setmetatable(state, -2);
}

The __gc metamethod is in there to delete the LuaWrapped::Ptr class (which is a wrapper to a boost::shared_ptr). I guess I'll leave that along, and store the pointer in a lightuserdata field on the normal table.


Experimental custom metatable against normal table issue (per discussion in comments):

void
LuaContext::push(lua_State* state, boost::shared_ptr<LuaWrapped> wrapped) {
    static struct luaL_Reg methods[] = {
        { "__index", LuaWrapped::static_get },
        { "__newindex", LuaWrapped::static_set },
        { "__len", LuaWrapped::static_len },
        { "__ipairs", LuaWrapped::static_ipairs },
        { "__pairs", LuaWrapped::static_pairs },
        { "__gc", LuaWrapped::static_gc },
        { "__eq", LuaWrapped::static_eq },
        { NULL, NULL }
    };

    lua_newtable(state);
    LuaContext::push(state, "pointer");
    lua_pushlightuserdata(state, new LuaWrapped::Ptr(wrapped));
    lua_settable(state, -3);

    lua_newtable(state);
    luaL_openlib(state, NULL, methods, 0);
    lua_setmetatable(state, -2);
}

int
LuaWrapped::static_get(lua_State* state) {
    int argc = lua_gettop(state);
    for (int i = 1; i <= argc; i++) {
        const char *type = lua_typename(state, i);
        std::cout << type << std::endl;
    }
    ....

Expected output on a get:

table, string

Actual output on a get (Lua 5.2, Ubuntu 14.04):

boolean, userdata

1
You want to store a table as a property of your userdata or you want a table to be able to function as a userdata/object?Etan Reisner
@etanreisner I want a table as a property. The idea of using a normal table with a userdata metatable seems like the way to implement this (but I'd be quite happy to use a different solution).Phil Lello
I'm trying to figure out why are trying to mess with the table at all. If the point is to store the table on the userdata then that's not different then storing any other value on the userdata (you just need to associate a table with the userdata and store the references in that) no messing with metatables or anything involved. How are you currently storing properties on the userdata.Etan Reisner
@EtanReisner All my current properties are handled by __index / __newindex calling methods on a C++ object.Phil Lello
Right... and you want to be able to store a table also. How are you expecting that a table with your method metatable is going to be used here? Or, more to the point, why do you expect to be doing method calls against this table? You just need to handle this table-accepting property in your __index function and, instead of storing the value in C++ somewhere, store the table you are handed in lua somewhere.Etan Reisner

1 Answers

5
votes

Storing arbitrary data along with a userdata is what userdata environments/uservalues are for.

The lua 5.2 method for doing this is to use the lua_setuservalue and lua_getuservalue functions to associate a table with the userdata. This table can then be used to store and retrieve arbitrary values related to the userdata.

In lua 5.1 the more general environment concept was used for this purpose through lua_setfenv and lua_getfenv but the idea is the same.