In this post I’ll go over how I’ve laid out the parts of my ECS framework, with some emphasis on issues related to the .Net framework. Check out some previous posts here and here.
By all means this is not meant to be authoritative. In fact, I’ll be outlining several problems I see with the way I’ve done things.
Overview
I’ll start with a few top level diagrams. First, the EntityManager:
The EntityManager contains the list of all the existing entities (which are just light wrappers over a few ids), and a ComponentManager for each type of component. So the components are stored separately from the entities, each in their own list.
A particular entity is associated with various components, like so:
The other main piece of the pie is the SystemManager:
The systems (e.g. RenderSystem, AnimationSystem, SoundSystem) contain the bulk of the logic. A system provides its logic by inheriting from a base class and overriding a few key methods. A system declaratively states which components it is interested in, and the base class is responsible for keeping the system’s entity list updated with all entities that have exactly the components in which the system is interested. As a result, the system can just loop through this list while doing its processing.
Memory Layout
One of the motivations for storing the components separate from the entities is that like components are usually processed together. If we store all the components of one type in a single list, then we can (theoretically) access them sequentially in memory as we process them. This results in good locality of reference. So it is mainly a performance consideration.
If, instead, the components were located in a collection off of each Entity object, then many of our operations would involve touching scattered memory. While this is certainly a valid way to go (I’ve seen several implementations like this in languages with managed memory), you won’t achieve any of the theoretical performance benefits of ECS frameworks.
In C++ you have strong control over memory allocation patterns. In C#, not so much.
Ideally, we want all the components of one type to exist in contiguous memory. The obvious way to do that in C# is to make the components value types (structs). Unfortunately this is too cumbersome – among other things, it prevents us from having a common base class or interface for components, and it is restrictive to deal with references to structs (for instance, you can’t return a struct by reference from a function).
The good news is that we can still put things in contiguous memory if the components are reference types (classes). I mentioned in a previous post that allocations made one after the other are generally allocated sequentially in memory too. I actually couldn’t find this mentioned/documented anywhere, but my testing shows it to be true (at least on my desktop computer).
Of course, the CLR is free to move things around in memory. But in my testing, my arrays of components always had each component object located adjacent to each other in memory. (Note: the CLR Profiler shows this is not true. But I believe this is because when running with the profiler enabled, the allocation logic is different).
This is all only useful if we access these components in order. Unfortunately with my current implementation, I do not.
The details
As the game runs, entities and components are frequently being added and removed. How do I manage these lists so that enumerating through them is efficient? How do I ensure adding and removing is efficient?
My current implementation has as one of its goals the efficient adding and removing of entities and components. This means that we avoid any O(n) operations (such as shifting all the components down in the array when one is removed). Add/remove operations are done in linear time.
To accomplish this, I need to keep 3 lists: The pool of entities themselves, a “compact” array of indices for lookup into the entity pool, and a reverse lookup array which I’ll call the roster.
If I allocate one entity, it would look like this:
|
Entity Pool [LiveId] |
LiveId [RosterIndex] |
Roster [LiveId] |
|
EntityA |
0 |
0 |
|
EntityB |
1 <— NEXT FREE |
-1 |
|
EntityC |
2 |
-1 |
|
EntityD |
3 |
-1 |
Now let’s add two more:
|
Entity Pool [LiveId] |
LiveId [RosterIndex] |
Roster [LiveId] |
|
EntityA |
0 |
0 |
|
EntityB |
1 |
1 |
|
EntityC |
2 |
2 |
|
EntityD |
3 <— NEXT FREE |
-1 |
At this point, it’s worth explaining how I can enumerate through all entities. I use the LiveId list, which is indexed by RosterIndex. I simply loop from 0 to “NEXT FREE” and look up the LiveId. I then use the LiveId to index into the Entity Pool.
Now let’s remove a Entity A. The result will be this:
|
Entity Pool [LiveId] |
LiveId [RosterIndex] |
Roster [LiveId] |
|
EntityA |
2 |
-1 |
|
EntityB |
1 |
1 |
|
EntityC |
0 <— NEXT FREE |
0 |
|
EntityD |
3 |
-1 |
There is now a gap in the Entity Pool, but it doesn’t matter since I just enumerate through the list of LiveIds (the middle column), which never has a gap. We took the now empty spot for Entity A and swapped it with the last one in the list (and decremented NEXT FREE). The Roster[LiveId] list is just needed so we can perform the swap. It let’s us know the index in LiveId[RosterIndex] where the removed guy lives.
Now when I enumerate through the entities, I’ll encounter LiveId 2 (Entity C) and then LiveId 1 (Entity B). So you’ll note that the enumeration order is different. In fact, the more add and remove operations I perform, the more chaotic the enumeration order becomes.
I use this system (which allows for linear time add/removals, and “compact” enumerations) in 3 different places:
- For the list of entities in the EntityManager, as just described
- For the list of components in each ComponentManager
- For the list of entity LiveIds in each System
Just for kicks, I ran my game for a while and played a few levels. I took a look at how the items were laid out in memory for cases 1 and 2 above.
It’s maybe a bit hard to explain this image, but a perfectly sequential-in-memory order would be a smooth gradient from left to right. Instead, you can see chunks of sequential memory, and then random jumps to other places. Over time, this would become more and more randomized.
In summary, I have optimized for a fast adds and removes at the expense of potentially pool locality of reference.
A system that operates over a single type of component will be affected by this.
What about a system that operates over of two types of components (such as the RenderSystem, which needs to know about both Aspect and Placement)? Well, there is already an inherent problem there. Even if we were to somehow fix our spatial locality issue, we are simultaneously iterating over two separate lists. If one were guaranteed to be in nice enumeration order, the other would necessarily not.
So fundamentally, there are three things working against us for achieving good locality of reference:
- It’s possible (though not likely) that our original list won’t be contiguous in memory, and the .net garbage collector can move things around if it wants. Of course, this is not a problem in C++.
- My algorithm for storing Components/Entities ends up “randomizing” their order as things are added/deleted. This could get worse as time passes.
- The fact that most systems enumerate over multiple components means that access of at least one of those component lists will be “random”.
To sum up, I can’t honestly claim that locality of reference is a big benefit to my ECS framework with the way I have it currently implemented. Of course ECS has many other benefits.
It gets worse
I haven’t yet gone into detail about how a system enumerates through the entities it cares about. I’ll do that now.
As mentioned before, each system is automatically kept up-to-date with a compact list of entities to enumerate. It is actually just a list of LiveIds. LiveIds allow us to perform a simple array lookup to get an entity from the EntityManager (thus a LiveId is a number that is guaranteed to be unique for the lifetime of an Entity). Systems aren’t typically interested in the entities themselves though: just in their components. So a system might do something like:
For each entity LiveId in my compact list GetComponentA GetComponentB Do stuff with those components.
I need to explain another thing now. We have LiveIds for components, just like we have LiveIds for entities. The component LiveIds are quick lookups into the array in the ComponentManager for a component type. We could get away with re-using the Entity LiveIds, but then each ComponentManager would have to be large enough to hold n components, where n is the max number of entities we have. This could work with common components like Aspect and Placement (which are on most entities), but it would be a waste of space for other ones. So instead, we tailor the size of each ComponentManager to the number of components it is expected to have.
So where are these component LiveIds stored? The EntityManager has a giant array of them indexed by entity LiveId and ComponentId. ComponentId is just a fixed number that corresponds to a particular component type (e.g. Aspect = 0, Placement = 1, SoundLoop = 2, up to the number of component types we have).
So if we have 1000 entities and 20 different kinds of components, this array would be 20,000 elements.
So going back to our enumeration in the system:
For each entity LiveId in my compact list GetComponentA
GetComponentA involves the following:
- index (in a non-sequential fashion) into the giant array, using [LiveId, ComponentId] to find the component LiveId
- index (in a non-sequential fashion) into the ComponentManager’s array using that component LiveId
So that’s essentially two random accesses into arrays in order to get the component we need. Hardly ideal. Of course, a system that only cared about a single component type could directly enumerate through that type’s ComponentManager, thus incurring only a single lookup.
Thoughts
I haven’t used my ECS framework for a large enough game world yet to really know the performance implications. So it’s hard to make any conclusions about that, other than I know the implementation could definitely use some improvement.
I’d be interested to hear about other people’s implementation details, regardless of whether or not they are in native or managed code. I don’t think managed code is hurting me too much here.

























