The weirdest thing is that the ECS as a way of building a game is inherently object oriented. You take a set of components and compose an object called an entity. The components on the entity define not only it's data but also it's behavior by the set of systems that act on the corresponding components. And you can take these object definitions and inherit them to add additional behavior or change the existing behavior by adding more components to the new definition.
Then if you solve the entity communication conundrum with message passing and don't allow entities to directly access one another's data you basically have all the elements.
> You take a set of components and compose an object called an entity.
That’s an overly broad definition of “object”, since under that same definition a record type (C struct) or any other blob of memory is an object.
In the common type of “components only store data” ECS, the entity is an ID (think a foreign key) that connects multiple records together and systems are independent functions (they are not tied to nor live in an entity) that operate on collections of subsets of these components.
That sounds a lot more like old school C-like procedural programming to me than it does like OOP. There’s more to OOP than the data attributes a class contains (eg the associated methods)
I suppose it depends on your game engine and your ECS, but since entities don’t contain logic, it’s the systems that communicate between each other (either by sending messages or by accessing the other entities components or by just calling functions of other systems). This isn’t all that different from different parts of a procedural program communicating. Although I do personally think that making a system be an OOP object does makes sense, but it doesn’t have to be.
With that said, it seems pretty common in games to use a component system that isn’t “pure ECS” (like the default Unity components prior to their new ECS), which definitely seems like typical OOP to me, just decomposed a bit more.
> That’s an overly broad definition of “object”, since under that same definition a record type (C struct) or any other blob of memory is an object.
I think that's because you seem to have stopped at the second sentence the rest is important as well. I'm also talking about a level above the ECS implementation. What is the running thing actually doing.
> With that said, it seems pretty common in games to use a component system that isn’t “pure ECS” (like the default Unity components prior to their new ECS), which definitely seems like typical OOP to me, just decomposed a bit more.
Yes this also models much the same thing at runtime.
It's the conceptual organization. The Entity is defined by data (components) that bring along behavior (systems). So an entity executing at runtime (say you're making Pacman and it's the Red Ghost) is an object and is defined by the combination of data and behavior.
The underlying implementation is irrelevant basically. You could implement the ECS in an OOP style and the same it true. You could do it in a functional style and it would be true. You could do it in straight bytecode for some obscure hobby VM and it would be true.
Unlike traditional OOP, the data and behavior are decoupled though. Similar to data and functions.
That is, you can add components that don’t get operated on by any particular systems because the entity doesn’t have the other prerequisite components and you can have systems that don’t operate on the components. You can have many systems operate on one particular component and many components operated on by a system.
In OOP, the data and the operations are packaged to whether as one. You also typically have encapsulation and it’s considered bad practice for one class to operate on another classes data directly.
It seems that both models achieve similar things, but they’re far from the same thing. Just like how procedural or functional programming achieve similar things to OOP, and you can do OOP in these paradigms or these paradigms in OOP. There’s a lot of cross over, but that doesn’t make them all the same thing.
If anything, I’d say that ECS are a relational model but with a very limited query system compared to something like SQL.
> Unlike traditional OOP, the data and behavior are decoupled though. Similar to data and functions.
Except the data and behavior aren't decoupled. The components are decoupled from the systems, but the systems are still very much dependent on the components. Just like a method is usually dependent on the instances data or a function is dependent on the data passed in.
> That is, you can add components that don’t get operated on by any particular systems because the entity doesn’t have the other prerequisite components and you can have systems that don’t operate on the components. You can have many systems operate on one particular component and many components operated on by a system.
You can have a member that isn't operated on by any methods and methods that don't operate on members.
At the level your talking about there isn't much difference between a function and a method. It's mostly syntax.
method(instancedata);
verses
instancedata.method();
Really we're getting caught up in implementation details because a class definition isn't the be all of how to define an object. There is really no reason we couldn't define objects in a programming language through composition.
ECS very much is a relational model and you're right it's very limited in comparison to things like SQL because it's trying to model something very simple. Game Objects! The relations defined are exactly what brings data and behavior together under to create the runtime object we call an Entity under the pattern conventions.
> Just like a method is usually dependent on the instances data or a function is dependent on the data passed in.
Just like a C function operating on a C struct. So, what, in your opinion, is the difference between procedural programming and OOP?
> It's mostly syntax.
Which is why I think there is more to OOP than a classes attributes and it’s methods. There is also inheritance, encapsulation levels, the fact that an objects identity is its attributes (the object is its data, an entity has its components but is separate from them), the fact that an object is a singular thing which it’s methods operate on (as opposed to how systems operate on collections of components, imagine a class system where a method operated on all instances of that class!).
Sure at the end of the day it’s all the same and we’re just arguing semantics, but that was my point and what I lead with: it’s an overly broad definition. If definitions are too broad then they really don’t add any value, but I believe a distinction between OOP and ECS is useful because they are used in different ways.
But fundamentally I don’t disagree, I even once wrote a blog post about how all of the OOP principles exist in an ECS! I just don’t believe that thinking of them as slightly different implantations of OOP is useful because of how their properties differ.
I actually wrote way back at the start about encapsulation and inheritance (along with message passing). So I'm not sure my definition really is overly broad.
I'm also mostly talking about the runtime consequences of the things that most people worry about at the time of programming.
> The components on the entity define not only it's data but also it's behavior by the set of systems that act on the corresponding components. And you can take these object definitions and inherit them to add additional behavior or change the existing behavior by adding more components to the new definition.
This seems to miss what ECS actually is, unless you're just referring to the old-school way of doing entity components and not the data-oriented way.
Data-oriented ECS way of doing things is to separate state and behaviour. Entity components essentially become structs where their only behaviour is potentially some getter/setter utilities.
Behaviours are then state-less systems (just functions, essentially) which act on a set of components.
For example, a PhysicsUpdateBehaviour might take in a RigidBodyComponent and a HealthComponent to perform a physics update and apply physics/fall based damage.
The main benefit of ECS (imo) isn't even really performance. It makes code in complicated game projects much easier to manage by clarifying the game loop and by making it much more obvious how and when entity state is being modified.
It's the kind of thing that potentially complicates a smaller project, but makes larger more complex projects easier to manage.
I know what an ECS is. Components are decoupled from systems (not not visa versa) but the actual behavior of an entity is defined by the set of systems that run on the set of components so in that sense the set of components defined what the Entity is including it's behavior. An Entity is defined in terms of it's data and it's data brings along behavior.
Sure ECS is object oriented in the same way C99 is. Yeah, technically you are building up some OOP functionality, the same way you emulate constructors and instance methods in C by making functions to init data structures and functions that take references to a struct to modify it's data. That doesn't make C object oriented.
In ECS you are decoupling data from behavior, which is basically the entire paradigm of languages like rust and go. You could argue that by defining systems in a way that they run on certain components you are defining behavior and data in one, but I think that's a stretch.
It clearly differs from OOP when I have 2 entities with components that have overlapping and non-overlapping systems. If e1 has components c1 and c2, and e2 has components c2 and c3, and c1 and c2 are used in system s1 while c2 and c3 are used in system s2, I don't see how you would model that with OOP without adding data to classes that don't need it. In OOP both e1 and e2 would need all the logic from s1 and s2, or needlessly specialized versions of s1 and s2. Which would be solved via inheritance (either class based or interface based).
In ECS your data exists in an array of components and any part of your program can operate on any component however it wants. I've never needed message passing for anything I've worked on.
That's not to mention that the main benefits of ECS have nothing to do with language paradigm. ECS main advantage is cache coherency and easier parallelism.
You're too worried about the underlying implementation. Think a bit more about the runtime expression in terms of the resulting Entities and how the set of components linked to them defines data and behavior and what you could do conceptually to extend that.
> That's not to mention that the main benefits of ECS have nothing to do with language paradigm. ECS main advantage is cache coherency and easier parallelism.
My comment has nothing to do with implementation. We're talking about ECS in the context of DoD so I'm not sure what relevance being data-oriented by default has. It seems like you're just confusing the concepts of ECS, DoD, and composition.
Your entire post on ECS is, "If you don't use DoD with ECS than you're not using DoD". Well yeah.. obviously? If you implement an ECS and then use it without data-oriented structures, then yes obviously you don't have data-oriented design.
You're creating a strawman. You're saying if you take ECS, remove the idea of storing components independently of entities, and pass them in an inefficient manner to systems, then you don't have DoD. ECS isn't inherently DoD, literally nothing is. Arrays aren't inherently cache friendly. There's nothing stopping you from making a language that allocates data randomly throughout reserved memory and every array element points to each location. No one is arguing ECS is inherently DoD, but it is a good design to facilitate DoD.
> For example we might want to do damage to another entity entirely.
Add a damaged component to the entity to damage. Consume damage component in a system.
> Or we might want to look up the properties of the piece of ground we're stood on
Use a position component on the entity standing on the ground. Consume the position in a system and look at the properties of the terrain map at that position. Even simpler for grid based maps.
> We're also ignoring interacting with other components or the world and how that might work
You interact with other components by defining interactions in systems based on those components.
You've created an ECS in a way that doesn't take advantage of any of the benefits, and complaining that all you're left with is the disadvantages.
The approach I talk about with archetypes is the one used by Unity and many open source ECS implementations. It’s a pretty standard way to solve the issue.
From a distant point of view, everything is OOP. You can treat anything like a black box that you push button on to make things. You push things on your keyboard, without knowing how it works. Your keyboards activate things on your computer, without knowing how it works. The computer ask the screen to update with the new date, without knowing how it works.
From a distant point of view, everything is data oriented. Your thought are transformed into keyboards presses by the keyboards, that are transformed into events by your computer, that is transformed into what you see by your screen.
I could do the same with a frozen pizza factory: you can see the ingredients flow in the machines (functions), or you can see the different machines passing things to others like objects. The problem is that then the classification between "OOP" and "non-OOP" doesn't mean anything anymore and is now useless.
This isn’t a distant point of view though. I make games every day professionally and think a lot about how to make making them more accessible. Both are part of my job. People building them shouldn’t just consider how an ECS looks under the hood but how it works for someone using it which is as a system for building runtime objects. Particularly if you look a little deeper than the popular Internet view of the basics to what an actual usable implementation looks like.
ECS is really just OOP with dynamic multiple inheritance: an object can inherit from multiple base "classes" (with "components" providing the data and "systems" providing the code) and this inheritance structure can be changed at runtime, by adding/removing components. Everything else (struct-of-arrays vs. array-of-structs) is just low-level implementation details.
When I implemented a variation on ECS for a game I'm building, I did exactly as you suggest, re, message passing: components receive and respond to messages but their implementation is hidden.
Technically, it's neither. Unless your programming language directly supports ECS, you're not going to be implementing the relationship between entities and components as either proper inheritance or a collection of data members, because neither of those can be changed dynamically.
No, "technically" and in all other regards, it's composition, plain and simple.
There's nothing special about composition that it requires language support, or that it has to be static for it to be considered composition. You can implement dynamic composition in OOP simply by having a List of components, and that's how many games that don't use ECS did and still do. Composition has absolutely nothing to do with inheritance or with requiring static data members.
Unlike you're claiming, the relationship between entities and components definitely does exist in ECS, just not in an OOP way, because ECS is not OOP (even though ECS can be implemented in any language).
Inheritance is one way of describing it but I don't think the term really fits. An object is composed of multiple components, and the composition can be changed at runtime. Saying that the object inherits from multiple base "classes" seems like it just makes the concept less clear.
Some languages have class-based systems with inheritance: one class inherits from another, and methods implemented in the superclass can be used in the subclass. Some languages have prototype-based systems with inheritance: one object inherits from another, and methods implemented in the prototype can be used in the object.
Component-based systems don't really fit my mental model of inheritance here.
ECS (which I have not used) sounds a lot like Traits. The name and core concepts for Traits were defined in 2003 in an ECOOP paper [1]. I think traits were first implemented by Squeak Smalltalk in 2005.
Very similar although in an ECS the relationship is backwards, Entities get behaviour based on what data they contain rather than getting behaviour from traits and needing to add state to make them work.
The ECS approach can lead to some confusing things like adding a component to an Entity and having strange behaviour result as a system the programmer didn’t expect to be triggered is run. This can lead to systems having quite complex definitions based not just on the components the system needs to run but also on the components that shouldn’t be present and so on.
Then if you solve the entity communication conundrum with message passing and don't allow entities to directly access one another's data you basically have all the elements.