9 ms·
Entity–component–system (ECS) back and forth
- pault 8y agoThis is interesting. The vertical slicing the author talks about sounds a lot like the conventional way to structure a functional component based UI. Components + state tree + selectors + reducers in a typical react app fit together in this way and it makes it very difficult to apply traditional OOP design patterns. This is a huge source of contention on my team between the front end and the back end developers who are all c# dotnet guys. If anyone has any suggestions on where to find more information about functional, flow-based application architecture I would really like to learn more.
- bjornroberg 8y agoI's suggest reading a bit on the Elm Architecture. You don't have to use Elm to appreciate or use the architecture.
- x3n0ph3n3 8y agoA definition of "ECS" would have been nice.
- maxxxxx 8y ago"Entity-Component system"?
- HelloNurse 8y agoPlural "Systems" in ECS architecture are the separate explicit and implicit modules (functions, objects, processing passes, etc.) that do something with the data represented by Entities and Components, and thus represent the third equal leg of the architecture, like in other three letter acronym architectures (MVC, BDI...); it isn't just an architecture organized as a system (singular) of Entities and Components, even if some kind of framework to manage them is necessary. Systems could be managed by a framework too, for example to process them automatically in a correct order.
- epage 8y agoTo add a quick summary to the given acronym expansion, I think of Entity-Components as a native, in-memory database where Entities are your Primary Keys and Components are your Tables. Systems are a way of organizing and running operations on your entities over time. native: native to your programming language, ie no serializing / deserializing types
- gpm 8y agoEntity-Component-System (Which I think is more correct than Entity-Component system) It's a pattern of organizing data and code that consists of: Entities: Conceptually these are like objects, in memory they might just be an index. Components: Conceptually these are like fields on objects, they can typically be added and removed unlike normal fields. If entities are being represented as index's, each different type of component is generally stored as some form of Map<Entity, Component>. Systems: These are where you store your logic, they typically iterate over all entities with a given set of components and do something. For example iterate over all entities with a position and velocity components, and do position += velocity * dt.
- fenomas 8y agoHere's my simple explanation. Making a game with object hierarchies, you might do the equivalent of this: monster = {} // create an entity monster.hp = 10 // give it properties monster.pos = [0,0,0] Using ECS, you instead do the equivalent of this: currID = 1 // ECS init hpData = {} posData = {} id = currID++ // create an entity hpData[id] = 10 // give it properties posData[id] = [0,0,0] Obviously neither version is really implemented that way, but hopefully it shows the key insight - instead of storing properties on the object they describe, you store them in tables full of like data, keyed by the ID of the object they describe.
- reificator 8y agoShowing the how without showing the why is probably not helpful IMO. Why is important here. Using ECS is somewhat like treating your game state like a normalized database. It lets you perform operations on all the items of a certain type at once. Say in my game I need to recenter my origin on a regular basis. In an open world game, you want to make sure that players don't start to see errors caused by floating point arithmetic, and the further away their position gets from 0, the worse this gets. To fix it, one option is to transform all positions by the same amount to keep the player's absolute position near (0, 0, 0). To do this in the hierarchy style, the code might look like this: (Using JS because I was writing it today so that's where my brain is) var gameObjects = world.getAllGameObjects(); var offset = [-1000, 0, 500]; // Pretend it's a real vector // Iterate through every single object in the game // (Not using foreach or filtering in either example, sue me) for(var gameObject in gameObjects) { // Branch on every game object just to see if it's positioned in the world if(gameObject.position !== null) { // Do what I really wanted to do gameObject.position.add(offset); } } Meanwhile, the ECS version would look more like this: // This can vary greatly based on implementation // I'm going to go with an option that reads easily // This should just give back the list of components, which we'll say is an array. // Lookup time in this fictional system is the cost of a dictionary lookup. var positions = world.getComponents('position'); var offset = [-1000, 0, 500]; // Pretend it's a real vector // Iterate through only the things we care about // Not using map here, sue me for(var position in positions) { // Do what I really wanted to do position.add(offset); } Not only is the second example shorter, but it's also doing a lot less work. We're adding a vector to an array of vectors. No branching, no touching objects we don't care about, nothing extra. Many ECS systems will allow you to filter down to the set of entities that have two or more components at the same time. Obviously more expensive, but still not as wasteful as checking every object or adding an update function to run every tick on every relevant entity. The above code was written just before bedtime and not against a real ECS library or OO hierarchy, so please forgive any obvious errors.
- rgoulter 8y agoCatherine West's RustConf keynote provides a good discussion about why you might want to organise "a program where objects interact" using an ECS system. She discusses the "how you might write it with standard-OOP", some drawbacks to this, and iterates this towards using an ECS system. https://kyren.github.io/2018/09/14/rustconf-talk.html https://kyren.github.io/2018/09/14/rustconf-talk.html I like that both this and TFA mention a focus on the data, and "row"/"column" roughly compares to (say) an SQL table. - It's an interesting way of thinking about objects/entities.
- skypjack 8y agoThanks for the link. I haven't read it before. It looks really interesting indeed.
- platz 8y agoAnd the response by Jonathan blow https://youtu.be/4t1K66dMhWk https://youtu.be/4t1K66dMhWk One of his points is that you still have to solve synchronization for allocations and deletions from the arrays.
- house_atr 8y agoIsn't the whole definition of OOP as some kind boogeyman god class with everything cramed into it, a bad comparison? Worse, this seems to be the only description of OOP used by it's detractors. A very trendy topic now.
- bitwize 8y agoECS is one of those things you "get religion" about, especially as a game developer. Like, "Oh, I've been fighting the class hierarchy and overcomplicating my code for so long... it doesn't have to be this way!" I've written about this as well, whilst developing my own game: https://nullawesome.tumblr.com/post/146127692039/entities-and-components-in-nullawesome https://nullawesome.tumblr.com/post/146127692039/entities-an...
- reificator 8y ago> ECS is one of those things you "get religion" about, especially as a game developer. That's what worries me to be honest. I'm right there with you that ECS is just the right way, but whenever I can't come up with good arguments against my choices I feel like I'm blinded by faith. My main argument for why I wouldn't use ECS in a greenfield game project is that a lot of existing tools don't play nice with it. That's about it.
- skypjack 8y agoTo be honest I use composition over inheritance because it fits better with my mental patterns. I've nothing against OOP in general and I'm pretty sure we can have code with good performance also without a component-based model. The fact is just that I'm not as good at designing things with OOP in mind as I'm when I design them with components in mind. But this is me, not a golden rule.
- skocznymroczny 8y agoI like a good balance of composition and inheritance, but I feel like the current hype for ECS is a bit overwhelming. I noticed many beginners are confused and try to shoehorn ECS into EVERY aspect of their game project, as in "OOP/inheritance is lava" way. Also I see people obsessing about performance of ECS vs OOP, when it's not relevant for most of the indie game projects that have a much smaller scope than a high-end AAA game projcet.
- skypjack 8y ago
- jayd16 8y agoI feel like blogs about ECS are needlessly verbose when the idea is really simple. Problem: Most games are built in with a game loop ticking frames through many gameobjects/actors. Many actors share some functionality like physics collision, but there is also a lot of unique functionality on the same objects. Naive solution: Use inheritance to build out your core functionality and override functionality as needed. -This leads to a mess of interacting parts a massive type tree with many permutations of functionality. -The deep type hierarchies blow out cache locality as the natural way is to tick through all functionality for a single object as you loop through all objects ECS: Break functionality apart into discrete systems. Use composition to build the permutations of functionality. -Its easier to to manage what you intended and also easier to experiment with functionality through composition than inheritance. -The natural way to loop through functionality is one system at a time. You loop over your entities many times but because you can run the same system code in a tight loop its really speeds things up. -Systems provide singleton like functionality to store global state in a private way. In a heavy actor model we have to rely on static fields, which can be ok, but now you have a large cumbersome hierarchy of functionality with global access to state. tl;dr -Composition over inheritance is good in all walks of coding. -Many discrete tight loops of systems is usually better than one loop of complex logic.
- skypjack 8y agoI don't think the idea is difficult actually. What I've found difficult when I decided to implement my own tool (https://github.com/skypjack/entt https://github.com/skypjack/entt) was that technical details on how to design something that was both easy to use and with good performance were scattered all around the web. I'm just trying to summarize what I've discovered so far for the "future me". :-)
- ah- 8y agoSadly the article doesn't mention how ECS typically solve the "holes" problem, where your arrays might be quite sparse which leads to inefficiency. Do you happen to know how that's usually done?
- Jyaif 8y agoOne day I'll write a blog post myself writing about how I went from ECS to OOP. The short version is that ECS works against creativity. I want my entities to be unique: they move differently, they react to damage differently, they die differently, etc... In a ECS world, this means having at least 5 different components and 5 different systems per... type of entities. With just 10 entities you already have 100 different classes... that are never going to be reused. Also, because I reimplemented my game to OOP I was also able to compare the performance and noticed that the ECS performed worse (although maybe my implementation was not good?).
- skypjack 8y agoMaybe. However it makes sense to stick with the paradigm with which you are more comfortable. There is no shame with using OOP instead of component-based models or the other way around. I think that knowing both of them can help sometimes, because they fit different problems and can work side-by-side tho. That said, if you'll ever write such an article, ping me!! I'm pretty sure it will be an interesting point of view to think of.
- dkersten 8y ago> In a ECS world, this means having at least 5 different components and 5 different systems per... type of entities. Shouldn't it be more data data-driven than that? That is, instead of 5 components and 5 systems, you have one or two components and one or two systems and the components declarative describe the differing behaviours in a data-driven manner. In my personal experience, if you have objects or inheritance to define every possible behaviour, the codebase becomes a huge tangle and performance will suck and actually creating the content becomes a pain. I'd personally much rather have components that define the general flavour of the behaviour and then use data to fine-tune it. And for the edgy edge cases.. have a script component that runs Lua or whatever. Of course, you can do that with OOP too and you can certainly write high performance OOP, I just personally find it much harder due to how it encourages distributing state across the codebase, especially when you want to do it in a multithreaded environment. But if you've had better experiences with OOP, then more power to you, use what works for you. I would, however, not write my own ECS, but use a well designed and well tuned existing one, like the article authors EnTT.
- valkum 8y agoHere is a talk by Blizzard about Overwatch which uses ECS: https://youtu.be/W3aieHjyNvw https://youtu.be/W3aieHjyNvw
- hortense 8y agoI've been wondering for a long time about how ECS can be cache friendly. If you look at Overwatch's ECS ( https://youtu.be/W3aieHjyNvw?t=326 https://youtu.be/W3aieHjyNvw?t=326 ), there are many systems that can read from 10+ different components. For every entity in this system, at least 10 reads with no locality whatsoever are done. That seems crazy slow to me.
- skypjack 8y agoI saw the video, but they don't go much in details on the actual implementation, so it's hard to say where, how and if things are optimized or not. Moreover, the speaker stresses also on the fact that ECS is used for code organization in most cases and they benefit a lot on this aspect.
- bfrydl 8y agoIt's ten reads, but each of the ten reads is typically just an index into a big sequential array. Also, when you're reading components, you can run that system in parallel with any other systems that do not write to those components.
- meheleventyone 8y agoThe point of data oriented patterns is to structure data how it is used. So if these lookups were bottlenecks you'd collapse components together if they were always used together. The other idea is that where there is one access pattern there will be more of the same. So by updating by System you keep as much as possible hot in cache by doing all the similar lookups together. So whilst the first lookup might have ten complete misses the next one probably won't. At a gameplay level there's a lot of chaos going on and entities are not generally doing things that are easy to organize in a way that avoids cache misses anyway. On top of which you are juggling ease of change with optimization. At which point you just need to be fast enough. I'd also bet most bottlenecks for Overwatch were not in gameplay code. Mostly though people should be thinking in a data oriented way rather than grabbing an ECS framework and expecting that to magically make things cache efficient.
- cma 8y agoConsider instruction cache locality as well. That system will run sequentially running the same code for all entities that need it, and is likely to all stay cached. Whereas if you tick each entity separately and run all the logic, each new entity tick is following on to so much unique code having run that it is probably starting all over on uncached instruction fetches.
- eXpl0it3r 8y agoI really like the following article on ECS, as it shows the complete setup in a very compact way, making it not only easy to understand, but actually quick to implement yourself: https://blog.therocode.net/2018/08/simplest-entity-component-system https://blog.therocode.net/2018/08/simplest-entity-component...
- plopz 8y agoI'd be interested to read a follow-up on how to incorporate other data structures such as quad trees, spatial hashes or scene graphs into an ECS.
- meheleventyone 8y agoI look at it as EC and S. It's a datastore keyed by Entity handles that lets look up Components. Data transformation is then driven by Systems. The behavior of a game is then driven by the tick order of the Systems. From this view of the world it's fairly obvious that other structures need to reference entities through their handle. Or in the case of middleware with its own view of the world a translation layer keeps things synchronized.
- skypjack 8y agoStay tuned then. I was already planning to write something about scene graphs and component-based models. Nothing forbids to write also about the rest.
- vbuwivbiu 8y agois this right ?: 'entities' ~= object references (ids of objects) 'components' ~= properties (instance/member variables) of objects (maps of entity ids to components) 'systems' ~= algorithms that operate on lists of components