Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

Question What are the advantages of ECS separating behavior and components over classic unity(OOP)?

Discussion in 'Entity Component System' started by sass00n1, Dec 3, 2022.

  1. sass00n1

    sass00n1

    Joined:
    Oct 31, 2018
    Posts:
    4
    Note that this question has nothing to do with performance and multi-threading!:(
    Note that this question has nothing to do with performance and multi-threading!:(
    Note that this question has nothing to do with performance and multi-threading!:(

    I would like to ask you for your thoughts on game architecture. The conclusions of searching this kind of problem on Google are all vague, saying that this kind of separation is more modular, decoupled, maintainable, etc. But there is no comparison between ECS design and classic unity design examples. It's all a description of what others say, without any actual examples.

    In classic Unity, for example, if I create an enemy movement MonoBehaviour component, I can mount it to all enemies, and if I want to change it, I only need to change it once.

    In ECS, an enemy movement system is established to manipulate all enemies, and if you want to change it, you only need to change it once.

    Both of these need to be changed once. I don’t understand why ECS is more modular than classic Unity in terms of architectural thinking. Can you give me a practical example?

    In the past few days, I have carefully read the blog of the ECS paradigm definer: http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog- development-part-1/ he said the entity system meant I could implement the complex latency hiding and prediction techniques with minimal interference with the coding of all the rest of the game systems. When you've got 20+ programmers on a team, You really don't want to be placing yourself in a position where you're going to have to redesign code for all of them just to make it “network friendly”.

    But with classic Unity, similar functions can also be achieved. For example, you only need to attach a "NetworkTransform" to the gameobject, and then the network code does not need to interfere with the main game logic. Classic Unity also uses the component pattern to reduce cross-cutting concerns.

    So in ECS, separate behavior from components. How is it better than classic Unity? What is the philosophy behind it? what is the reason?Are there any real examples to illustrate?

    In other words, how is ECS's method of traversing "entities" relying on "systems" better than traditional OOP? OOP divides the game into individual game objects, and then lets them communicate with each other. Is this method really not suitable for game programming?
     
    Last edited: Dec 3, 2022
  2. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    330
    In many cases it makes handling logic easier. My problem with OOP is the class is the owner of some data, instead of the Entity. All logic should be able to easily access data.

    Example: When you're trying to separate your Controller class to multiple scripts, other scripts need to reference the Controller because they need to read data from it. After some time you're trying to prevent cyclic references between scripts but you can't, because you need to read that data. You give up eventually and just make it one big script.

    Example: I want Destroy all 'Dead' Objects. How would I know Player is dead? Well it has PlayerController MonoBehaviour with "bool IsDead", Enemy has another script I need to check, other one has another script I need to check... You may derive from base classes but not always, and inheritance turns into mess quickly. Even if you have a single script for checking if an Entity is Dead, it's still messy because why would I need a script to do that? Compare with ECS: Just include 'Dead' components in you Query and done.

    Example: Entity enters a slow terrain and I want to slow it down by 30%, with GameObjects okay which script I need to check for slowing down... with ECS just grab the MoveSpeed component and reduce it (this is very simplistic but hope you get the point).

    Modularity Example: If Player can have slow resistance in his PlayerController but Enemies can't in EnemyController, now you have to parse both of the scripts separately. In ECS you just check if Entity has the SlowResistance component, you don't have to know if it's enemy or player.

    Easier debugging in my opinion, data is transparent and you can see read/writes easily, and check at runtime which systems are operating on your Entity at runtime.

    But still I think performance is the strongest argument for the architecture.
     
    Last edited: Dec 3, 2022
    RaL and sass00n1 like this.
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,112
    The main benefit of class component on GameObjects, is the inspector parameters. You can change quite easily.
    Wih DOTS manipulating individual entity components via inspector is harder. But tha may change in upcoming Unity DOTS.

    Without me repeating great points above, is the separation of data from the logic.
    Last thing you want to handle, is tones of circular reference between OOP components.

    And event mentioned, I repeat too, is the performance gain, which is massive by thousands.
     
    RaL likes this.
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,471
    Alright. Let's pretend we live in a world where performance in games is not a concern whatsoever. That means, in Unity, the most modular way to write code is to use GameObject.GetComponent inside of any method whenever we need a reference to another component. Let's also assume good Single-Responsibility Principle is practiced.

    So we have components A and B which each on their own have their own behaviors. But, when they are both present on a GameObject, some additional behavior is required. So, which component's responsibility is it to perform that logic? Is it A? Is it B? It can't be some new C because then someone is going to forget to add C to the GameObject.

    When you separate logic from data in an ECS, you get the benefit of existence-based processing and don't have to answer that question. You can add new relationships between sets of components without having to modify any of the components individually. That's a new level of modularity ECS offers.
     
    davenirline, apkdev, Vacummus and 6 others like this.
  5. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    1,837
    With Entities, Jobs and Burst, you also get the additional benefit of having certain architectural (and technical) rules forced upon you.

    This makes it harder to shoot yourself in the foot, or for most cases, it'll prevent you and your mates from accidentally deviating from the agreed-upon architecture, separation of concerns, etc - for classic OOP anything goes and you have to apply discipline to maintain a solid architecture and constantly review and refactor it.

    Entities and Jobs require you to work in a narrow subset of C# using only value-types and native collections so you are forced into a certain architectural pattern to begin with. It also puts everyone in the right mindset: no use of managed objects, and suddenly everything starts thinking differently and is more focused and attentive about the use of data and its processing.

    Also: Entities & Jobs code for the most part will not trigger garbage collection cycles. So you're also in control of memory allocations through native collections. You don't have that level of control with managed objects and for programmers it's often not a concern - but it can be a big issue, like when you allocate garbabe in a loop but you weren't considering that players might eventually have a game state so complex that the loop isn't just running hundreds of times like in your development playtests, but hundreds of thousands of times - possibly per update!
     
    Last edited: Dec 3, 2022
    Jes28 likes this.
  6. Laicasaane

    Laicasaane

    Joined:
    Apr 15, 2015
    Posts:
    147
    Let's ask another question.

    Let imagine that you are in a middle of a typical ARPG battle where a bunch of characters are fighting each others. Swords are swinging and hitting. Physical arrows and magical bolts are flying and exploding. AoE skills are being casted irregularly. Blood is spilling. Voices of shouting and crying are all over the place.

    So, could you tell for sure how many OOP objects are working at a frame of this battle? Could you point out which object is doing what action? And could you predict what will happen at the next frame?

    Or more technically: Can you know the state and every vectors of the world at a given frame?

    For this kind of battle, possibly there are hundreds of variables and vectors taking effect. And I don't think anyone can calculate all of them when the autonomous objects of the OOP world are working behind the scene.

    OOP objects are autonomous, but to work they must know something from the world surrounds them. They will be constantly querying or scanning the world and other objects to get the data they want. Furthermore some objects will always need the others to do something for them, such as object A needs to invoke some methods of object B. OOP objects are always in need of knowing what the others are doing, or waiting on some actions to get the result so they can resume their own work. Not many objects can work entirely on their own, and if you could figure out any, I doubt they would be of any importance. Important objects would always be highly coupled by method calls.

    This is where the spaghetti comes from when you oftenly have to let objects depend on the methods of the others. We have Inversion of (Creation) Control to solve the dependency on object identity. Do we have anything to solve the dependency on methods? A design for Inversion of Flow Control?

    At the end of the day, even though object A don't know about the identity of object B, it still depends on the methods of B to work - abstraction by interfaces. But what will happen if the implementation of a method changed? For examples, if the order of some method calls inside
    ObjectB.MethodX
    was changed, would the logic of
    ObjectA.MethodY
    still work? Usually we must also adjust
    ObjectA.MethodY
    because its assumptions on
    ObjectB.MethodX
    have turned wrong. Then what's about object C, D, E, F,... in this chain of dependency?

    With this kind of highly coupled dependency in our mind, lets answer the questions: Can we know exactly what is going on in the battle at a given frame? Can we be deterministic about it?

    To add to the high coupling of OOP methods, there it comes another problem: How can B prevent A from making assumptions about its methods? How can F prevent Z from using its methods the wrong way? How can a weapon require a character to utilise its functionality correctly? To enforce the rules on method calls we would have to invent more check-lists, more dependencies, and the flow of data and logic will quickly become a buggy mess.

    Do you still believe we can achieve true modularity with OOP? Can we truly work the Single Responsibility Principle into our architecture?

    I suggest you to read this series of blog posts from Sebastiano Mandalà (author of Svelto.ECS). He is one of those people who are utilising ECS to achieve better code architecture and maintainability.
    https://www.sebaslab.com/category/code-design/

    But TLDR; ECS is the highest manifestation of Inversion of Flow Control. It gives us the position of God where we hold the total control over our worlds. It also makes the chain of method/function calls, along with their rules and requirements, highly visible to us. With this God Eye view of the worlds, we can be very deterministic.
     
    Last edited: Dec 3, 2022
    Vacummus, CodeSmile and RaL like this.
  7. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    1,837
    Just wanted to stress that (good) point: determinism. ECS is entirely deterministic, including physics.

    This opens up new possibilities. I would imagine that designing physics-based games is easier, more reliable. Networking can be optimized if you know that a given state A always leads to a given state B. Likewise, debugging will be less time-consuming, there won't be any "random" fluctuations of position, trigger timing and such.
     
  8. TheOtherMonarch

    TheOtherMonarch

    Joined:
    Jul 28, 2012
    Posts:
    456
    I just want to note that OOP can be deterministic. OOP is not equivalent to .NET or MonoBehaviour. Classical lockstep determinism works fine with OOP. ECS does allow easier roll back.

    Separating data from code is not a concept that ECS invented. Rather it is part of Functional Programming. Literally that is the whole idea of Functional Programming separating logic and data in pure functions. Where Functional Programming starts having problems, I think is with data structures. Traditionally functional languages have immutable data structures and no random access. OOP is really good at doing complex data structures literally everything is a data structure in OOP. Functional Programming is the opposite literally everything is a function. ECS try to make everything an Entity but fails at that. Still it is far superior to MonoBehaviour.



    Advantages of ECS

    1. The advantage of ECS is that the state can easily be serialized/deep copied.
    2. It is designed for multithreading. This is really important we are getting more and more CPU core and standard OOP is failing badly at being able to utilize them.
    3. Minimizing random access
    4. Minimizing cache misses

    Disadvantages of ECS
    1. Everything is one giant shared state which potentially can create huge side effects. You need to understand what each system does.
    2. Purely using entities is not viable. Everything is not an Entity. Other persistent data structures besides ECS will get mixed in.
     
  9. Laicasaane

    Laicasaane

    Joined:
    Apr 15, 2015
    Posts:
    147
    Don't let them get mixed in then. We can abstract away anything that doesn't work the ECS way. Each tool for each problem and everything will be a black box to each other. There will be sync points along the way where we must compromise the performance. But it's OK as long as we know what we are doing.

    So another disadvantage of ECS: We have to have some skills in code design, at seasoned or expert levels. Or else we might unknowingly fall into the pit of mixins which is the worst of both worlds.
     
  10. TheOtherMonarch

    TheOtherMonarch

    Joined:
    Jul 28, 2012
    Posts:
    456
    True ECS would not have other persistent data structures accessed by systems. Right now that is not really viable. Your line of thinking sounds a lot like OOP.

    Storing index's/key's on a entity that correspond to a separate data structure is totally fine. It is obviously the opposite of a OOP black box. Thinking of systems as black boxes/classes is not a good idea.
     
    Last edited: Dec 3, 2022
  11. Laicasaane

    Laicasaane

    Joined:
    Apr 15, 2015
    Posts:
    147
    I didn't mean it that way. What I said is when you have to use something from OOP world for the game to work (GUI, Animation), you have to abstract them away from the ECS world. "Do not let them get mixed in" means you don't put OOP code into ECS code. We all know that ECS can't solve everything, thus Unity have to resort to the hybrid approach. But it's fine as long as we let the 2 worlds separated. ECS don't have to know how GUI or Animation works. But there will be sync points where ECS can transfer its data to the OOP world. And these "sync points" will be the only place that depends on the "black boxed" interfaces of the OOP world.
     
  12. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    197
    That's because the "mono behavior" workflow has the same "conceptual" architecture ideas as ecs....
    Ecs just has a different underling implementation with a lot more restrictions and thus more performant.

    Doing an entity query is functionally the same as doing a FindObjectsOfType(). And nobody prevents u from having mono behaviors (u know, "components") that only contain data and then others that only contain logic.

    To be pedant, ecs is framework that implements and enforces a given architecture. Oop is paradigm. They are in completely different levels of abstraction.



    Now on the particulars, and please don't take this as somethin personal:

    The "separation of data from logic" is a moot argument. There's nothing that makes doing that "always better" than not doing that, it all comes to what u are doing and how u are doing it. (hint, study the history of software architecture and programming paradigms).

    Unfortunately a lot of people in these forums wrongly takes the properties of a particular architecture or a particular library/framework implementation and attributes it to the "oop" paradigm. Its like saying that because you don't like "red paint", all "paint" is bad and we should always use wallpapers instead of paint in our walls. It ends up sounding as some sort of religious sermon instead of a software architecture discussion....

    Serializability? That has nothing to do with ecs or oop or whatever, but on how u organize your game state. The series of restrictions and underlining data layout enforced by ecs can make it easier to serialize a whole game state automagically but it does nothing if u need to do manual "cherry-picking" serialization of some entities and their data.

    Coupling? That has nothing to do with ecs or oop or whatever. Please read more about software architecture... If u need to decouple things, there's dozens of patterns to do so. Heck the "game object" workflow always had the "sendMessage" api which decouples things (if it is slow or not that's something to blame to unity. the api itself doesn't have to be). With ecs u are kind of forced (not really but almost) to use a very simple/loose/inefficient "event based approach" to communication by creating and destroying "flag" entities and things like that. Again, nothing unique to "ecs", there's hundreds of really efficient event based architectures/designs.

    Determinism? That has nothing to do with ecs or oop or whatever. It happens to be that unity decided to implement a new physics engine with determinism in mind along with ecs. It could have been done under any other framework/paradigm.

    For all the other "imposed" restrictions that come with ecs (value types, etc). Nothing prevents us from using the same restrictions in whichever paradigm, and such things always come with their own set of trade offs (no free lunch yet again).
    "Use value types so u don't generate garbage. Garbage is terrible etc etc". It's not like it is a bad general advice but... does you game really care???
    Just to give an example... "minecraft' is the best selling game, ever. "Modded" minecraft is one of the most streamed (and probably played) games ever. A typical minecraft mod pack allocates hundreds of MB per second......
    So... would it be better if it didn't? probably..
    Does it need to do it to be a successful game? evidently not..


    Again, please don't take any of these as some personal attack. I happen to be a software architecture professor and well, being "firm" when communicating is part of the job :p
     
    Jes28 and Lurking-Ninja like this.
  13. Laicasaane

    Laicasaane

    Joined:
    Apr 15, 2015
    Posts:
    147
    Alas, I'm not trying to make successful games. So I think I can speak for another perspective.

    My job is to ensure a project can be maintainable for a long time, by different people. After utilising various OOP patterns in many projects, I realized that OOP is not the good way for games. For a game that grows its content every month and its devs can be replaced every 6 months, OOP will soon open the hell gate unknowingly or unavoidably. You might argue that documentation is the way to go. But we don't really want to write documentation, or to maintain it separately. Documentation takes time but we don't want to spend time on it. Nevertheless the code indeed needs documentation. So I was trying many things in hope to find a way to impose specifications in the code: they must be explicit and always visible to the devs, without hidden rules. The code is then the main documentation.

    It just that I came across ECS reasonings that resonate greatly with me. And after some experiments I finally choose ECS over OOP. In the long search of my past I'd never encountered anything in the OOP world that is very useful to assist my cause.
     
    Last edited: Dec 4, 2022
  14. Jes28

    Jes28

    Joined:
    Sep 3, 2012
    Posts:
    972
    We start move discussion into ECS vs OOP land.

    Just want to sate that there is not such thing as ECS vs OOP because ECS is OPP and it better implementation of OOP than tools integrated into languages.

    One of good articles that show this idea:
    https://www.sebaslab.com/the-quest-for-maintainable-code-and-the-path-to-ecs/

    My personal thought is:
    If your OOP don't looks like ECS before you even first hear about ECS than your OOP was bad :)
     
  15. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    190
    This GDC talk from 2017 gives some good examples of how ECS has dramatically reduced the complexity of implementing their game, Overwatch, and how it made their netcode more deterministic (and also why it was not possible to get similar results with OOP):

     
    RaL likes this.
  16. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    190
    ECS != OOP. ECS at its core, like functional programming, decouples data from behavior. But OOP couples data with behavior via encapsulation. The moment you stop coupling data with behavior then you are no longer doing OOP.
     
    RaL likes this.
  17. Salmakis

    Salmakis

    Joined:
    May 14, 2018
    Posts:
    15
    This is a nice discussion, thanks to all contributors.

    How can the physic be full deterministic if it is using float point values for its calculations?
    What am i missing here?
     
  18. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,471
    It is deterministic on consoles and devices with Apple-made CPUs. And that determinism is determinism that comes from ensuring operations on shared containers are deterministic. The moment you use NativeThreadIndex, this is lost and can only be recovered by sorting data based on a deterministic index. And for all other platforms outside the ones I called out, square root operations are a bigger problem than people realize.