The simplest implementation of the Entity Component System

Original author: Tobias Widlund
  • Transfer

We start the fourth stream “Developer C ++” , one of the most active courses we have, judging by the real meetings, where not only the “crusaders” come to communicate with Dima Shebordaev :) Well, in general, the course has already grown to One of the biggest among us, it remains unchanged that Dima holds open lessons and we select interesting materials before the start of the course.



The Entity Component System (ECS, "entity-component-system") is now at its peak of popularity as an architectural alternative that emphasizes the Composition over inheritance principle. In this article I will not go into the details of the concept, since there are already enough resources on this topic. There are many ways to implement ECS, and, but I, most often, choose rather complex ones that can confuse newbies and take a lot of time.

In this post, I will describe a very simple way to implement ECS, the functional version of which requires almost no code, but completely follows the concept.


Speaking of ECS, people often mean different things. When I talk about ECS, I mean a system that allows you to define entities that have zero and more pure data components. These components are selectively processed by pure logic systems. For example, the position, speed, hitbox and health of the component are tied to the entity E. They just store the data. For example, the health component can store two integers: one for current health and the second for maximum. The system can be a health regeneration system that finds all instances of the health component and increases them by 1 every 120 frames.

Typical C ++ implementation

There are many libraries offering ECS ​​implementations. Usually, they include one or more items from the list:

  • Inheritance of the base Component / System class GravitySystem : public ecs::System;
  • Active use of templates;
  • Both are in some CRTP form;
  • The class EntityManagerthat controls the creation / storage of entities in an implicit way.

Some quick examples from google:

All these methods have the right to life, but there are some flaws in them. How opaque they process data means that it will be difficult to understand what is going on inside and whether there is a slowdown in performance. It also means that you will have to examine the entire abstraction layer and make sure that it fits well with the already existing code. Do not forget about the hidden bugs, which are probably hidden a lot in the amount of code that you have to debug.

The template-based approach can greatly influence the compile time and how often you will have to rebuild the build. While concepts on the basis of inheritance can worsen productivity.

The main reason why I think these approaches are excessive is that the problem they solve is too simple. In the end, these are just additional data components related to the entity and their selective processing. Below I will show a very simple way of how this can be implemented.

My simple approach


In some approaches, the Entity class is defined, while others work with entities as ID / handle. In the component approach, an entity is nothing but the components associated with it, and for this the class is not needed. An entity will explicitly exist based on its associated components. To do this, we define:

using EntityID = int64_t; //только для целей этой статьи, int64_t - произвольный выбор


Components Components are different data types associated with existing entities. We can say that for each entity e, e will have zero and more accessible component types. In essence, these are component-based key-value relationships and, fortunately, for this there are standard library tools in the form of maps.

So, I define the components as follows:

{float x;
    float y;
{float x;
    float y;
{int max;
    int current;
template <typename Type>
using ComponentMap = std::unordered_map<EntityID, Type>;
using Positions = ComponentMap<Position>;
using Velocities = ComponentMap<Velocity>;
using Healths = ComponentMap<Health>;
    Positions positions;
    Velocities velocities;
    Healths healths;

This is enough to label entities through components, as expected from ECS. For example, to create an entity with a position and health, but without speed, you need:

//given a Components instance c
EntityID newID = /*obtain new entity ID*/;
c.positions[newID] = Position{0.0f, 0.0f};
c.healths[newID] = Health{100, 100};

To destroy an entity with a given ID, we simply remove .erase()it from each card.


The last component we need is systems. This is the logic that works with components to achieve a specific behavior. Since I like to simplify everything, I use normal functions. The health regeneration system mentioned above may simply be the next function.

voidupdateHealthRegeneration(int64_t currentFrame, Healths& healths){
    if(currentFrame % 120 == 0)
        for(auto& [id, health] : healths)
            if(health.current < health.max)

We can put a call to this function in a suitable place in the main loop and pass it to the repository of the health component. Because the health store only contains entries for entities that have health, it can process them in isolation. It also means that the function takes only the necessary data and does not touch the irrelevant.

What if the system works with more than one component? Let's say a physical system that changes position based on speed. To do this, we need to intersect all the keys of all the involved component types and iterate their values. At such a moment, the standard library is no longer enough, but writing helpers is not so difficult. For example:

voidupdatePhysics(Positions& positions, const Velocities& velocities){
    //это шаблон вариативной функции, который берет N карт и// возвращает набор ID, присутствующих на всех картах. std::unordered_set<EntityID> targets = mapIntersection(positions, velocities);
    //теперь target’ы будут содержать только те записи, в которых//есть и позиция, и скорость для безопасного доступа к картам.for(EntityID id : targets)
        Position& pos =;
        const Velocity& vel =;
        pos.x += vel.x;
        pos.y += vel.y;

Or you can write a more compact helper, allowing more efficient access through iteration instead of searching.

voidupdatePhysics(Positions& positions, const Velocities& velocities){
    //это шаблон вариативной функции, который определит пересечение//ключей на картах. Он проитерирует эти ключи и передаст данные//из карт напрямую данному функтору.
    intersectionInvoke<Position, Velocity>(positions, velocities,
        [] (EntityID id, Position& pos, const Velocity& vel)
            pos.x += vel.x;
            pos.y += vel.y;

Thus, we got acquainted with the basic functionality of the usual ECS.


This approach is very effective, as it is built from scratch, without limiting abstraction. You do not have to integrate external libraries or adapt the code base to the predefined ideas of what Entities / Components / Systems should be.
And since this approach is completely transparent, you can create any utilities and helpers based on it. This implementation grows with the needs of your project. Most likely, for simple prototypes or games for game jam'ov, you will need only the functionality described above.

Thus, if you are new to this entire ECS area, such a straightforward approach will help you understand the main ideas.


But, as in any other method, there are some limitations. In my experience, just such an implementation using unordered_mapin any non-trivial game will lead to performance problems.

Iteration of key crossing on multiple instances unordered_mapwith multiple entities does not scale well because you are actually doing N*Msearch operations, where N is the number of intersecting components, M is the number of matching entities, and unordered_map not very well with caching. This problem can be eliminated by using a key-value store that is more suitable for iteration instead unordered_map.

Another limitation is boilerplating. Depending on what you are doing, identifying new components can become tedious. It may be necessary to add an advertisement not only in the Components structure, but also in the spawn function, serialization, utility debug functions, and so on. I ran into this myself and solved the problem using code generation - I defined components in external json files, and then generated C ++ components and helper functions at the assembly stage. I’m sure you can find other pattern-based ways to fix any boilerplate problems you’ll run into.


If you have questions and comments, you can leave them here or go to an open lesson with Dima , listen to him and ask around there.

Also popular now: