Is The "entity" Of Ecs Really Necessary?

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

I am putting the finishing touches on my engine and I have a question about whether or not is is useful (to the general gamedev population) to have the "entity" abstraction as part of the entity component system.

In the current version of my system, the entity is just an ID, components are stored in packed arrays, and components can be a member of more than one entity. The "engine" is just a collection of arrays for the different component types. All logic is implemented in systems that can each modify the contents of the engine in their update() method. I have a prefab system similar to Unity that allows instancing of premade collections of resources. Each prefab instance gets associated with an entity ID in the engine when it is created.

I am thinking about removing the entity concept because it would greatly simplify the bookkeeping and make the overall architecture much simpler. In that case, there wouldn't be any explicit mechanism for grouping components together - the engine would just be a big unordered collection of resources that are part of the current simulation. This seems like it would work for my use cases but maybe I am not foreseeing all common needs (since I'm mostly a low level tech developer). I can envision a scenario where a user script might need to access sibling components of the same prefab instance, is this a common need? Or should I just require the user to explicitly link components via resource references so that the script could later access them?

Advertisement

You're basically comparing regular component based programming that's been around for decades (and will continue to be) vs a fad pattern that declares that one level of composition is enough for anyone, and often the stranger assertion that having one member of a certain type is enough for anyone...

So no, you don't need common component ID's as a way to identify sibling components.

Last time I implemented an "entity/component" system, Entity was itself a Component, which meant that an entity might have another entity as a child component, and an entity could be a sibling of a component... Components also were not aware of their parent entity - so in order to search for a sibling, the current function would need to be informed of it's parent entity via an argument. There's no standard / right way to make an entity system for a game.

In OOP there's no standard way to find a sibling object, or even a object's parent object, and everyone deals with that fine. I don't think I've ever in C++ thought to myself, "I wish I could write SiblingType& sibling = *object_that_has_this_as_a_member(this)->get_member_by_type<SiblingType>().begin()" :lol:

In case you can't tell by my tone, I don't think that strict ECS is a good general purpose pattern at all, and only makes sense in specific domains, such as a system for managing spell effects on characters, etc... :wink: so balance my pessimism out with some people who actually like ECS!

Binding different components together using an entity is almost the entire point of the system. It's about having different data and functionalities, and bundling that together in a way with minimal memory, runtime and abstraction overhead (the opposite of which would be huge inheritance trees).

In practical terms it's because too much code will need it. Have an animation system and a combat system? Well, if my character is hit (which inevitably ends up modifying that combat component somehow, which probably contains all the stats like HP, armor, damage, etc.) I'd like to use the animation system to set up a response for that particular character, which will in the end modify that particular animation component. Get the combat component and animation component using the entity... ez pz. Stuff like that.

If you're viewing this from a low-level perspective, it's easy to get the impression that you're mostly dealing with independent systems. A physics engine, that takes care of all its own stuff, a renderer that doesn't care about what happens on the outside, why would an audio system require outside components ever?? etc...

But that's not what ECS is really for (big IMO). It's about that part of the code above that which uses all these systems and bundles them together to make a game with it.

edit: Remember that a big part of the complexity of ECS is also requirements like being able to attach and detach components at runtime, being able to do data-driven stuff, etc... So in the end those users of your component system will just end up with similarly complex abstraction layers which tie this stuff together at runtime.

It's entirely up to you. I personally don't like the "entities as an ID" approach because I think that de-emphasises the composition aspect. You ask whether accessing sibling components is a common need and in my games that use components this is absolutely essential. But other people engineer around that in other ways that suit them.

One reason why you might not want to force components to only communicate via explicit connections is that it makes hot-swapping of components a lot harder. Going via the entity allows that extra level of indirection.

