10 ms·
A Thought Experiment: Using the ECS Pattern Outside of Game Engines
- adamnemecek 7y agoI've been playing around with ECS. It does seem to solve a lot of architectural problems I had.
- jrhurst 7y agoI find ECS a pretty intuitive concept, but it always ends up being a massive yak shave to me
- jayd16 7y agoAgreed. The vast majority of the benefit is simply from learning to use composition over inheritance. Most applications that aren't games don't need to support things like finding all active objects of a type or maintain many discrete actors executing their own run loops. But Unity isn't dumping a lot of marketing into talking about the decorator pattern so everyone is jumping right to ECS now.
- ratww 7y agoWhat were your problems with it? Was it using a particular library or engine, or you did it yourself? I'm curious because I started using ECS-like techniques exactly because traditional OOP in games felt a lot like yak shaving.
- kevingadd 7y agoWhen performance matters I often find myself yak shaving existing code to basically turn it into structure-of-arrays format (like is required for ECS), if not converting to ECS entirely. The alternatives scale so poorly...
- netgusto 7y agoI did this lightweight ECS for Go and TypeScript: TS: https://github.com/netgusto/ecs-typescript https://github.com/netgusto/ecs-typescript Go: https://github.com/bytearena/ecs https://github.com/bytearena/ecs Very interesting programming concept indeed! --- Edit: These implementations support tagged queries, and indexed views for faster iterations (ECS uses iterations a lot).
- zubairq 7y agoHow is an Entity Component System different from Objects and Controls in something like Visual Basic for example?
- adamnemecek 7y agoEcs is more like a database.
- TeMPOraL 7y agoECS is all these things and none of these things. It's several separate concepts under the same name; different applications/games pick different pieces of it. It's kind of meta, really; you can imagine ECS itself as an entity, and different meanings of it as different components that could be included in it.
- dyarosla 7y agoECS is meta? Love the recursive definition ;)
- m12k 7y agoI wrote a comment on another post where I explain the cache optimization benefits of ECS, https://news.ycombinator.com/item?id=21674818 https://news.ycombinator.com/item?id=21674818
- ratww 7y agoVisual Basic controls are more like React components, I think. ECS is, traditionally, just a way to dynamically add behaviour (components) into "blank slate" entities. Instead of making a EvilMonster class in a game you just have it as a conceptual entity (normally it's just an integer, like a database id) associated with a bunch of components (normally just plain structs) and their parameters: Renderable, RigidBody, Collider, HasEnergy, HasTransform, AudioEmitter, etc. You can stitch entities and components together using either code or some external data source. The "S" part of ECS is called the system, and it implements the behaviour defined by components. The upsides of this IMO is that this is a fantastic way to structure your code. The data-oriented aspect is also great for enabling non-coders to assembly complex entities. And some other clever people (such as the sibling answer) found out this is also a great optimisation technique. There are multiple implementations, of course, so people have different opinions.
- aliswe 7y agoI'm accidentally using this approach in my CMS for saving .NET Core classes (with interfaces - and supports explicit interface implementations) into document stores. EDIT: Actually, now when I think about it, this approach sorta makes every object (or Entity) into an independent, isolated database. Hm ...
- TeMPOraL 7y agoWhich ECS? :). Despite "clear" definition from Wikipedia, ECS as a pattern suffers from multiple personality disorder. There are several different goals that all share the same name, and different implementations pick a different goal set, ending up looking not quite the same, and getting different kinds of benefits. So outside games, if you pick "ECS as composition over inheritance", you get something like here, or Clojure's "let's use maps for everything". If you go for "ECS as a performance optimization" - a frequently touted benefit - you'll end up storing a lot of global arrays (perhaps arranged in structures), each packing every instance of a component's property across all entities. If you go for "ECS as a way to have data-driven entities modifiable on the fly" or "ECS as a way to make game logic cleaner", then congratulations, you've just reinvented a relational database! Since you're not developing a game, you might as well store your data in SQLite and call it a day. (In fact, I'm currently working on a roguelike game as a side project, whose distinct feature is that it stores all its game data in an in-memory SQLite database, precisely to experiment with a pure form of "ECS for game logic" pattern.) In the end, the author explored one way of doing ECS. There are plenty others, worth their own thought experiments :).
- codetrotter 7y ago> If you go for "ECS as a performance optimization" - a frequently touted benefit - you'll end up storing a lot of global arrays (perhaps arranged in structures), each packing every instance of a component's property across all entities. This is basically what I am doing in the backend I am writing for an app that I am working on. (Also not a game btw.) No database, no overhead :D You really can fit a lot of data in memory, and I think a lot of people tend to forget that. I so so wish that I get to see the day when memristors become cheap and abundant. It would be so pleasant to program for a system like that I think. No persisting to disk or loading from disk, ever – imagine that!
- BubRoss 7y agoNew NVMe drives claim 5GB/s on PCIe 4, is memory mapping something like that not good enough for you already?
- jayd16 7y agoIsn't this usually referred to as the decorator pattern when used this way outside of a game loop?
- golergka 7y agoThe critical differences are memory layout and explicit declaration of logical dependencies: this makes the code parallel by default.
- jayd16 7y agoThis is just false unless you think the featured article isn't ECS. Rendering is an ordered operation that at the very least shouldn't be considered parallel by default. Simply putting it in an ECS pattern doesn't guarantee anything like that. Its probably better to say it might help you write tighter loops because (ideally) you have a small amount of code looping over a large array of data, instead of a sea of actors hopping around the heap. Of course, you can make tight loops in a lot of different patterns...
- kevingadd 7y agoRendering on modern hardware is fundamentally parallel by default, even if the commands you issue appear to be sequential. In practice multiple commands can be issued in parallel by a modern GPU and fragments are rasterized in parallel as well (divide and conquer), see https://youtu.be/Nc6R1hwXhL8?t=465 https://youtu.be/Nc6R1hwXhL8?t=465 and note how it's chunking many triangles up into groups and rasterizing them in parallel (there's a predictable spatial order, but it's not rendering one tri at a time or one screen quadrant or a time). This is necessary to exploit the massive number of cores on these GPUs (thousands, in some cases). Newer graphics APIs also allow you to build many command buffers at once (in parallel) and allow you to fill GPU vertex/index/texture buffers in parallel from multiple threads once you've mapped them into your address space. GPU compute is also basically async and operates in parallel with rendering on modern GPUs. See https://www.extremetech.com/extreme/213519-asynchronous-shading-amd-nvidia-and-dx12-what-we-know-so-far https://www.extremetech.com/extreme/213519-asynchronous-shad... Rendering is, in practice, parallel. You can enforce sequential ordering if you need it, but you often don't. (Z-buffer based rendering effectively makes parts of your scene parallelizable since the rendering is order-independent, and as demonstrated above tris can be rendered in parallel) I've been doing scene rendering in parallel for something like 8 years on Direct3D 9 (XNA) and classic OpenGL. Most of my current parallelization is explicit ordering of scene elements which allows me to prepare buffers/draw commands in parallel, and filling GPU buffers in parallel. If I ever move to Vulkan or D3D11/12 I'll be able to exploit parallelism more there. Those old APIs allow mapping GPU resources into user address space which in some cases already allow you to prep future rendering while existing operations are in flight. It's also common for modern D3D and OpenGL drivers to create hidden threads in your processes that perform rendering operations behind the scenes while you issue your sequential commands from your threads. This effectively turns those APIs into secretly-parallel APIs, and the driver threads can exploit any parallelism hidden away like performing multiple buffer uploads at once or building command buffers in parallel.
- golergka 7y agoThat's exactly the idea that I have been toying with recently: using ECS (and even spec crate in particular) to implement an authoritative game server. Haven't yet figured out how to make spec's and tokio's scheduling to play along, though.
- JoshMcguigan 7y agoI look forward to seeing the ladder logic editor. I run PLCFiddle [0], which is a purely React ladder logic editor. [0]: https://www.plcfiddle.com https://www.plcfiddle.com
- fulafel 7y agoIn the Clojure world, this used to be a popular way to structure backend apps: https://github.com/stuartsierra/component https://github.com/stuartsierra/component > Components provide some basic guidance for structuring a Clojure application, with boundaries between different parts of a system. Components offer some encapsulation, in the sense of grouping together related entities. > Each component receives references only to the things it needs, avoiding unnecessary shared state. Instead of reaching through multiple levels of nested maps, a component can have everything it needs at most one map lookup away.
- TeMPOraL 7y agoUsed to be? What replaced it? I used it in my last Clojure project, and I'm about to start expanding it further; should I replace it with something else?
- fulafel 7y agoIt's fine, hasn't just been getting much press lately.
- heretoo 7y agotry integrant https://github.com/weavejester/integrant https://github.com/weavejester/integrant You can move from using component's functions/macros to using a map and cross references using reader literals. Having used both, I find integrant to work better for me.
- dkersten 7y agoStuart Sierra components and ECS components are not really the same thing though. They share the name “components” but not thaaat much else. Stuart Sierra Components are more akin to Unity’s behaviour components prior to their introduction of an ECS. They’re a way of encapsulating state which may have dependencies and has a lifecycle (needing to be started or stopped). ECS components are purely blobs of (usually fine grained) state, the existence and combination of which in an entity creates behaviours (that is, the systems which implement the behaviour will dynamically do so for any entity that has the prerequisite components, but the components are just data). I always preferred naming ECS components as “traits” (they are the traits that an entity has, eg “animated”) and the systems “behaviours” (they are the behaviours exhibited by entities that have certain traits).
- spankalee 7y agoSo many of these kinds of OOP modeling complaints and solved by mixins.
- _carl_jung 7y agoI'd earnestly like to hear the responses to this from the down-voters. I had the same thought and suspect there is a good reason not to use mixins. One problem off the top of my head is run-time changing of components. An entity that inherits from multiple mixins can't inherit from new mixins (or lose existing ones) at runtime.
- skybrian 7y agoYes, ECS is usually about runtime changes in behavior. It also uses IDs rather than hard references, like in a database. Deleting an object makes references to it invalid, rather than references keeping objects alive like in a functional or object-oriented object graph.
- spankalee 7y agoBut the complaints about OOP in the article are all about the difficulty in modeling orthogonal traits in a single-inheritance hierarchy. That is definitely addressed with mixins. Nothing in the "Inheritance isn't Always the Best Tool for the Job" section talks about runtime changes in behavior.
- skybrian 7y agoIt does touch on it towards the end: > Most object-oriented languages are designed so that an object's underlying type will be the same for its entire lifetime. This makes things interesting when users want to scale a Circle without maintaining aspect ratio.
- aliswe 7y agoAre you saying that this is different from mixins?
- raverbashing 7y agoThis sounds a lot like OOP purists finding out not everything can be solved by inheritance so they rename something that existed already.
- 5cott0 7y agoApple provides an ECS in gameplaykit that can be used in iOS/UIKit apps, not sure why anyone would want to though. https://developer.apple.com/documentation/gameplaykit https://developer.apple.com/documentation/gameplaykit
- Razengan 7y ago> not sure why anyone would want to though. What do you mean by that? GameplayKit has its limitations, but I've been building an entire engine around it, and I like it so far, especially for the ability to stick with pure Swift and native APIs: https://github.com/InvadingOctopus/octopuskit https://github.com/InvadingOctopus/octopuskit
- almindor 7y agoI've built the Texel ASCII Art Editor (https://crates.io/crates/texel https://crates.io/crates/texel) using Specs (https://crates.io/crates/specs https://crates.io/crates/specs) as my first experience with ECS. The original goal was an ASCII game but since I needed to create the resources for it it morphed into Texel. I think the use of ECS here wasn't a bad decision but Specs proved to be just too cumbersome and overoptimizing. As others have mentioned ECS is a bit "loosly defined" and each implementation seems to go over the line to add more specializing one way or the other. I want to switch to something more simple and elegant like DCES (https://crates.io/crates/dces https://crates.io/crates/dces) for my next refactor. I think for "runtime resource management" ECS is fine if you need a fairly large, distinct pool of entities to handle. One of my main usage problems was the re-use of same components in the same entity. E.g. imagine having an entity with a global world position, but also an internal position for something like "last cursor position". It's not possible to just "add another Position component" to the same entity, for good internal reasons, but still. It's pretty important people take these kind of limitations into account before planning out their entity/component maps.
- nabla9 7y agoI have been using this. Common Lisp Object system perfect and natural for this kind of programming.
- shoo 7y agoSee also: Richard Fabian's "Data-Oriented Design" book. The fourth chapter is about component-based objects, and the preceding chapters about relational databases and existential processing are relevant. http://www.dataorienteddesign.com/dodbook/ http://www.dataorienteddesign.com/dodbook/ Prior HN discussion: https://news.ycombinator.com/item?id=20380397 https://news.ycombinator.com/item?id=20380397
- ncmncm 7y agoWho are these people who think that inheritance is the solution to every architectural problem, or any? I didn't think that Java coders were especially well represented in the gaming world. It has been over 20 years since C++ programmers learned that inheritance is just a way to dress up function pointers (not that that does no good, but function pointers are pretty specialized machinery). Seriously, you don't need to use a named style to write programs. Your language offers lots of different facilities you can put together in any way that is useful to achieve your aims. Functional, OO, ECS, MVC, procedural, data-oriented, reactive, whatever -- it is all just code. You have only three problems: code needs to be organized well enough that you can understand it, code should run fast enough, and you should be able to change one thing without rewriting everything else. Nothing else matters.