0

I have an entity-based-component system.

A GameObject (entity) keeps all pointers to all related GameComponents.
All GameComponent keep a pointer to the GameObject.

If I know that I have to query one component from another component often (Physics<->Graphics), I will cache pointer of another component.

class Physics : public GameComponent {
    Graphics* graphic;  //different GameObject
};
class Graphics: public GameComponent {
    Physics * physic ;  //different GameObject
};

The cost of querying pointer from each other (Physics->Graphics and Graphics->Physics) is very small.

As time go by ...

Storing Graphics's pointer in Physics and vice versa becomes less sensible and causes maintainability problem.

For example, if I want to add a new relation, e.g. Physics-Player, I will have to add a field into both classes :-

  • add Player* to class Physics
  • add Physic* to class Player

If I proceed, totally simple components will be clogged with many fields about the relation that hard to be tracked and maintained.

class Physic: public GameComponent {
    Graphic* graphicsMain;
    Graphic* graphicsSecondary;
    Player* playerPtr;
    Enemy* enemyPtr;
    //10+ as project grow
}

Code about query an object from another object are scattering around in many places.

Question: How to solve the maintainability issue without performance penalty?

My workaround

I created a new GameComponent named RelationNode.
Then create a new system named Relation to manage every type of relation in a single place.
Pros: I can code all utility that I want, and it will work for every relation type.

class Physics : public GameComponent {
    //no keep Graphics pointer anymore ... clean :)
};
class Graphics: public GameComponent {
    //no keep  Physics pointer anymore ... clean :)
};
public RelationNode : public GameComponent {
    //... some code ... cache other component ...
    RelationNode* slot01; //shared Physics->Graphics and Graphics->Physics
    RelationNode* slot02; //Physic->graphicsSecondary
    .......
    RelationNode* slot20;
    //^ I can reuse same slot of variable if I am sure.
    //instead of "RelationNode*" , it can also be "GameObject*"
};
class Relation{ 
    Graphics* getGraphicsFromPhysics(Physics* gameComponent){
        RelationNode* a = toNode(gameComponent); //waste 1-2 indirection
        RelationNode* b = queryGraphicFromPhysics(gameComponent);
        Graphics* bCom= fromNode(b);             //waste 1-2 indirection
        return bCom;
    }
    //the real code is more elegant than this, but suffer the same issue
};

Now my code is so clean but suffer performance penalty from indirection.
It cost about 1-30% (profiled), depends on test case.

I have another crazy idea about add field to component using macro, but I don't like macro - it tends to cause other problems.

My old idea is to use hashMap e.g. HashMap<Graphics*,Physics*>, but it is much slower than my workaround (tested).

Community
  • 1
  • 1
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • 1
    Not sure but this question might be *too broad*. You should look into **"Data Oriented Design"** and try to avoid all this pointer indirection by separating data from logic and thinking about component storage from a different perspective. I wrote [my Bachelor's thesis on ECSs](https://www.researchgate.net/publication/305730566) - it might interest you. – Vittorio Romeo Feb 22 '17 at 09:31
  • Have you looked at the mediator pattern? https://en.m.wikipedia.org/wiki/Mediator_pattern – linuxfever Feb 22 '17 at 09:32
  • @Vittorio Romeo Thank, .... it is very long. Which pages? Should I print all 150 pages? – javaLover Feb 22 '17 at 09:33
  • @linuxfever I have never dive into detail about it. Thank. Is it fast? I believe it costs some indirection, same as mine. – javaLover Feb 22 '17 at 09:34
  • @javaLover: why print...? The first part of the thesis covers general ECS design strategies, the second one covers my particular implementation. – Vittorio Romeo Feb 22 '17 at 09:36
  • 1
    @javaLover yes, there is indirection which is exactly what breaks the coupling. In terms of speed, you will have to test it I am afraid... good luck! – linuxfever Feb 22 '17 at 09:48
  • @Vittorio Romeo My bad ... the document is already well bookmarked. I found a few parts that has potential .... **1. Address-based** same as I am using (very coupling). **2. Subscription-based** and **Message-based** : not related to me. **3.Inter-entity** = entity call another entity's function, not related to me. .... I don't find any part address the issue about how to design database/system to manage relation between entity. I guess it is ECS's limitation. (?) – javaLover Feb 22 '17 at 09:58
  • You can manage relationships between entities by having "glue" components that store the ids of the connected entities. Then you can have a system that processes those "glue" components and gets the entities to perform logic on all of them at the same time – Vittorio Romeo Feb 22 '17 at 10:40
  • @Vittorio Romeo `RelationNode` is a glue, isn't it? (sorry if my code is not clear) It costs some indirection, though. Is there any glue technique that has CPU cost same as the shiny **Address-based** way? – javaLover Feb 22 '17 at 10:45

0 Answers0