Lua用户数据对象管理

我正在尝试将一个 Lua 类对象推入栈中。多个函数可以返回该对象的指针。

换句话说:我需要推入 userdata 值,同时仍然能在它们上使用“==”,“~=”等。因此,如果它是相同的 C++ 对象,则 userdata 指针必须相同。

-- 这应该将对象推入栈
local firstObject = GetClassObject();
firstObject:doSomething();

firstObject将由Lua脚本存储,稍后在代码中,我将需要再次执行此操作:

-- c++ 类指针在这里没有更改
-- 因此,我希望推送与第一次调用中相同的 userdata 指针...
local object = GetClassObject();

-- 如果不这样做,则以下情况将失败... :C
if object == firstObject then
...

我的 Push 函数应该基本上检查是否已经存在相同的 C++ 类指针,如果是,则推送相应的 userdata 指针(无论我如何推送,对象都应该完全一样)

如果不存在,则应创建一个新的 userdata(将其推送到堆栈上)并将其内容设置为类对象。

这是我的代码:

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

    // 这里我需要检查是否已经有这样一个 C++ 对象(同样的 tObject)!
    //
    // 如果是,我想要推送相关的 userdata。

    // 对象尚不存在 -> 我们需要一个新的 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 是我编写的一个函数,用于不抛出 Lua 错误:

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;
}
点赞
用户1450890
用户1450890

在Lua中,任何两个相同的userdata实例都保证是相等的。但是,当你像你现在这样封装一个C++类实例时,每个被封装的实例都会被放入一个新的userdatum中,这意味着它们不能直接进行比较。

所以你需要为你的对象定义一个__eq元方法。它可能看起来有点像这样:

int l_compare_things(lua_State* l)
{
    MyClass* a = reinterpret_cast<MyClass*>(lua_touserdata(L, 1));
    MyClass* b = reinterpret_cast<MyClass*>(lua_touserdata(L, 2));

    lua_pushboolean(L, (*a) == (*b));

    return 1;
}

这里假设MyClass有某种operator==重载。你可以将这个函数设置为与你的MyClass userdata项相关联的元表中的__eq元方法。你似乎已经涵盖了元表处理,所以我在这里不再多说。

下一个问题是:你正在封装整个类实例作为lua full userdata项。你可能不希望一次又一次地推送相同的东西,并使用了所有可用内存......如果你只推送指针,则这就不那么成问题,但你并没有这样做。因此,你将需要一些唯一的方式来标识你的C++类的每个实例。这里举个有字符串的例子:

class MyClass
{
private:
    std::string _id;
public:
    MyClass(const std::string& id) : _id(id) {}

    const std::string& get_id() { return _id; }

    // setters and operator= overrides not included.
};

void l_push_thing(lua_State* L, const MyClass& thing)
{
    // try to get our instance by ID from the registry table:
    lua_getfield(L, LUA_REGISTRYINDEX, thing.id());

    // if so, return, leaving it at the top of the stack.
    if (lua_isuserdata(L, -1))
        return;

    void *ud = lua_newuserdata(L, sizeof(MyClass));
    *reinterpret_cast<MyClass*>(ud) = thing;
    // set up the metatable, etc

    // duplicate the userdata reference:
    lua_pushvalue(L, -1);

    // push our new userdata into the registry. pops the duplicate from the stack
    lua_setfield(L, LUA_REGISTRYINDEX, thing.get_id());
}

(注意:我没有编译或测试过这个例子。可能有误!)

这将在堆栈顶部留下与某个特定MyClass实例相关联的userdatum。你需要采取自己的步骤来“取消注册”类实例;在这种情况下,每个实例的硬引用存在于注册表中,因此userdatum在你销毁该引用之前将_不会被垃圾回收_。你可能考虑在这里使用弱/短暂表。

2012-06-24 19:06:25
用户1478081
用户1478081

这是弱表的工作方式吗?

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))
    {
        //创建新的弱表
        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); //不存在,需要弹出
    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);
}
2012-06-25 21:06:13