Entity Component System: Component

2017-03-18

The second pillar of the ECS model is the Component. A component is some data associated with an Entity.

Coming to a precise definition of component is not easy. ECS literature does not clearly define a single component and public ECS examples differ greatly in their interpretation. We must start somewhere. What’s the smallest useful component?

Before we further define what a component is let’s define what a component isn’t. A component is not a resource or an asset such as a texture, mp3 track, collision model, or shader. These are examples of shared resources; components have exclusive ownership bound to an entity. These resources are also often tied to external libraries which will not conform to our ECS requirements. Our texture could be a Vulkan image, our music track could be stored in a WWise bank, and our collision model could be a Havok shape.

A component can reference a resource.

In fact a component with no state, empty, zero bytes, could by itself be useful. Its mere existence can signal some trait or behavior to a system. An empty component cannot be useful unless we can distinguish what type of component it is. The ability to query particular entity to see if it has a component of a certain type would allow this behavior. This feature also necessitates there be a relationship between an entity and a component. This leads to our first conjecture in component design.

A component must bind to an entity and be distinguishable by type

Since every component, including one with no other state, needs this information it is possible to lift this functionality out of the component. That is to say we can manage entity component relationships at a higher level than individual component types.

One step above an empty component is a component with a single piece of state. Take for example a component that stores the ‘x’ component of the position of an entity. Unless you are making a 1d game it is unlikely that any entity with an ‘x’ component would not also have at least a ‘y’ and likely ‘z’ component as well. There is some baseline overhead in creating managing and accessing components as shown above. Since these components will always exist together we can reduce that overhead by combining them into a single component. In fact this makes for our second conjecture in component design.

Consolidate mutually required fields in a single component.

There is however a potential benefit to storing ‘x’ by itself that should not be overlooked. If ‘x’ components are stored in contiguous memory we can use SIMD operations to churn through 4 components at a time for certain operations. A particle system is a great example of this being useful in practice. There are other examples where padding the position with a 4th component making a homogeneous would be beneficial in allowing all components of a position to be processed with a single SIMD operation. And counterexamples where storing just the 3 xyz components contiguously will give the best cache utilization. There is no representation that is clearly best in all times and all places.

A position component is a viable minimal component but there is other data that is almost always used along with a position. An affine transform component would consist of a position rotation and scale. These are almost always used together and can be represented as a single 4x4 or 3x4 matrix. It’s certainly possible to keep them as separate components but there is a complexity and management overhead for doing so. Imagine transforming a point into an entities local space. You would need separate code paths for every possible combination of components. There is a strong argument here for potentially wasting a small amount of memory in order to reduce code size and complexity. The wasted size of storing 3 floats for an unused (all ones) scale may even be fully offset by removing the bookkeeping necessary for a separate scale component.

Sticking with our transform component most games have some form of Scene Graph that lets you make parent child relationship between entities. When a parent moves all of its descendants will move an equal amount. Most systems don’t actually care about these relationships. The rendering system doesn’t care that sword is a child of the hand. All it cares about is the final world space position of the sword. The IK system might care about this relationship, if your sword hits a wall the game may want to push back on the hand which will in turn push back on the arm. Walking the tree and computing the world transform by concatenating all child to parent transforms can expensive. We shouldn’t do this every time we need to know the world position of an entity. We can solve this with 2 transform components, local_transform and world_transform or we can solve this by storing 2 both sets of data inside a single transform component. This decision may rest on usage patterns, code reusability, and data-oriented design. We’ll do a more thorough treatment of hierarchies in a future post

It may be useful to think of an ECS as a relational database. What is the best analog for a component? I will disagree with some sources and say a component is best modeled by a table where columns are attributes of the component and rows are instances of the component. The primary key for each component table would be the entity id that owns the component. The set of all rows across all component tables with the same entity id is the state of that entity. An empty component is simply a table with no columns except for the entity id. There are limits to this metaphor and you won’t find won’t many people recommending using a SQL database directly for your ECS.

There are man more component problems that must be solved before we can write an ECS. Future posts will cover topics including:

  • Can you have more than one component of a type on an entity?
  • Data-oriented component design
  • Component allocation and storage
  • Component initialization
  • Component serialization and replication
  • How to bind components to their owner
  • Component references
  • Component queries and iteration
  • Component attribute access
  • How to update Component state
  • Singleton state
  • Shared components, flyweight for many copies of same state

Finally here are like to previous ECS posts: