Using ECS for complex engine and game logic

Started by
24 comments, last by Tom Sloper 5 months ago

I recently watched a talk by Bobby Anguelov on Entity/Object Models, which goes over past approaches to game object management (OOP, object-component model) and discusses their benefits and drawbacks. He also discusses ECS and praises how it fixes certain problems, but he also brings up several concerns, which he feels are not adequately addressed by this new paradigm. I listed what I think are the most important points below:

  1. “Iterating on components” sounds nice, but in a real game project, how many processes will fit neatly into this pattern? It makes perfect sense in a trivial example ("Boid simulators" as Bobby puts it), but most games are not a simple “update position using velocity and delta time” loop, and will have a lot of complex logic, edge cases, one-to-many connections, etc.
  2. Is “performance” really the most important issue? ECS libraries tend to place a lot of emphasis on making their component access insanely fast, often via complex storage logic. But again, realistically, is this going to be the bottleneck? More often than not, game code in particular will be messy by necessity, and a good system ensures that developers still have an easy time with it. ECS makes a lot of things nicer, but a lot of other things seem to become needlessly convoluted.
  3. How to “componentize” complex logic? Bobby shows a few examples where you want certain entities to have special behavior despite having the same components as others. Most ECS libraries will solve this using “tags”, which then leads to complexities with branching the logic in the systems (if/else blocks, filtering, etc.) which goes against the purported “efficiency” of ECS
  4. How to handle hierarchies, one-to-many relationships, etc? Most ECS libraries will enforce 1:1 ownership between an Entity and any component type, allowing for 1:N is rare, especially when trying to enforce the “performant arrays” mentioned above.
  5. Component inheritance? Is it really a good idea to just throw out OOP altogether?

To be clear, I'm not here to bash ECS, nor do I agree with everything Bobby says. I think ECS is a wonderful paradigm, and composition seems like the most sensible choice for game development. The main problem, in my opinon, is that there is a serious shortage of documentation, case studies, and best practices for ECS. Most of what you can find are extremely basic introductory pieces (usually involving the simple “transform + velocity” example mentioned above), or they are focused purely on the low-level engineering aspects, which in my opinion places way too much emphasis on “performance” and not on the “Big Picture”.

Generally speaking, I have yet to find material on ECS that actually dives deeper into how you can use it in a more complex scenario, with multiple larger modules interacting with each other, and entities/components being used to run multiple processes across a frame. In isolation, one can of course think of a way to make any particular process work via “iterating on components”, but it gets a lot less intuitive when one has to think of the entire game simulation.

Just to name a few things:

  1. How to design components so that they can be reused across multiple systems? Can they even be reused? Should “Transform” be the same one used for rendering and physics?
  2. Should a single ECS be responsible for the entire game? Or could it even be multiple different “ECS worlds” dedicated to a specific part of the game (gameplay, rendering, etc.), with entities from different worlds linking to each other?
  3. How to manage “key” entities (e.g the player, the AI Director), i.e those who are few in number but may have by far the most components per-entity. Is it even worth adding them to the ECS?
  4. How to run processes that require random access, e.g collision detection? Should these even be part of the ECS? Or should it be delegated to another system, with a cached result that the entities can iterate on afterward?
  5. How should rendering interact with ECS? What kind of data should be stored in the components, as opposed to what might be better to store in dedicated structures in the render module?

While these may seem like banal questions, there is actually precious little information on any of this when it comes to ECS usage. And yes, the trivial answer to this is “well it depends on the game”, but that doesn't mean we can't learn from past experiences.

Advertisement

yah-nosh said:
Is “performance” really the most important issue? ECS libraries tend to place a lot of emphasis on making their component access insanely fast, often via complex storage logic. But again, realistically, is this going to be the bottleneck?

I agree with this point quite a bit. Any time you have enough entities/components to make the ECS the bottleneck (100k or so), you will have far bigger performance problems elsewhere in the engine, e.g. in physics or rendering. Worrying about cache misses in the ECS is not likely to bear significant performance wins except in artificial examples.

yah-nosh said:
How to design components so that they can be reused across multiple systems? Can they even be reused? Should “Transform” be the same one used for rendering and physics?

This can be done by separating the storage and lifetime of components from the systems. Instead of tight coupling, you have a ComponentStorage<T> object or something like that to create and destroy components in a memory-efficient way (e.g. as slots in a preallocated array). The systems don't necessarily need to know about the component storage. In my engine systems can access any component of any type. Pointers to the active components, organized by component type, are stored in the engine context (which also has a list of systems). Systems then operate on the components in the engine of the type(s) they need. I don't make any distinction between “entity” and component, entity is just another type of component that can have child components. Most things in the world are modeled as a SceneObject type, which contains a transform and list of child components (as well as child SceneObjects).

