I complained for a while about the difficulty I had in coming up with a satisfactory solution to loading various types of objects from XML area files. The problems of having recursive, semi-cyclic structure to the files posed quite a challenge. I eventually solved it by describing an object's persisted state in terms of a list of "properties" that are tied to actual members on instances of classes.
It was nice (really nice) to see my design validated at some level today, when I was able to write only the obvious code I had floating around in my brain and get saving working. Simply put, the saving process uses the same properties that were used in loading to extract data from the corresponding members of classes and put them automatically into named elements in the XML file. Again: the same mechanism I used to establish a relationship between XML elements in the area file and data members could be used both for reading and writing. Yes, I'm patting myself on the back for getting this right (at least right enough).
Gotchas for Bags
There are never no gotchas, but luckily the gotchas were easy to solve this time. The structure of a Bag
(i.e. container of things--dealies, mobs, etc.) in XML is a single element containing multiple elements, one for each thing in the bag, each with the name item
, but differentiated by an attribute called order
. Therefore, the "name" of each property of a bag in XML is actually an xpath like item[@order='1']
. The naive method of creating a new element with the "name" of the property and filling it with the contents of the item won't work, because it's not really a name.
To get around this, we make two passes. In the first pass, the bag creates empty elements with the correct element name and order
attribute. In the second pass, we look for each property's (i.e. item's) element in the XML, which is identified by a unique xpath query, and--rather than creating a new element for the item--reuse the existing one and fill it with the item's serialized representation.
Probably worth illustrating. Here's the first pass. The bag has one item inside, a food dealie. It creates an empty element that has the order set correctly. You can imagine if it had two items within, the next one would have a different order. The bag has a single property, that can be described roughly as: the element identified by item[@order='0']
is tied to the element with index 0 in this container1.
<inventory kind='dealie_bag'>
<b><item kind='food_dealie' order='0'/></b>
</inventory>
In the second pass, we examine each property of the bag. As I mentioned above, there's just one property, identified by a specific XPath. We search within the inventory
element for that sub-element, find it (bolded above), and proceed to fill it with the item's own data:
<inventory kind='dealie_bag'>
<item kind='food_dealie' order='0'>
<short_description>the cover for a barn</short_description>
<long_description>The spare roof of a barn is here.</long_description>
<proto_lid area_name='main' local_id='10'/>
<nourishment>10</nourishment>
<keywords>
<keyword order='0'>roof</keyword>
<keyword order='1'>cover</keyword>
<keyword order='2'>barn</keyword>
</keywords>
</item>
</inventory>
Gotchas for Container Dealies
Container Dealies, which are items that can contain other items, have the special characteristic of having their "short description" and "long description" methods overridden to indicate that the item is a container.
<type commands for a list> look
Room: The second room
The spare roof of a barn is here.
<b><C></b> How many hands have been carried in this bag?
a man is here.
<type commands for a list>
As you can see above, the container has a marker in front telling that it's a container. Usually, the property for the short_description
in XML is tied to the ContainerDealie.shortDescription()
method. This yields an XML representation that includes the container marker, which means when it's loaded, the marker is loaded as being part of the unadorned description, and you end up with a duplicate container tag. The fix is to tie the short description XML property at write-time to the object's unadorned short/long description attribute instead, which is saved off when the object is loaded originally.
As it turns out, both of these special cases fit in elegantly to the current design, so I don't consider them all that special. I'm fairly confident that this same method is flexible enough to be used to save anything to XML. I'm kicking around the idea of having a totally persistent world, where things don't respawn on reboot, necessarily. There's a lot of issues to work out there, though. Thoughts?
1 Actually, I use procs, not indices (indices are so passe), to identify each item within the container. In a loop through the bag's items, each iteration has a variable that references the current item. At that point, I can create a proc that forever references that item and pass it into the property to save off. I like to think it's a pretty cool trick.