At least regarding games, ECS is the popular flavor-of-the-month alternative to OOP as a design strategy. The main problem being that games often have a lot of special cases, which break the inheritance hierarchy quite quickly.
E.g. Defining a weapon > {sword, wand} hierarchy, with respective properties for melee and casting, and then defining a unique weapon spellsword which is capable of both melee and casting. You could inherit from weapon, and copy & paste sword/wand code, or inherit from sword/wand, and copy & paste the other, but the hierarchy is broken.
ECS would rather have you define [melee] and [casting] components, and then define a sword to have [melee], wand to have [casting] and spellsword to have [melee, casting]. So instead of representing the relationships as a tree of inheritance, you represent it as a graph of components (properties). And then you generically process any object with the melee tag, and any object with the casting tag, as needed.
And of course then you could trivially go and reach out across the hierarchies and toss [melee] onto your house object and wield your house like a sword -- I don't know why you'd want to do that, but the architecture is flexible enough to do so (perhaps to your detriment).
That's probably more an example of "metadata-driven" but it's ultimately the same thing -- an entity in the game is defined by its components, and the job of the game engine is to simply drive those components through the simulation. That particular example has its metadata (e.g. aesthetics: [CREATURE_TILE:249][COLOR:2:0:0]), its capabilities (e.g. [AMPHIBIOUS][UNDERSWIM]) and its data (e.g. [PETVALUE:10][BODY_SIZE:0:0:200]).
exactly the post I was trying to remember when talking about spellswords :)
Those posts are also cool in that defining games as a set of rules that operate on things within it is a really neat mental model -- the program should basically look like a DnD rulebook, with statblocks and all.
The way to really grasp ECS architecture is not to look too hard at the implementations(which are all making specific trade-offs) but to recognize where it resembles and deviates from relational database design. A real-time game can't afford the overhead of storing data in a 3NF schema, but it can design a custom structure that preserves some data integrity and decoupling properties while getting an optimized result for the common forms of query.
The behavioral aspects are subsumed in ECS to sum types, simple branching and locks on resources, where the OOP-embracing mode was to focus on language-level polymorphism and true "black-boxing". Since the assumed default mode of a game engine is global access to data and the separation of concerns is built around maintaining certain concurrency guarantees(the order in which entities are updated should have minimal impact on outcomes), ECS makes more sense at scale.
The implementation trade-off comes in when you start examining how dynamic you want the resulting system to be: You could generate an optimal static memory layout for a scene(with object pools used to allow dynamic quantities to some limit) or you could have a dynamic type system, in essence. The latter is more straightforward to feed into the edit-test iteration loop, but the former comes with all the benefits of static assumptions. Most ECS represents a point in the middle where things are componentized to a hardcoded schema.
> E.g. Defining a weapon > {sword, wand} hierarchy, with respective properties for melee and casting, and then defining a unique weapon spellsword which is capable of both melee and casting. You could inherit from weapon, and copy & paste sword/wand code, or inherit from sword/wand, and copy & paste the other, but the hierarchy is broken.
Or, you could, in OO Python:
class SpellSword(Sword, Wand):
...
or, possibly even:
class Melee:
class Casting:
class Sword(Melee):
class Wand(Casting):
class SpellSword(Melee, Casting):
> So instead of representing the relationships as a tree of inheritance, you represent it as a graph of components (properties).
Multiple inheritance also represents relationships as a graph of properties.
I've been hearing about ECS for a decade so it's definitely more then flavor of the month. However the issue is that unfortunately Unreal/Unity are both OO first.
It's definitely been around but I think Unity's (never-finishing) ECS + Rust gamedev community's focus on it has really spiked its popularity/interest lately. Otherwise pretty much every recommendation/engine is OOP-based, with a few straggling extensions/libraries for ECS here and there.
This has been a recognized problem for game devs for a while and recently Unity introduced DOTS to gain performance. From what I understand, doing traditional OOP means going through a lot of pointers to find information. If you put everything into arrays instead, you reduce the time spent looking up things.
I’m not sure it is; the heart of OOP is encapsulation of data + methods — the object itself. (Or message-passing if you’re being kinky)
ECS is almost the opposite; the object in question is barely defined — rather it’s derived from the composition of its parts. Like a DB, there’s roughly no encapsulation at all; just relations defined where, should you put them all together properly, you get the “object” back.
That is, in OO a particular object is composed of certain properties and capabilities. In ECS, if certain properties and capabilities exist, we call it a particular object.
Implementation-wise, the main difference seems to be the SoA vs AoS argument
But there’s also things like unity’s ECS deviating from rust’s ECS definition — I believe in Unity, components (data) and systems (behavior) are coupled, which fits better towards the OO world (components are the object of question). In rust ECS they’re decoupled (which unity is moving to, someday) and you’re really just dealing with structs and functions. And there’s a whole thing about the “true” ECS definition but it doesn’t really matter.
I've described an ECS to my friend as a combination Strategy pattern and Composition, so you can have a huge list of "traits/components/behaviours" and then build your object hierarchies (and their behaviours) from that, instead of from Inheritance. The way most game editors are made, when you configure things using the ECS, it visually looks like inheritance, but under the hood it is more like Composition. Once you go down the rabbit hole, you would see that and ECS/Strategy pattern & Composition makes more sense (and way easier to cater to exceptions) than plain old inheritance.
I want dare say that the enterprisey-version of an ECS would be the Onion Architecture that's been floating around in Java/C# camps. Not 100% match but they feel the same to me.
Obviously for normal GUI programming (at the outer layer that we see at least), inheritance is still king.
E.g. Defining a weapon > {sword, wand} hierarchy, with respective properties for melee and casting, and then defining a unique weapon spellsword which is capable of both melee and casting. You could inherit from weapon, and copy & paste sword/wand code, or inherit from sword/wand, and copy & paste the other, but the hierarchy is broken.
ECS would rather have you define [melee] and [casting] components, and then define a sword to have [melee], wand to have [casting] and spellsword to have [melee, casting]. So instead of representing the relationships as a tree of inheritance, you represent it as a graph of components (properties). And then you generically process any object with the melee tag, and any object with the casting tag, as needed.
And of course then you could trivially go and reach out across the hierarchies and toss [melee] onto your house object and wield your house like a sword -- I don't know why you'd want to do that, but the architecture is flexible enough to do so (perhaps to your detriment).
Dwarf Fortress probably has the best example of this: https://github.com/BenLubar/raws/blob/archive/objects/creatu...
That's probably more an example of "metadata-driven" but it's ultimately the same thing -- an entity in the game is defined by its components, and the job of the game engine is to simply drive those components through the simulation. That particular example has its metadata (e.g. aesthetics: [CREATURE_TILE:249][COLOR:2:0:0]), its capabilities (e.g. [AMPHIBIOUS][UNDERSWIM]) and its data (e.g. [PETVALUE:10][BODY_SIZE:0:0:200]).
And it even has inheritance :-)