5 ms·
Honestly I find ECS dogmatic and difficult. Data oriented design as a general practice is a good thing, but there’s layers to all things. OOP is a very broad ca
by moth-fuzz 5y ago
Honestly I find ECS dogmatic and difficult. Data oriented design as a general practice is a good thing, but there’s layers to all things. OOP is a very broad category of practices and designs, and ECS usually refers to one specific architecture.
The specific architecture in question tends to be full of soft dependencies - most ECSes don’t allow you to simply store a piece of data without opting in to all systems matching the data type executing arbitrary code on it. So much for separation of data and behaviour. No, now they’re even More dependent than they are in the usual OOP sense and you might not even know it.
Furthermore, usually when you want to think about in-game entities, you want to look at a single class. Now all entities’ data is split into numerous components and all entities’ behaviour is split into numerous systems and you don’t know frame by frame how they’re gonna interact, provided you even know about all systems in the first place. It’s total spaghetti code.
I’m much more inclined lately towards a shallow actor system . If I want to know how player’s behaviour functions, I need only look at player.cpp and nothing else. Then, to reap the benefits of data oriented design, certain objects can use a certain allocation scheme that makes sense for the object in question. In the general sense, any Component<T> can just have a vector<T> or an unordered_map<T> of all components and the memory access is abstracted away without it being detrimental. That’s C++’s whole deal actually, zero cost abstractions.
I wouldn’t call an entire paradigm shift in which one rewrites everything from memory allocation all the way up to ‘use WASD to move’ zero-cost in any sense.
In C++ it is trivial to overload new, or derive from some class which does, or to write a custom allocator, and frankly there is zero need for the same person who’s writing ‘use WASD to move’ to know about memory management.
- lamontcg 5y agoAfter studying ECS for all of a week, I was left wondering if there wasn't a way to reintroduce strong typing to an ECS system (without reintroducing all the problems of inheritance). So you have a player_entity factory that ensures that a player_entity only wrap entities that are actually players. Then you can pass that around to strongly typed functions, but keep the overall design reasonably inheritance-free so it was more like rust/go's strong typing systems.
- jms55 5y agoI've thought about this in the past too, and come to the conclusion it is too difficult. Part of what I don't like about ECS is that it's too dynamic, you can't add static typing like this. Sure, you can say that a "Skeleton" entity has an HP component, an AI component, etc. But there's no way of enforcing static typing on this. Say you have some function that takes an Entity, validates it's a Skeleton, and then returns a SkeletonEntity which is just a wrapper around Entity for static typing purposes. Perhaps add some helper methods for fetching components, allowing you to operate on an OOP-like API with very little runtime cost. Seems like it works. But there's no way to guarantee the skeleton STAYS as a skeleton for the future. You might add another thing later on that converts an entity with AI into FriendlyAI, and your SkeletonEntity implicitly relied on the AI being a SkeletonAI. Heck, you can't trust _any_ Entity. That handle to an entity you have might not even point to an Entity anymore, it could've been deleted. ECS is very dynamic, which is great for designing open-ended games where you don't/can't plan every interaction in advance. It also has great performance, and is typically the _only_ sane way to implement games in a language without inheritance (I don't think Composition + Interfaces is very scalable). But for heavily structured games that want tight coupling between entities, it relies on you implicitly keeping your promises about what an entity means. You can't go and add new behavior that modifies existing entities later on - The compiler won't warn you, and you may end up crashing your program at runtime, unless you were very diligent about adding checks in your code for the coupling you assumed existed, and having good fallbacks for one those assumptions are violated.
- lamontcg 5y ago> But there's no way to guarantee the skeleton STAYS as a skeleton for the future. It seems like there needs to be some guarantees made about this. You can't have background jobs asynchronously changing players into spaceships and vice versa while there's other jobs running systems against those. I would guess that most games made with ECS systems that are threaded would deal with this by having a queue of requests to change entity components that would get processed once at the start of an update cycle and then a consistent state would be shown to all the systems in that update. That's really a state change in a finite state machine and I would imagine there's some work out there on systems of FSMs and concurrent updates and keeping things sane. Deletion could similarly be scheduled until the next tick and since systems should be stateless they shouldn't be saving those handles (or if you allow them to save the handle for some reason, you require them to check if the handle has been marked as deleted every tick).
- setr 5y agoThe way I view it is that you look at components like a database -- a bunch of tables that don't really tell you much about the business logic; the main thing they do is offer data coherence. And like a webserver, the business logic has been entirely moved out of the data storage -- you construct an "object" out of the raw parts from the database, and you operate on that. The main thing is that you can construct multiple objects from the same raw dataset -- different views (as in MVC). I think however it's a mistake when you assume most game's design -- where largely there are a very small amount of entities, and a small amount of behaviors, and really the game design is about careful placement of these fairly rudimentary entities on the map. In that scenario, the flexibility of ECS does you no good -- the game design is itself inflexible, so making your logic flexible is largely a premature optimization. If there's only one reasonable "View" of the entity, being able to construct an infinite set of alternative views is pointless. ECS appeals to me more when you start talking about simulation-style games, where the game is far less hard-coded. Dwarf Fortress is ridiculously flexible in its game design (at runtime), and ECS would be a natural fit for that (entities in DF are literally defined by tags, and groups of tags, and those tags get modified at runtime[0]). It's not spaghetti code then -- it's really the only reasonable way to approach the problem. Defining each entity uniquely makes a lot of sense when your entities are largely unique (perhaps with a common base, e.g. for physics). ECS makes more sense when your entities share of lot of logic, but random subsets of it, and especially so when the game itself treats that random subset as dynamic. [0] http://www.dfwk.ru/Creature_standard_1.txt http://www.dfwk.ru/Creature_standard_1.txt
- moth-fuzz 5y agoDwarf Fortress is one of my favorite games if not my favorite so props for citing its internal representation. I definitely think ECS makes sense in many contexts, and I think it's at its most powerful in tandem with a more encapsulated actor system. Example - struct Player: public Actor { Component<Sprite> sprite; Component<Transform> transform; void update() override {...} }; Where Component<T> is a handle to some backing storage indexed by an Actor's ID. This way, you can go the traditional route of having Player update itself in its own update() method as well as being able to Component<Sprite>::iter() along with other components for the render loop ECS-style. My point now and my point then was going all-in on ECS as the basis for your entire architecture rather than taking a more principled approach drawing the strengths of OOP and DOD is dogmatic and difficult. It's interesting that you mention web services - I have a lot of respect for databases and I enjoy the process of using them, however, I wouldn't ever program a web app in SQL. That's what pure ECS feels like to me. I agree that having objects manipulate the state via upholding internal invariants is the way to go. Whether you call them Actors, or Systems, or Controllers, it's kinda one and the same. That's why I love DOD as a base architectural layer to be abstracted upon, but dislike ECS as a programming paradigm.