(I also wouldn't have a component as part of multiple entities. Again, that seems to break the composition aspect. Something shared between entities is more of a 'service' to me.)

In the current version of my system, the entity is just an ID, components are stored in packed arrays, and components can be a member of more than one entity.

What's the purpose of sharing components among entities? How do you track lifetime of the component?

I can envision a scenario where a user script might need to access sibling components of the same prefab instance, is this a common need?

This makes me think you've designed your architecture before having a problem (game) to solve with it :P

As others have mentioned, it's fairly common to have code (a system) that requires multiple components from an entity. Although you might be writing something more like "for this entity, give me the A and D and J components", rather than "for this A component, give me the D and J components on the same entity".

If the compnents arent associated in some way, how would you know which transform component to get the position from when rendering the mesh component (or render component or whatever it may be called)

Either you would need to combine EVERYTHING needed to perform some action within a system in the component that system operates on, or store a list of components within the component to see which ones are associated, or use a common id between related components (and so the entity id is born)

For example - many systems will probably need the position information for things in the game in order to do anything useful - would you store a copy of this in each component? If so, then why have "components" at all?

Thanks for all of the input. It's clear that I still need some way to group related components, and that hierarchies of entities should be allowed. I think I will use Hodgman's idea of treating entities as just another component type. This allows me to get rid of the cumbersome entity-as-an-ID approach, and also allows multiple entity types and hierarchies of entities to exist. It also fits well into my existing prefab system.

Then the systems that operate on entities can go engine.getComponents<MyEntityType>() to access the current entities of a given type in the engine, the same as any other component type. The entity is just a list of pointers to components in this case, so it is easy to access child components with a certain type or name.

If the compnents arent associated in some way, how would you know which transform component to get the position from when rendering the mesh component (or render component or whatever it may be called)

For example - many systems will probably need the position information for things in the game in order to do anything useful - would you store a copy of this in each component? If so, then why have "components" at all?

For this particular case, I do indeed store the transforms separately for graphics/physics/sound objects. Communication between the components is implemented using a data flow graph that is processed by the engine before and after each system update. Data flow connections can have various types (trajectories (transform+derivative), tensors (scalar/vector/matrix values), sound, image, etc.), and can be updated at a user-defined rate (e.g. 60Hz, 1Hz, or only at build time). For example, to control a graphics object using the transform from a physics object, the "transform" output of the physics object is connected to the "transform" input of the graphics object. After the physics simulation update, the physics object writes to the connection's local transform storage, then before graphics rendering the graphics object reads the transform.

What's the purpose of sharing components among entities? How do you track lifetime of the component?

I don't have a good answer for the first question. I guess I don't want to place unnecessary restrictions on how the components can be used.

Components are stored in an object called a ResourceSet that is a container of arbitrary resource/component types and is also the in-memory representation of my engine's file format. Components are loaded from disk into a ResourceSet, and can then be instantiated within an engine simulation context. The engine holds raw pointers to the components and doesn't care about the object lifetimes, components can be added directly to the engine, or a copy can be created (stored in another ResourceSet that contains only runtime instantiated data). If a component is shared among multiple entities, its reference count within the engine tracks the number of entities using the component. When the reference count goes to zero, the component is removed from the engine (but not deallocated). The object instancing system is higher-level and built on top of the engine and manages the lifetime of the ResourceSet(s).

If the compnents arent associated in some way, how would you know which transform component to get the position from when rendering the mesh component (or render component or whatever it may be called)

The ECS pattern is about having implicit (magic) connections between components. A mesh component can fetch data from a transform component without explicitly being told to, via something like: this->parent->child(typeof(Transform)) / this->sibling(typeof(Transform)).
The alternative to this is having explicit connections between components. When initializing/creating the parent entity, it explicitly plugs child components into each other, via something like: this->mesh->transform = this->transform.

If I had a data format for defining entity types, the only difference between these two systems would be:
Implicit linking: myEntity = { Transform(0,0,0), Mesh('foo.model') }
Explicit linking: myEntity = { transform = Transform(0,0,0), mesh = Mesh('foo.model', transform) }

IMHO, implicit coupling is evil, which is why ECS comes off as an anti-pattern to me, sitting alongside the singleton :wink:

It's about having different data and functionalities, and bundling that together in a way with minimal memory, runtime and abstraction overhead (the opposite of which would be huge inheritance trees).

Comparing ECS and deep-inheritance is kind of a straw-man argument, because deep-inheritance goes against the rules of OOP as well. Jumping from "deep-inheritance based OOP" to "ECS based composition" is just swapping one extreme for another. IMHO anyone making this leap should stop and actually learn OOP first before throwing the baby out with the bathwater.

This topic is closed to new replies.

Advertisement