Object Creation Through XML (Pluggable Factories?)

Started by
14 comments, last by vovansim 19 years, 8 months ago
For my next trick, i want to create a "world" populated by objects which are created via XML. so lets say i have this very simple arrangement:
<WORLD>
<ENEMY X="100" Y="200" HEALTH="50" />
<WALL X="20" Y="20" WIDTH="50" HEIGHT="50" />
</WORLD>
Now when the world gets loaded, i want to create one Wall object and one Enemy object. Q: Would a Pluggable Factory be good for this situation? The XML would be put into a neat little condensed stringstream and sent to an object factory as a parameter. Then the object is created. What i imagine is that each class is pluggable and also would know how to decode it's own XML string. Am i on the right track or is there a better way to do this? Somehow i want to get from XML to a fully fleshed out environment. This seems like the best way from what i know. thanks for your input.
Advertisement
I use something similar (although simpler) and it works quite well - and dead easy to maintain as well. :) Although instead of just an <ENEMY tag I include a 'type' attribute. All ENEMY tags then get forwarded to the factory which creates the object based on the type tag (and configures it with the remaining info). By explicitly catching different catergories (enemies, walls, etc.) and creating them via different factories the returned objects are all of a nicely defined interface.
Thanks. If you could elaborate on your system it might help me a bit. I've never programmed anything quite like this so i'm trying to get a feel for any potential traps down the road.

The one thing that i'm wondering right now is what to pass to the factory as the parameter list:

- the straight unparsed XML (hard to do with the current system i have)

- the "pre-digested" version (elements returned by TinyXML parsing)

- the full condensed stringified stream version (requires parsing beforehand which possibly only the object itself knows how to do correctly).

In a project I’m working on now, we do something similar to what your suggesting. It works well, so I think your idea is good. I wouldn’t allow the objects to decode their XML string, however. I would keep the XML out of the objects. I prefer to decouple the objects from the object builders. This makes it easier to update the XML reader code (even replacing it with something else - like a binary format) without affecting the objects themselves.

I've never used the TinyXML. That's probably because it doesn's support the DOM. I've used Xerces and MSXML which both support DOM. I find it hard to do any major XML work without DOM support.

In our case, we pass a DOM element to the factory method for the object. The factory uses normal DOM methods to read the XML and create a struct which has all the necessary initialization data for the object. This struct is passed to the object.
Thanks for your input wmroll. That's basically what i'm leaning towards now. However, if i pass the object creator an "XML element" (that describes elements and attributes of the XML doc), i will certainly be coupling XML know-how with the classes themselves. That is not totally out of the question, and is certainly the easier way to go, but i'm just trying to think out other possibilities.

If i only pass in a string to the class, it adds two extra steps of serializing and deserializing the data into/out of a string. Furthermore, whoever gets that data (the factory) before the class itself does will have to know how to decode it all and "nice it up" for the class constructor. I would much rather just give the class the data it needs and let it construct itself through a regular constructor. But using just a string stream would cut down the unecessary dependencies.

The idea about decoupling was to keep the xml decoding out of the the object classes, not necessarily out the factory classes.
Lei,

I've posted this before around here somewhere, but here goes again: check out this here library:

XiMoL

It's basically a C++ stream implementation which wraps around XML decoding, and makes it nice and easy, so you don't have to fiddle with strings.

On a more high level note, I, personally, would rather put the XML parsing code into the specific class than a common factory. The reason is because if you have many different kinds of objects, then the factory will really get bloated with XML parsing code, and I like to keep files nice and simple and small, so I don't have to search a thousand lines of repetitive code (as it is bound to get) for something I need to change.

On the other hand, this approach is "bad" because now you've tighly coupled your class representation with it's physical representation on disk. Say you decide you don't wany XML any more, now you got every class that has a function you aren't using.

Technically, what you could do is implement what I believe is called the memento pattern. Basically, the idea is this:

1. Your factory method gets the XML - be it parsed form or raw or whatever you want.
2. The method then creates the memento from the XML - that is, you could have an associative array - or a hashmap - that has all the attribute-value pairs on your XML.
3. Based on the name of the XML tag, or some type attribute, the factory instantiates an appropriate class.
4. The instance is then given the memento to do with as it pleases.
5. The instance then reads the memento and matches keys to its own properties, and initializes them as appropriate.

