Map of factories

Started by
7 comments, last by alvaro 12 years, 8 months ago
In my shmup, each enemy type has it's own factory so the missions can spawn enemies based on what I describe in the mission file. My idea is to create a map linking an identifier string to it's factory. When a level is loaded, it uses the identifier string to grab the factories from the map that it needs to spawn the enemies. My question is, how do I populate the map with these factories? My current idea is make the map global and just assign the factory to the map where I define the enemy. Is there a better way to do it? The problem with this design is everything is global, which could get messy.

When I say map and factory, I mean the programming paradigmstongue.gif
Advertisement
You can make the map static. And using static "register" objects, insertion in the map is more or less automatic.
Here is an example that I googled (I don't know if they do it correctly because it's too complicated stuff for me to grasp with just a quick read, but they have the idea at least).

Is there a better way to do it? The problem with this design is everything is global, which could get messy.

Why does it have to be global? Otherwise it sounds sensible.
If you can describe your enemies in terms of components and data then, in my mind, a scripting system is better suited for this.

Automagic registering is rarely, if ever, a good idea. If nothing else it means you have things going on in the background before you hit 'main' which can cause problems AND it right away rules out using your own memory pools to track memory usage etc without some major compiler voodoo being involved.

Instead do the work on startup; if this involves a hard coded function or something like a Lua script which knows about modules it can load in and register for components (by sweaping a set of DLLs or even scripts) that's your call (clearly the later is harder work).

