Rambling about naming objects and this NamedObjects module...

My premise is this: in order to take tools like Tiled/Lime/Corona to the next level, you have to have some kind of name-object system that allows you to specify relationships about objects that aren't there (yet). As always, one additional level of indirection solves everything, so if we use an extra naming abstraction to point at objects, we can use names (==strings) to specify/declare relationships. In concrete terms, we want to use names in our Tiled-object properties to hook objects up to event handlers...

Stating the requirement is easy, and having a simple global name-object table is also easy, and if we would have enough memory it would be that easy - unfortunately, we have to consider the memory restrictions on the devices - we have to be able to free up the memory of objects that we do no longer use. The good thing is that Corona/Lua's runtime-environments does automatically free up memory - it has an automatic garbage collection system (garbage collector - GC) that recycles the objects that are no longer in use - i.e. objects that are no longer referred to in you code.

That last part, "objects that are no longer referred to in you code", is a nasty one that doesn't work well with a simple name-object registry because any name-object entry essentially holds a reference to that object. Therefor if your code has no more active references to your object but the name-object registry does... the GC can not do its work - the object will remain in existence and eat up precious memory.

Of course you can put the burden again on the programmer to delete any name-object entry in the registry that are no longer used... but that feels very much the same as explicitly deleting objects, which is something you only wish upon C/C++ programmers (...and IOS-ObjectiveC ones...).
However, the world is not that perfect, as we are forced to removeSelf any of the display objects that we do not use anymore... or at least most of the time as they sometimes do get deleted automatically... so at least those times when we have to, which is unfortunately not very well documented by Ansca...(understatement). One area where you run into that issue easily is with event handlers that are sent to removeSelf'ed display objects. The later do still exist as a Lua-table but have lost their Corona-magic as they barf when you call any of the functions on them that used to work.

Anyway... what this all implies for me is that we need a name-object registry that is GC-friendly and is display-object-orphan-aware: name-object entries should live as long as the object lives, and if the object lives as a crippled display-object-orphan, I want to know when I resolve its name. With those features, the name-object registry would not become a memory-hug that would impede performance further, and would help to prevent the use of those dreaded orphans that could crash the program.

Lua provides so-called weak-tables that can help to implement a GC-friendly name-object registry. Those weak-table allow you to keep entries that will not count as hard-references for the GC. So if the entry in a weak table is the last existing reference to an object, that object will be GC'ed and the entry will disappear at the same time. Great... that problem is solved then.

Not exactly solved... as we have use cases where would actually like the name-object registry to hold-on to a reference to "park" the object until we need it and we can get a hold of it thru its name resolution. So now we have competing requirements...

What we can do, is to make the registry entry itself depend on a context, i.e. the "existence" of another object (just one extra level of...). An explicit example of this is the following: when I read-in a map with "local map = lime.loadMap("tutorial12-fs.tmx")", then in general, all the objects created that are associated with that map will-be and/or should-be deleted when the map gets teared-down/destroyed. Such a map is probably equivalent to a game-level, so if you're done with that level, you want to clean-up all and everything that is associated with that level/map.

By associating the name-object entries with a context, like a map-object, we can either explicitly delete all those associated entries later in one statement, or we can rely on the GC to once it collects the map-object memory it automatically frees and removes all associated name-object entries from the registry (again thru the clever use of a weak-table for all the context associated entries).

So the name-object registration is thru: NamedObjects.registerNameObject("player", player, map), which allows me to get a reference to the player-object with NamedObjects.getObject("player") or get the name for the player-object thru NamedObjects.getName(player). The ("player", player) entry will at least exist as long as the map-object exists - if the latter gets GC'ed, the entry will disappear. Furthermore, the entry can also be explictly removed thru: NamedObjects.removeNameObject("player") or NamedObjects.removeNameObject(player) - note that there is a 1-1 mapping maintained between names and objects, so either will remove the entry. One can also remove the entry with: NamedObjects.removeNameObjectContext(map), which will not only explicitly remove that player-entry but all entries that were registered under the map-object's context.

There are two predefined contexts that are referred to by name: "WeakContext" and "PermanentContext". The first one is kind of weak, meaning that all entries within that "WeakContext" will have no hard-reference to that object and will not stand in the way of GC'ing. The "PermanentContext" is kind of permanent and will hold hard references to the entry's object for ever so you have to remove those entries explicitly if you want thos object to be GC'ed. Both context have their use cases.

Note that the situation will exist that the program will have no more references to an object and that object is literally waiting to be GC'ed, but a entry for that object is still in a weak-table - in that case you could still get a hold of a reference thru a name resolution and "revive" that object into real live... however, you'd better not make your program logic depend on it as it is a bug. To avoid ambiguous situation like that, you should explicitly remove entries when you know they are no longer valid and there is a slight chance that some part in the program may still refer to it by name.

Lastly, when you ask for the name or object resolution of a display object, the system detects/tests whether it is a healthy one or a crippled orphan, and return a nil instead of the orphan-reference. This should help program logic that only works with healthy display objects and also allows one to clean-up event handlers and such. Unfortunately, this detection can only be done if the healthy display object has been seen before, so one has to call: NamedObjects.registerDisplayObject(aDisplayObject) with a healthy display object before the system can detect the orphan. The following calls can also help: NamedObjects.isDisplayObject(aDisplayObject) and NamedObjects.isDisplayObjectOrphan(aDisplayObject).

I'd appreciate any comments/suggestions/alternatives/discussion... as this topic is not an easy one to explain and the associated issues not easy to solve.

Thanks for listening, FrankS.

PS. That NamedObjects module is part of the changed CollectibleItems tutorial that I posted about yesterday. Please look for NamedObjects.lua on that github link for the implementation.
"https://github.com/franks42/CollectibleItems2.9FS/blob/master/NamedObjects.lua"

views:1740 update:2011/10/13 16:30:09
corona forums © 2003-2011