4

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

greatwolf
  • 20,287
  • 13
  • 71
  • 105
Phil Lello
  • 8,377
  • 2
  • 25
  • 34
  • 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 Jan 11 '15 at 19:54
  • @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 Jan 11 '15 at 20:22
  • 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 Jan 11 '15 at 20:36
  • @EtanReisner All my current properties are handled by __index / __newindex calling methods on a C++ object. – Phil Lello Jan 11 '15 at 20:51
  • 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 Jan 11 '15 at 20:53
  • I don't appear to be able to save a property against a userdata using rawset/rawget; I would expect that if I use a real table with custom metamethods that the rawset/rawget functions would work. Unfortunately my experiments in this direction aren't working out too well - specifically, I don't get the table as parameter 1 in the __index/__newindex callbacks. – Phil Lello Jan 11 '15 at 21:08
  • rawget/rawset ignore metamethods entirely, they don't trigger them. You use them to *avoid* those metamethods. You also can't use `rawset`/`rawget` on userdata, that's true. I'm not sure how `rawset`/`rawget` factor in to this conversation though. Perhaps you should put up one or more of your tests and how they didn't work? – Etan Reisner Jan 11 '15 at 21:13
  • The rawset/rawget mentioned was an experiment to see if calling these within __index/__newindex would allow me to store tables against the userdata, which as you correctly state, doesn't work. I'll update the question with an example of the issue with custom metatable. – Phil Lello Jan 11 '15 at 21:25
  • Ah, ok. Yeah, you can't use the userdata itself for arbitrary storage. You *can* associate a table with your userdata and store anything you want in there though. See `lua_setuservalue`/`lua_getuservalue`. – Etan Reisner Jan 11 '15 at 22:02
  • Brilliant! If you want to make that an answer, I'll gladly accept. – Phil Lello Jan 11 '15 at 22:22

1 Answers1

5

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.

Etan Reisner
  • 77,877
  • 8
  • 106
  • 148