c++luacallbackmeta-method

implementing __index metafunction in C/c++


I have a script to C++ callback/functor system that can call any "registered" C++ function using strings and/or variants.

//REMOVED ERROR CHECKS AND ERRONEOUS STUFF FOR THIS POST
int LuaGameObject::LuaCallFunction( lua_State *luaState )
{
    if ( lua_isuserdata( luaState, 1 ) == 1 )
    {
        int nArgs = lua_gettop( luaState );

        //Get GameObject
        OGameObject* pGameObject = static_cast<OGameObject*>(lua_touserdata( luaState, 1 ));
        if ( pGameObject )
        {
            //Get FunctionName
            const char* functionNameString = lua_tostring( luaState, 2 );

            //Get Args
            std::vector<OVariant> args;
            for ( int i = 3; i <= nArgs; ++i )
            {
                OVariant variant;
                variant.SetFromLua( luaState, i );
                args.push_back( variant );
            }

            //Call it!
            CallGameObjectFunction( luaState, pGameObject, functionNameString, args );

            return 1;
        }
    }

    return 0;
}

OVariant LuaGameObject::ExecuteLua()
{
    lua_State *lState = luaL_newstate();

    luaL_openlibs( lState );
    lua_register( lState, "Call", LuaCallFunction );

    luaL_loadstring( lState, m_pScript );

    //now run it
    lua_pcall( lState, 0, 1, 0 );

    //process return values
    OVariant result;
    result.SetFromLua( lState, -1 );

    lua_close( lState );

    return result;
}

In lua I can do something like this...

local king = Call("EmpireManager","GetKing")
Call("MapCamera","ZoomToActor",king)

However, I am feeling that I can use the __index metamethod to simplify the lua...

local king = EmpireManager:GetKing()
MapCamera:ZoomToActor(king)

I was hoping to achieve the simplified lua by using the following implemenation of the __index metamethod

Here is how I register the __index metafunction... (mostly copied from online examples)

void LuaGameObject::Register( lua_State * l )
{
    luaL_Reg sRegs[] =
    {
        { "__index", &LuaGameObject::LuaCallFunction },
        { NULL, NULL }
    };

    luaL_newmetatable( l, "luaL_EmpireManager" );

    // Register the C functions into the metatable we just created.
    luaL_setfuncs( l, sRegs, 0 );
    lua_pushvalue( l, -1 );

    // Set the "__index" field of the metatable to point to itself
    // This pops the stack
    lua_setfield( l, -1, "__index" );

    // Now we use setglobal to officially expose the luaL_EmpireManager metatable 
    // to Lua. And we use the name "EmpireManager".
    lua_setglobal( l, "EmpireManager" );
}

Unfortunately, I cant seem to get the callback setup right. Lua correctly calls my LuaGameObject::LuaCallFunction, but the stack does not contain what I would like. From within the LuaGameObject::LuaCallFunction, I can find the function name and EmpireManager object on the stack. But, I cant find the args on the stack. What is the proper way to set this up? Or is it not possible?


Solution

  • It is definitely possible to add methods to a userdata type in Lua, as explained in the Programming in Lua guide from the official website.

    When you type the following Lua code:

    myUserdata:someMethod(arg1,arg2,arg3)
    

    Assuming myUserdata is a "userdata" object, the interpreter will do the following.

    1. Call getmetatable(myUserdata).__index(myUserdata,"someMethod") to get the value of someMethod.
    2. Call someMethod(myUserdata,arg1,arg2,arg3). someMethod can be anything callable from Lua. Examples: a Lua or C function, or a table/userdata with a __call metamethod.

    Your __index metamethod should just return a function (or another object callable from Lua) implementing the method. Something like this:

    // IMO, quite a misleading name for the __index metamethod (there is a __call metamethod)
    int LuaGameObject::LuaCallFunction( lua_State *l)
    {
        // todo: error checking
        OGameObject* pGameObject = static_cast<OGameObject*>(lua_touserdata( luaState, 1 ));
        std::string memberName = lua_tostring( luaState, 2 );
    
        int result = 1;
        if (memberName == "method1") {
            lua_pushcfunction(l,LuaGameObject::luaMethod1);
        } else if (memberName == "method2") {
            lua_pushcfunction(l,LuaGameObject::luaMethod2);
        } else {
            result = 0;
        }
    
        return result;
    }
    

    Basic skeleton of the functions returned by the __index metamethod:

    int LuaGameObject::luaMethod1(lua_State* l) {
        // todo: error checking.
        OGameObject* pGameObject = static_cast<OGameObject*>(lua_touserdata(l, 1));
        float arg1 = lua_tonumber(l, 2);
        // get other args
        pGameObject->method1(arg1 /*, more args if any.*/);
        // optionally push return values on the stack.
        return 0; // <-- number of return values.
    }