Make your own ECS (Entity-Component System)
“ECS” is an acronym you can read quite a lot, since it seems to have been the common hype about game engines for the past few years. This tutorial/article is meant to get you to understand its concepts, and get a grasp of a basic implementation to build your own.
What IS an ECS?
The acronym ECS stands for Entity-Component System. It is an architecture pattern mostly applied to game engines.
Long story short: there is no proper definition of what an ECS actually is and, as such, there is no actual standard way to implement it.
An “ECS” clearly isn’t something well-defined. From what I’ve read about it though, I’d define the usual ECS being an implementation with:
- The Component pattern: prefering aggregation of characteristics over inheritance (
has a
associations instead ofis a
ones); - The Data locality pattern: optimizing CPU cache by conserving data (here our entities) in a single contiguous collection;
- The Update method pattern: make base classes for systems & components with a virtual
update
method specialized for each inherited class.
Disclaimer
You may have read/heard some statements that led you here, such as:
- ECS is good for performance
It depends. Implementing only the Component pattern won’t give you a performance boost. There’s a good chance it will be the opposite, actually. But it will give you an insane amount of flexibility, which is not negligible.
- An ECS is way more memory efficient than an OOP-based approach
Well, this is not wrong… but not entirely true either. Removing the inheritance avoids having a pointer to every inherited object due to the virtualization. On the other hand, in our ECS some space is lost because of the way of manipulating components & systems by indices. I lack experience to say this for sure, but I guess that in the long run it indeed tends to be way more memory efficient than the OOP-based design.
- Every major game engine uses an ECS, I should make one for my own
True. Although actually, Unity’s current state isn’t truly an ECS, since the systems are not accessible and thus not modular. It however implements the Component pattern. As a matter of fact, they’re in the process of changing the whole architecture into a real ECS at the moment.
That being said, converting your own OOP-based engine into an ECS should not be a brainless decision. If your current engine is simple enough and well architectured, you could lose a fair amount of time while gaining absolutely no performance at all, even losing some. But sure enough, you will gain a ton of flexibility, which should be your main reason when evolving into one.
Bottom line is: don’t exert yourself trying to implement something you may not have any gain from, if you have no good reason to do so. However, if your goal is to learn, then I can’t hold you back from implementing one. Break everything you want in your current architecture, start from scratch, do whatever you want with it, but I think learning should never be prevented in any way.
The Entity-Component System
Components
This part is actually what an ECS is really about. I guess a lot of people use the acronym “ECS” to talk about this specific pattern. A really good reference for this is the article from Game Programming Patterns.
To explain what the components are all about, let’s start with a basic and naive inheritance implementation example.
First of all, let’s assume you have a Mesh class. I won’t explain its inner structure, so let’s just say it contains data to represent a 3D mesh.
Now, a Mesh is supposed to be moved around the scene, right? You could then create another class, named for example ‘Model’, which will contain a Mesh and inherit from Transform, your class made to handle movements. This works fine, your mesh is moved into the scene through your Model instance.
Now, you want to add collisions to it with your physics system. You could do that by creating two other classes:
- Create a ‘CollidableMesh’ to have a static collidable mesh, containing a Mesh and inheriting from Collidable.
- Create a ‘CollidableModel’ to have a movable collidable mesh, inheriting from Collidable & Model.
This is just a starting example, but it seems pretty clear where it is going. In the end, you will have a ton of links between your classes, combining them every way you deem useful. It will be enough if you keep your engine simple, but in the long run this is not maintainable at all.
This is where the Component pattern jumps in: it is meant to favorize composition over inheritance. This means that, instead of having multiple classes inheriting from some others, we simply have a basic class containing components, which define the object’s behavior.
The purpose of having components might not be clear now, so let’s see the ‘E’ part of our ECS implementation: entities.
Entities
Writing in progress.
Systems
Writing in progress.
Update method
This one’s the easiest: in our System base class we will define a virtual methode update()
, which will be reimplemented by the inheriting systems. This will be where all our logic is, executed for each iteration of the game loop.
Optimization: data locality
Small explanation: a computer’s CPU possesses a cache, whose size vary but still is pretty small (for example, L3 cache (the largest but the slowest compared to L2 & L1) is ~6 MB on Intel i5s, ~8 MB on i7s, ~19 MB on AMD Ryzen 5s and ~20 MB on Ryzen 7s. This list is absolutely not entirely accurate, it may be different with each generation & model). As you can see, this cache is incredibly smaller than your amount of RAM, but is really faster than it.
Nowadays, CPUs perform what we call caching: copying data from the RAM into the cache so that it can access it way faster. However, this operation only copies data adjacent to the one you’re accessing. If the data you’re fetching from memory isn’t in this cache slice, this causes a “cache miss” which slows down the program to pick data from the RAM. That is, your program can be executed faster if you’re often selecting contiguous data.
There is no way that I can explain better than the Game Programming Patterns’ article on this, so I’ll let you read it if you didn’t understand.
In that way, entities are meant to be in a single memory-contiguous collection (like an std::vector in C++, std::Vec in Rust, etc). As such, CPU caching doing its job and your entire world being contained in this collection, you minimize the amount of cache misses and allow your program to process data faster.
References
- What’s an Entity System? - Wikidot
- Example of ECS implementation: EntityX - Alec Thomas
- Example of ECS implementation: anax - Miguel Martin
- Component-based engine design - Randy Gaul
- Sane usage of Components and Entity systems - Randy Gaul
- Writing a game engine in 2017 - Randy Gaul
- Implementation of a component-based entity system in modern C++ - Vittorio Romeo, CppCon 2015
- Game entity management basics - Vittorio Romeo, Dive into C++11
- Component pattern - Game Programming Patterns
- Data Locality pattern - Game Programming Patterns
- Update Method pattern - Game Programming Patterns