lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On 1/14/2013 10:22 PM, Hadriel Kaplan wrote:
Of course you'd have to make sure pushing the same object multiple times into Lua only creates one userdata, and the solution to that might yield a solution to the first problem.  

Bingo! That's how I solved it -- combined with shared_ptr, as William suggested in his message, and a destructor that cleared the Lua table associated with the object. I didn't really want multiple references to the same object floating around in Lua regardless. And I DID use a counting system to uniquely identify objects; I'd forgotten.

OK, since I have a moment to explain, here's the outline from the docs for my "ScriptLink" class (by "tagged", I mean you can add custom data fields in Lua to these objects):
/**
 *  [ScriptLink is ] a binder between C++ and Lua for objects that can be
 *  tagged in Lua. The tags are guaranteed to persist for as
 *  long as the object lives in C++. An active link to the
 *  object in Lua will keep the C++ object alive.
 *
 *  Pushing the object onto the Lua stack [from C++] also guarantees reuse
 *  of the existing Lua object, if one exists.
 *
 *  The "actual" Lua object is a USERDATA that contains a pointer
 *  to the object "this" as well as typical Dub bindings. The
 *  environment of the object is a table that is stored in the
 *  registry, indexed by m_luaLinkId.
 *
 *  The environment table also stores any tags accumulated by the
 *  object, as well as a weak link to the main object USERDATA,
 *  which is used to prevent extra redundant USERDATAs from being
 *  created when push is called from C++.
 *
 *  Finally, the environment table contains a reference to a
 *  shared_ptr<> to the class. When the USERDATA is garbage collected
 *  in Lua, the shared_ptr<> is deleted. The environment table will
 *  survive in the registry until the C++ object is destroyed, however,
 *  and if the object is later pushed back into Lua, a new shared_ptr<> and
 *  USERDATA will be allocated and bound to the previous environment table.
 *
 *  In this code, "maintable" refers to the environment table that's stored in
 *  the registry, and "weaktable" refers to a table that is marked to store weak
 *  values.
 *
 *  maintable[1] = weaktable
 *  maintable[2] = lightuserdata(shared_ptr) (or nil when no USERDATA exists)
 *  maintable[3] = "typename"
 *  weaktable[1] = USERDATA (or nil when no USERDATA exists)
 */
At the risk of being redundant with my docs above: The "maintable" will survive the object being garbage collected on the Lua side. If the C++ object then gets pushed again from C++, it will be bound again to maintable, and a new shared_ptr<object> will be stored in maintable[2]. The maintable itself is stored as the object environment table, making it fast to find, and allowing the USERDATA metatable to be constant for all objects of that class.

The "weaktable" keeps a weak reference to the existing Lua USERDATA, so when I "push" an object from C++ and there is an existing Lua object, it can push a reference to it rather than creating a new Lua USERDATA (I find that, in games especially, you end up with a LOT of extra garbage collection if you have to create a new USERDATA every time you want C++ to push the object). While the main object USERDATA exists, maintable[2] holds a strong C++ reference (shared_ptr) to the object, so it won't be deleted on the C++ side. When the USERDATA ends up being garbage collected, the shared_ptr<> is deleted, releasing the C++ reference.

On the question of whether it improves the codebase: Absolutely. I wouldn't do it any other way.

Tim