A.G.
A.G.

Reputation: 1319

Lua/C++ : __index is always invoked even the table's field is known

I would like to declare global metatables in Lua that are registered by a C++ app.

I define the __index metamethod of the metatable and some fields but when the lua script access known fields, the __index is always invoked in my C++ app.

For example I want to register a global table named User which contains three fields : FirstName, LastName and Age.

Here is how I register the table. The table is encapsulated in a class named TLuaStruct that make easier to register table in Lua.

bool TLuaStruct::InternalRegister( std::string tName, bool AddInGC )
{
    TLuaStack *ptStack;
    TLuaStruct **ptUserLuaStruct;

    ptStack = m_ptLua->GetStack();
    if( ptStack==NULL )
        return false;

    //  Create a metatable that won't be exposed to Lua scripts.
    //  The name must be unique
    //  Stack after the call:
    //  1,-1 | table
    m_ptLua->NewMetaTable( m_tMetaName );

    //  Register all of the properties
    PushProperties( false, false );

    //  Register the callback assigned to __index
    ptStack->PushCFunction( TLuaStruct_Index_CallBack );

    m_ptLua->SetField( -2, "__index" );

    // Create a UserData to store the address of this.
    //  Stack after the call:
    //  2,-1 | userdata
    //  1,-2 | table
    ptUserLuaStruct = ( TLuaStruct** )m_ptLua->NewUserData( sizeof( ptUserLuaStruct ) );
    *ptUserLuaStruct = this;
    m_pvLuaThisPtr = ( void* )ptUserLuaStruct;

    //  Switch the userdata and the table
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | userdata
    ptStack->Insert( -2 );

    //  Associate the metatable to the userdata
    //  Stack after the call:
    //  1,-1 | userdata
    m_ptLua->SetMetaTable( -2 );

m_ptLua->SetGlobal( tName.c_str() );

    return true;
}

If I assign the metatable itself to __index instead to a C callback, the scripts can access FirstName, LastName and Age in readonly (What I would like to do) but I can't know when the scripts try to access an unknown field cause my C++ app is not invoked.

Maybe Lua always invokes __index when the metatable is userdata so it makes sure the values are up-to-date but it could be nice to defined read-only variable such as function in a table otherwise calling __index all the time can slow down the app.

Is anyone know how to perform this ? Thanks.

Upvotes: 2

Views: 1686

Answers (2)

riv
riv

Reputation: 7323

The manual does a good job of explaining how metatables work, perhaps you misread it - arbitrary fields in a metatable have no effect, only __index adds "fake" fields to the object.

The best solution would be to put all static values in the __index table, then create another metatable for the __index table, and set its __index field to your function.

Update: As other have mentioned, a slightly more compact solution exists: set the first metatable's __index to itself (and fill it with static values), and set its metatable to another metatable, which has your function as its __index.

Upvotes: 4

A.G.
A.G.

Reputation: 1319

SOLUTION:

Riv was almost right with its solution but there's a small difference so I post a new answer.

The solution is to set the main metatable's __index to itself, create a second metatable in which its __index is set to a C callback function, and set this new metatable as the metatable of the main metatable and not its __index.

bool TLuaStruct::InternalRegister( std::string tName, bool AddInGC )
{
    TLuaStack *ptStack;
    TLuaStruct **ptUserLuaStruct;

    ptStack = m_ptLua->GetStack();
    if( ptStack==NULL )
        return false;

    //  Create a metatable that won't be exposed to Lua scripts.
    //  The name must be unique
    //  Stack after the call:
    //  1,-1 | table
    m_ptLua->NewMetaTable( m_tMetaName );

    //  Register all of the const members 
    PushProperties( false, false );

    //  Duplicate the metatable
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | table
    ptStack->PushValue( -1 );

    //  Set its __index to itself so it can access all of its const members
    //  Stack after the call:
    //  1,-1 | table
    m_ptLua->SetField( -2, "__index" );


    //  Create another metable that will be used for non-const members
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | table
    m_ptLua->NewMetaTable( "9999" );

    //  Push the C call back called when a non-const member is read
    //  Stack after the call:
    //  3,-1 | function
    //  2,-2 | table
    //  1,-3 | table
    ptStack->PushCFunction( TLuaStruct_Index_CallBack );

    //  Set the __index of the metable
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | table
    m_ptLua->SetField( -2, "__index" );


    //  Set the metatable of the main metatable
    //  Stack after the call:
    //  1,-1 | table
    m_ptLua->SetMetaTable( -2 );

    // Create a UserData to store the address of this.
    //  Stack after the call:
    //  2,-1 | userdata
    //  1,-2 | table
#warning check whether we must create a new pointer each time...
    ptUserLuaStruct = ( TLuaStruct** )m_ptLua->NewUserData( sizeof( ptUserLuaStruct ) );
    *ptUserLuaStruct = this;
    m_pvLuaThisPtr = ( void* )ptUserLuaStruct;

    //  Switch the userdata and the table
    //  Stack after the call:
    //  2,-1 | table
    //  1,-2 | userdata
    ptStack->Insert( -2 );

    //  Set the metatable of the userdata
    //  Stack after the call:
    //  1,-1 | userdata
    m_ptLua->SetMetaTable( -2 );

    //  Publish the userdata object with the name of the module.
    //  Stack after the call:
    //  -- empty --
    m_ptLua->SetGlobal( tName.c_str() );

    return true;
}

Upvotes: 3

Related Questions