0

Suppose that I have a game engine.

Let's say it contains class Graphic, GamePlay, and Physics system.
(The real case are 20+ systems.)

All 3 of them are derived from System.

This is a draft of the simple initialization.

main(){
    Game_Engine* engine = new Game_Engine();
    Graphic* sys1= new Graphic(engine); //set to System::engine_pointer
    GamePlay* sys2= new GamePlay(engine);
    Physics* sys3= new Physics(engine);
    engine->addSystem(sys1); //add to Game_Engine's hash map
    engine->addSystem(sys2);
    engine->addSystem(sys3);
}

Then, I want to make all system can call each other. Ex. Graphic can call GamePlay.

So I design the addSystem() as :-

class Game_Engine {  
    std::unordered_map<std::type_index,Sys*> hashTable; 
    void addSystem (System* system){
        hashTable.add( std::type_index(typeid(*system)), system );
    }
    template <class SysXXX> SysXXX* getSystem(){
        return hashTable.get(std::type_index(typeid(SysXXX)) );
    }
}

The result is that each System can call each other by using only class name :-

class Graphic : public System {
    void call_me_every_time_step(){
        engine_pointer->getSystem<GamePlay>()->... do something ;
    }
}

Now, it works as I wished, but

  1. I heard that typeid is bad for performance.

  2. Game_Engine.h now has to #include all Graphic.h, GamePlay.h and Physics.h, so compilation time increases.
    (I tried to not include them -> typeid of 3 derived System will return wrong result.)

Is it possible to avoid those drawback? How?
Are there any other disadvantage?
Is this a bad design in the first place? If so, what is a good design? (because I have very limited experience on C++.)

Edit 1 : Below section responses to gudok's answer

Adding a certain get/set function for each system is what I did.

However, I realized that it become harder to manage when there are more systems, at least for me.

I ran away from it and use the template code instead, as above.

For gudok's solution, a single system will increase programmer's work as followed:-

  1. add the field declaration in the "GameEngine"
  2. add another function to return a certain system
  3. when rename a class e.g. "Graphics" to "Render" by using automatic refactor tool, I have to rename the getGraphics() to getRender() too (to make code readable)

Comparing the code in the question, a single system cost only 1 line.

engine->addSystem(new Graphics(engine));

It is not so trivial, especially when most systems are changing name, and amount of systems are increasing constantly.

Edit 2 : Response to gudok's enhanced answer

Make the GameEngine derived from SystemHolder{T} can reduce the work per System to 2 places :-

: public SystemHolder<Graphics>

and

engine.addSystem<Graphics>(new Graphics());  

It is still 2 places, though.
The code in question uses only 1 place.
Therefore, it is not good enough, but thank for trying!

javaLover
  • 6,347
  • 2
  • 22
  • 67
  • This looks like it's going to leak memory like a sieve. In modern C++, you should almost *never* use `new` and `delete`. – Rob K May 18 '16 at 20:44
  • Rob K, may you provide some references (link), please? I heard it somewhere but I don't know how to apply. – javaLover May 19 '16 at 03:18
  • 1
    Get yourself [a good C++ book](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list?lq=1). C++ is really nothing like Java. – Rob K May 19 '16 at 12:32

1 Answers1

1

What is the reason to use hash map and typeids instead of storing each of systems separately in GameEngine? Semantically, all these systems do different things. I'd rather do following:

class GameEngine {
  std::vector<System*> systems;

  Graphics* graphics;
  Gameplay* gameplay;
  Physics* physics;

  void setGraphics(Graphics* graphics) {
    this->graphics = graphics;
    this->systems.push_back(graphics);
  }
  Graphics* getGraphics() {
    return this->graphics;
  }

  ...
};

The idea behind this solution is that:

  1. Each of systems is different from semantical point of view. When you access graphics from somewhere, most likely you will use functions specific to Graphics and not functions universal for all Systems. Storing each of systems separately removes necessity for typeids and unnecessary type conversions.
  2. When you need to handle all systems in some uniform way (for example, advancing game time), you use systems field:

    for (auto it = systems.begin(); it != systems.end(); it++) {
      it->tick();
    }
    

EDIT Here is enhanced solution. You add new system by additionally inheriting GameEngine from SystemHodler. Getting and setting instances of particular System is uniform by using getSystem<T> and setSystem<T> methods -- as you wanted.

#include <vector>

class System {
public:
  virtual ~System() {}
};
class Graphics : public System {};
class Physics: public System {};

template<typename T>
class SystemHolder {
public:
  T* getSystem() { return system; }
  void setSystem(T* system) { this->system = system; }

private:
  T* system;
};

class GameEngine: public SystemHolder<Physics>, public SystemHolder<Graphics> {
public:
  template<typename T>
  inline void addSystem(T* system) {
    systems.push_back(system);
    SystemHolder<T>::setSystem(system);
  }

  template<typename T>
  inline T* getSystem() {
    return SystemHolder<T>::getSystem();
  }

private:
  std::vector<System*> systems;
};

int main(int argc, char* argv[]) {
  GameEngine engine;
  engine.addSystem<Physics>(new Physics());
  engine.addSystem<Graphics>(new Graphics());

  engine.getSystem<Physics>();
  engine.getSystem<Graphics>();
}
gudok
  • 4,029
  • 2
  • 20
  • 30