Entity Component Systems and Inter-System Data

Started by
8 comments, last by haegarr 10 years, 3 months ago

I'm trying to implement a component based system for the first time and I understand the core concepts but I'm having trouble working out a framework that allows flexibility but is also cache friendly.

For example here's my Transform component:


struct TransformComponent
{
 glm::mat4x4* localTransform;
 glm::mat4x4* worldTransform;
 int* parent;
};

and Here's an abridged version of my transform system:


class TransformSystem
{
 vector<glm::mat4x4> worldTransforms;
 vector<glm::mat4x4> localTransforms;
 vector<int> parents;
 vector<size_t> denseToSpare;
 vector<InternalHandle> sparseToDense;
 
 size_t componentCount;
 size_t firstFree
 
 Handle createComponent();
 void destroyComponent(Handle target);
 TransformComponent getComponent(Handle target);
 void setParent(Handle child, Handle parent);
 void update()
 {
   glm::mat4x4* wT = &worldTransforms[0];
   glm::mat4x4* lT = &localTransforms[0];
   int* parent = &parents[0];
   for(size_t i = 0; i < componentCount; i++, wT++, lT++, parent++)
   { 
     if(parent == -1)
        wT = lT;
     else
       wT = worldTransforms[parent] * lT;
   }
 }
}

Here's the entity structure


struct  Entity
{
   unsigned int ID;
   hash_map<ComponentType, Handle> componentHandles;
};

-Components are stored in a dense array which is managed by a sparse array of handles.

-External "Handle"s contain an index into the sparse array and a version number

-Internal "InternalHandle"s contain an index into the dense array, a version number and an index for keeping a free list of internal handles in the sparse array

This all works fine. The trouble is inter-component and inter-system communication. For example the MeshRendererComponent, and associated system, are dependent upon the TransformComponent for position.

I've spent hours looking at this problem from every angle I can think of and I can't just seem to figure out a reasonable solution to retrieving the component from the other system without making the components aware of their entity.

If anybody has any insights they would like to share I would be very grateful. Thank you.

Advertisement

Is think thats usually done using some kind of bitmask on the entity, lets say you have 3 components

1 position

2 mesh

3 movement

One entity has all three components giving 111 and your MovementSystem which needs position and movement needs 101, so it would get this Entity

I'm not sure if this is the "correct" way or not; In my implementation rather than storing the components in a list (or similar structure) in the Entity itself, I have an EntityManager class that stores all the components for all entities, and manages Entity and Component instantiation. I have a GetComponent() call in the EntityManager that would fetch a component for a given EntityId. So, in any system I can do a few calls to grab the needed components for an Entity based on it's Id by referencing the EntityManager.

I expanded the idea a little and allowed systems to grab a reference to the entire member list for a given component in a single call rather than performing a GetComponent() call for every entity individually.

It took me a while to nail down the implementation, but it's worked quite well for me since.

[size="2"]Currently working on an open world survival RPG - For info check out my Development blog:[size="2"] ByteWrangler

From my understanding of the original post, it sounds like the render system, when iterating through the MeshRenderComponents, doesn't actually know/care about the entity id (so it can't fetch the TransformComponent, correct?).

The obvious fix, for systems that require more than one component to function, is to instead iterate through the entity ids not the components themselves. This may end up being less cache-friendly though, since you might essentially be random-accessing the components.


In my implementation rather than storing the components in a list (or similar structure) in the Entity itself, I have an EntityManager class that stores all the components for all entities

It's unclear from his post where things are actually stored. All that's in the Entity itself are handles for each component type, not the components themselves.

Transforms seem to be stored in the TransformSystem - or at least the data that contains them appears to be (the vectors of world/local matrices and parent indices). And TransformComponent has pointers into that array? Is there a reason you implemented it this way?

I would instead just have the two matrices and parent be directly part of TransformComponent. Instead of "many arrays of one", you should instead make "one array of many".

I would also not store the TransformComponents in TransformSystem (since other systems need them too - why would they belong to any one system?).

Also, if you're worried about cache performance, you might be able to store a more compact form of the transforms (e.g. something decomposed, like position, quaternion, scale).

It's unclear from his post where things are actually stored. All that's in the Entity itself are handles for each component type, not the components themselves.

What I meant was that in my implementation of Entity, there are no references to its components, be it a List of components or a hash_map of handles. My ECS is implemented in C#, so I was using terminology that wasn't really C++ oriented, my apologies for the confusion.

An abridged version of my Entity structure to illustrate what I mean:


