Thread: parse a template class via xml

  1. #1
    Registered User
    Join Date
    Nov 2010
    Posts
    19

    parse a template class via xml

    i cant see how its possible but would like to make sure

    i am using an xml parser to set up a game scene
    but need to be able to do

    engine->createModel<CmodelBasic>()

    engine->createModel<CmodelEnemy>()

    the only way i can see how to do this is with a massive if else statement's

    if (paserString == "CmodelBasic" )
    engine->createModel<CmodelBasic>();


    is there a better cleaner way to do this

  2. #2
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    O_o

    It looks like a bad design from the lower level if you ask me.

    Why should `createModel', which I assume is a component of the rendering or spatial systems, need to know if a model is an enemy or not?

    *shrug*

    In any event, a map of names and pointers would work and be simpler to use, but you'd still be doing something like that to create the map.

    Sometimes one has to work in order to program.

    Soma

  3. #3
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    I'm not seeing a way around it, although you can make it somewhat better with (horrors) a macro:

    Code:
    #define CREATEMODEL_CASE(parserString, model) if (parserString == #model) { engine->createModel<model>(); break; }
    
    parserString = getNext();
    
    do
    {
        CREATEMODEL_CASE(parserString, cmodelBasic)
        CREATEMODEL_CASE(parserString, cmodelEnemy)
        ... etc
    } while(0);
    Occasionally one of these things appears that requires a lot of seemingly redundant typing. Sometimes it's a sign of something wrong in the design, but sometimes it just happens.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  4. #4
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    If you use tinyxml you should be able to serialize and deserialize your classes. I use XML files for game scenes all the time and have found no issues with them.
    TinyXml Main Page

    Unfortunately in C++ you cannot instantiate a class from a string without using a parameterized factory. However with some ingenuity you can alleviate some of the pain by taking a string or ID directly from the XML and using that to instantiate the object. This way it is still data driven. Your templated approach while nice is going to require the type name and since there is no keyword or object called Type in C++ there isn't any way around this. You could have several abstract factories register for the string or ID of the type of object they create and then have them factory out that type. But this requires a hard-coded string in the registration process which is a PITA even if it comes from the XML. At some point you must be able to resolve the ID of the object in XML to the type of the object in C++. There is no simple way to do this.

    In C# this would be a snap b/c you could store the fully qualified name in the XML along with the assembly name and then use Assembly.Load() on the assembly name and Activator.CreateInstance() using the type name. C++ does not have reflection nor do all of its objects derive from a common base type.

  5. #5
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    There is no simple way to do this.
    O_o

    Map.

    I'm being serious here. Table driven development readily solves this problem.

    Sure, the table must be partially built from primitives available at compile-time, but it isn't more difficult than any other option.

    I mean, a factory that parses a dumb string isn't free either. (And I'd seriously suggest a table to implement that as well.)

    [Edit]
    As I said in my first post, this looks like bad design.

    I completely agree that it should be changed and a factory that can parse dumb strings is certainly a better alternative.

    It just happens that the template approach can be salvaged somewhat with a simple (compile-time) map to resolve the types and strings.
    [/Edit]

    Soma

  6. #6
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    [Edit]
    By the way, in case any one is wondering, this problem is what makes the "Ultimate++" framework garbage for a lot of problems.
    [/Edit]

    So, yea, I decided to make a simple (the problem hasn't been generalized) example where the result (return type) didn't need to be accounted for just for a way of explanation.

    This is all there is to the table. As you could probably guess, adding more types to the table is a trivial bit of code.

    Code:
    TABLE_ENTRY("One", One);
    TABLE_ENTRY("Two", Two);
    TABLE_ENTRY("Three", Three);
    TABLE_ENTRY("Four", Four);
    TABLE_ENTRY("Five", Five);
    TABLE_ENTRY("Six", Six);
    To resolve the type from the string I've implemented a strait forward recursive translation of a simple iterative linear search.

    The interface to that routine is simple enough.

    Code:
    void Find<StartIndex, EndIndex>::execute(???); // Where "???" is relevant parameters.
    The start and end index is important enough that it needs to be wrapped by convenience code. You can see how I've done that in the code.

    Soma

    Code:
    #include <iostream>
    #include <string>
    #include <typeinfo>
    
    struct ThisIsYourInterface
    {
        template
        <
            typename FTarget
        >
        void doSomething()
        {
            std::cout << "Type: " << typeid(FTarget).name() << '\n';
        }
    };
    
    template
    <
        unsigned int FTarget
    >
    struct Typifier
    {
    };
    
    template
    <
        unsigned int FTarget
    >
    struct Namer
    {
    };
    
    struct One{};
    struct Two{};
    struct Three{};
    struct Four{};
    struct Five{};
    struct Six{};
    
    #define STUPID_OLD_SUN_CC(X) X
    
    #define TABLE_ENTRY_IMPLEMENTATION(TARGET, NAME, TYPE) \
    template <> struct Typifier<TARGET> {typedef TYPE Type;}; \
    template <> struct Namer<TARGET> {static const char * Name;}; \
    const char * Namer<TARGET>:: Name = NAME
    #define TABLE_ENTRY(NAME, TYPE) TABLE_ENTRY_IMPLEMENTATION(STUPID_OLD_SUN_CC(__LINE__), NAME, TYPE)
    
    static const unsigned long MyStart = __LINE__ + 1;
    TABLE_ENTRY("One", One);
    TABLE_ENTRY("Two", Two);
    TABLE_ENTRY("Three", Three);
    TABLE_ENTRY("Four", Four);
    TABLE_ENTRY("Five", Five);
    TABLE_ENTRY("Six", Six);
    static const unsigned long MyEnd = __LINE__ - 1;
    
    template
    <
        unsigned int FStart
      , unsigned int FEnd
    >
    struct Find
    {
        static void execute
        (
            ThisIsYourInterface * fInterface
          , const char * fName
        )
        {
            if(std::string(fName) == Namer<FStart>::Name)
            {
                fInterface->doSomething<typename Typifier<FStart>::Type>();
            }
            else
            {
                Find<FStart + 1, FEnd>::execute(fInterface, fName);
            }
        }
    };
    
    template
    <
        unsigned int Match
    >
    struct Find<Match, Match>
    {
        static void execute
        (
            ThisIsYourInterface * fInterface
          , const char * fName
        )
        {
            if(std::string(fName) == Namer<Match>::Name)
            {
                fInterface->doSomething<typename Typifier<Match>::Type>();
            }
        }
    };
    
    struct MyFind
    {
        static void execute
        (
            ThisIsYourInterface * fInterface
          , const char * fName
        )
        {
            Find<MyStart, MyEnd>::execute(fInterface, fName);
        }
    };
    
    int main
    (
        int argc
      , char ** argv
    )
    {
        ThisIsYourInterface l;
        if(argc == 2)
        {
            MyFind::execute(&l, argv[1]);
        }
        return(0);
    }

  7. #7
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    That is certainly an elegant approach but I do not believe it solves the problem b/c these are dynamic types and from the XML alone one cannot figure out the exact type of the object. The idea is that the XML drives the generation of the objects with preferably no help from the code. You should be able to change the XML file and never touch the code and generate different objects from that XML. I still do not think there is an elegant way to do this in C++ since it lacks native support for reflection.
    The data source is usually generated by a tool that works with the system and so you can generate new data files quickly and never touch the code and get different results. To be purely data driven the data source must be the only thing that changes to get new behavior.

    There is one possible solution that is not free and that is to use Xerces XML library. Xerces can serialize / deserialize XML for transmission across a network or for storage purposes. It is not free and I am not sure how much it costs. It also has a fair bit of ugliness under the hood but thankfully you don't have to look at it much.

    I did find some fairly good factory code to handle some of this on CodeGuru.
    Code:
    template<typename T>
    inline RenderablePlugin * FactoryPlugin()
    {
        return new T;
    }
    
    template <typename T> 
    inline T * Factory::CreateInstance(const char *pName)
    {
        FACTORY_FUNC func = m_createFuncs.Get(pName);
        return dynamic_cast<T *>(func());
    }
    
    template <typename T> 
    inline bool Factory::AddCreationType(const char *pName)
    {
        return m_createFuncs.Add(pName,&FactoryPlugin<T>);
    }
    
    typedef Factory *(*CREATE_FUNC)();
    typedef void (*DESTROY_FUNC)(Factory *pFactory);
    
    extern "C"
    {
        RENDERABLES_DECL Factory * CreateFactory();
        RENDERABLES_DECL void DestroyFactory(Factory *pFactory);
    }
    This still requires types to be known in order to create them at run time and since you cannot create a type from a string or use a string as a type and factory the type...it has the same problems as all the other approaches.

    Another way to approach this though is to stop creating new types for each object. Use a base type for the object and use aggregation to add more functionality to the base type. In other words all objects in the system are the same type and have no functionality by themselves. The functionality comes from the aggregate type which adheres to a strict interface that is used by the object that owns the aggregate. If this aggregate type is Script or some type that can run an external script then object behavior is defined by the script and not in the code. I would suggest using Lua or some scripting language as the behavior of the object since you can load a script by name easily and can put the name of the script in XML. The code should read the scenegraph for the object so it can render it correctly and the Update() would call into script which would use a state machine to determine how the object should be updated. Rendering would not be done in the script. The easiest way I have found to bind C++ to Lua is through LuaBind which is available on the Lua website.

    In a game ideally the rendering is done by the engine (which makes no decisions about the rendering but follows a strict set of instructions to render - aka scenegraph) and all behavior is defined in script. So if you change the scenegraph...you get a different scene and if you change the script you get different behavior. Do not put behaviors that are subject to change in the compiled code or you will be kicking yourself later.
    Last edited by VirtualAce; 02-29-2012 at 07:38 PM.

  8. #8
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    That is certainly an elegant approach but I do not believe it solves the problem b/c these are dynamic types and from the XML alone one cannot figure out the exact type of the object.
    I'm not sure what "dynamic types" are, but it absolutely solves the problem of associating a dumb string with a type. It does this at compile-time.

    I don't know what "XML alone one cannot figure out the exact type of the object" means either because if you can use a run-time factory to create an object you can absolutely build that necessary information into XML.

    The idea is that the XML drives the generation of the objects with preferably no help from the code.
    O_o

    My code example can be used to drive the creation of objects. I admit, the creation of the objects would be specified in code (via `new' maybe), but I'm not sure how that's a relevant criticism considering that you'd need to write the code, or use a library, to parse the XML in any event.

    You should be able to change the XML file and never touch the code and generate different objects from that XML.
    You can absolutely do that. That's what my code does. It doesn't use XML. (It uses an even dumber string.) It doesn't create any instances. (It only calls a template method with the exact type associated with the string.)

    Well, okay, for a minute let's assume you are using different jargon than I'm using. Are you saying "create a new class" in XML?

    That is, are you saying have this:

    Code:
    class NewClass
    {
    void newMethod(){// code}
    };
    in whatever it might look like in XML and it actually work in C++ (via `new NewClass' in whatever form)?

    You certainly can't do that. You can't do that in most any language--including many with reflection.

    To be purely data driven the data source must be the only thing that changes to get new behavior.
    This is true, but you only need the interfaces to be stable to use an object with C++. The implementation and data can vary independently. I make liberal use of the "Bridge" and "Refinement" patterns and so the interfaces I code my applications to never even know what the implementation looks like or the data on which it depends.

    There is one possible solution that is not free and that is to use Xerces XML library.
    Xerces-C++ solves the problem of associating a string with a type and has some measure of factory support, but it does "infect" the client code to a point. What I'm saying is, if you serialize a `MyAwesomeClass' in some application another application that knows nothing about `MyAwesomeClass' can't make use of that object even though it could make use of that data. Maybe your not saying it can; I'm not really following your dialog because you seem to be using "object" to mean "C++ class" in some places but not in others.

    I did find some fairly good factory code to handle some of this on CodeGuru.
    That code does solve the problem of associating a string with a type at run-time. My code solves the same problem at compile-time. (Which I still argue should be an unnecessary step.)

    This still requires types to be known in order to create them at run time and since you cannot create a type from a string or use a string as a type and factory the type...it has the same problems as all the other approaches.
    Maybe this is why I'm having such a hard time understanding you. I don't see "requires types to be known" as a problem. I don't see "needs a stable interface" as a problem. Those things mean that needing to use a table or a factory from a module to build an object based on a string isn't a problem for me.

    Would it be less of a problem for you if I told you the code I posted could be adapted to work with "RTTI" and `dynamic_cast' as well? You'd still need a list of valid types on the client side. (The association table to know how to cast the reference.) However, and I can't stress this enough, that table is only necessary to get a stable interface. The actual (derived) type can be unknown to the client. (Obviously, the actual type must be known by a factory or module or else it couldn't actually create an instance.)

    Another way to approach this though is to stop creating new types for each object. Use a base type for the object and use aggregation to add more functionality to the base type. In other words all objects in the system are the same type and have no functionality by themselves. The functionality comes from the aggregate type which adheres to a strict interface that is used by the object that owns the aggregate.
    Composition and aggregation are certainly reasonable approaches to design, but that doesn't solve this problem. It circumvents it.

    *shrug*

    I guess you might say I'm splitting hairs but I don't think so. With a proper chain of responsibility and reasonable components you don't need a solution to build an instance of "class UniqueNPCEnemy" or "class FemaleHeroPC" based on a string; you let the model loader, the texture loader, the logic loader, the model manager, the texture manager, and the logic manager (even if it is a VM) all do their job by handing you models, textures, and logic that you then associate with a particular actor which also doesn't need specially derived types.

    If this aggregate type is Script or some type that can run an external script then object behavior is defined by the script and not in the code. I would suggest using Lua or some scripting language as the behavior of the object since you can load a script by name easily and can put the name of the script in XML. The code should read the scenegraph for the object so it can render it correctly and the Update() would call into script which would use a state machine to determine how the object should be updated. Rendering would not be done in the script. The easiest way I have found to bind C++ to Lua is through LuaBind which is available on the Lua website.
    Moving some code into a VM is all fine and good, but that is about separating responsibilities.

    I'm really not sure how reflection would help with separating responsibilities. Is it that you figure that a C++ with reflection could create objects "on-the-fly" that perform faster than the VM? I'd just recommend more separation of responsibilities. For example, I apply damage to actors for my little RPG engine in a script loaded for my VM but the actual damage is calculated by a callback into real C++ code.

    So, yeah, you've implied that you use a significantly different design than the original poster did or I would, but that's all you've done.

    O_o

    Is that your problem... er... with the problem? If so, yea, I'm right there with you. The design implied by the original post is broken. You shouldn't need to create such highly specific components, at compile-time no less, for a graphic (or game) engine.

    In a game ideally the rendering is done by the engine (which makes no decisions about the rendering but follows a strict set of instructions to render - aka scenegraph) and all behavior is defined in script. So if you change the scenegraph...you get a different scene and if you change the script you get different behavior. Do not put behaviors that are subject to change in the compiled code or you will be kicking yourself later.
    I agree with every word of this, but that separation of model and logic does not require any of the solutions being discussed to solve the problem offered in the original post.

    My own library happily loads model data (skeleton and textures and such) and logic (the type character and any artificial intelligence) from files without ever needing such things.

    Soma

  9. #9
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    You finally arrived at my point near the end of your post. Yes I am implying that overall there is something wrong with the original design which requires this type of behavior or makes it appear like it requires it. There are 'ways' to accomplish what the OP wants but hopefully after looking at your solution and my solution as well as some of my other 'possible' solutions they will realize that the design needs another trip to the drawing board.

    Often I do not provide an answer per se but rather provide a thought pattern that may lead to several other answers. This is probably due to the fact that when people ask me questions I do not like to tell them an answer b/c that is my take on the issue which may or may not be the best one. I have found in the past that allowing others to formulate their own solutions by providing them with possible approaches eventually leads them to a completely different approach and solution than either of us thought of in the beginning.

    You are certainly a more to the point person and while I think we both agree and are saying much the same thing we are saying it differently. I am saying you could do this or that but that there are probably more approaches to the problem or more importantly better designs that would remove the need for this type of functionality. You take this as talking about items that are not related to the problem at hand but in my book they are.

    In my view the best way to build a game is to build the core rendering code and leave all behavior and UI stuff to scripts. Build the core rarely to change 'stuff' in C++ and build the rest in script. This means you instantiate one type of object therefore removing the need for various types of objects (IE: you would use one called Entity or something along those lines) and the behavior is handled by code that is not compiled with the project. The engine merely provides functionality for managing resources, rendering from scenegraphs, etc. So when I say things like this I am implying that I do not agree with the design that has been presented. Again this is my approach and it is certainly not the only way or the best way and it has its fair share of issues but it is the approach I have used through the years and it has served me well. I'm also interested in your design since it is a different approach to the exact same set of problems.
    Last edited by VirtualAce; 03-02-2012 at 08:39 PM.

  10. #10
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    You take this as talking about items that are not related to the problem at hand but in my book they are.
    ^_^

    "Rewrite the engine." is simply such a large problem I didn't consider it significant in the face of solutions for using the existing code.

    I'm also interested in your design since it is a different approach to the exact same set of problems.
    Well, at this point we've thoroughly frightened the original poster. I'm sure he has sought pain relief and other avenues.

    Still, I think I'll drop you a private message instead of going further into "Rewrite the engine." territory.

    Soma

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 3
    Last Post: 01-30-2011, 04:28 PM
  2. Template class nested in template class
    By yahn in forum C++ Programming
    Replies: 7
    Last Post: 04-18-2009, 11:40 AM
  3. Replies: 4
    Last Post: 11-01-2006, 02:23 PM
  4. Template <class T1, class T2, class T3> error LNK2019
    By JonAntoine in forum C++ Programming
    Replies: 9
    Last Post: 10-11-2004, 12:25 PM