lua-utils: add support for dynamically collected Lua objects.
authorKrisztian Litkey <kli@iki.fi>
Thu, 16 Oct 2014 19:33:11 +0000 (22:33 +0300)
committerKrisztian Litkey <kli@iki.fi>
Sat, 25 Oct 2014 10:16:41 +0000 (13:16 +0300)
This patch tries to address a number of limitations in the
Murphy Lua object infrastructure that currently prevents us
from creating Lua objects with expected Lua object lifecycle
characteristics and subject to normal Lua garbage-collection
rules.

This patch is monsterous in size and I already regret having
squashed it beyond what would have been more reasonable. I'm
still reluctant to go through the trouble of splitting it up
again to more self-contained orthogonal chunks, as the patch
only touches lua-utils/objects.[hc]. Instead I sum up here
the logically distincts parts the patch is made of:

1) Introduce dynamic classes and implement their necessary
   behavior. This involves introducing the DYNAMIC flag, as
   well as providing versions of the simple CLASSDEF macros
   that take also a flag as it is expected that many of the
   simple classes will need to be dynamic.

   Implementing the necessary behaviour touches object
   creation, destruction (including userdata destuction),
   and pushing. As part of the necessary changes, dynamic
   objects can not be pushed by reference any more (since
   they do not install global self-references). Therefore,
   objects have been modified so that two Lua objects can
   share a (refcounted) userdata_t (the essence of a Murphy
   Lua object) by adding a layer of indirection. IOW, now
   instead of a userdata_t being directly allocated as a Lua
   (userdata) object on the stack, the corresponding Lua
   (userdata) object now contains a pointer to a reference
   counted userdata_t (Murphy Lua object). Sorry about the
   confusing terminology, for once I'm not the one to be
   blamed... Anyway, now pushing a dynamic object becomes
   merely creating a new Lua object pointing to the same
   userdata_t and increasing the refcount of the latter.
   Popping/destroying simply becomes decreasing the refcount
   and freeing the object when the last reference is gone.
   Dynamic objects are not supposed to be explictly destroyed
   and indeed the explicit destructor will refuse to do
   lifecyclewise anything to dynamic objects. It will print
   an error if th eexplicit destructor is ever called with a
   dynamic object.

   Note that if you use the more recently introduced but
   arguably slightly more complex MRP_LUA_DEFINE_CLASS macros
   to declare your classes, even Lua extensions of your objects
   keep working as before. For classes created with the original
   macros, things might not work as you'd expect (but this
   should be really trivial to fix by switching static objects
   over to the newer explicit extension mechanism/infra).

2) Slight cleanups and fixes to the object-infra/userdata_t.
   userdata has been slightly modified to collect all the
   various references under a single refs structure with luatbl
   replaced by refs.self, exttbl replaced by refs.ext and reftbl
   replaced by refs.priv. The reference and extension table
   cleanup functions have been (hopefully) fixed to properly let
   go of all per-object references upon object destruction.
   MRP_LUA_CLASS_PRIVREFS has been made implicit and removed
   altogether. Hence, all the explicit referencing functions now
   operate on private references on a per object basis.

3) The object create/push/destroy/pop/unref code pathes have
   now been planted with more thorough debug messages for better
   debuggability.

4) Two functions have been added for debug-dumping both static and
   dynamic object instances in a unified format.

Here are a few more words about the various bits this patch is
comprised of...

The patch loosens the assumptions about whether instances of
all Lua classes always need to be implicitly self-referencing.
Since most of our classes and objects are still configuration
objects or Lua scripting extensions to C objects, neither of
wich are supposed to be destroyed from Lua, we default to self-
referencing classes. Instances of such classes always get a
global reference automatically installed, essentially making
them static and preventing garbage collection from taking place
until they are explicitly destroyed (which also removes their
global reference).

With this patch in place, one can now mark a class dyanmic thus
opting out from implicit self-referencing on a per class basis.
This happens by setting the flag MRP_LUA_CLASS_DYNAMIC for the
class definition. Instances of such classes will not auto-
matically create a globally reachable self-reference into the
registry and hence will be subject to more natural Lua object
lifecycle rules. Once all reference to such an object go out of
scope the object will be garbage collected and destroyed.

Added a level of indirection between murphy Lua objects and their
userdata. Instead of userdata being directly allocated by Lua and
being part of the Lua object itself, userdata is now allocated as
a separate reference-counted chunk of memory. A Lua object now is
just metadata plus a pointer to the separately allocated userdata.

Pushing an object instance on the Lua stack now does not require
keeping a Lua reference to the object. Instead, a new Lua object
(Lua userdata) is created, made to point to the existing userdata
and the userdata reference count is increased. The destructor for
userdata_t has been modified to decrease the reference count of
the object and only destroy it when the last reference is gone.

With this new scheme, several Lua (userdata) objects can exist for
the same userdata (Murphy Lua object), each keeping its own reference
in the userdata reference count. The constructor, push and destructor
have been set up to reference count the potentially shared userdata
so that once all Lua instances representing (pointing to) the same
object instance go out of scope and get collected, the object instance
itself is destroyed.

