Lua userdata: Unable to have simultaneous array access and methods
Asked Answered
A

1

2

I had this guy's problem: Lua userdata array access and methods

wherein when I set the __index of my userdata's metatable, it always called the getter, instead of my other methods that weren't declared for meta-events. The solution to the above link is in Lua, and I attempted a C implementation which seems inelegant, but regardless, it creates a new problem in that my new methods can no longer take arguments, and I get this error:

attempt to call method 'asTable' (a table value)

on this Lua statement:

print_r(c:asTable() )

This is how I set everything up:

//Methods, many of which are overridden Lua meta-events (with the underscores)
static const struct luaL_reg vallib_m [] = {
    {"asTable", PushLuaTable}, //these functions are not called
    {"asCopy", CopyLuaVal}, 

    {"__newindex", SetLuaVal},
    {"__index", GetLuaVal},
    {"__tostring", ValToString},
    {"__gc", GarbageCollectVal},
    {"__metatable", HideMetaTable},

    {NULL, NULL}
};

//Static library functions
static const struct luaL_reg vallib_f [] = {
    {"specialprint", PrintVals}, 
    {NULL, NULL}
};


int luaopen_custom(lua_State *L)
{
    luaL_newmetatable(L, "custom.Value");
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);  /* pushes the metatable */
    lua_settable(L, -3);  /* metatable.__index = metatable */

    luaL_register(L, NULL, vallib_m);
    luaL_register(L, "special", vallib_f);

    return 0;
}

Then in my getter, which is called by default (via __index), I first check for other events that I intended to be called and transfer control to them as follows. Note that I remove the argument containing the name of the function from the stack.

//TODO: this is a tentative fix, I would rather do this with metatables
//checking for methods
if (lua_isstring(L, 2))
{
    field = luaL_checkstring(L, 2);
    if (unlikely(!field))
    {
        reporter->Warning("Fail in getter -- bad string as method attempt");
        return LUA_FAILURE;
    }

    if (strcmp(field, "asTable") == 0)
    {
        lua_remove(L, 2); //delete string "asTable"
        return PushLuaTable(L);
    }
    else if (strcmp(field, "asCopy") == 0)
    {
        lua_remove(L, 2); //delete string "asCopy"
        return CopyLuaVal(L);
    }
    //... other methods. 
    else
    {
        //Insert string back into stack??
    }

}

It doesn't treat my method as a function regardless of how many arguments are passed, and throws an error if there are even any parentheses or a colon. (It can be accessed by c.asTable, which works fine for methods that takes no arguments but I plan to add some that do, and regardless, the syntax is inconsistent with methods.

In any case, it would be preferable to NOT call these functions through my C getter, and instead solve this with metatables. If this is possible, please provide an example using the C API -- there are already StackOverflow solutions in Lua, but I haven't been able to translate them to C.

Americana answered 22/8, 2016 at 21:53 Comment(7)
See second part of this answer.Galiot
That's helpful, thanksAmericana
A bit complex though -- So in my case, I would call moon_propindex(L, vallib_m, setLuaVal?, ?) ? It would be great if you could elaborate in a more general context and I'll mark it as a solutionAmericana
Hi @siffiejoe, I used the moon toolkit to register my methods shown above using def_moonobject( L, "custom.Value, vallib_m, sizeof(vallib_m), 0) ) and I'm seeing the exact same behavior with asTable not being recognized. vallib_m is unchanged. Is there anything obvious I am missing for usage?Americana
@Galiot sorry, final detail: in moon_defobject, I deleted the definition of all the __meta properties that overlapped with ones in vallib_mAmericana
@Leo: I edited my question (the one you refered to) and added the C code I wrote back then. Hope it helps. Feel free to ask, although it has been 2 years since I wrote those lines...Chatham
@Chatham Thank you, much appreciated! That was very simple, I'll try it out.Americana
G
1

For accessing fields of a C struct from Lua you'll need a function as the __index metamethod, because you need access to the userdata object, and you won't get it if __index is a table:

-- Lua example code; obj* should be userdatas ...
-- an example __index function
local function anIndex( o, k )
  print( "accessing", k, "in", o )
  return 1
end

local obj = {}
local meta = { __index = anIndex }
setmetatable( obj, meta )
print( obj )
--> table: 0xfcb060
print( obj.x )
--> accessing x in  table: 0xfcb060
--> 1

This works fine for properties, but it's uncomfortable (and inefficient) for accessing methods shared by all userdatas of the same type. An __index table would be better for that:

-- an example method
local function aMethod( o )
  print( "calling aMethod on", o )
  return 2
end

local obj2 = {}
local methods = { aMethod = aMethod }
local meta2 = { __index = methods }
setmetatable( obj2, meta2 )
print( obj2 )
--> table: 0xfcada0
print( obj2:aMethod() )
--> calling aMethod on  table: 0xfcada0
--> 2

But now we want both!

Metamethods can be chained in Lua, so we could try to set an __index function as fallback for the __index table (methods in this case):

setmetatable( methods, meta )
print( obj2 )
--> table: 0xfcada0
print( obj2.x )
--> accessing x in  table: 0xfcade0
--> 1
print( obj2:aMethod() )
--> calling aMethod on  table: 0xfcada0
--> 2

But if you look closer, you'll see that the __index function gets a different object than obj2 ...

print( methods )
--> table: 0xfcade0

It gets the methods table as the first argument instead. So we lose access to the original userdata (table in this example), and we can't actually look up any fields. So that won't work.

setmetatable( methods, nil ) -- let's undo this ...

Fortunately, an __index function can do arbitrary things, including accessing another table (e.g. one that's stored in an upvalue):

local obj3 = {}
local meta3 = {
  __index = function( o, k )
    local v = methods[ k ]  -- methods is an upvalue here
    if v == nil then
      print( "accessing", k, "in", o )
      v = 1
    end
    return v
  end
}
setmetatable( obj3, meta3 )
print( obj3 )
--> table: 0xfc23a0
print( obj3.x )
--> accessing x in  table: 0xfc23a0
--> 1
print( obj3:aMethod() )
--> calling aMethod on  table: 0xfc23a0
--> 2

Now this worked great! In case this happens more often, we could write a helper function that creates an appropriate __index function for us. The indexfunc that's passed as argument is only concerned with field lookup, and doesn't have to handle methods at all. The generated function will do that:

local function makeindex( methodstable, indexfunc )
  return function( o, k )
    local v = methodstable[ k ]
    if v == nil then
      v = indexfunc( o, k )
    end
    return v
  end
end

local obj4 = {}
local meta4 = { __index = makeindex( methods, anIndex ) }
setmetatable( obj4, meta4 )
print( obj4 )
--> table: 0xfc92b0
print( obj4.x )
--> accessing x in  table: 0xfc92b0
--> 1
print( obj4:aMethod() )
--> calling aMethod on  table: 0xfc92b0
--> 2

If you try to translate that to the Lua C API, you'll see that it's more convenient to take a luaL_Reg array instead of a methods table, and a lua_CFunction pointer instead of a stack index to a Lua function. And this is what the moon_propindex() function linked in this answer does (additionally it let's you set upvalues for all methods like luaL_setfuncs()).

Galiot answered 28/8, 2016 at 20:17 Comment(2)
1k5 essentially did this in C except using rawget to prevent recursion, and that translation helped me solve my problem: #26970816Americana
That prevents you from using an __index metamethod on the methods table, e.g. for inheritance. If you get infinite recursion, it's a bug in how you set up your metatables, not a matter of raw vs. non-raw access to the methods table.Galiot

© 2022 - 2024 — McMap. All rights reserved.