ECS Terms

Table of Contents

ECS has a fair share of new terminology that can be a bit daunting at first, especially when you don’t know how all these things play together. Knowing the terms will help you greatly in understanding the ECS documentation and tutorials and it will also make it easier for you to ask for help online.

Entities and Components

An entity is a thing that lives in your game. It can be a player, a bullet, a spell, anything really, it doesn’t even need to be visible to the player. Each entity can have zero or more components attached to it. The components define the properties that these entities have, e.g. how much health, what kind of speed, fuel levels, or whatever you need for your game. From a first glance this looks very much like the GameObjects and their MonoBehaviours you already know from classic Unity.

There is however an important difference. First, in Unity an entity is simply an ID that groups components together. So an entity is little more than just a number to which components can reference. All components that reference to the same entity ID belong to an entity.

Using Unity’s ECS API you can manage components that are attached to entities:

var entity = EntityManager.CreateEntity();

// add some health component
EntityManager.AddComponentData(entity, new Health { Value = 100 });

// remove the health component
EntityManager.RemoveComponent<Health>(entity);

// modify the contents of the health component
EntityManager.SetComponentData(entity, new Health { Value = 150 });

You don’t add or modify components directly on the entity class, but you ask the entity manager to do this. The entity manager will internally assign components to entities by referencing the entity’s ID in the component.

Components hold all your interesting data, e.g. how much health does a unit have, where is it located, what powers does it have, etc. In contrast to MonoBehaviours they are pure data holders. There is no code in components. All the code that actually does something is in systems (to which we’ll get in a minute). In Unity you have to declare components as struct instead of class (something that I forgot quite often when I got started) and the struct has to implement IComponentData:

public struct Health : IComponentData {
  public int Value;
}

Why structs, you might ask. The reason is that Unity will keep your entity and component data in unmanaged memory so it can be accessed really fast later. For this to work, Unity needs to copy data from managed memory (where your game code lives) into unmanaged memory and back. I’m not too familiar with how this is actually done, but suffice it to say, that this only efficiently works for value types so your components need to be a struct and as such a value type.

Also all members of your struct have to be a value type as well and on top of it, they also have to be blittable. This requirement severely limits what you can actually put into a component. So adding strings or any non-struct type to your component will not work and you will need to design your data around this restriction.

You can also create components that have no members at all. Such components are called marker components or tag components as their presence simply tags or marks an entity. E.g. you could mark poisoned units and perform some special code on them every tick:

public struct Poisoned : IComponentData {}

Archetypes

Archetype is a peculiar concept. It certainly took me a good while to figure out what this is actually good for. An archetype is basically a collection of component types that somehow interest you. For example you may be interested in the position and health of all your units. You can then create an archetype for this:

var archetype = EntityManager.CreateArchetype(
          ComponentType.Create<Unit>(),
          ComponentType.Create<Health>(),
          ComponentType.Create<Position>()
      );

But what can you actually do with an archetype? At first glance, surprisingly little. You can create entities using the archetype as a template:

var entity = EntityManager.CreateEntity(archetype);

This will give you an entity having the given components pre-attached to it. I found that I almost never use this. So why are archetypes important then? Can you forget about them and move on?

The answer to this is - as you may have guessed - no. Archetypes are used to provide you with an effective means of iterating over entities that interest you. Before you can dive into that, you should first have a look at entity queries though as they are closely related to this.

Entity Queries

Entity queries (formerly known as Component Groups) are the main means of accessing component data. Coming back to the previous example, you may be interested in all units having a position and health to apply some area damage to them. Now you need to have a way of getting these. You can do this with an entity query:

var interestingUnits = GetEntityQuery(
          ComponentType.ReadOnly<Unit>(),
          ComponentType.Create<Health>(),
          ComponentType.ReadOnly<Position>()
    );

Now this looks quite similar to the archetype stuff that you just saw. And indeed there is a relationship. Internally EntityQuery will create an archetype (or reuse an existing one) with the components that interest us. This archetype is then used to effectively lay out these components in memory, so you can iterate over them really fast.

var positions = interestingUnits.ToComponentDataArray<Position>();
for (var i = 0; i < positions.Length; i++)
{
    var position = positions[i];
    // do something with the position here
}

You never see the archetype appearing anywhere in the code, but it is used under the hood to organize memory efficiently, and you need to know that is there to understand chunks, which I’ll talk about in a minute.

