Signature ECS with optimizations using CRTP

Summary

An Entity Component System implementation that utilizes signatures for entity asignment filtering in systems. This implementation uses templates to make the development process easier, where everything "just works" when getting and assigning component types. It also uses static polymorphism for indexing, constant look up time and assignment within all component related containers to improve performance.


Why ECS?

When writing a game engine, using some type of component system is a common approach for modular development. However since these components need to be stored somewhere, it can result in unaligned memory which can significantly slow down the performance of the application due to system cache. Inheritance can become a huge issue as the scale of the project increases with time. Using an Entity-Component-System can solve these issues.

An ECS uses a data driven style of development and has all components aligned in memory, similar to the object pool pattern. One of the major perks to this approach is that accessing and manipulating large blocks of memory can become very easy. As a person who specializes in engine and graphics programming, this was the primary reason for me pushing for using this when developing our custom engine during our studies at The Game Assembly.

In this post, I will go through the steps taken during the development of our game engine's ECS implementation. It does have room for improvement, however it is a solid foundation and implementing any optimization to the ECS would not alter any interface.

Implementation

Using an ECS would encompass creating and modifying data containers (components), systems utilizing subsets of these containers and entity registration. The EntityManager, ComponentManager and SystemManager classes are created for these purposes. These objects encapsulate and handle their data and memory allocation by themselves. Additionally, to make the utilization of these objects more straight-forward during development, the World facade object is created.


EntityManager

The EntityManager is a glorified FIFO-queue, where an entity is claimed and returned using the Create and Destroy methods.

ComponentManager

A std::vector is currently only used for convenience since a custom allocator is not implemented at the moment of creating this system. The container initialization process lies within the component registration, but it will be pool allocated in the future. Additionally, myComponentContainers will only include pointer offsets to a base memory location of the aforementioned block allocation to enhance cache utilization.

SystemManager

Similar to the ComponentManager, a dynamic container is used for convenience and is something that will be changed in the future. The params available when Registering a system is used for custom constructors, allowing the developers to use whatever information the desire.

Performance issues :c

When initially implementing the ECS, getting a working look-up table is really important. The naive solution to this problem is using the std::unordered_map container. However since the systems will most likely perform A LOT of GetComponent calls, the performance losses from using an unordered map will really start to add up. This something that also only gets worse with each additional component registered in the ECS.

Solution!

Using the Curiously recurring template pattern (CRTP) you are able to use static polymorphism, and so by storing the component container index "within" the component type itself, you are able to get the index directly from the templated variable without knowning the type. Therefore bypassing the need for a separate look up table used for getting each component container.

You may notice at all containers are not placement allocated. As previously stated, it is due to no custom allocator being implemented at the moment and all containers will be pool allocated at a later date.

Conclusion

This is the ECS that has been used in the custom game engine we are developing at The Game Assembly, and it has worked really well! It is quite RAM hungry if unproperly set up, but if the developers specify the desired component container size during component registration it should be accounted for. I really do recommend implementing an ECS if you haven't already! Especially since a data driven development process is a super useful and powerful tool. The source code for this implementation will be linked at a later date on this page!