1

Lua クラス オブジェクトをスタックにプッシュしようとしています。そのオブジェクトへのポインターは、複数の関数から返すことができます。

言い換えれば、「==」、「~=」などを使用する機能を維持しながらユーザーデータ値をプッシュする必要があるため、同じ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
...

私の Push 関数は基本的に、どこかに同じ C++ クラス ポインターがあるかどうかを確認し、関連付けられているユーザーデータ ポインターをプッシュする必要があります (どのようにプッシュしても、オブジェクトは 1:1 で同じように動作するはずです)。

そうでない場合は、新しいユーザーデータを作成 (スタックにプッシュ) し、その内容をクラス オブジェクトに設定する必要があります。

これが私のコードです:

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;
}
4

2 に答える 2

1

そうです、Luaでは、同じユーザーデータの任意の2つのインスタンスが等しいことが保証されています。ただし、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==オーバーライドがあることを前提としています。この関数は、userdataMyClassアイテムに関連付けたメタテーブルの__eqメタメソッドとして設定できます。あなたはすでにメタテーブル処理をカバーしているようですので、ここでは気にしません。

次の問題は、クラスインスタンス全体をluaの完全な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はガベージコレクションされません。ここでweak/ephemeronテーブルの使用を検討してください。

于 2012-06-24T19:06:25.627 に答える
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 );
}
于 2012-06-25T21:06:13.017 に答える