Atomic components in ECS

Started by
9 comments, last by wintertime 9 years, 4 months ago

In an ECS architecture imagine a very simple component representing the position of an entity on a game board. It only requires two integers: X and Y.

Possible approaches:

1) Plain component


class Position
{
    public int X;
    public int Y;
}

Very immediate. What bothers me is that I end up having multiple components like this (X and Y only) to represent other positions on the board, say a target location.

2) Component using a custom type


class Position
{
    public Point Coords;
}

Same as above but it has the advantage of being able to reuse the features of the Point type (math operators come to mind). Actually the component IS the position so having to store its coords in a separate object/value seems awkard. Nonetheless I can't see any real drawbacks here, I think this is often better than #1.

3) Using a Point value as the component itself

Makes more sense imho. The serious drawback is that components cannot be referenced by their type anymore, they would need to be named (of course, that would be the same if we reused the Position class).


var entity = /* ... */

var myPosition = entity.GetComponent<Point>("Position");
var targetPosition = entity.GetComponent<Point>("Target");
var homePosition = entity.GetComponent<Point>("Home");

Naming is necessary because multiple Point components exist.

***

Actually I'm using solution #2. What's the best (usable in most cases) way of handling such situatuons in your opinion?

Advertisement

I would go with the second option. The first option is fine although if you find yourself repeating yourself often, in terms of the declaration of the members or the functions operating on them, that's a classic scenario for when you may want to move to a more encapsulated representation (the Point structure).

The third option seems downright ridiculous and reeks of the over-granularized, over-zealous "make everything a component zomg!" attitude that often comes with trendy overthought designs like the "component-based entity system."

I would go with the second option. The first option is fine although if you find yourself repeating yourself often, in terms of the declaration of the members or the functions operating on them, that's a classic scenario for when you may want to move to a more encapsulated representation (the Point structure).

The third option seems downright ridiculous and reeks of the over-granularized, over-zealous "make everything a component zomg!" attitude that often comes with trendy overthought designs like the "component-based entity system."

But how would you address having multiple components of the same type representing different things?

Example:


class Position
{
    public Point Coords;
}

class TargetPosition
{
    public Point Coords;
}
But how would you address having multiple components of the same type representing different things?

Ideally, don't. Generally, I find that such a need suggests that one is modelling components at too granular a resolution, resulting in a system where you have basically replaced member variables with components, introducing rather significant complexity and overhead for no particularly good reason.

If you must, however, there are two points to consider:

  • Having multiple instances of components with the same type doesn't always mean you have to be able to individually query for each one. Typically, a well-performing component-based entity-system will use an outboard approach where so-called "systems" are the authoritative containers for all component instances and process/update them all in bulk (rather than the more naive, cache-inefficient 'for each entity, for each component therein, update' approach). In this system it doesn't necessarily matter if an entity has two components of the same type since they are all still processed.
  • If you really, really have to have multiple instances and refer to them directly (for example, you have to call GetComponent(...) to read a bunch of data from it inside some script somewhere) you have no real choice but to support discriminators of some form. A name is the typical way to do this (but ideally you'll hash this name or somethign to avoid constant string-based searches for components, which can have a noticeable impact on your performance).

The thing is, if you're at the point where you need to have named discriminators for components, what do you have? What, precisely, is the different between having two Point components (one called Position, the other called TargetPosition) and having a single structure (perhaps called TrackingComponent) with Position and TargetPosition members? When you get to this level of granularity you have usually started to replace member variables with components, which is not super efficient. It would almost certainly(*) be better to make the component a broader package of data and/or behavior (depending on what your components model).

(*) I will concede that there are scenarios where this can be acceptable, but they are reliant on specifics that you have not provided in your discussion thus far.

But how would you address having multiple components of the same type representing different things?

Ideally, don't. Generally, I find that such a need suggests that one is modelling components at too granular a resolution, resulting in a system where you have basically replaced member variables with components, introducing rather significant complexity and overhead for no particularly good reason.

If you must, however, there are two points to consider:

  • Having multiple instances of components with the same type doesn't always mean you have to be able to individually query for each one. Typically, a well-performing component-based entity-system will use an outboard approach where so-called "systems" are the authoritative containers for all component instances and process/update them all in bulk (rather than the more naive, cache-inefficient 'for each entity, for each component therein, update' approach). In this system it doesn't necessarily matter if an entity has two components of the same type since they are all still processed.
  • If you really, really have to have multiple instances and refer to them directly (for example, you have to call GetComponent(...) to read a bunch of data from it inside some script somewhere) you have no real choice but to support discriminators of some form. A name is the typical way to do this (but ideally you'll hash this name or somethign to avoid constant string-based searches for components, which can have a noticeable impact on your performance).

The thing is, if you're at the point where you need to have named discriminators for components, what do you have? What, precisely, is the different between having two Point components (one called Position, the other called TargetPosition) and having a single structure (perhaps called TrackingComponent) with Position and TargetPosition members? When you get to this level of granularity you have usually started to replace member variables with components, which is not super efficient. It would almost certainly(*) be better to make the component a broader package of data and/or behavior (depending on what your components model).

(*) I will concede that there are scenarios where this can be acceptable, but they are reliant on specifics that you have not provided in your discussion thus far.

I do agree, I'm using components as if they were object properties replacement which is clearly too granularized.

I suppose that every case needs specific design where one has to decide at which level of detail components have to be broken down.

It is not uncommon to add a semantic to variables of the same type, perhaps by using a naming scheme. It is done for example for vertex attributes ;) and it is done on the basis of daily programming anyway.

However, as Josh has mentioned, it should be considered how granular the components should be. For example, in a ECS you have a PlacementComponent which contains an instance of Placement, denoting the position and orientation of the entity in the world. The method is in fact comparable to #2 in the OP, although it will probably be more efficient to have any of these Placement instances managed within a system (e.g. something called the SpatialServices) so that PlacementComponent is reduced to a Placement instance during installation of the entity.

However, now think that entity #2 should be coupled by forward kinematics to entity #1. This means 3 things: A link from the 2nd entity to the 1st one, a local Placement for the 2nd entity, and a piece of code that implements forward kinematics based on the aforementioned data. Notice that there is another Placement involved. But instead of adding another PlacementComponent and hence introducing the semantics problem, you add a ParentingComponent which has a reference to the parent entity and a Placement as members, as well as the code (directly, or indirectly if done by a system).

The above way is the one I've chosen. As soon as there are 2 components of the same type in a single entity, I need to have a mechanism to either select one of them or to guarantee that only one of them is active at a time, or else to blend them in a feasible way.

Honestly, just steer away from ECS. There are far easier and simpler component-based designs that do more to help you make a quality game more quickly/cheaply.

The micro-facet approach you find all over the place is because people are blindly copying ECS architectures without any freaking clue _why_ they were designed the way they are. What people call ECS today originated in a performance-sensitive Java world. Java is a pretty bad language for high-performance anything; Java essentially requires you to use the micro-aspect architecture in order to achieve the same kind of performance you can get in C++ or C# much more easily.

Aim for fatter, richer components. A component should only handle one concern, but position is not a "concern." It's just a property. MyCurrentPosition is a property of a Transform component. MyTargetPosition is a property of a pathing/steering component. And so on. A position on its own is semantically meaningless, as you're discovering.

I wouldn't even worry about Systems vs Components and would just skip Systems entirely; put logic right into components. In most cases the Systems part of ECS (and it's not an ECS if you don't have Systems; that's what the S stands for in ECS) solve problems that you just don't have in a more capable language while Systems can create whole new problems you wouldn't have otherwise (like additional complexity, poorer composition, and less designer-driven behavior). You can use Systems in only those cases where they do help (after you actually identify those cases).

