By Kyle Wilson
Sunday, November 03, 2002
When I first started work at HeadSpin, one of the most mind-expanding concepts I encountered was the notion of an object registry. Every game object in a Plasma scene had a unique name. On load, every object registered itself by name with a master registry. Through the registry, you could look up a registry key for any object. A registry key was simply struct containing a name string and a pointer to a game object. Registry keys were generated for any name looked up in the registry, and once generated, were never removed.
This all sounds simple, and perhaps obvious, but at the time I wouldn't have believed how well it worked. I would have thought that all the string comparisons to look up and insert registry keys would be prohibitively expensive. But almost all lookups were done at load time rather than at execution time; objects simply stored pointers to the registry keys of other objects as needed. Most frames went by without a single key lookup. And while thousands of lookups were done at load time, tens of thousands of string comparisons are still pretty fast compared to the time required for disk I/O. Registry key lookups only accounted for about 2% of our total load time, in the end. My initial instinct would also have been that the space required for all the strings in the registry would be a problem, especially since we never deleted registry keys once they were created. The average object name ended up being about ten characters long, though. Even if we ended up having 25,000 game objects in realMyst, the registry would have taken less memory than a single 256 x 256 texture.
And what benefits did we get from the use of a registry? First, it made debugging vastly easier. Having unique and understandable identifiers for every object and being able to look up any object by name helped in tracking down errors more than I can possibly convey. Similarly, it helped in the creation of gameplay. In setting up triggers and actors, our designers were able to call objects by name, instead of by arcane identifiers. Finally, the registry was tremendously liberating when I was writing export/import and save/load code. Objects referred to one another indirectly, and could look up registry keys even for other objects which were not yet loaded. This meant that objects could load in any order. Eventually, for speed reasons, groups of objects were paged in and out on the fly. Because objects referred to one another indirectly through registry keys, the whole system handled the switch to paging with remarkable grace.
Another potential benefit of indirect reference, though one we never needed, is that objects can be moved in memory to reduce fragmentation. Because registry keys are handles offering indirect references to objects, objects can be relocated in memory and the pointer in the registry key can be updated to the new location without affecting any other objects.
My experiences at HeadSpin and Cyan made me a big fan of string-based solutions to problems of object interaction. I think that instead of calling between objects by checking type IDs and casting, I can expose pairs of string identifiers and functors. And I think that a HeadSpin-style registry could be extended in interesting ways to, say, map object structures into a navigable UNIX-style virtual directory structure, or keep micro-registries with every object so its components can look one another up by name.
I'm Kyle Wilson. I've worked in the game industry since I got out of grad school in 1997. Any opinions expressed herein are in no way representative of those of my employers.