Formalisation of Concepts behind ECS and Entitas
There was a thread in Gitter Chat of Entitas-CSharp. People discussed that it would be great to have a list of concepts behind Entitas, specifically for those, who want to port it to another language. In this post I will try to formalise the concepts for ECS in general and Entitas specifically. If you are familiar with ECS and think I made some false assumptions, please do comment.
The first Concept of ECS
Separate state from behaviour. This is the main rule and it also indicates if the implementation is an actual ECS or something different.
Second Concept of ECS
Atomic data is represented by components. When working with ECS we tend to think about the smallest/atomic parts of our data and express them as a components (object, or class, or struct, or record, or what ever your programming language has in stock for simple value types)
Third Concept of ECS
Components are associated logically by an entity. Entity can be an object or just an ID. It depends on ECS implementation.
Fourth Concept of ECS
All entities are managed and are querable(searchable/iterable). Means that we create an entity through a managing data structure. The name of the data structure can be World, Universe, Matrix, Pool, Repository, Manager, Database, Context or what ever term the implementor of ECS pattern come up with. Querability depends on the implementation. Here is a list of possible queries:
- Give me(iterate over) all entities
- Give me(iterate over) entities associated with following component type
- Give me(iterate over) entities matching following logical expression all(A, B, C).any(D, E).none(F) -> Entities which have A, B and C has either D or E and don’t have F components. In Entitas-CSharp we call representation of such logical expression a Matcher.
- Give me(iterate over) entities which comply with following predicate. Predicate is a “function” which examines the entity and returns boolean value (a typical filter use case)
- Give me(iterate over) entities which has following values in following components. In Entitas-CSharp we call this concept indexing.
Fifth Concept of ECS
All behaviour (creation, transformation and destruction of entities) is implemented in Systems, wich are called periodically. Following this rule lays on the shoulders of Application developer. I saw projects where people don’t follow it at all and come up with there own event or command based structure. I saw project which follow it religiously, or pick a mid ground.
Now I would like to examine how Entitas relates to the stated concepts and what kind of additions it brings.
Entity is an object not just an ID.
We would like to have a nice API where user can ask entity for it’s component. Something like entity.position or at least entity.get(Position)
Component holds data or is just a flag.
Components are with fields which hold only data. No methods are allowed. A component without methods and field is a flag and is used for fast querying. A component can be marked as unique. In this case we make sure there will be only one entity holding a component of this type.
We don’t change components directly.
We use add, remove and replace methods on entities when we want to do some state transformation. An entity is observed by it’s managing data structure. This way we can provide efficient querying, event logging, validation and reactive behaviour.
Entities are referenced in Groups.
User can ask for a group of entities from the managing data structure. The group is identified through a Matcher. The group will be always up to date, as it will be updated in the moment entity fires a change event.
Groups are observable.
User can observe groups for changes. The observer will get notified if an entity is added or removed from group.
Changed entities can be collected or indexed.
By using the group observing mechanism, we can collect changed entities and we can build up indexes and keep them up to date for very efficient querying (see Forth rule of ECS).
Entity can be backed by a dictionary or an array.
This implementation detail has a strong effect on performance, API and need for code generation. I don’t want to elaborate on this point more because it is an article for itself.
Partitioning entities by defining multiple managing data structure.
As an entity is created and managed by a context (or what ever it is called) it is coupled to this context and can’t switch to another context. This produces a boundary, which can be useful for performance reasons, memory efficiency, or multi threading scenario. It also helps developers to partition/decouple logic.
Systems can have different characteristics.
In Entitas we end up with following types of Systems:
- Initialise System. This system is executed once on “application start”. It is useful to populate data before the periodic systems will kick in.
- Continues system. The simplest periodic system. It will be executed periodically and the developer is responsible for querying data.
- Reactive system. Even though reactive system is also executed periodically, the code provided by the developer will be executed only as a reaction to a group event. Internally every reactive system is coupled with an entity collector (see: Changed entities can be collected or indexed) Reactive system drowns the collector and passes collected entities to the code provided by the developer. A developer is able to define, which group and event is of interest. We also provide multi reactive systems, where the system can observe multiple groups. As reactive system is executed periodically, collected entities will be processed only once per cycle. This means, collected entity might become stale. This is why we provide infrastructure for developer to filter the entities before they got passed. We also discussed back pressure and streaming, but haven’t implemented it yet.
- Cleanup system. This system is also executed periodically, but after all continues and reactive systems. Its purpose is to clean up state.
- Tear down system. Is like initialise system but should be executed on application stop.
I think I managed to sketch most important concepts, I will update the post if more things will come up. And please do comment and leave feedback if something is not clear or wrong.