So whenever you are interested in getting a certain set of components for entities that have these components, think EntityQuery.

Chunks

Now it gets a bit more technical, at least if you’re not coming from a systems programming background where you juggle memory blocks all day. Chunks are used to organize data for efficient processing. They closely tie in with archetypes. In the previous example you looked at units having health and a position. An EntityQuery was created to iterate over them and I said it would create an archetype internally. Yet still, it was not really explained what the archetype is actually good for.

Look what happens in memory when you create the EntityQuery and the underlying archetype:

Unity prepares a segment in memory. Then Unity copies the data of the components that are interesting for us to this segment and tightly packs them. Unity needs the archetype to know which components are interesting so it can efficiently pack them for us. Now what would happen if we decide to create more units or maybe delete a few of them?

Deleting an entity punches a hole into the tightly packed structure. Also for new units Unity would have to allocate a larger memory segment and then re-pack everything. This can get quite expensive if your segment is sufficiently big. To solve this problem the large segment is divided into smaller segments, called chunks, which have a fixed size (of currently 16kb, but this may change down the road).

Now if some entities are disappearing (or just losing the components that were are interested in) Unity doesn’t need to re-pack the whole segment, but only the chunk that contained the changing entity. Same goes for adding new entities. Their data either gets appended to a chunk that is not yet full or Unity simply allocates a new chunk and puts it there.

So if you actually iterate over components, the processor can now do this really fast as everything is tightly packed in chunks that happen to fit into the processor cache, so it doesn’t need to access the (comparatively slow) RAM to get your data.

You may now ask why this is important for you at the game level, where you really don’t want to bother with these “implementation details”. The reason is, that this organization has some implications for you.

On important implication is, that you cannot rely on the order of components in your iteration. Unity will freely move component data to other positions in the chunk to fill holes efficiently. So just because you got unit number 50 at index 10 in your first iteration doesn’t mean it will be at index 10 in the next iteration.

Another implication is that adding and removing components frequently on many entities may have a detrimental effect on performance as Unity will need to re-pack the chunks for your archetypes more often.

Systems

Until now I have mostly talked about entities, components and how they are stored in memory and how archetypes and chunks help organize the data. However at the end of the day, all the data is useless if you don’t do anything with it. Systems contain the code that actually works on the data defined in your entities. There are basically two types of systems available: ComponentSystem and JobComponentSystem.

ComponentSystem is a system that does it’s work on Unity’s main thread much like the old MonoBehaviours do:

public class MySystem : ComponentSystem {
  protected override void OnUpdate() {
    // do your work here
  }
}

JobComponentSystem is a specialized system type that allows you to offload work to jobs, using Unity’s new-ish C# job system.

public class MyJobSystem : JobComponentSystem {
   protected override JobHandle OnUpdate(JobHandle inputDeps) {
        return new MyJob().Schedule(this, inputDeps);
   }
}

It has a slightly different OnUpdate method that gives you a JobHandle with input dependencies and expects a JobHandle back. We’ll talk about how this works in detail in the part about ECS jobs. For now suffice it to say, that when you schedule your jobs, you’ll use the inputDeps as dependencies for your jobs and return the JobHandle from scheduling your own job as output dependencies. This allows Unity to schedule multiple JobComponentSystems jobs efficiently without having to have a hard synchronization boundary between them.

Entity Command Buffer Systems

Entity command buffer systems (previously known as “Barriers”) are a special type of system that is used for synchronizing changes to entities you do in a job. For example, lets assume you have a job that creates 50 new entities:

public struct MyJob : IJob {
    public EntityCommandBuffer CommandBuffer;

    public void Execute() {
        for(var i = 0; i < 50; i++) {
            CommandBuffer.CreateEntity();
        }
    }
}

Inside of jobs you have no access to an entity manager, as you cannot have references in jobs. Instead you have an EntityCommandBuffer where you write your changes to. An EntityCommandBuffer is always assigned to an EntityCommandBufferSystem. When your job is finished, the EntityCommandBuffer is now filled with instructions on what actions to perform on your entities (in this example, create 50 new entities) but these instructions are not executed yet. They will be executed when the EntityCommandBufferSystem to which the EntityCommandBuffer is assigned is run.

So basically, you collect instructions in jobs on multiple threads and then execute these instructions in an EntityCommandBufferSystem on the main thread later. Again we’ll talk about this in more detail in the part about ECS jobs.