1

My abstract implementation for a GameObject for my components-based game engine is the following:

GameObject

  • Unique ID
  • isActive flag
  • Array of components

I have been reading about component-based design, and one thing that caught my attention is that linear traversal of arrays is fast due to cache, so this made me decide to use basic arrays to hold entities/game objects.

Now another thing that caught my attention is that some resources separated the components away from a game object. What they did instead is have a map inside an entity manager. The map had game objects as keys and an array of components as values. In this situation, A GameObject only had an ID.

Is there an advantage (performance and/or design wise) of having a map attaching game objects to components instead of having components inside the GameObject class?

In addition, GameObjects are going to pooled (recycled) from a Pool object, in order to avoid frequent memory allocation.

lambda
  • 1,225
  • 2
  • 14
  • 40
  • 1
    If the `GameObject` has an array of components, the size of your object is large because it carries that stuff around with it. If you have a map that keeps that correlation, the creation/copy/deletion of `GameObject`s is much less expensive. That being said, I also would suggest you consider `std::vector` to hold your components, though that is an orthogonal concern to your original questing. – Cory Kramer Jul 22 '14 at 16:49
  • If a `GameObject` already has a reference for its components, and the maps keys are `GameObject`s, then the map is completely useless. Maps in general are used to provide quick access to values by some keys, for example `Map` could make sense. – Balázs Édes Jul 22 '14 at 16:50
  • I clarified a bit. In the map situation, the `GameObject`s only had an ID and not the array of components. – lambda Jul 22 '14 at 16:53
  • The more you separate them the better for you I think. Try not to store everything in one class, but rather have references from that class. That's the approach I take when I design something. I personally prefer the std::map. Also prefer std::vector over array. – santahopar Jul 22 '14 at 17:10
  • In a game engine, performance can impact/outweigh design considerations. If you see benefits in both approaches, for different applications (e.g. sometimes you need to quickly access the components in a GameObject, and sometimes you need to quickly access one component without necessarily knowing which GameObject it belongs to) you could combine the approaches: keep the components themselves in the GameObject and keep a separate lookup map with just pointers (C++) or references (Java) to the components. – CompuChip Jul 22 '14 at 17:31

2 Answers2

3

In most game component system you'll need both arrays and dictionaries (maps).

For linear traversal (ie during update) the array is used to enumerate objects and components.

For lookup by ID, name or other properties one or more dictionaries can be used to find components by the respective type. This is mostly needed where linear search over an array would add up to a noticeable performance penalty.

Furthermore there could be additional arrays storing game objects sorted by components, for instance an array of game objects having a specific component like CombatComponent.

Usually this will be wrapped in a single class allowing you to access components (or game objects) by index, enumerating them, or getting them by some criteria such as ID. The class' implementation can then be updated over time to speed up access to specific components by trading higher memory usage for better performance (ie introducing an additional dictionary to index components by enabled status, or game objects by spatial position).

In short: there are always tradeoffs. It's mainly a matter of convenience and performance to start using additional dictionaries and arrays but it's never an either/or decision.

The default storage implementation should always start with arrays if the objects need to be enumerated sequentially. The main problem with dictionaries is that the order of enumeration may change frame over frame, which has to be avoided in games.

CodeSmile
  • 64,284
  • 20
  • 132
  • 217
1

It really also depends on the type of game you make.

In my experience, loosely coupled objects almost always perform better since memory allocation occurs very infrequently, while referencing objects usually occurs in batches, and for specific tasks. I liked this approach because it prevents you to mess with a whole lot of virtual tables and class hierarchies. Virtual pointers break the code cache. So in this case I keep objects together in different managers, but with the same handle/id, and just run across a relevant set.

If expensive visibility queries are involved, it is often cheaper to keep several lists handy and update them only when necessary (e.g. time-sliced)

Another approach is to build dependency traversal trees using linked lists with parent/child relationships to only the relevant objects (a kind of tree-based skip-list). This is useful because somethimes you want to pre-build the transforms, or loop over all light-emitters, or first run all objects through collision detection and response, etc.. logically, any type of 'event' that has to occur on your gameworld can then be translated in a linked-list traversal. It saves you the managers, but it creates a whole lot of memory jumping and management to keep the parent/childs up-to-date.

If you can vectorize your calculations and get 100x speedup, well, that's a good reason to look at alignments and packing. At one time, we used a small object cache, to keep only those bits aligned in memory that would frequently be needed such as transforms etc.. in reality, though, you end up with so many isXYZ() calls before you even get to the transform, that this did not really pay off anymore. The idea was good, and there was some speedup at the time, but logic convoluted to the point it became meaningless.

The right thing to say is of course that it depends on the tasks and the number of objects or subsets thereof you have to deal with. If you don't know this at first hand but you suspect it can get out of hand, it depends on the time you have: If quite a lot, loosely coupled might be just the thing. Added bonus is that refactoring types is not really going to impact you that much. Otherwise, you may be better of just storing everything together and hope for the best..

StarShine
  • 1,940
  • 1
  • 27
  • 45