Reputation: 1319
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
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
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