From: Krisztian Litkey Date: Thu, 16 Oct 2014 19:33:11 +0000 (+0300) Subject: lua-utils: add support for dynamically collected Lua objects. X-Git-Tag: v0.0.60~8 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=8b6c91d10156a2b54649b35a58ea83cfb7d82c54;p=profile%2Fivi%2Fmurphy.git lua-utils: add support for dynamically collected Lua objects. 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. --- diff --git a/src/core/lua-utils/object.c b/src/core/lua-utils/object.c index 956f0a5..c0c4ac7 100644 --- a/src/core/lua-utils/object.c +++ b/src/core/lua-utils/object.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -58,9 +59,12 @@ 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]), " 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]), " 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"]=libmethods> */ - 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 = libmethods> */ + 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), "@%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, "");; } @@ -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) { diff --git a/src/core/lua-utils/object.h b/src/core/lua-utils/object.h index 5638b3a..5be687e 100644 --- a/src/core/lua-utils/object.h +++ b/src/core/lua-utils/object.h @@ -128,8 +128,52 @@ .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 */