Component-based entities in ICE
During the development of ICE, I made a component-based entity system similar to the design described on Adam Martin’s blog. This was a massive improvement over my previous projects which used OOP-style entities. The complex inheritance-based hierarchies disappeared, and all entities were defined in data (YAML files), which made ICE very moddable.
That said, this entity system had its own issues, mostly because it was written in hurry without thinking through its high-level design.
The main problems were:
Every component type had to implement code to load a component from YAML. This resulted in various little inconsisitencies (especially in combination with non-trivial data members such as arrays), which made it impossible to add a general serialization system on top of the entity system without a massive rewrite.
System run order dependencies
Every system would modify components that would then be processed by another system. The game behaved differently depending on the order systems were run, leading to some unavoidable and some unexpected dependencies between systems. This escalated to the point where changing the order of systems would break the game.
Threading (or lack thereof)
The initial design of ICE did not include multithreading; it was ‘to be added later’. Once multiple threads were needed, it was impossible without locking every entity by every system, which would lead to massive overhead. Run order dependencies described above made it even worse, as did the ability to get direct pointers to entities and their components. There were too many cases where a system could modify data processed in a different thread.
Entities/components in ICE were in arrays for cache locality, and could be accessed by pointers. This made it impossible to move them. There was no efficient way to reuse memory after removing an entity; erasing an element from the middle of an array would be too slow. The only easy way to reuse dead entities was to reuse space when an entity with exactly the same components was added. Even so, over the course of a single game, the amount of wasted memory continued to increase.
Components with ‘subcomponents’
Similarly to other RDBMS-style entity systems, ICE allowed either zero or one component of any single type in an entity. Furthermore, adding or removing components from an entity was not allowed. This allowed very efficient storage of components, but I ended up using components consisting of multiple ‘subcomponents’. For example, a unit might have multiple weapons, or multiple spawn conditions. I used RAII containers here, eventually requiring hidden static allocators. It would be more natural to allow multiple components.
After the first (and probably last) release of ICE I started an overhaul of the entity system. Around that time, Elvis Zhou made a post
at the D forums about a D port of Artemis, an entity system framework. That made me notice existing entity systems similar to the one I used in ICE, and many of the same flaws. For example, Artemis has the same problem with dependencies of systems on the order they run, and its design does not help threading. Also, unlike ICE, its components are quite heavyweight and cache unfriendly.
None of the component based entity frameworks I’ve seen seems to fit my requirements (especially with regard to threading and efficiency). That’s why I decided to start Tharsis, an entity framework that should hopefully address these issues.