Is The "entity" Of Ecs Really Necessary?

Started by
48 comments, last by Shannon Barber 7 years, 8 months ago

I doesn't make sense to me to create a system just for the sake of storing an array of components.


1. Why not? Where would you put the components? Something has to own them. Or are you suggesting that they go in whatever glue code owns the systems?
2. If you're creating this system by extracting some state out of an old system, presumably there was some behaviour related to that state provided by the old system. Why wouldn't you move some of that over?

How do you manage adding/removing components from entities at runtime? Does whomever needs to do that require a dependency on the particular system that owns the component?


That sounds like a job for the glue code that owns the systems.
Advertisement
I consider an entity a type of asset along with textures, shaders, audio clips, scenes, etc.

For each asset type I have an object with an array of the assets and functions for loading/saving the assets. The object which contains the entities also contains all of the component vectors. Components are serialized within entity files.

All of these container objects live in a container called a plugin. A plugin can then be loaded/saved enabled and disabled - a plugin file is nothing but a text file with file names of assets.

I like having the components bundled up with the entities like this because i have found it useful for being able to prototype games quickly - since a system can potenttially use any component array you can quickly create a system that acts as a God system to get very basic things working right. Then you can logically break this down to more maintainable code and separate functions - add a new component type if needed.
1. Why not? Where would you put the components? Something has to own them. Or are you suggesting that they go in whatever glue code owns the systems? 2. If you're creating this system by extracting some state out of an old system, presumably there was some behaviour related to that state provided by the old system. Why wouldn't you move some of that over?

1. I want to be able to add/remove components in a consistent way no matter what the component. Having to locate the a system that happens to provide storage for them so that you can add/delete a component seems onerous. I want to just say "entity.AddComponent<Awesome>", or "world.AddComponentToEntity<Awesome>(entity)". Why should code need to know which system happens to store the component?

2. I was responding to your statement " If multiple systems need access to the same data, pull the data out into its own system and then those multiple systems can reference that one", which I interpreted creating a system whose sole purpose is to store a type of component.

That sounds like a job for the glue code that owns the systems.

If I understand you correctly, you're saying that if you have code that wants to add/remove components, it needs to access the glue code (e.g. some World/context object) to do this, and that glue code knows which systems to forward this request to? That's fundamentally the same thing as I'm suggesting, except that by moving all component storage into the "world" object, it simplifies things IMO. For instance, testing is made easier, because you can test a system in isolation. If I'm testing the "StarvationSystem", for instance, I don't need to fire up a HealthSystem and an InventorySystem just to do so.

Fundamentally, the component storage should just be an array of structs with perhaps some common utility functionality associated with it. I don't see any reason to gate access to that behind an additional dependency.

1. I want to be able to add/remove components in a consistent way no matter what the component. Having to locate the a system that happens to provide storage for them so that you can add/delete a component seems onerous. I want to just say "entity.AddComponent<Awesome>", or "world.AddComponentToEntity<Awesome>(entity)".


That seems very Unity-ish, which isn't at all what I have in mind for this sort of architecture. If you really don't want the system adding the component to know about the system that owns the component, you could queue up an event with the entity ID and the component type you want, then have the glue code process it, but I don't totally see the point of this, because you can always go systems.AwesomeComponentSystem.add(entity) or awesomeComponentSystem.add(entity) if you really want to, and not only is that about the same amount of code, but passing the world or the awesome component system makes the dependency explicit. That's a good thing. That one system can create components of a type that it doesn't manage (ie. that live on another system) is a dependency, no matter how much you decouple the two systems from each other.

In general, I want to be able to look at the header file for a system and know what it does. If it creates new components of a specific type, I'd like to be able to see that right up front without having to dig around in the code for it.

Why should code need to know which system happens to store the component?


Because generally the behaviour of the component goes on the system, not the component itself. By definition if a system knows about a component type, it will always know about that component's system, too, because it needs to know about the system to interact with the component. The component isn't really a "thing" in and of itself. It's just a piece of data that lives in a system. All the communication is between systems.

For instance, testing is made easier, because you can test a system in isolation. If I'm testing the "StarvationSystem", for instance, I don't need to fire up a HealthSystem and an InventorySystem just to do so.