So your game startup looks something like
  • Do all pre-data setup such as create memory pools etc
  • Startup scripting system and load in a 'startup' file
    • startup file looks for files in a certain directory and runs them to register functions (dlls)
    • Any internal types get registered here via the scripting systems C-API using the same interface
    • (optional) load in 'enemy' types which describe them in the foam of components
    • Expose scripting system to 'enemy factory' so that it can query it for 'types'
  • Do other things for start up


  • Then on world load you;
    • (optional) load in ememy types if you didn't before and register them with the same scripting system you exposed to the factory
    • Parse data file for level looking for 'enemies'
      • These types are passed into the factory which uses it's table to create a new one (protype pattern would probably work well here; create one and then copy it about - note copy)
    This results in a nice flexible data drive system which can be extended pretty simply once setup with new components simple to add via code directly or dlls

    Thanks for the suggestions everybody. Phantom, I've been reading about componentized entities and using Lua scripting on here but that's more advanced than I want to get on this project. This project is more for me just to get a better grip with writing C++ , get a feel for actually finishing a halfway decent game, and generally learn how everything fits together. I'll probably look into different ways to design the code on my next project.

    Here is the test code I created so I could see how everything would fit together before implementing it in my game. The createRandomClass() is where the biggest difference will be in my game code, as that will be handled by whatever class is in charge of directing the mission.

    [source]#include <iostream>
    #include <vector>
    #include <time.h>

    class BaseClass;

    int randomInt(int max = 0) {
    srand(time(NULL) + rand());

    if(max == 0) {
    return rand();
    } else {
    return rand() % max;
    }
    }

    class Factory {
    public:
    virtual BaseClass* create() = 0;

    static void subscribe(Factory *f) {
    mClassFactoryList.push_back(f);
    }

    static void deleteSubscriptions() {
    std::vector<Factory*>::iterator itr;
    for(itr = mClassFactoryList.begin(); itr != mClassFactoryList.end();) {
    delete (*itr);
    itr = mClassFactoryList.erase(itr);
    }
    }

    static BaseClass* createRandomClass() {
    return mClassFactoryList.at(randomInt(mClassFactoryList.size()))->create();
    }

    private:
    static std::vector<Factory*> mClassFactoryList;
    };

    std::vector<Factory*> Factory::mClassFactoryList;


    class BaseClass {
    protected:
    class BaseClassFactory;
    public:
    virtual void method() = 0;

    static Factory *getFactory() {
    return new BaseClassFactory;
    }

    protected:
    class BaseClassFactory : public Factory {
    public:
    BaseClass* create() {
    return NULL;
    }
    };

    };

    class DerrivedClass : public BaseClass {
    protected:
    class DerrivedClassFactory;

    public:
    void method() {
    std::cout << "Derrived Class!" << std::endl;
    }

    static Factory *getFactory() {
    return new DerrivedClassFactory();
    }
    protected:
    class DerrivedClassFactory : public Factory {
    public:
    BaseClass* create() {
    return new DerrivedClass();
    }
    };
    };

    class SecondDerrivedClass : public BaseClass {
    protected:
    class SecondDerrivedClassFactory;
    public:
    void method() {
    std::cout << "SecondDerrived Class!" << std::endl;
    }

    static SecondDerrivedClassFactory* getFactory() {
    return new SecondDerrivedClassFactory();
    }

    protected:
    class SecondDerrivedClassFactory : public Factory {
    public:
    BaseClass *create() {
    return new SecondDerrivedClass();
    }
    };
    };

    int main() {

    Factory::subscribe(DerrivedClass::getFactory());
    Factory::subscribe(SecondDerrivedClass::getFactory());

    BaseClass *b = Factory::createRandomClass();
    b->method();
    delete b;

    b = Factory::createRandomClass();
    b->method();
    delete b;

    b = Factory::createRandomClass();
    b->method();
    delete b;

    b = Factory::createRandomClass();
    b->method();
    delete b;

    b = Factory::createRandomClass();
    b->method();
    delete b;

    b = Factory::createRandomClass();
    b->method();
    delete b;

    b = Factory::createRandomClass();
    b->method();
    delete b;

    b = Factory::createRandomClass();
    b->method();
    delete b;

    b = Factory::createRandomClass();
    b->method();
    delete b;

    Factory::deleteSubscriptions();

    return 0;
    };
    [/source]

    Automagic registering is rarely, if ever, a good idea. If nothing else it means you have things going on in the background before you hit 'main' which can cause problems AND it right away rules out using your own memory pools to track memory usage etc without some major compiler voodoo being involved.


    If it is done correctly, is there really a problem with things running before main ?
    Also, a few pointers in a map is relatively lightweight, so maybe using your own pool is not absolutely necessary for these.

    I ask just out of curiosity. I won't defend religiously the static registration, because I usually find such code a bit messy, and anyway if you forget to register a factory in a manual system, this is the kind of bug that you quickly detect.

    [quote name='phantom' timestamp='1311464737' post='4839405']
    Automagic registering is rarely, if ever, a good idea. If nothing else it means you have things going on in the background before you hit 'main' which can cause problems AND it right away rules out using your own memory pools to track memory usage etc without some major compiler voodoo being involved.

    If it is done correctly, is there really a problem with things running before main ?
    [/quote]

    The order in which global objects get initialized is hard/impossible to control. Someone else will try to be clever and make some initializations before `main' that require using your factory function, and they might be unlucky enough that it works when they first put that into the system. Of course there is no guarantee that the automatic-registry functions have been run already, and now you are in trouble.

    But the main reason why you shouldn't run anything before `main' is that it makes your code harder to understand.

    After having to deal with some order-of-initialization problems in a large project, I try to not make objects global, and when I do they never have non-trivial constructors. I normally only write one factory function in the base class, which has a bunch of if statements to determine what needs to be constructed, and the derived classes don't have their own factory functions. When someone wants to understand how it works, they only need to read one function, and it's brain-dead simple.

    The order in which global objects get initialized is hard/impossible to control. Someone else will try to be clever and make some initializations before `main' that require using your factory function, and they might be unlucky enough that it works when they first put that into the system. Of course there is no guarantee that the automatic-registry functions have been run already, and now you are in trouble.

    OK I see the point. But then, there is no fool-proof solution: if you initialize the map manually in main, that "clever" guy who does things before will also crash your system. I guess the problem comes from having the factory global/static/singleton/whatever, as usual.


    But the main reason why you shouldn't run anything before `main' is that it makes your code harder to understand.

    Despite my questions, I totally agree with that ! I never wrote a static registration, just had to suffer from having to maintain one (and it was buggous by itself, without even needing that clever guy) :)

    [quote name='alvaro' timestamp='1311615102' post='4840079']
    The order in which global objects get initialized is hard/impossible to control. Someone else will try to be clever and make some initializations before `main' that require using your factory function, and they might be unlucky enough that it works when they first put that into the system. Of course there is no guarantee that the automatic-registry functions have been run already, and now you are in trouble.

    OK I see the point. But then, there is no fool-proof solution: if you initialize the map manually in main, that "clever" guy who does things before will also crash your system. I guess the problem comes from having the factory global/static/singleton/whatever, as usual.[/quote]

    If you initialize the map manually in main(), you are making it very clear that the map needs initialization, so the clever guy is more likely to know he is not supposed to use it uninitialized. Also, if he doesn't realize that, it is guaranteed to not work, so he probably won't commit the change.

    Finally, I described the way I implement factory functions, and they don't need initialization.

    This topic is closed to new replies.

    Advertisement