yah-nosh said:
Should a single ECS be responsible for the entire game? Or could it even be multiple different “ECS worlds” dedicated to a specific part of the game (gameplay, rendering, etc.), with entities from different worlds linking to each other?

It could. It can also be useful to have the ability to have multiple instances of the engine active at the same time. I use this ability in my editor for play mode. However I wouldn't have a separate ECS for rendering or physics, ECS is not well suited to those tasks and it will just add extra complexity. The main place where ECS is useful is in high-level gameplay code. Don't try to cram other stuff into ECS, it will probably lead to bad performance because ECS isn't suited to the details of those tasks.

yah-nosh said:
How to manage “key” entities (e.g the player, the AI Director), i.e those who are few in number but may have by far the most components per-entity. Is it even worth adding them to the ECS?

I would have systems that manages those things. There can be a PlayerSystem to manage the logic regarding the player, or AIManagerSystem system to control all the AI agents in the scene. Those systems can then operate on the components that are active in the engine. You can have a PlayerComponent to store the player-related state that is operated on by the PlayerSystem.

yah-nosh said:
How to run processes that require random access, e.g collision detection? Should these even be part of the ECS? Or should it be delegated to another system, with a cached result that the entities can iterate on afterward?

Global things like physics, collision detection, and rendering should be implemented as systems. Probably you will want to wrap an existing physics library to do these tasks. In that case, you can have the PhysicsSystem be the bridge between physics components (e.g. RigidBody, CollisionShape) and the internal implementation of the physics library. For example, in my engine, I have the SceneObject type which is the bridge between all the main systems (graphics, physics, sound). The physics system reads the transform from the SceneObjects (for kinematic objects only) before stepping the simulation, then writes the updated transform back for the non-kinematic objects. The graphics system updates after the physics system, where it reads the transforms from the SceneObjects and copies that into its internal data structures which are used for rendering. This way you can communicate information indirectly between system using components.

yah-nosh said:
How should rendering interact with ECS? What kind of data should be stored in the components, as opposed to what might be better to store in dedicated structures in the render module?

SceneObject has pointer(s) to things that should be rendered as children of the object. Generally you should only store a weak reference or pointer to the rendering data (meshes, textures, materials) in the ECS, so that you can share things between components. Like I said earlier, rendering should be a system that uses the current ECS state to control what is rendered where. Most of the implementation of rendering and physics systems should be independent of ECS. This results in the most optimized solutions for each individual system, rather than being hamstrung by a shared ECS architecture.

Just adding here, to crunch past replies about ECS (there was an ECS topic boom here) to you while you wait for the new ones. Have you tried to search “ECS” in this forum? https://gamedev.net/search/?q=ECS&c=all

I hope you can look at them as additional replies and find anything fit to your concerns, typically performance, "ECS for everything”, and relationship (this one is most often asked, and always answered differently).

There are blogs too but somehow never showed up in that search, but one that I can remember is this one, about proper OOP (… vs ECS) https://www.gamedev.net/blogs/entry/2265481-oop-is-dead-long-live-oop/

@arly I have indeed tried to search, but the results came up in somewhat random order, mostly from several years ago, and they tended to just take for granted that ECS is the way to go, with no specifics on how one should implement a game with it (and thus address the things I mentioned in my post).

The blog you linked is a really good read though, I'll make sure to dig into that one. Thanks!

@Aressera Thank you, I can definitely work with that.

This is slightly off-topic, but I figured it might still help to mention: regarding how things like renderer or physics systems should be implemented, independently from ECS, are there any good case studies you would recommend? Arguably this falls even more into the “depends on the game” category, but again, I think any example can be educational.

Ironically, much like how OOP became a fad due to the apparent elegance of interfaces and inheritance, ECS has a similar appeal: it shows a clever structure that seems “too good not to use” ("contiguous arrays" are like magic words to a programmer), and precisely because there is a pattern to use as reference, one is tempted to try and fit said pattern onto everything they can. I have seen numerous examples of people trying to make transform hierarchies work in ECS, just to name one example…

