lua-users home
lua-l archive

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


Hi all,

my preferred way of binding C++ objects to Lua has always been the direct and lightweight way as described in [1] and [2].

In summary, from the mid section of [2], where C++ objects are represented in Lua by userdata:

a) Prepare table to be used as metatable for each supported type, possibly with inheritance.

b) Prepare an auxiliary table with weak values to find already existing userdata by lightuserdata (object pointer) later.

c) For each C++ object for which a Lua "alter ego" is requested, find the existing userdata in the table from step b). If not found, create a new userdata, assign the proper metatable, and add an entry to table from step b).


A key feature of this approach is that Lua determines the lifetime of the C++ object -- if Lua can garbage collect the related Lua userdata, the related __gc metamethod will naturally destroy the C++ object.

Keeping a pointer to the C++ objects in the C++ code is thus not possible (it might get deleted in __gc), and especially we cannot hold the C++ object longer than the Lua state exists.

One possible solution to this is to "anchor" the userdata in the Lua state, e.g. in another table with "strong" keys and values. However, I found such anchoring remarkably cumbersome to handle -- depending on the details, required management in the C++ code gets relatively complex. Anyways, this prevents our instance from being deleted via the garbage collector, but doesn't help with the lifetime beyond the Lua state.

Eventually I figured that it might help to keep in userdata not a pointer to the C++ object, but a smart pointer, such as shared_ptr<>, intrusive_ptr<>, whatever.

I've not yet tried it, but this seems to solve the problem very well:
Neither C++ nor Lua forces the lifetime of the object onto each other, because both in the normal C++ code as well as in __gc only smart pointers expire, and the actual object only with the very last of them.


Unfortunately, even this very appealing approach seems to have one weakness:

Consider a windowing system, where R is the root window, we have the ability to add extra attributes to our Lua userdata (e.g. in an attached table), and do something like this:

    w = CreateNewWindow()

    -- Define the event handler in Lua.
    w.OnClick = function() ... end

    -- The implementation adds a smart pointer to w in R,
    -- but doesn't create another anchor for w in Lua anywhere.
    R.AddChildWindow(w)

    w = nil

Now when w gets garbage collected, it won't be a problem for R, which in C++ code may keep a shared_ptr<> to w, but if we ever tried to call the OnClick() function of w, w would be recreated as described in step c) above, but OnClick() would not be there, because it was garbage collected with the original w earlier.

So I was wondering if there was something like another callback metamethod for userdata, e.g. __mark(), that the garbage collector calls in order to decide if the userdata should be marked for collection. By default, it would return "true" of course, but in my case, I would w only want to be marked if all other smart pointers (those in C++) were gone already. That is, I'd want to return "true" only if the reference count of the C++ instance was 1 (by the Lua userdata smart pointer). This way, as long as the C++ object is in use in the C++ code, it's alter ego in Lua would be prevented from being garbage collected.

Is it possible to have a callback like __mark somehow?
(Or anything else that achieves a similar result?)

Best regards,
Carsten


[1] Programming in Lua, 2nd edition
[2] Game Programming Gems 6, chapter 4.2 "Binding C/C++ Objects to Lua"



--
   Cafu - the open-source Game and Graphics Engine
for multiplayer, cross-platform, real-time 3D Action
          Learn more at http://www.cafu.de