2012-06-24 86 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