2012-06-24 51 views
1

我试图将一个Lua类对象推入堆栈。指向该对象的指针可以由多个函数返回。Lua userdata对象管理

换句话说:我需要推送userdata值,同时仍然保持对它们使用'==','〜='等的能力,所以如果它的相同C++对象的userdata指针必须相同。

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

firstObject将由LUA脚本存储后来在代码中,我需要再次做到这一点:

-- 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 
... 

我的Push功能应主要检查是否已经有相同的C++类的指针某处并推动相关的用户数据指针,如果是这样的话(无论我如何推它,对象应该工作1:1相同)

如果不是,它应该创建一个新的userdata(将它推入堆栈)并设置它到类对象。

这里是我的代码:

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是自己的功能,我写的不是抛出一个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; 
} 
+0

我发现很难找出你在这里想要做什么。你只是试图缓存一个lua userdata实例创建后? – Rook

+0

我试图推动userdata值,同时仍然保持对它们使用'==','〜='等的能力,所以如果它的相同C++对象的userdata指针必须是相同的。 – user1478081

+0

Lua userdata对象在内部通过引用进行比较。从相同的基础指针创建的两个userdata实例应该相等比较。你是说在这种情况下'=='不起作用吗?你有没有重写'__eq' metatable条目? – Rook

回答

0

难道表格是如何工作的?

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); 
} 
+0

请不要忘记缓存在弱表中的userdata可能仍然存在在第一个用户数据垃圾收集阶段并需要额外检查之后,哪个表单取决于udata块中存储实际的类实例指针的方式。简单地检查非零值会错过该情况。如果udata包含单个指针,则必须对其进行检查,因为这是__gc元方法应该做的。否则,你将复活但死去的物体送回口译员。 – user3125367

1

右键,在Lua中,相同的用户数据中的任意两个实例保证是平等的。然而,当你正在做一个C++类实例时,每个盒装实例都被放入一个新的userdatum中,这意味着它们不能直接进行比较。

你需要做的是为你的对象定义一个__eq metamethod。它看起来有点像这样的东西:

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用户数据项关联的元表中的__eq元方法。你似乎已经涵盖了metatable处理,所以我不会在这里打扰。

现在,下一个问题:你正在将整个类实例装箱为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()); 
} 

(注:!我没有编译或测试这个比例E & OE)

这会留下一些特别MyClass例如顶部相关的userdatum的堆栈。您需要采取自己的步骤来“注销”类实例;在这种情况下,注册表中存在对每个实例的硬引用,因此用户数据块将不会被垃圾收集,直到您销毁该引用为止。你可以考虑在这里使用弱/ ephemeron表。

+0

谢谢你,可悲的是,我不知道他/她有多弱/ ephemeron表工作? – user1478081

+0

我试图将所有的用户数据和它们的值一起映射到一个std :: map并添加一个自己的'__gc'方法,但它不起作用。我将完整的用户数据指针作为轻量级用户数据推送,该dsn't工作:c – user1478081

+0

@ user1478081一步一个脚印。在您的基本推送系统正常工作之前,不要担心软弱的桌子!我不确定你想用你的'std :: map'来完成什么;你能利用我上面的代码吗? – Rook