2012-06-24 2 views
1

Я пытаюсь вставить объект класса Lua в стек. Указатель на этот объект может быть возвращен несколькими функциями.Lua userdata управление объектами

Другими словами: мне нужно указывать значения пользовательских данных, сохраняя при этом возможность использовать '==', '~ =' и т. Д., Поэтому указатель userdata должен быть тем же самым, если это тот же объект C++.

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

Моя функция Нажмите в основном должна проверить, есть ли уже указатель класса же C++ где-то и нажимайте соответствующий указатель userdata, если это так (независимо от того, как я его нажимаю, объект должен работать 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

Мне сложно определить, что вы пытаетесь сделать здесь. Вы просто пытаетесь кэшировать экземпляр userua lua после его создания? – Rook

+0

Я пытаюсь использовать значения userdata, сохраняя при этом возможность использовать '==', '~ =' и т. Д., Поэтому указатель userdata должен быть тем же самым, если это тот же объект C++. – user1478081

+0

Объекты User Lata Lua сравниваются по ссылке внутри. Два экземпляра userdata, созданные из одного и того же базового указателя, должны сравниваться как равные. Вы говорите, что '==' не работает в этом случае? И переопределили ли вы мета-запись '__eq'? – 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 содержит единственный указатель, он должен быть проверен с помощью NULL, потому что это, вероятно, должен делать метаметод __gc. В противном случае вы возвращаете воскрешенные, но мертвые объекты обратно к интерпретатору. – user3125367

1

Право, в 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== переопределения. Вы можете установить эту функцию как метатет __eq в метатете, который вы связали с вашими элементами данных MyClass. У вас, похоже, уже есть покрытие с метаданными, поэтому я не буду беспокоиться об этом здесь.

Теперь, следующая проблема: вы боксируете все экземпляры классов как элементы полной юаней lua. Вы, вероятно, не хотите продолжать толкать одну и ту же вещь снова и снова и использовать всю доступную вам память ... это не проблема, если вы только нажимаете указатели, но вы этого не делаете. Итак ... вам понадобится уникальный способ идентификации каждого экземпляра ваших классов 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)

Это оставит userdatum, связанный с какой-то конкретной MyClass например, в верхней из стека. Вам нужно будет предпринять свои собственные действия, чтобы «отменить регистрацию» экземпляров класса; в этом случае жесткая ссылка на каждый экземпляр существует в реестре, и поэтому userdatum не будет собирать мусор, пока вы не уничтожите эту ссылку. Вы можете использовать здесь слабые/эфемерные таблицы.

+0

Спасибо за это, к сожалению, я не знаю, как работают слабые/эфемерные таблицы, что они делают? – user1478081

+0

Я попытался отобразить все пользовательские данные вместе со своими значениями на std :: map и добавить собственный метод '__gc', но это не сработало. Я толкнул полный указатель userdata как легкие пользовательские данные, которые dsn't работают: c – user1478081

+0

@ user1478081 Один шаг за раз. Не беспокойтесь о слабых таблицах, пока ваша базовая система push не будет работать правильно! Я не уверен, что вы пытаетесь выполнить с помощью 'std :: map'; могли ли вы использовать код, который у меня выше? – Rook

Смежные вопросы