ECS, Finally
mark-pekala.dev
mark-pekala.dev
I'm not very proud of the code I've written because I've found writing a game to be much more confusing than building websites + backends, but, as the author notes, it certainly feels more elegant than OOP or globals given the context. I couldn't imagine trying to build a large game using a more rigid organizational approach.
I'm building for WASM and Bevy's parallelism isn't supported in that context (yet? https://github.com/bevyengine/bevy/issues/4078), so the performance wins are just so-so. Sharing a thread with UI rendering suuucks.
The biggest issues I experience are:
1) How generic should I make my systems? It's easy to make them very specific, but, at a certain specificity, it feels like it's just lazier OOP. More generic and I start spending my time handling edge-cases that don't actually need to exist.
2) Intelligently handling updates when systems run in parallel rather than serially. By default, Bevy runs everything in parallel, and it's heavily encouraged, but my brain finds it a lot easier to just force everything to run serially and to not rely on frame-buffering. This doesn't seem necessarily ECS-specific, though, but is a problem I'm made more aware of due to the defaults given to me.
If anyone wants to browse some code or ask questions, feel free! https://github.com/MeoMix/symbiants
Making a game is already hard so my advice is to “make the game” and get specific. Personally it doesn’t matter if you go top-down or bottom-up you really want to go from simple->complex at all levels. Doing this will help structure the game and make it easier to restructure as you go. You can also always generalize later.
Managing dependencies is definitely trickier in the parallel ECS context because often you’re naturally inclined to thinking about Entities interacting but systems update across all Entities in a piece-wise manner so it’s very easy to end up with implicit ordering dependencies between systems that cause subtle weirdness in the game when evaluated out of order. Especially when the execution order isn’t fixed. In some respects the Actor model would be a better fit.
feels like this opens up your game to become nondeterministic, right? :/
I consider nondeterminism to be a game logic anti-pattern. I enjoy games that are truly deterministic FSMs that can be understood frame-over-frame.
https://bevyengine.org/news/bevy-0-10/#base-sets-getting-def...
Context: I'm an aged web dev with very little game dev experience, and I did less frontend after React appeared. In my limited experiences with React, it's much more about declaring React components (not ECS components) in terms of other components, so it's structure based rather than attribute based. Whereas jQuery feels more like ECS, since code mostly spends its time querying across the DOM and attaching behaviours to objects (nodes) based on attributes.
Obviously I'm massively generalising here, and web apps tend to behave very differently to games. But it does explain a little about why jQuery feels more adaptable as a web app grows.
Bevy is still declarative in some ways, though. You create a Sprite, but you don't have to worry about the Sprite's rendering process/lifetime. You just spawn/despawn/update its properties. There's an entire "render world" which is managed by Bevy and is distinct from your app's world. In this way, you're still working through a layer of abstraction prior to rendering, like React, rather than manipulating the DOM directly, and you reap performance benefits by leveraging this abstraction.
There are no rules on how to manage the logic flow.
If you look at systems like React Redux, and the general pattern of a central store to which different pieces of code can subscribe to, it's perhaps the thing that is closest to ECS on the frontend.
See https://redux.js.org/understanding/thinking-in-redux/three-p... and the examples at: https://react-redux.js.org/api/hooks#useselector-examples
Rather than drilling state through hierarchical props, one can create a specification for a TodoListItem that has it subscribe to the "todos" part of storage, and rerender when that changes. And then you have a separate "system" (using the word both figuratively and in the ECS way!) that responds to UI input or network traffic, updates some isolated global state that only that system controls (say, the "todos"), and notifies all entities that are subscribed to the derived state.
So then, a React functional component that religiously uses Redux (or a similar external storage system) for state management becomes akin to an ECS-entity whose set of ECS-components corresponds to which Redux useSelector and useDispatch hooks it chooses to use. And the ECS-systems are what mutate state in response to dispatched actions - you just need to make sure they have good boundaries.
If you adhere to this, the parent-child relationship between components really doesn't matter; you could nest a TodoListItem just about anywhere and it would behave the same. It's just that in web dev it's so common to say "I am the box for an ordered list of things, and as I move and reshape my children should do the same" that while you could in theory have an ECS system that track hierarchies and repositions children using JS as the parent moves, it's more natural to do something a bit more hybrid and allow the natural DOM hierarchy to control certain things - but not all.
React apps work as you'd like when you put shared state in something like redux & then components query state
As an aside, I was surprised that the author goes to Harvard yet his resume looks shockingly normal, not terribly different (in terms of software internships) than the people I went to school with. It's bizarre, given the fact that Harvard/YPSM admits are basically superhuman in every conceivable quality compared to the rest of us mortals.
[0]: https://zig.news/kristoff/struct-of-arrays-soa-in-zig-easy-i...
It launched as EC2 Container Service, but changed the name at some point (which made sense once they added Fargate)
https://aws.amazon.com/about-aws/whats-new/2014/11/13/introd...
People make a big deal out of ECS, but apecs is so trivial to use that you don't even think twice. You just say "is that it?" and enjoy.
You may have one loop to update all entities but that's it. And will you even update all of them ? What about the ones that are not visible ? Maybe you want to update only the visible ones .. in that case you query the partition to get the ones visible around your camera
So practically you never query the ecs registry to loop over all of them
I've written about the details here: https://taintedcoders.com/bevy/archetypes/
The gist is that instead of partitioning by type you sub partition by usage. Bevy does this by optimizing your storage based on the bundles (groups of components) you create. Flecs does the same but I'm not sure of the exact mechanism.