struct Entity
{
   unsigned int ID;
};
[size="2"]Currently working on an open world survival RPG - For info check out my Development blog:[size="2"] ByteWrangler

Let Entity be a Unique ID. When you add component to entity, you actually connect a specific component to a specific unique ID (Entity). Now you have a bi-directional relationship:

1. Given Entity you can get all its components

2. Given a specific component, you can know to what entity its attached, hence going back to #1 you can get all other components

From here, your render system can query the entity manager (or whatever you call it) for components based on given entity.

This is the method I prefer. It keeps the design flat, and allows per sub-system update (rather than per entity update).

The down side, is that you need to have some validation process when creating an entity (what if entity X created with render component but without position component? you need somehow to handle this). Maybe even a pre-process of the entities before the game starts or at build time (before packaging the game), to make sure that all entities being created correctly.

Also I'm not sure about caching of such approach. As I see it, it should be simple, since every system keeps a hash/list of Uniqe ID->Component and retrieval should be O(1).

I would love to change the world, but they won’t give me the source code.

Thanks for the responses. So to clear some things up.


It's unclear from his post where things are actually stored. All that's in the Entity itself are handles for each component type, not the components themselves.

Transforms seem to be stored in the TransformSystem - or at least the data that contains them appears to be (the vectors of world/local matrices and parent indices). And TransformComponent has pointers into that array? Is there a reason you implemented it this way?

Currently the transforms are stored inside the transform system. It may make more sense to store them in some kind of component pool outisde of the systems.

The parent component is an index into the dense array. The theory being that as long as the parent appears in the array prior to the child (p < i) then the transforms can be calculated in a single (cache-friendly) linear pass rather than having to dance around a tree structure which could be anywhere in memory.


I would instead just have the two matrices and parent be directly part of TransformComponent. Instead of "many arrays of one", you should instead make "one array of many".

I got carried away with the "structure of arrays" concept. In hindsight there's very little point in separating them like that as every component of the structure is accessed in every instance. So I will combine them into a structure.

I think what I'll do is move all of the components out of the systems and in to a component manager where each pool can be accessed by some kind of type identifier.

Some code off the top of my head

Note: my VBO's are assigned at the max size indexable by an unsigned short and then broken into blocks by a page/pool allocator and I use managers to track the currently bound vao, ibo, vbo, shader etc to minimise state change.


struct MeshRendererComponent
{
  VertexBufferBlock* VertexBuffer;
  ShaderProgram* Shader;
  Handle TransformHandle;
};
class MeshRendererSystem
{
   update()
   {
      MeshRendererComponent* currentComponent = ComponentManager.getPool<MeshRenderComponent>().front();
      ComponentPool<TransformComponent>* transformPool = &componentManager.getPool<TransformComponent>();
      for(size_t i = 0; i < numComponents; i++, currentComponent++)
      {
         ShaderManager::Bind(currentComponent->Shader);
         
         //Ignore this. I'm not done with the shader system yet
         currentComponent->Shader->setMatrix4x4("modelMatrix", transformPool[currentComponent->TransformHandle]);
 
         VertexBufferManager::DrawBlock(currentComponent->VertexBuffer);
      }
   }
};

I attempted this in one of my revisions of my entity/component framework and I am curious how you approached a few concerns I found with it.

My initial thought was to have ComponentPool<T> have a constructor argument that controlled the maximum number of components the pool could maintain. This inevitability acted much like calling reserve(size) on a vector, eliminating any future allocations mid-frame that could impact the main loop's execution. I saw this as a great way to have total control over the memory budget of varying systems and this upper-cap could either be some static cap or data-driven by scene metadata.

One of my first concerns I saw with it was how to handle component releasing:


ComponentPool<BoxCollider>* pColliderPool = GetComponentManager().GetPool<BoxCollider>();
BoxCollider& collider1 = pColliderPool->GetComponent(colliderId1); //! get box collider with id colliderId1
pColliderPool->Release(colliderId2); //! free collider with id colliderId2
collider1.someProperty = someValue;

When a component is released, it seems pretty common that the version aspect of the component id/handle gets incremented; thus invalidating any existing external references if they attempt to use the handle/id in future operations. Additionally, releasing a component also generally means that the dense array needs to be repacked by swapping the component at the freed index with the last active index.

The problem when you swap the two indices is that it invalidates any existing references up to that point and it invalidates iterators, much like what you'd expect of typical vectors. To address this, I thought about actually eliminating the swap step in the release process and impose some logic on the exposed iterator api to be capable of determining whether a pool index is occupied by an active element or one that has been released and simply skip the released ones.