What is the difference between queueing up a bunch of components in a list and passing it to a system, and queuing up a bunch of objects in the system that manages the component's state and passing the second to the first? Besides, most of the time the first system is going to interact with the components through the system that owns them, anyway, so you need to spin up the second system and pass it in regardless because it has the behaviour of the components.

Don't forget that with a system like this, the "components" themselves may not actually be monolithic objects. They may be separated out into structures of arrays for batch processing on the system that owns them. In the same way that "there is no game object", in a data-oriented system there may not be "components", either. My game's combat system, for instance, stores character attack timer state in a separate data structure from the hitboxes, and which characters are attacking is stored in. Here's the state that system stores:
TypedInstancePool<ObjectType::Character, CharacterCombatState> m_combatState;
TypedInstancePool<ObjectType::Character, sf::FloatRect> m_hitboxes;
std::vector<TypedHandle> m_lungingCharacters;
std::vector<TypedHandle> m_parryingCharacters;
std::vector<Attack> m_attackQueue;
std::vector<Block> m_activeBlockZones;
Again, though, YMMV on whether this (or anything I'm talking about) is really a pure ECS. As already noted, ECS is prone to "no true scotsman"-type arguments.

because you can always go systems.AwesomeComponentSystem.add(entity) or awesomeComponentSystem.add(entity) if you really want to. That's about the same amount of code, and explicitly passing the world or the awesome component system makes the dependency explicit.

You're causing one system to have a needless dependency on the logic a particular other system performs, whereas really it only needs a dependency on the data (component). Your TransformSystem may be responsible for managing visual hierarchy (something like calculating absolute transforms from local transforms + parent transforms, say), but all your AudioSystem cares about is that an entity exists at a position - it just needs a Transform. It shouldn't need to access TransformSystem, which has functionality it can do without.

You can have a dependency on a particular set of data being present, without having dependency on code that manipulates that data in some fashion.

Besides, most of the time the first system is going to interact with the components through the system that owns them, anyway, so you need to spin up the second system and pass it in regardless because it has the behaviour of the components

Well, in that case there is a legitimate system-to-system dependency, so none of my arguments apply.

They may be separated out into structures of arrays for batch processing on the system that owns them. In the same way that "there is no game object", in a data-oriented system there may not be "components", either. My game's combat system, for instance, stores character attack timer state in a separate data structure from the hitboxes, and which characters are attacking is stored in

That seems like a valid argument. From what I'm understanding, you have component that logically makes sense to group together ("Combat"), but you want to store it in a custom manner (structure of arrays), so that the main system that uses it can iterate through it most efficiently.

You can have a dependency on a particular set of data being present, without having dependency on code that manipulates that data in some fashion.


Good point, touche.

From what I'm understanding, you have component that logically makes sense to group together ("Combat"), but you want to store it in a custom manner (structure of arrays), so that the main system that uses it can iterate through it most efficiently.


Exactly, yes. Sometimes the data another system needs isn't even stored in a way that's simple for the other system to access, so the system that owns the component in question is responsible for providing access to it, as well. This also lets the owner system do things like enforce that the component is only modified within the owner system, cutting down on the number of places where state changes can happen. Using systems to access data even when that isn't really needed buys you consistency, though.

In the ECS implementation I've used, I would store core combat information (hitpoints, abilities, etc...) in a component that is "globally" accessible. But I also allow systems to store their own private per-entity state (I use this to interface between the physics library I use, for instance) - I just don't call them components because they aren't accessible by other systems (except indirectly though the system holds that state) :P . That's probably where I'd put things like the current attack timer state and such.

You still dont need to store the data within the system and have the system be the interface to the data - if the data is in another world type of object that all systems have access to the systems which need to process that component data would know how to do that.

I know i keep nosing my way in to the conversation - sorry about that this is just a very interesting topic

I think this is all proof that there are several valid ways people can choose to slice this up, each with some good justifications, but each also working in ways that other people will find counterintuitive.

Personally, I'm sticking to Components-Lite - i.e. the Unity method of building GameObjects out of encapsulated components that hold state and logic. That's the way that makes most sense to me and which works best with how I develop, understand, and debug code.

You still have an entity abstraction. You've just distilled it to a single number which is a multi-variant handle.
This optimizes memory consumption over easy-of-use, which is meh, but may end up more cache-friendly (than alternatives) depending on how you process updates.

- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

This topic is closed to new replies.

Advertisement