These changes should be completely transparent to all existing object
implementations. Self-referencing objects still keep a global reference
to themselves in the registry just like they used to preventing the
initially created object to ever get out of scope. Self-referencing
objects are still pushed to the Lua stack by reference.

src/core/lua-utils/object.c
src/core/lua-utils/object.h

index 956f0a5..c0c4ac7 100644 (file)
@@ -42,6 +42,7 @@
 #include <murphy/common/log.h>
 #include <murphy/common/env.h>
 #include <murphy/common/mm.h>
+#include <murphy/common/refcnt.h>
 
 #include <murphy/core/lua-bindings/murphy.h>
 #include <murphy/core/lua-utils/object.h>
 typedef struct {
     void *selfish;                       /* verification pointer(ish) to us */
     mrp_lua_classdef_t *def;             /* class definition for this object */
-    int  luatbl;                         /* lua table */
-    int  reftbl;                         /* table of private references */
-    int  exttbl;                         /* table of object extensions */
+    struct {
+        int self;                        /* self reference for static objects */
+        int ext;                         /* object extensions */
+        int priv;                        /* private references */
+    } refs;
+    mrp_refcnt_t refcnt;                 /* object reference count */
     int  dead : 1;                       /* being cleaned up */
     int  initializing : 1;               /* being initialized */
     mrp_list_hook_t hook[0];             /* to object list if we're tracking */
@@ -121,9 +125,6 @@ static struct {
 static mrp_lua_classdef_t **classdefs;
 static int                  nclassdef;
 
-/** Our (reference to our) shared table for storing references within object. */
-static int reftbl = LUA_NOREF;
-
 /**
  * Macros to convert between userdata and user-visible data addresses.
  */
@@ -495,13 +496,61 @@ mrp_lua_type_t mrp_lua_class_type(const char *type_name)
     return class_by_type_name(type_name)->type_id;
 }
 
+/** Dump the given oject instance for debugging. */
+static char *__instance(userdata_t **uptr, const char *fmt)
+{
+    static char buf[16][256];
+    static int  idx = 0;
+
+    userdata_t *u = uptr ? *uptr : NULL;
+    char       *p = buf[idx++];
+
+    MRP_UNUSED(fmt);
+
+    if (u != NULL)
+        snprintf(p, sizeof(buf[0]), "<%s:%s>%p(%p+%ld)",
+                 u->def->flags & MRP_LUA_CLASS_DYNAMIC ? "D" : "S",
+                 u->def->type_name, uptr, u, USERDATA_SIZE);
+    else
+        snprintf(p, sizeof(buf[0]), "<NULL> instance");
+
+    if (idx >= (int)MRP_ARRAY_SIZE(buf))
+        idx = 0;
+
+    return p;
+}
+
+/** Dump the given object for debugging. */
+static char *__object(userdata_t *u, const char *fmt)
+{
+    static char buf[16][256];
+    static int  idx = 0;
+    char       *p = buf[idx++];
+
+    MRP_UNUSED(fmt);
+
+    if (u != NULL)
+        snprintf(p, sizeof(buf[0]), "<%s:%s>(%p+%ld)",
+                 u->def->flags & MRP_LUA_CLASS_DYNAMIC ? "D" : "S",
+                 u->def->type_name, u, USERDATA_SIZE);
+    else
+        snprintf(p, sizeof(buf[0]), "<NULL> object");
+
+    if (idx >= (int)MRP_ARRAY_SIZE(buf))
+        idx = 0;
+
+    return p;
+}
+
+
 /** Create a new object, optionally assign it to a class table name or index. */
 void *mrp_lua_create_object(lua_State *L, mrp_lua_classdef_t *def,
                             const char *name, int idx)
 {
     int class = 0;
     size_t size;
-    userdata_t *userdata;
+    userdata_t **userdatap, *userdata;
+    int dynamic;
 
     MRP_UNUSED(class_by_userdata_id);
 
@@ -526,25 +575,38 @@ void *mrp_lua_create_object(lua_State *L, mrp_lua_classdef_t *def,
     lua_pushliteral(L, "userdata");
 
     size = USERDATA_SIZE + def->userdata_size;
-    userdata = (userdata_t *)lua_newuserdata(L, size);
+    userdata = (userdata_t *)mrp_allocz(size);
+
+    if (userdata == NULL) {
+        mrp_log_error("Failed to allocate object of type %s <%s>.",
+                      def->class_name, def->type_name);
+        return NULL;
+    }
 
-    memset(userdata, 0, size);
+    userdatap = (userdata_t **)lua_newuserdata(L, sizeof(userdata));
+    *userdatap = userdata;
+    mrp_refcnt_init(&userdata->refcnt);
 
     if (cfg.track)
         mrp_list_init(&userdata->hook[0]);
 
-    userdata->reftbl = LUA_NOREF;
-    userdata->exttbl = LUA_NOREF;
+    userdata->refs.priv = LUA_NOREF;
+    userdata->refs.ext  = LUA_NOREF;
 
     luaL_getmetatable(L, def->userdata_id);
     lua_setmetatable(L, -2);
 
-    lua_rawset(L, -3);
+    lua_rawset(L, -3);              /* userdata["userdata"]=lib<def->methods> */
 
-    lua_pushvalue(L, -1);
     userdata_setself(userdata);
     userdata->def    = def;
-    userdata->luatbl = luaL_ref(L, LUA_REGISTRYINDEX);
+
+    if (!(dynamic = def->flags & MRP_LUA_CLASS_DYNAMIC)) {
+        lua_pushvalue(L, -1);       /* userdata->refs.self = lib<def->methods> */
+        userdata->refs.self = luaL_ref(L, LUA_REGISTRYINDEX);
+    }
+    else
+        userdata->refs.self = LUA_NOREF;
 
     if (name) {
         lua_pushstring(L, name);
@@ -573,6 +635,9 @@ void *mrp_lua_create_object(lua_State *L, mrp_lua_classdef_t *def,
         mrp_list_append(&def->objects, &userdata->hook[0]);
 
     def->nactive++;
+    def->ncreated++;
+
+    mrp_debug("created %s", __instance(userdatap, "*"));
 
     return USER_TO_DATA(userdata);
 }
@@ -613,22 +678,40 @@ void mrp_lua_destroy_object(lua_State *L, const char *name, int idx, void *data)
     userdata_t *userdata = userdata_get(data, CHECK);
     mrp_lua_classdef_t *def;
 
-    if (userdata && !userdata->dead) {
+    if (userdata) {
+        if (userdata->dead)
+            return;
+
         userdata->dead = true;
         def = userdata->def;
-        def->nactive--;
-        def->ndead++;
 
-        object_delete_reftbl(userdata, L);
-        object_delete_exttbl(userdata, L);
+        if (!(def->flags & MRP_LUA_CLASS_DYNAMIC)) {
+            mrp_debug("destroying %s (name: '%s', idx: %d)",
+                      __object(userdata, "*"), name ? name : "", idx);
 
-        lua_rawgeti(L, LUA_REGISTRYINDEX, userdata->luatbl);
-        lua_pushstring(L, "userdata");
-        lua_pushnil(L);
-        lua_rawset(L, -3);
-        lua_pop(L, -1);
+            def->nactive--;
+            def->ndead++;
+
+            object_delete_reftbl(userdata, L);
+            object_delete_exttbl(userdata, L);
 
-        luaL_unref(L, LUA_REGISTRYINDEX, userdata->luatbl);
+            if (userdata->refs.self != LUA_NOREF) {
+                lua_rawgeti(L, LUA_REGISTRYINDEX, userdata->refs.self);
+                lua_pushstring(L, "userdata");
+                lua_pushnil(L);
+                lua_rawset(L, -3);
+                lua_pop(L, -1);
+
+                luaL_unref(L, LUA_REGISTRYINDEX, userdata->refs.self);
+                userdata->refs.self = LUA_NOREF;
+            }
+        }
+        else {
+            mrp_log_error("ERROR: %s should be called for static object",
+                          __FUNCTION__);
+            mrp_log_error("ERROR: but was called for %s",
+                          __object(userdata, "*"));
+        }
 
         if (name || idx) {
             mrp_lua_get_class_table(L, def);
@@ -648,9 +731,18 @@ void mrp_lua_destroy_object(lua_State *L, const char *name, int idx, void *data)
             lua_pop(L, 1);
         }
 
+#if 0
+        /* remove initial reference */
+        mrp_debug("removing initial reference of <%s>@%p(%p) ", def->type_name,
+                  "of class <%s> (%s)", userdata, data);
+                  def->type_name);
+        mrp_unref_obj(userdata, refcnt);
+#endif
+
     }
 }
 
+
 /** Find the object corresponding to the given name in the class table. */
 int mrp_lua_find_object(lua_State *L, mrp_lua_classdef_t *def, const char *name)
 {
@@ -672,7 +764,7 @@ int mrp_lua_find_object(lua_State *L, mrp_lua_classdef_t *def, const char *name)
 /** Check if the object @idx is ours and optionally of the given type. */
 void *mrp_lua_check_object(lua_State *L, mrp_lua_classdef_t *def, int idx)
 {
-    userdata_t *userdata;
+    userdata_t *userdata, **userdatap;
     char errmsg[256];
 
     luaL_checktype(L, idx, LUA_TTABLE);
@@ -681,16 +773,26 @@ void *mrp_lua_check_object(lua_State *L, mrp_lua_classdef_t *def, int idx)
     lua_pushliteral(L, "userdata");
     lua_rawget(L, -2);
 
-    if (!def)
-        userdata = (userdata_t *)lua_touserdata(L, -1);
+    if (!def) {
+        userdatap = (userdata_t **)lua_touserdata(L, -1);
+
+        if (!userdatap) {
+            luaL_argerror(L, idx, "couldn't find expected userdata");
+            userdata = NULL;
+        }
+        else
+            userdata = *userdatap;
+    }
     else {
-        userdata = (userdata_t *)luaL_checkudata(L, -1, def->userdata_id);
+        userdatap = (userdata_t **)luaL_checkudata(L, -1, def->userdata_id);
 
-        if (!userdata || def != userdata->def) {
+        if (!userdatap || def != (userdata = *userdatap)->def) {
             snprintf(errmsg, sizeof(errmsg), "'%s' expected", def->class_name);
             luaL_argerror(L, idx, errmsg);
             userdata = NULL;
         }
+        else
+            userdata = *userdatap;
     }
 
     if (userdata_getself(userdata) != userdata) {
@@ -788,7 +890,7 @@ int mrp_lua_pointer_of_type(void *data, mrp_lua_type_t type)
 /** Obtain the user-visible data for the object @idx. */
 void *mrp_lua_to_object(lua_State *L, mrp_lua_classdef_t *def, int idx)
 {
-    userdata_t *userdata;
+    userdata_t *userdata, **userdatap;
     int top = lua_gettop(L);
 
     idx = (idx < 0) ? lua_gettop(L) + idx + 1 : idx;
@@ -799,13 +901,15 @@ void *mrp_lua_to_object(lua_State *L, mrp_lua_classdef_t *def, int idx)
     lua_pushliteral(L, "userdata");
     lua_rawget(L, idx);
 
-    userdata = (userdata_t *)lua_touserdata(L, -1);
+    userdatap = (userdata_t **)lua_touserdata(L, -1);
 
-    if (!userdata || !lua_getmetatable(L, -1)) {
+    if (!userdatap || !lua_getmetatable(L, -1)) {
         lua_settop(L, top);
         return NULL;
     }
 
+    userdata = *userdatap;
+
     lua_getfield(L, LUA_REGISTRYINDEX, def->userdata_id);
 
     if (!lua_rawequal(L, -1, -2) || userdata != userdata_getself(userdata))
@@ -816,17 +920,63 @@ void *mrp_lua_to_object(lua_State *L, mrp_lua_classdef_t *def, int idx)
     return userdata ? USER_TO_DATA(userdata) : NULL;
 }
 
+
 /** Push the given data on the stack. */
 int mrp_lua_push_object(lua_State *L, void *data)
 {
     userdata_t *userdata = userdata_get(data, CHECK);
+    userdata_t **userdatap;
+    mrp_lua_classdef_t *def = userdata ? userdata->def : NULL;
+
+    /*
+     * Notes:
+     *
+     *    This is essentially mrp_lua_create_object with a few differences:
+     *      1) No need for name or idx handling.
+     *      2) No need for adding global reference, already done during
+     *         initial object creation if necessary.
+     *      3) Instead of creating a Lua userdata pointer and a new userdata,
+     *         we only create a Lua userdata pointer, make it point to the
+     *         existing userdata and increate the userdata reference count.
+     *
+     *   userdata_destructor has been similarly modified to decrese the
+     *   reference count of userdata and destroy the object only when the
+     *   last reference is dropped.
+     */
 
     mrp_lua_checkstack(L, -1);
 
-    if (!userdata || userdata->dead)
+    if (!userdata || !def || userdata->dead) {
         lua_pushnil(L);
-    else
-        lua_rawgeti(L, LUA_REGISTRYINDEX, userdata->luatbl);
+        return 1;
+    }
+
+    if (!(def->flags & MRP_LUA_CLASS_DYNAMIC)) {
+        lua_rawgeti(L, LUA_REGISTRYINDEX, userdata->refs.self);
+
+        mrp_debug("pushed %s", __object(userdata, "*"));
+    }
+    else {
+        lua_createtable(L, 1, 1);
+
+        luaL_openlib(L, NULL, def->methods, 0);
+
+        luaL_getmetatable(L, def->class_id);
+        lua_setmetatable(L, -2);
+
+        lua_pushliteral(L, "userdata");
+
+        userdatap = (userdata_t **)lua_newuserdata(L, sizeof(userdata));
+        *userdatap = userdata;
+        mrp_ref_obj(userdata, refcnt);
+
+        luaL_getmetatable(L, def->userdata_id);
+        lua_setmetatable(L, -2);
+
+        lua_rawset(L, -3);
+
+        mrp_debug("pushed %s", __instance(userdatap, "*"));
+    }
 
     return 1;
 }
@@ -864,24 +1014,50 @@ static bool valid_id(const char *id)
 
 static int userdata_destructor(lua_State *L)
 {
-    userdata_t *userdata;
+    userdata_t *userdata, **userdatap;
     mrp_lua_classdef_t *def;
 
-    if (!(userdata = lua_touserdata(L, -1)) || !lua_getmetatable(L, -1))
+    if (!(userdatap = lua_touserdata(L, -1)) || !lua_getmetatable(L, -1))
         luaL_error(L, "attempt to destroy unknown type of userdata");
     else {
+        userdata = *userdatap;
         def = userdata->def;
 
-        if (cfg.track)
-            mrp_list_delete(&userdata->hook[0]);
+        if (mrp_unref_obj(userdata, refcnt)) {
+            mrp_debug("freeing %s", __instance(userdatap, "*"));
 
-        def->ndead--;
+            if (cfg.track)
+                mrp_list_delete(&userdata->hook[0]);
 
-        lua_getfield(L, LUA_REGISTRYINDEX, def->userdata_id);
-        if (!lua_rawequal(L, -1, -2))
-            luaL_typerror(L, -2, def->userdata_id);
-        else
-            def->destructor(USER_TO_DATA(userdata));
+            if (def->flags & MRP_LUA_CLASS_DYNAMIC) {
+                def->nactive--;
+
+                object_delete_reftbl(userdata, L);
+                object_delete_exttbl(userdata, L);
+            }
+            else {
+                def->ndead--;
+            }
+
+            def->ndestroyed++;
+
+            lua_getfield(L, LUA_REGISTRYINDEX, def->userdata_id);
+            if (!lua_rawequal(L, -1, -2))
+                luaL_typerror(L, -2, def->userdata_id);
+            else
+                def->destructor(USER_TO_DATA(userdata));
+
+            *userdatap = NULL;
+            mrp_free(userdata);
+        }
+        else {
+            mrp_debug("unreffed %s", __instance(userdatap, "*"));
+
+            if (!(def->flags & MRP_LUA_CLASS_DYNAMIC))
+                mrp_log_error("Hmm, more refs for a static object ?");
+
+            *userdatap = NULL;
+        }
     }
 
     return 0;
@@ -1383,58 +1559,52 @@ static int seterr(lua_State *L, char *e, size_t size, const char *format, ...)
 
 static void object_create_reftbl(userdata_t *u, lua_State *L)
 {
-    if (u->def->flags & MRP_LUA_CLASS_PRIVREFS) {
-        lua_newtable(L);
-        u->reftbl = luaL_ref(L, LUA_REGISTRYINDEX);
-    }
-    else {
-        if (reftbl == LUA_NOREF) {
-            lua_newtable(L);
-            reftbl = luaL_ref(L, LUA_REGISTRYINDEX);
-        }
-        u->reftbl = reftbl;
-    }
+    lua_newtable(L);
+    u->refs.priv = luaL_ref(L, LUA_REGISTRYINDEX);
 }
 
 
 static void object_delete_reftbl(userdata_t *u, lua_State *L)
 {
-    int tidx;
+    int prividx, ref;
 
-    if (u->reftbl == LUA_NOREF || u->reftbl == reftbl)
+    if (u->refs.priv == LUA_NOREF)
         return;
 
-    lua_rawgeti(L, LUA_REGISTRYINDEX, u->reftbl);
-    tidx = lua_gettop(L);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.priv);
+    prividx = lua_gettop(L);
+
+    /*
+     * Notes:
+     *     I'm not sure whether explicitly unreffing all references
+     *     is necessary. I couldn't find anything about it in the
+     *     documentation, so let's try to play safe.
+     */
 
     lua_pushnil(L);
-    while (lua_next(L, tidx) != 0) {
+    while (lua_next(L, prividx) != 0) {
+        ref = lua_tointeger(L, -1);
+        mrp_debug("freeing reference %d for %s", ref, __object(u, "*"));
+        luaL_unref(L, prividx, ref);
         lua_pop(L, 1);
-        lua_pushvalue(L, -1);
-        lua_pushnil(L);
-        lua_rawset(L, -3);
     }
 
-    luaL_unref(L, LUA_REGISTRYINDEX, u->reftbl);
-    lua_pop(L, 1);
-    u->reftbl = LUA_NOREF;
-}
-
+    luaL_unref(L, LUA_REGISTRYINDEX, u->refs.priv);
+    u->refs.priv = LUA_NOREF;
 
-static inline int get_reftbl(userdata_t *u)
-{
-    return (u != NULL ? u->reftbl : reftbl);
+    lua_settop(L, prividx);
+    lua_pop(L, 1);
 }
 
 
 /** Refcount the object @idx for or within the given object. */
 int mrp_lua_object_ref_value(void *data, lua_State *L, int idx)
 {
-    int tbl = get_reftbl(userdata_get(data, CHECK));
+    userdata_t *u = userdata_get(data, CHECK);
     int ref;
 
-    if (tbl != LUA_NOREF) {
-        lua_rawgeti(L, LUA_REGISTRYINDEX, tbl);
+    if (u->refs.priv != LUA_NOREF) {
+        lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.priv);
         lua_pushvalue(L, idx > 0 ? idx : idx - 1);
         ref = luaL_ref(L, -2);
         lua_pop(L, 1);
@@ -1448,13 +1618,11 @@ int mrp_lua_object_ref_value(void *data, lua_State *L, int idx)
 /** Release the given reference for/from within the given object. */
 void mrp_lua_object_unref_value(void *data, lua_State *L, int ref)
 {
-    int tbl;
+    userdata_t *u = userdata_get(data, CHECK);
 
     if (ref != LUA_NOREF && ref != LUA_REFNIL) {
-        tbl = get_reftbl(userdata_get(data, CHECK));
-
-        if (tbl != LUA_NOREF) {
-            lua_rawgeti(L, LUA_REGISTRYINDEX, tbl);
+        if (u->refs.priv != LUA_NOREF) {
+            lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.priv);
             luaL_unref(L, -1, ref);
             lua_pop(L, 1);
         }
@@ -1464,46 +1632,53 @@ void mrp_lua_object_unref_value(void *data, lua_State *L, int ref)
 /** Obtain and push the object for the given reference on the stack. */
 int mrp_lua_object_deref_value(void *data, lua_State *L, int ref, int pushnil)
 {
-    int tbl;
+    userdata_t *u = userdata_get(data, CHECK);
 
-    if (ref != LUA_NOREF) {
-        tbl = get_reftbl(userdata_get(data, CHECK));
+    if (ref == LUA_REFNIL) {
+    nilref:
+        lua_pushnil(L);
+        return 1;
+    }
 
-        if (ref != LUA_REFNIL && tbl != LUA_NOREF) {
-            lua_rawgeti(L, LUA_REGISTRYINDEX, tbl);
-            lua_rawgeti(L, -1, ref);
-            lua_remove(L, -2);
-        }
+    if (ref == LUA_NOREF) {
+        if (pushnil)
+            goto nilref;
         else
-        nilref:
-            lua_pushnil(L);
-
-        return 1;
+            return 0;
     }
-    else {
+
+    if (u->refs.priv == LUA_NOREF) {
         if (pushnil)
             goto nilref;
         else
             return 0;
     }
+
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.priv);
+    lua_rawgeti(L, -1, ref);
+    lua_remove(L, -2);
+
+    return 1;
 }
 
 /** Obtain a new reference based on the reference owned by owner. */
 int mrp_lua_object_getref(void *owner, void *data, lua_State *L, int ref)
 {
-    int otbl, dtbl;
+    userdata_t *uo = userdata_get(owner, CHECK);
+    userdata_t *ud = userdata_get(data , CHECK);
 
     if (ref == LUA_NOREF || ref == LUA_REFNIL)
         return ref;
 
-    if ((otbl = get_reftbl(userdata_get(owner, CHECK))) == LUA_NOREF ||
-        (dtbl = get_reftbl(userdata_get(data , CHECK))) == LUA_NOREF)
+    if (uo->refs.priv == LUA_NOREF || ud->refs.priv == LUA_NOREF)
         return LUA_NOREF;
 
-    lua_rawgeti(L, LUA_REGISTRYINDEX, otbl);
-    lua_rawgeti(L, LUA_REGISTRYINDEX, dtbl);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, uo->refs.priv);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->refs.priv);
+
     lua_rawgeti(L, -2, ref);
     ref = luaL_ref(L, -2);
+
     lua_pop(L, 2);
 
     return ref;
@@ -1513,31 +1688,40 @@ int mrp_lua_object_getref(void *owner, void *data, lua_State *L, int ref)
 static void object_create_exttbl(userdata_t *u, lua_State *L)
 {
     lua_newtable(L);
-    u->exttbl = luaL_ref(L, LUA_REGISTRYINDEX);
+    u->refs.ext = luaL_ref(L, LUA_REGISTRYINDEX);
 }
 
 
 static void object_delete_exttbl(userdata_t *u, lua_State *L)
 {
-    int tidx;
+    int extidx, ref;
 
-    if (u->exttbl == LUA_NOREF)
+    if (u->refs.ext == LUA_NOREF)
         return;
 
-    lua_rawgeti(L, LUA_REGISTRYINDEX, u->exttbl);
-    tidx = lua_gettop(L);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
+    extidx = lua_gettop(L);
+
+    /*
+     * Notes:
+     *     I'm not sure whether explicitly unreffing all references
+     *     is necessary. I couldn't find anything about it in the
+     *     documentation, so let's try to play safe.
+     */
 
     lua_pushnil(L);
-    while (lua_next(L, tidx) != 0) {
+    while (lua_next(L, extidx) != 0) {
+        ref = lua_tointeger(L, -1);
+        mrp_debug("freeing reference %d for %s", ref, __object(u, "*"));
+        luaL_unref(L, extidx, ref);
         lua_pop(L, 1);
-        lua_pushvalue(L, -1);
-        lua_pushnil(L);
-        lua_rawset(L, -3);
     }
 
-    luaL_unref(L, LUA_REGISTRYINDEX, u->exttbl);
+    luaL_unref(L, LUA_REGISTRYINDEX, u->refs.ext);
+    u->refs.ext = LUA_NOREF;
+
+    lua_settop(L, extidx);
     lua_pop(L, 1);
-    u->exttbl = LUA_NOREF;
 }
 
 
@@ -1546,7 +1730,7 @@ static int object_setext(void *data, lua_State *L, const char *name,
 {
     userdata_t *u = DATA_TO_USER(data);
 
-    if (u->exttbl == LUA_NOREF) {
+    if (u->refs.ext == LUA_NOREF) {
         if (err)
             return seterr(L, err, esize, "trying to set user-defined field %s "
                           "for non-extensible object %s", name,
@@ -1557,7 +1741,7 @@ static int object_setext(void *data, lua_State *L, const char *name,
                               u->def->class_name);
     }
 
-    lua_rawgeti(L, LUA_REGISTRYINDEX, u->exttbl);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
     lua_pushvalue(L, vidx > 0 ? vidx : vidx - 1);
     lua_setfield(L, -2, name);
     lua_pop(L, 1);
@@ -1570,12 +1754,12 @@ static int object_getext(void *data, lua_State *L, const char *name)
 {
     userdata_t *u = DATA_TO_USER(data);
 
-    if (u->exttbl == LUA_NOREF) {
+    if (u->refs.ext == LUA_NOREF) {
         lua_pushnil(L);
         return 1;
     }
 
-    lua_rawgeti(L, LUA_REGISTRYINDEX, u->exttbl);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
     lua_getfield(L, -1, name);
     lua_remove(L, -2);
 
@@ -1587,13 +1771,13 @@ static int object_setiext(void *data, lua_State *L, int idx, int val)
 {
     userdata_t *u = DATA_TO_USER(data);
 
-    if (u->exttbl == LUA_NOREF) {
+    if (u->refs.ext == LUA_NOREF) {
         return luaL_error(L, "trying to set user-defined index %d "
                           "for non-extensible object %s", idx,
                           u->def->class_name);
     }
 
-    lua_rawgeti(L, LUA_REGISTRYINDEX, u->exttbl);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
     lua_pushvalue(L, val > 0 ? val : val - 1);
     lua_rawseti(L, -2, idx);
     lua_pop(L, 1);
@@ -1606,12 +1790,12 @@ static int object_getiext(void *data, lua_State *L, int idx)
 {
     userdata_t *u = DATA_TO_USER(data);
 
-    if (u->exttbl == LUA_NOREF) {
+    if (u->refs.ext == LUA_NOREF) {
         lua_pushnil(L);
         return 1;
     }
 
-    lua_rawgeti(L, LUA_REGISTRYINDEX, u->exttbl);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
     lua_rawgeti(L, -1, idx);
     lua_remove(L, -2);
 
@@ -1855,7 +2039,7 @@ int mrp_lua_set_member(void *data, lua_State *L, char *err, size_t esize)
     vtype = lua_type(L, -1);
 
     mrp_debug("setting %s.%s of Lua object %p(%p)", u->def->class_name,
-              m->name, data, u);
+              m->name, u, data);
 
     if (!u->initializing && (m->flags & MRP_LUA_CLASS_READONLY))
         return seterr(L, err, esize, "%s.%s of Lua object is readonly",
@@ -2005,8 +2189,10 @@ int mrp_lua_set_member(void *data, lua_State *L, char *err, size_t esize)
 
         lua_pushliteral(L, "userdata");
         lua_rawget(L, -2);
-        if ((v.obj.ptr = lua_touserdata(L, -1)) != NULL)
+        if ((v.obj.ptr = lua_touserdata(L, -1)) != NULL) {
+            v.obj.ptr = *(void **)v.obj.ptr;
             v.obj.ptr = USER_TO_DATA(v.obj.ptr);
+        }
         lua_pop(L, 1);
 
         if (m->setter(data, L, midx, &v) == 1)
@@ -2383,8 +2569,7 @@ static int override_tostring(lua_State *L)
         if (u->def->tostring == NULL ||
             u->def->tostring(MRP_LUA_TOSTR_LUA, buf, sizeof(buf),
                              L, data) <= 0) {
-            snprintf(buf, sizeof(buf), "<object %s(%s)>@%p(%p)",
-                     u->def->class_id, u->def->type_name, data, u);
+            snprintf(buf, sizeof(buf), "<%s)", __object(u, "*"));
         }
 
         lua_pushstring(L, buf);
@@ -2514,7 +2699,14 @@ ssize_t mrp_lua_object_tostr(mrp_lua_tostr_mode_t mode, char *buf, size_t size,
 ssize_t mrp_lua_index_tostr(mrp_lua_tostr_mode_t mode, char *buf, size_t size,
                             lua_State *L, int index)
 {
-    return mrp_lua_object_tostr(mode, buf, size, L, lua_touserdata(L, index));
+    userdata_t **userdatap;
+
+    userdatap = (userdata_t **)lua_touserdata(L, index);
+
+    if (userdatap != NULL)
+        return mrp_lua_object_tostr(mode, buf, size, L, *userdatap);
+    else
+        return snprintf(buf, size, "<invalid object, no userdata>");;
 }
 
 
@@ -2526,22 +2718,25 @@ void mrp_lua_dump_objects(mrp_lua_tostr_mode_t mode, lua_State *L, FILE *fp)
     userdata_t         *u;
     void               *d;
     int                 i, cnt;
+    char                active[64], dead[64], created[64], destroyed[64];
     char                obj[4096];
 
     fprintf(fp, "Lua memory usage: %d k, %.2f M\n",
             lua_gc(L, LUA_GCCOUNT, 0), lua_gc(L, LUA_GCCOUNT, 0) / 1024.0);
 
+    fprintf(fp, "Objects by class/type: A=active, D=dead, "
+            "c=created, d=destroyed\n");
     for (i = 0; i < nclassdef; i++) {
         def = classdefs[i];
 
-        if (def->nactive == 0 && def->ndead == 0) {
-            fprintf(fp, "No active or dead objects for Lua class <%s> (%s).\n",
-                    def->class_name, def->type_name);
-            continue;
-        }
+        snprintf(active, sizeof(active), "%u", def->nactive);
+        snprintf(dead, sizeof(dead), "%u", def->ndead);
+        snprintf(created, sizeof(created), "%u", def->ncreated);
+        snprintf(destroyed, sizeof(destroyed), "%u", def->ndestroyed);
 
-        fprintf(fp, "%d active, %d dead objects for Lua class <%s> (%s)\n",
-                def->nactive, def->ndead,def->class_name, def->type_name);
+        fprintf(fp, "<%s/%s>: A:%s, D:%s, c:%s, d:%s\n",
+                def->class_name, def->type_name,
+                active, dead, created, destroyed);
 
         cnt = 0;
         mrp_list_foreach(&def->objects, p, n) {
index 5638b3a..5be687e 100644 (file)
         .flags         = 0,                                             \
     }
 
+#define MRP_LUA_CLASS_DEF_FLAGS(_name, _constr, _type, _destr, _methods,      \
+                                _overrides, _class_flags)                     \
+    static mrp_lua_classdef_t _name ## _ ## _constr ## _class_def = {         \
+        .class_name    = MRP_LUA_CLASS_NAME(_name),                           \
+        .class_id      = MRP_LUA_CLASS_ID(_name, _constr),                    \
+        .constructor   = # _name "." # _constr,                               \
+        .destructor    = _destr,                                              \
+        .type_name     = #_type,                                              \
+        .type_id       = -1,                                                  \
+        .userdata_id   = MRP_LUA_UDATA_ID(_name, _constr),                    \
+        .userdata_size = sizeof(_type),                                       \
+        .methods       = _methods,                                            \
+        .overrides     = _overrides,                                          \
+        .members       = NULL,                                                \
+        .nmember       = 0,                                                   \
+        .natives       = NULL,                                                \
+        .nnative       = 0,                                                   \
+        .notify        = NULL,                                                \
+        .flags         = _class_flags,                                        \
+    }
 
 
+#define MRP_LUA_CLASS_DEF_SIMPLE_FLAGS(_name, _type, _destr, _methods,  \
+                                       _overrides, _class_flags)        \
+    static luaL_reg _name ## _class_methods[]   = _methods;             \
+    static luaL_reg _name ## _class_overrides[] = _overrides;           \
+                                                                        \
+    static mrp_lua_classdef_t _name ## _class_def = {                   \
+        .class_name    = MRP_LUA_CLASS_NAME(_name),                     \
+        .class_id      = MRP_LUA_CLASS_ID(_name, _constr),              \
+        .constructor   = # _name,                                       \
+        .destructor    = _destr,                                        \
+        .type_name     = #_type,                                        \
+        .type_id       = -1,                                            \
+        .userdata_id   = MRP_LUA_UDATA_ID(_name, _constr),              \
+        .userdata_size = sizeof(_type),                                 \
+        .methods       = _name ## _class_methods,                       \
+        .overrides     = _name ## _class_overrides,                     \
+        .members       = NULL,                                          \
+        .nmember       = 0,                                             \
+        .natives       = NULL,                                          \
+        .nnative       = 0,                                             \
+        .notify        = NULL,                                          \
+        .flags         = _class_flags,                                  \
+    }
+
 
 #define MRP_LUA_FOREACH_FIELD(_L, _i, _n, _l)                           \
     for (lua_pushnil(_L);                                               \
@@ -234,10 +278,10 @@ typedef enum {
     MRP_LUA_CLASS_NOTIFY     = 0x004,    /* notify when member is changed */
     MRP_LUA_CLASS_NOINIT     = 0x008,    /* don't initialize member */
     MRP_LUA_CLASS_NOOVERRIDE = 0x010,    /* don't override setters, getters */
-    MRP_LUA_CLASS_PRIVREFS   = 0x020,    /* private references */
-    MRP_LUA_CLASS_RAWGETTER  = 0x040,    /* getter pushes to the stack */
-    MRP_LUA_CLASS_RAWSETTER  = 0x080,    /* setter takes args from the stack */
-    MRP_LUA_CLASS_USESTACK   = 0x100,    /* autobridged method uses the stack */
+    MRP_LUA_CLASS_RAWGETTER  = 0x020,    /* getter pushes to the stack */
+    MRP_LUA_CLASS_RAWSETTER  = 0x040,    /* setter takes args from the stack */
+    MRP_LUA_CLASS_USESTACK   = 0x080,    /* autobridged method uses the stack */
+    MRP_LUA_CLASS_DYNAMIC    = 0x100,    /* allow dynamic GC for this class */
 } mrp_lua_class_flag_t;
 
 /*
@@ -628,6 +672,8 @@ struct mrp_lua_classdef_s {
 
     mrp_lua_tostr_t tostring;            /* stringification handler */
     mrp_list_hook_t objects;             /* instances of this class */
+    uint32_t        ncreated;            /* number of objects created */
+    uint32_t        ndestroyed;          /* number of objects destroyed */
     int             nactive;             /* number of active objects */
     int             ndead;               /* nuber of dead objects */