There are oft-touted performance advantages to an ECS architecture even for C++ and C#, but if you don't intimately understand those then you shouldn't use them as justification to go the ECS route. Especially as you can indeed achieve similar performance with simpler component models. That's where you start hearing about "data-oriented design," which is a design methodology, not a hard set of rules or ways to build software.

Keep it simple.

Sean Middleditch – Game Systems Engineer – Join my team!


I wouldn't even worry about Systems vs Components and would just skip Systems entirely; put logic right into components. In most cases the Systems part of ECS (and it's not an ECS if you don't have Systems; that's what the S stands for in ECS) solve problems that you just don't have in a more capable language while Systems can create whole new problems you wouldn't have otherwise (like additional complexity, poorer composition, and less designer-driven behavior). You can use Systems in only those cases where they do help (after you actually identify those cases).

Systems are ideal for logic that needs to reason about multiple objects or components simultaneously. I currently work with an engine where logic exists solely in components, and any time multiple objects or components need to coordinate to arrive at some final behavior or solution it becomes a mess of awkward cross-object/component communication and code flow, because none of them can easily reason about the "big picture". Some of that can be solved by better separation of responsibility between components, but often it just comes down to components that work well on their own, suddenly needing to work with others, and they do so very poorly without higher-level organization.

Systems are ideal for logic that needs to reason about multiple objects or components simultaneously. I currently work with an engine where logic exists solely in components, and any time multiple objects or components need to coordinate to arrive at some final behavior or solution it becomes a mess of awkward cross-object/component communication and code flow, because none of them can easily reason about the "big picture". Some of that can be solved by better separation of responsibility between components, but often it just comes down to components that work well on their own, suddenly needing to work with others, and they do so very poorly without higher-level organization.


In my experience, these are systems that live outside of components. Physics, graphics, AI, etc. are not dependent on components, they don't provide components, and they can't provide "systems" in the ECS sense. The components serve as glue that ensure the other modules create their own data entries and associate them with the correct game object and initialize the data properly and that's about it. You're pretty much forced to do this with things like physics in any significant game anyway (most games used third-party physics libraries) but it's just as appropriate for your home-grown graphics and AI libraries as well.

This offers a number of advantages: components can easily create multiple entries in other modules when appropriate (very useful for both physics and graphics), helper or game logic code can create entries that aren't associated with specific game objects at all (useful for HUDs and UI code in particular), and automated testing of the modules becomes far easier.

In any event, you can use an ECS' Systems-like approach where appropriate even in a simpler component architecture. Add them when you need them. Don't indiscriminately force them on every single use of components.

Sean Middleditch – Game Systems Engineer – Join my team!


In my experience, these are systems that live outside of components. Physics, graphics, AI, etc. are not dependent on components, they don't provide components, and they can't provide "systems" in the ECS sense. The components serve as glue that ensure the other modules create their own data entries and associate them with the correct game object and initialize the data properly and that's about it. You're pretty much forced to do this with things like physics in any significant game anyway (most games used third-party physics libraries) but it's just as appropriate for your home-grown graphics and AI libraries as well.

This offers a number of advantages: components can easily create multiple entries in other modules when appropriate (very useful for both physics and graphics), helper or game logic code can create entries that aren't associated with specific game objects at all (useful for HUDs and UI code in particular), and automated testing of the modules becomes far easier.

I would consider those examples of lower-level systems/libraries that wouldn't depend on any particular high-level game object architecture, let alone ECS. However I've also come across enough logic at said game level that would benefit from "systems" that act on a particular set components. I agree that most components won't need this, but for the few that do, it's a life-saver.

This topic is closed to new replies.

Advertisement