That way, you still have to write the property-matching code in your objects, as you would otherwise, but:

1. The code is independent of how the information was loaded from disk, so if you choose to later create the memento using a database, or a binary file, or whatever else, you are free to do so.

2. Your XML parsing code becomes general, and you need only one copy of it, because now the factory doesn't know anything about what the object is going to do with the memento, and it doesn't really care either.

Basically now an example. Suppose you have the following XML file:

<objects>  <object type="CEnemy" x="1" y="2" strength="uberenemy" />  <object type="CWall" x="10" y="10" thickness="reallyreallyreallythick" /></objects>


Then after the factory method processes the first object, for instance, it would create the following map:

"type" -> "CEnemy""x" -> "1""y" -> "2""strength" -> "uberenemy"


Then, it will look at the type attribute and say, hmmm, I need to instantiate a CEnemy, and so it will do. After that, the map will be passed to some initialize method, which will iterate over the map and each iteration will look something like this:

string key = //get current key
string value = //get current value
if (key == "x") //initialize x from the value, using that fancy new casting routine that uses streams.
//etc

In fact, you could even go a little further and abstract the whole iteration over the map thing out into some abstract class, and then at each iteration it would call some virtual method

void initializeProperty(string key, string value);

So you can get away with only writing the bare minimum of matching in each new class you write.

Vovan
Vovan
Just to throw into the pot,

we use a system like this for our sprites,

due the the co-felxability of subclassable objects and XML, we are able to allow our object subclasses take advantage of extra data that is in the xml file.

for instance, a sprite might just use one or two tags about the basic properties of a sprite, but then an enemy subclass of sprite might make use of new tags in the xml file, that describe other properties.

the xml file is loaded via an XMLDocument class, which parses and caches to an object model with common querying functions.

The sprite object then gets a pointer to the document, and can query the data it needs, any subclass also gets the chance to query it's data in turn, via a virtual function chain, it is _very_ important to cache your XML data, or some equivilent, to avoid re-loading and re-parsing your data each time you make a new character, out method was to simply store the loaded XMLDocument (in it's object model form, not raw text) in a key-map using the file path as the key, and having a system built into the document loader, that if you try and load a new XML file it checks the cache, and will not re-load a chached document, and the new XMLDoc class simply shares the original model pointer.

It has worked well for us =)

an example of the base sprite
<?xml version="1.0"?><sprite name="candle1">	<imagesets>		<imageset name="candle1" file="data/candle1.gmb" cols="4" rows="1" anchorx="16" anchory="85" areax="1" areay="1"></imageset>	</imgesets>	<light on="1" radius="200"></light>	<action>flicker</action>	<actions>		<action name="flicker" imageset="candle1" begcol="0" endcol="3" loops="-1" fps="12"></action>	</actions></sprite>