But at some point, the dense array needs to be reorganized and actually be dense rather than have gaps. But this process needs to happen at secure points in the code to avoid problems, thus I exposed a Pack() method that essentially takes the released indices, swaps them with the last active element, updates the internal structure index references, etc.

What I am curious is whether this is somewhat common place and normal?

Are there other alternatives that I might not have considered to handling the release of a component?

What about drawbacks to this approach?

This approach does seem quite spot on to me, at least on the surface.

We've satisfied the need to keep components in contiguous memory for cache locality. Simple systems that need to iterate components can do so just by iteracting with the component pools directly. Systems that require additional information beyond what components expose can create their own internal structures, store them in cache friendly means and have those structures reference the pool's component id, allowing for one level of indirection lookup for a component's data where needed.

Referring to your original post, I handle inter-system dependencies by using "super-systems", which handle the interactions between multiple component-systems. For example, if I have an inventory component that holds an array of item components (meaning that any entity with an inventory can hold entities that are items... of course, I do all of this using unsigned long integers, to minimize footprints), before the item is passed to the inventory to check for storage space, I use a sort of item super-system that checks to make sure that the entity I'm storing in my inventory is, in fact an item.

This may not be the most cache friendly manner of handling this situation, but I've managed to get a fairly robust set of systems running without any real inter-component-system dependencies.

In your case, assuming that anything with a MeshRendererComponent (MRC) will also have a TransformComponent (TC), a sort of render super-system could assume that any entity with a MRC also has a TC (and thus handle the situation appropriately) or, if it doesn't have a TC, deal with it appropriately.

I should note that my example comes from my first foray into the realm of component-system design, but it also rose from the frustration of trying to handle inter-system dependencies (at one point, at least half of my systems were either dependent on or dependents of another system). I don't think this is the best solution, but it is a solution, and I hope that this has been helpful, even just a little bit.

Inspiration from my tea:

"Never wish life were easier. Wish that you were better" -Jim Rohn

soundcloud.com/herwrathmustbedragons

Where to draw the border between SoA or AoS (or in fact a combination of both) depends on the use case. Quoting the entity placement once more:

1.) For so-called static geometry no transform is needed because the vertex positions are already given in world co-ordinates. These entities are renderable but provide no placement component. This may be true for some special things like terrain, too.

2.) All other geometry has a placement component but not necessarily a dynamic one. This placement is obviously most meaningful in world co-ordinates.

3.) Dynamic placement may be done in world co-ordinates or in model co-ordinates, or perhaps in another local system. The dynamic may be driven by an animation or a mechanic (from which forward kinematic is one possibility).

If you now think of the amounts of occurrences you may notice that in general from a performance point of view (where doing nothing is usually the best choice) an AoS is not necessarily good.

Let's assume that there is a sub-system where WorldPlacement components are stored, and these are (well) given in world co-ordinates. Entities belonging to 1. will not be represented therein, so the array is lesser in its length than the renderable entity list is. Sound and graphic rendering can assume that the placements are up to date and ready to use when it comes to rendering at the end of the game loop. Geometry is tagged whether it has a placement or not (you can, if you will, also request for the WorldPlacement and assume that the geometry is in word co-ordinates if you don't get a placement back).

Let's further assume that there is a sub-system that stores LocalPlacement components, expressing a parent-child relationship with forward kinematic behavior. Each entry herein is defined to have such a relation, and it can be safely assumed that the length of the array is noticeable lesser than those of the WorldPlacement. If local placement chains are allowed, it is obviously best to have the LocalPlacement components in an order that guarantees that higher levels in a chain are already processed when a lower level is next.

Let's further assume that there is a sub-system that drives key framed animation. One typical use case is to do a Placement transform with it. It may effect a placement stored in WorldTransform or in LocalTransform. This is not necessarily a problem because animations are manifold and often switched at runtime, so doing some binding is probably needed anyway. And because from this point of view a placement is a placement regardless whether it is store in the WorldPlacement or else in the LocalPlacement sub-system.

With these assumptions, the first update runs on the animation sub-system. It does its job and alters some world and some local placements. The next update runs on the LocalPlacement sub-system (which is naturally more constrained than an animation). After this point all WorldPlacement components are up to date.

As can be seen, rendering, placement in the world, local placement, and mechanisms to alter placements in general not only can be separated but should be separated because they occur in different frequencies and at different update times. The only match here is that LocalPlacement can combine storage and transform in a simple way. Notice that the WorldPlacement sub-system is passive by itself. It stores the placements and provides a central access point, but it never changes them.

This topic is closed to new replies.

Advertisement