With that said, it seems tempting to use an “ECS-like” approach even for the lower-levels of the render system, since intuitively it works something like this: the system receives a batch of objects that need to be rendered, and objects need different rendering logic depending on their parameters. Some objects need to be culled, some need to be sorted, the pipeline state needs to change, etc. One could of course separate all of these processes into dedicated sub-systems with highly-optimized containers, but that implies a lot of related objects now having to reference each other, meaning tons of indirection, cache misses, etc. It also makes it more complicated to think about what kind of “interface” the higher-level code must interact with, e.g to make sure a high-level logical “render object” will clean up all of its low-level representations once it gets removed.

Not to say ECS is the solution to this, I am just using this as illustration: it appears sensible to go with the same approach as all the banal ECS examples, i.e have entities with the relevant components, and systems can iterate on them (e.g culling iterates on the “BoundingVolume” components) to achieve the different forms of rendering logic that are required. Once any indirection is introduced, that implies more complex bookkeeping is needed, and there is very little (good) material on that, at least that I was able to find.

yah-nosh said:
Not to say ECS is the solution to this, I am just using this as illustration: it appears sensible to go with the same approach as all the banal ECS examples, i.e have entities with the relevant components, and systems can iterate on them (e.g culling iterates on the “BoundingVolume” components) to achieve the different forms of rendering logic that are required. Once any indirection is introduced, that implies more complex bookkeeping is needed, and there is very little (good) material on that, at least that I was able to find.

Maybe there is no good material to find, because ECS (or any other higher level paradigm / philosophy) does not solve the indirection problem. There is no general way to do everything in a given optimal way, even if ‘new’ trends like ECS often appear as general problem solvers, raising such assumptions or hopes.

I think we always have to decide specifically, hoping to pick good compromises with not too many flaws to show up later.
For example, for a game i might think this way:
The physics system will take most CPU time. E.g. because collision detection is a O(N^2) class of problem, while processing game logic over game objects is usually just O(N). Thus i will optimize my related data structures for physics, and i'll also develop the physics system without using the ECS library. There is no need for it, so why should i make it less modular by introducing dependencies which are not needed. So even if i would write the physics system from scratch, it's integration into the game engine would not differ much from integrating a 3rd party physics engine.

For the graphics system it's the same arguments. And it goes on for audio or network as well.

The ECS would then be used mostly to implement gameplay and tools code. E.g. a character controller would use the ECS, but the physics simulator implementing a colliding capsule would not.
At this point performance compromise is surely needed, to glue all the systems together. We may even need to convert some data forth and back, or do some other redundant work, and waste some memory.
But it's better to have it here than down in the performance critical physics system.

That's at least how i imagine to use ECS. So far i have no experience with it.
Still, i think i know enough to say 'don't use ECS for everything'. This can be good if you are really convinced about the ECS idea. But then you would not ask - you would already have a vision on how to make it a core design idea of everything.
If you are not totally convinced about such vision, there is likely not much anybody could tell you to change this. People can only tell you what worked for them, in their specific situations. They should, and we should pay attention, and along this process, capital letter words often appear to be those super powerful problem solvers everybody expect myself is using without any issues, doubts, or limitations.

“Component iteration” sounds nice: this name is taken from a misunderstanding of programming from the “C++ language segment”, that is, from the C++ developer-programmer sector : this is the wrong approach - in essence, it is the opposite approach to procedural programming: when the meaning of ECS - as a differentiator - is tried to be leveled

There’s not much to discuss here, but it turns out that in gamedev everyone needs knoweledge up by ECS: but it turns out that almost no one knows this: but it’s necessary for screenwriters, game designers and programmers, especially web developers

I Master ECS : but look on the visible Internet - there are no game developers - there are games - but there are no developers: matrix: those games that are released mainly below the waterline, only the TOP games have a certain virtual sense of epicness

Alice Corp Ltd

Aressera , I don’t want to praise you, after our non-conversation in your topic, but you have some understanding in this regard, I’m a little surprised

Alice Corp Ltd

shadow of meaning Of ECS : entity components system

suitable for almost any main engines and almost any main language: effective popular

___________

Phantom_entity_system_12 next gen

https://docs.google.com/document/d/e/2PACX-1vTDwCvP46i4bagwoJCGqZwUDCIAq1hyhwbTffmayLAvgsAq7OQu2PPXa-MeeEG2sY8OxWnDTu9hLiC-/pub

___________

cosmic tomb

Alice Corp Ltd

people, think: do you want a Jedi with a laser sword to program something for you?! probably need to realize who is who

Alice Corp Ltd

This topic is closed to new replies.

Advertisement