an example of a subclassed sprite
<?xml version="1.0"?><sprite name="morning" occlude="1">	<delegate>MorningDelegate</delegate>	<tooltip>Princess Morning</tooltip>	<portrait>data/portrait_morning.gmb</portrait>	<lifebarcolor r="255" g="222" b="0"></lifebarcolor>	<light on="0" radius="200"></light>	<imagesets>		<imageset name="morning_peace_idle" file="data/morning_peace_idle.gmb" cols="14" rows="8" anchorx="50" anchory="85" areax="1" areay="1"></imageset>		<imageset name="morning_peace_move" file="data/morning_peace_move.gmb" cols="14" rows="8" anchorx="50" anchory="85" areax="1" areay="1"></imageset>		<imageset name="morning_battle_idle" file="data/morning_battle_idle.gmb" cols="1" rows="8" anchorx="50" anchory="85" areax="1" areay="1"></imageset>		<imageset name="morning_battle_move" file="data/morning_battle_move.gmb" cols="14" rows="8" anchorx="50" anchory="85" areax="1" areay="1"></imageset>		<imageset name="morning_battle_attack" file="data/morning_battle_attack.gmb" cols="14" rows="8" anchorx="50" anchory="85" areax="1" areay="1"></imageset>		<imageset name="morning_dead" file="data/morning_dead.gmb" cols="14" rows="8" anchorx="50" anchory="85" areax="1" areay="1"></imageset>	</imagesets>	<action>idle</action>	<actions>		<action mode="0" name="idle" imageset="morning_peace_idle" begcol="0" endcol="13" loops="-1" fps="15"></action>		<action mode="0" name="move" imageset="morning_peace_move" begcol="0" endcol="13" loops="-1" fps="33"></action>		<action mode="0" name="die" imageset="morning_dead" begcol="0" endcol="0" loops="-1" fps="33"></action>		<action mode="1" name="idle" imageset="morning_battle_idle" begcol="0" endcol="0" loops="-1" fps="15"></action>		<action mode="1" name="move" imageset="morning_battle_move" begcol="0" endcol="13" loops="-1" fps="33"></action>		<action mode="1" name="attack" imageset="morning_battle_attack" begcol="0" endcol="13" loops="0" fps="33"></action>		<action mode="1" name="die" imageset="morning_dead" begcol="0" endcol="0" loops="0-1" fps="33"></action>	</actions>	<attributes>		<vitality>10</vitality>		<spirit>8</spirit>		<strength>8</strength>		<wisdom>4</wisdom>		<agility>10</agility>		<lifepoints>100</lifepoints>		<manapoints>80</manapoints>	</attributes></sprite>



You might also notice that our sprite files have optional tags for image loading, and describe animation sequences, and since this file is used for a subclass it also stores character stats information.

We dont use 'plugable factories' but we do have a system for runtime by-name object instantiation. our engine has an in code and script command,

CreateSprite("name","class","delegate","file");

wherein this creates a new sprite of type class, sets some properties and has it load 'file' which is an xml file, like above.


as for keeping things generic, you cant assume to much when it comes to using say, XML or a Database as your data provider, you will need a translation layer for sure, so i recomend either doing it in the factory class *centralized but lots of code* or in the sprite class itself *small, easy to locate code, but spread around*


Hope that gives you some ideas=D

[Edited by - EDI on August 19, 2004 11:13:12 AM]

Raymond Jacobs, Owner - Ethereal Darkness Interactive
www.EDIGames.com - EDIGamesCompany - @EDIGames

Quote:Original post by leiavoia
Thanks for your input wmroll. That's basically what i'm leaning towards now. However, if i pass the object creator an "XML element" (that describes elements and attributes of the XML doc), i will certainly be coupling XML know-how with the classes themselves. That is not totally out of the question, and is certainly the easier way to go, but i'm just trying to think out other possibilities.


I have a similar system that I use - my solution to hiding the XML implementation is to use a Object Parameters class. This simply has methods to retrieve the attributes values by name e.g. position, fileName etc.. Of course the underlying implementation is simply a XML parser that contains the XML heirarchy from the object being created. This then allows the object to create child objects as well to give a heirarchial structure.

After using this a while, and trying to incorporate this into a scenegraph with multiple methods of accessing e.g. spatial, heirarchial, material. I'm leaning more towards using this as a sort of configuration file for a scene compiler instead of using this directly in the engine.

James
Thanks for the input guys ("hi vovan!"). Those are both ideas i will probably use.

The only issues i have with passing a bla "momento" to the actual class is that it needs to be bla-typed. Everything will end up being a string. I'm not very familar with streams yet though (one of those things i've never stopped to get very involved with), but i think i might be able to make it easier if i just dumped the variable values into a stream (?)

Ultimately, i would like to be able to do this from the "momento"

Class::Class ( std::map< string, some_kind_of_stream > momento ) {   float_variable_x << momento["x_position"];   float_variable_y << momento["y_position"];   integer_variable << momento["health"];   string_variable << momento["name"];   }


But i don't think anything would work so elegantly as that (if i'm wrong please let me know!).

EDI gives me another idea but i'm not sure it will work. I'm trying to stuff the entire object creation interface through the factory, but some objects you know have extra parameters or whatever. The factory needs to know a lot of this, but i think just passing in a file name to parse additional data is a good idea. i'll keep that in mind.

This topic is closed to new replies.

Advertisement