In the following example a userdata
value is created of type MyType
and a table is created with a metafunction __tostring
which calls LI_MyType__tostring
. The code creates a closure-based lua OOP. My gripe with the example provided is it appears as though there is only one way to associate userdata
with a method call, via upvalues. In and of itself, this isn't problematic unless I want to share the same metatable across instances.
In an ideal world - and what I'm hoping to unearth with this question - is there a way to associate an upvalue with a value (e.g. userdata
) without associating it with a function call via an upvalue? I'm hoping there is a trick that will let me continue to use closure-based lua OOP and share the same metatable across instances. I'm not optimistic, but I figured I'd ask to see if someone has a suggestion or a non-obvious trick.
using FuncArray = std::vector<const ::luaL_Reg>;
static const FuncArray funcs = {
{ "__tostring", LI_MyType__tostring },
};
int LC_MyType_newInstance(lua_State* L) {
auto userdata = static_cast<MyType*>(lua_newuserdata(L, sizeof(MyType)));
new(userdata) MyType();
// Create the metatable
lua_createtable(L, 0, funcs.size()); // |userdata|table|
lua_pushvalue(L, -2); // |userdata|table|userdata|
luaL_setfuncs(L, funcs.data(), 1); // |userdata|table|
lua_setmetatable(L, -2); // |userdata|
return 1;
}
int LI_MyType__tostring(lua_State* L) {
// NOTE: Blindly assume that upvalue 1 is my userdata
const auto n = lua_upvalueindex(1);
lua_pushvalue(L, n); // |userdata|
auto myTypeInst = static_cast<MyType*>(lua_touserdata(L, -1));
lua_pushstring(L, myTypeInst->str()); // |userdata|string|
return 1; // |userdata|string|
}
I'm hoping there's a way of performing something like (this is pseudo-code!):
// Assume that arg 1 is userdata
int LI_MyType__tostring(lua_State* L) {
const int stackPosition = -1;
const int upvalueIndex = 1;
const auto n = lua_get_USERDATA_upvalue(L, stackPosition, upvalueIndex);
lua_pushvalue(L, n); // |userdata|
auto myTypeInst = static_cast<MyType*>(lua_touserdata(L, -1));
lua_pushstring(L, myTypeInst->str()); // |userdata|string|
return 1; // |userdata|string|
}
I know this is similar to how things would be for the "normal" metatable style of OOP, but I want to keep things closure based and avoid introducing the colon syntax.
Another way of asking this question would be, is there a way to share metatables across userdata
instances while using a closure-based OOP? Using lua's syntax from the scripting side of things, I don't think it's possible, but I'm hoping there's something that can be done on the C side of things.
UPDATE (2013-10-10): Based on @lhf's answer to use lua_setuservalue()
and lua_getuservalue()
the protocol I've settled on which allows me to reuse metatables is this:
luaL_newmetatable()
. This metatable can now be shared across userdata
instances because no upvalues are used when registering the metatable.userdata
value (lua_newuserdata()
).userdata
value (lua_setmetatable()
).userdata
.lua_setuservalue()
on userdata
to store a reference to the per-instance attribute/method table.__index
) to use the userdata
's uservalue table.As a consequence:
It's still not possible to escape creating a method/attribute table per userdata, but that overhead is nominal. It would be nice if obj.myMethod()
would pass obj
to function myMethod()
somehow without using :
, but that's exactly what :
does because this isn't possible another way (unless you do make use of an upvalue).
lua_setuservalue
seems to be exactly what you need. There is also of course lua_getuservalue
.
(I'm skipping the C++ code and answering the question in the title.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With