c++classlualua-userdata

Lua userdata object management


I'm trying to push a Lua class object onto the stack. The pointer to that object can be returned by multiple functions.

In other words: I need to push userdata values while still keeping the ability to use '==', '~=' etc. on them so the userdata pointer must be the same if its the same C++ object.

-- this should push the object onto the stack
local firstObject = GetClassObject();
firstObject:doSomething();

firstObject will be stored by the lua script and later in code i will need to do this again:

-- the c++ class pointer has not changed here
-- so I would like to push the same userdata pointer as in the first call...
local object = GetClassObject();

-- if I would not do this the following here would fail... :C
if object == firstObject then
...

My Push function should basically check if there is already the same C++ class pointer somewhere and push the associated userdata pointer if so (no matter how i push it, the object should work 1:1 the same)

If not it should create a new userdata (push it on the stack) and set the content of it to the class object.

Here's my code:

template <typename T>
void Push( const T &tObject )
{
    lua_State *L = GetLuaState();

    // Here i need to check if such a C++ object (the same tObject)
    // already exists!
    //
    // If so i want to push the associated userdata.


    // Object didn't exist yet -> we need a new userdata
    void *pUserData = lua_newuserdata( L, sizeof( tObject ) );
    *reinterpret_cast<T*>( pUserData ) = tObject;
}

template <typename T>
void Push( const T &tObject, const char *pszTable )
{
    Push( tObject );
    lua_State *L = GetLuaState();
    luaL_getmetatable( L, pszTable );
    lua_setmetatable( L, -2 );
}

template <typename T>
T& Get( int nIndex )
{
    T *pUserData = reinterpret_cast<T*>( lua_touserdata( GetLuaState(), nIndex ) );
    if( pUserData == nullptr )
        throw std::exception( "Invalid userdata!" );

    return *pUserData;
}

template <typename T>
T& Get( int nIndex, const char *pszTable )
{
    T *pUserData = reinterpret_cast<T*>( LuaToUData( nIndex, pszTable ) );
    if( pUserData == nullptr )
        throw std::exception( "Invalid userdata!" );

    return *pUserData;
}

LuaToUData is an own function which i wrote to not throw a lua error:

void* LuaToUData( int nIndex, const char *pszTable )
{
    void *pUserData = lua_touserdata( g_luaState, nIndex );
    if( pUserData != nullptr )
    {
        if( lua_getmetatable( g_luaState, nIndex ) != 0 )
        {
            lua_getfield( g_luaState, LUA_REGISTRYINDEX, pszTable );
            bool bEqual = ( lua_rawequal( g_luaState, -1, -2 ) == 1 );
            lua_pop( g_luaState, 2 );

            if( bEqual )
                return pUserData;
        }
    }

    return nullptr;
}

Solution

  • Is that how weak tables work ?

    void Push( const T &tObject )
    {
        std::ostringstream o;
        o << tObject;
        std::string sIdentifier = o.str();
        const char *pszIdentifier = sIdentifier.c_str();
    
        lua_State *L = GetLuaState();
        luaL_getmetatable( L, "lua_userdata" );
        if( !lua_istable( L, -1 ) )
        {
            // create new weak table
            luaL_newmetatable( L, "lua_userdata" );
            lua_pushstring( L, "v" );
            lua_setfield( L, -2, "__mode" );
        }
    
        lua_getfield( L, -1, pszIdentifier );
        if( lua_isuserdata( L, -1 ) == TRUE )
            return lua_remove( L, -2 );
    
        lua_pop( L, 1 ); // didnt exist yet - getfield is nil -> need to pop that
        void *pUserData = lua_newuserdata( L, sizeof( UINT64 ) );
        *reinterpret_cast<UINT64*>( pUserData ) = UINT64( tObject );
    
        lua_pushvalue( L, -1 );
        lua_setfield( L, -3, pszIdentifier );
        lua_remove( L, -2 );
    }