[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: linking tables to C++ objects?
- From: Rici Lake <lua@...>
- Date: Mon, 26 Feb 2007 16:50:07 -0500
On 26-Feb-07, at 4:08 PM, Graham Wakefield wrote:
It depends on why you need to export the table to Lua. (Note:
I call this table the "peer table" below.) Often,
you can just export the userdata directly, and make the table
the userdata's environment; then the userdata's __index meta
points to the environment, and the environment's __index meta
points to your base instance-method table for the object type.
Unfortunately, that requires creating an individual metatable
for each userdata, in addition to the peer table; combining
the two is possible but somewhat less protective.
An alternative is providing an __index metamethod which does
the lookup, which can be done with a single metatable but
imposes an additional runtime overhead on key lookups.
I normally use this solution; I find the runtime overhead
acceptable but YMMV. (It's more flexible, because it allows
the definition of getter and setter methods for specific
keys.)
Since a userdata's environment table is not garbage collected
until after the userdata's __gc metamethod is run, the __gc
metamethod can still access the peer table. If the userdata
is visible in Lua, and the peer table itself is not directly
visible, then there is no direct reference from Lua to the
peer table and consequently, the __gc metamethod can perform
necessary finalization on the peer table as well as the C++
object (in the very rare cases where that might be necessary).
In any event, if the translation from c++ pointer to userdata
is done in the context of a known lua_CFunction (or a small
set of them), you can keep the translation table (lightuserdata
to userdata) as an upvalue of the lua_CFunction(s), thus saving
a REGISTRY lookup. That makes translations almost twice as fast,
but you need to create all the closures of the lua_CFunctions
with the upvalue, which is not always convenient. Of course, there
is nothing stopping you from keeping it in the Registry, and
then optimizing critical lua_CFunctions by adding the upvalue
just to those ones.
Ah, now I read Rici's note.
So in pseudocode again:
// creation (synchronized) of table, userdata & c++ instance:
ptr = [C++ instance]
udata = userdata[ptr] // boxed pointer
table = lua_newtable
setfenv(udata, table)
REGISTRY[weakvaluedtable][ptr] = udata
REGISTRY[weakvaluedtable][table] = ptr
// If you did it this way, the table->ptr table would need
// to be weak-keyed rather than weak-valued, but you could
// achieve the same effect by making the single table fully
// weak (lightuserdata are strong even in weak tables).
// from C++ ptr to table:
getfenv(REGISTRY[weakvaluedtable][ptr])
// from table to C++ ptr:
REGISTRY[weakvaluedtable][table]
// garbage collection:
udata.__gc { // remove ptr & table entries from
REGISTRY[weakvaluedtable]) }
Removing the entries from the weak table is unnecessary.
That's why you use a weak table. :) If you don't have any
other finalization to do, you don't even need a __gc
metamethod, although it's likely that you'll want one if
you're using boxed pointers.
If the C++ object can be destroyed when the last Lua
reference to it is gone, and if it has no destructor
(and none of its members have destructors), then you
could allocate it directly into a userdata with
placement new, and thereby avoid the __gc metamethod
altogether. That's only occasionally feasible, but it's
worth keeping in mind. Also, remember that with Lua 5.1,
you can provide a custom allocator to Lua, if that helps
you at all.
Surely there is a more efficient way to do this!?
Hope some of the above helped.