Search Unity

What's your opinion on ECS? (video by Bobby Anguelov)

Discussion in 'Data Oriented Technology Stack' started by laurentlavigne, Dec 29, 2020.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    5,371
    This is one person's opinion and some of the examples are not much to bother with (skinmesh) so, since you folks have a lot of experience with Unity ECS, what's your opinion about it in regard to making gameplay, dealing with hierarchy, cognitive load, human error etc...?

     
    Last edited: Dec 29, 2020
    GilbertoBitt and Lukas_Kastern like this.
  2. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    659
    I was glad to see that many of the issues he raises about ECS (like trouble with deriving components, or tag components leading to cache misses) are solved by Unity’s approach (no derivation, chunks).

    Ditto for things like group transform changes.

    Also I’m not sure why he considers it a problem that a single GameObject might be represented by multiple Entities in ECS. Besides changing how you think about it, it’s never caused us much trouble. You can always store Entity IDs in the components of a parent entity. Often the composite Entities don’t even need Transforms, or to live in a scene hierarchy.

    I respect his experience, and what he’s saying. But I do think he’s a bit too quick to spot a potential issue and stop there, saying “Look! Issue.” Without actually trying to solve it. It was too difficult to think of solutions to some of his problem cases, while I was explaining them. Though - I did have the benefit of an outside perspective.

    In general, I wish he would make examples to prove that his problem cases are actually problems.
     
    Last edited: Dec 29, 2020
    Jes28, RaL, GliderGuy and 5 others like this.
  3. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    What is the total cost of a GameObject? What is its biggest cost factor? Is it the Transform-component? I remember reading something about this a while ago but can't seem to find it again. I think it was related to the amount of functionality that a GameObject inherits, which will always be there, but will it be an issue if it is not used in terms of performance, or will it only impact space used?
     
  4. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    659
    Man, I’m sad to say that by the end of his section on ECS, he’s convinced himself that a lot of problems exist, when they don’t need to. He’s walking away from things for which there are good solutions.

    and he’s scarecrowing all over the place with imaginary claims about what ECS should be able to do. I was disappointed.
     
  5. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    659
    I did like his example about transformation dependencies in a modular tank. He talked about the need to rotate a turret to point at a target, on the same frame that the tank moves forward. If the tank and turret are separate entities, he reasons that you’d need to run your systems like this:

    1. Calculate tank movement.
    2. Update tank and turret transform.
    3. Calculate angle turret need to rotate to face target.
    4. Update turret transform.

    That’s a good example of something that isn’t super elegant in ECS...but only if you insist on solving that problem like you would in a GameObject-Component setup.

    for example, you could store an offset value in a component on the turret entity. Then you could use that offset to find the position at which the turret will sit after the tank moves. Using that position, you could calculate the turret’s angle.

    so your systems could look like:

    1. Find tank Movement.
    2. Find Turret Angle.
    3. Update Transforms.

    I imagine he’s have a problem with storing the offset data. It is a trade off. But the trade off of adding that extra variable is getting use all the other ECS DOTS benefits.
     
  6. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    9,093
    It is the burden it carries with it. The tons of null components, which may not be required for specific project.
    Now if you multiply that by 10k or 100k of instances, that quite a bit of memory taken unnecessarily.

    With ECS, you can clamp components number, to only what is required.

    I think that this is subject to the design requirement, or even maybe a design issue, if doing so.
     
  7. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    659
    Well, not necessarily. There can be benefits to splitting up one “thing” into several entities. Logic can be simpler if different parts of an object have their own transform components, meshes, or animations.

    splitting things up can also help with chunk utilization - keeps the component count down, per entity. But Id say this is never the reason, alone, why you’d want to do it.

    And it’s not necessarily going to give you a perf hit, either. If your code is organized well, I’m not sure it’s really something you need to avoid.
     
  8. nyanpath

    nyanpath

    Joined:
    Feb 9, 2018
    Posts:
    77
    And which are these? The ones you AddComponent<T> with or something else?
     
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    9,093
    Assuming we are talking about Unity GameObject right?
    I suggest to put breakpoint in debugger in VS, or other IDE you use, where GO is. Check its data.
    Then you may ask, do you need most of these components for that particular object?

    Not my example, but here is a case of transform alone



    And GO has many other components with it.

    It may be, that some of your entities may not require transform at all.
    But you have it in GO by default.

    That is fair point. I thought more about the case, where some entities represents specific set of components, for same GO. Like for example duplicates.
     
    Last edited: Dec 29, 2020
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    2,596
    Massive thought-dump incoming!


    0:00
    I generally agree with much of the presenter’s arguments on GameObject-Component architectures. It feels really good to get away from that regardless of performance.


    52:42 – Deriving Components for variations of functionality

    You can tell this is the presenter’s unfamiliarity with ECS speaking here. This issue is that in an ECS, systems explicitly define what they want, not what they “kinda want” which is what a derived component would be. If you are running into issues with derived components, that is a failure of creating abstracting components and abstracting entities. These tools are the ECS equivalent of polymorphic ScriptableObjects in classical Unity. And while yes, it can be a pain to refactor code that wasn’t initially designed to use these into code that does, the good news is that because of the explicitness of ECS, the breakages you introduce in such refactors tend more towards compiler errors rather than runtime errors. Also, the explicitness makes finding the touch points much easier as well. I have an example of what such a refactor looks like if people are interested.

    54:25 – Making existing systems NOT process otherwise-matching archetypes
    WriteGroups in Unity theoretically solve this. So do abstracting components. You need to be willing to modify source code though for this to work, as most systems don’t use WriteGroups. As for tag components explicitly coupling themselves to systems, he is thinking about it backwards. The tag component is data that represents “Hey, I’m not quite like the others, so treat me special” and the systems determine what that “special” means. It is the same one-way coupling as any other component-system relationship. If one system that checks this tag component gets refactored into multiple systems, that doesn’t necessarily break the component. This is a naming problem if anything.

    As for the proliferation of component types, yes. That does require some kind of organization and discipline to account for the complexity, just like any other complex thing. Establishing conventions, tooling, and abstracting components and entities when this proliferation starts to steer problematic is important. Unity’s ECS handles the performance aspects of this better than most ECS architectures.

    58:55 – Lack of Spatial Hierarchy
    That “single GameObject” is actually an “abstracting GameObject” and you can do the same thing with Entities. Now it is just optional. Really multiple renderables on a single entity is just deciding to merge all data on an entity that would represent a renderable into a single structure, and then having multiple instances of those in an array. You can still do that if you like, as long as your systems know how to work with that data representation. It is a lot easier to make a system work with that data type than to have every system have to worry about potentially multiple instances of a component on any given entity.

    With regards to the transform hierarchy being up-to-date, that’s not ECS’s fault. That is the fault of a transform hierarchy being thread-safe. The only sane way to do that is to have some kind of double-buffering. And ECS makes that easier by explicitly laying out read-write patterns so that you can write to delta components and read from absolute components. This is essentially free compression of your write buffer.

    1:09:20 – Performance of ECS
    He’s right. Most ECS implementations are actually pretty pathetic with regards to performance. Really the performance benefit they are getting comes from aggressive assumptions about things not being null which is more of a flaw of other approaches rather than an advantage of ECS. A lot of ECS architectures using indexing tables. Some of the better ones will use archetype arrays. Unity uses reusable memory blocks of shared archetype, which solves a lot of problems but has a very large cost to get up and running smoothly. Fortunately Unity is eating that cost for us (that’s kinda what they do as a Game Engine company).

    As for going wide or not, this is why a deferred job system is awesome! Unfortunately, some of Unity’s higher-level features like physics and animation make a mess of this. Physics has a stupid dependency API that makes it difficult to inject things because now you are worried about component dependencies, acceleration structure dependencies, and system dependencies. Those should really be one and a half things, not three. For animation, it actually is a system that runs sequentially and runs wide. There’s a sync point right at the start. And I don’t see that going away unless DataFlowGraph breaks the “no schedule jobs from job” rule.

    “Things become complicated, fast.” And this is why there’s a difference between an “engine” and a “game”. If you are going to do complicated things that require performance, things are going to be complicated. Don’t conflate complicated problems that need to be solved once (a task graph) with problems that need to be solved repeatedly (parallelizing a gameplay mechanic).

    There’s also a chicken-and-the-egg problem here. The reason we have low numbers of these dynamic entities is because that’s all we could afford to do with past approaches. With these new approaches, we can do a lot more. But yes, the cognitive overhead of task graph management in a project where task graph utilization (using .Schedule and .ScheduleParallel instead of .Run) is required is a problem for some programmers. I don’t have a solution for that right now other than “git gud”. But hey, losing multi-threaded performance by using .Run and not worrying about the task graph except where it actually matters still avoids all the other issues in the first 50 minutes of the video and gives you so many choices.

    1:16:52 – ECS is not DOD
    Nope it is not. But it enables a lot more things to be DOD than the Object-Component model does.

    1:35:08 – Inheritance is allowed
    Proper support for this and RAII is missing in Unity’s ECS. They aren’t the most performant things and you can still shoot yourself in the foot with them, but they also can be much simpler to understand compared to the alternatives.

    1:35:08 – Encapsulated logic on components
    I believe this is possible in Unity’s ECS. Unfortunately, components don’t have RAII which limits the usefulness of this. With C#8 readonly member functions and some clever API and maybe codegen magic, this could be taken a lot further.

    The rest is him discussion a spatially-mapped ECS versus an archetype-mapped ECS (what Unity and most ECS solutions use). It looks like a pretty sane object-oriented multithreaded ECS. It has some unique tradeoffs. I would need to benchmark it though before I could really say more. There's definitely a lot of cool ideas though. I like the forward kinematics for spatial hierarchy updates. If I had to C++ a game that was too complex for my old ES, I would probably do something like this with custom block allocators for the blob of components and just give up load-store costs since that is a pain to optimize out of C++ anyways.
     
  11. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    609
    Amazing video and I agree with the entire speech. One thing to note here is that he is not talking about Unity DOTS neither compares Unity's own GameObject-Component model with Unity's own ECS. So there are a few key points to note here when we bring it into Unity's context:

    - Lack of Spatial Components on ECS

    When using GameObjects in Unity, you already need multiple GameObjects to create a single object, as each GameObject only supports one Renderer, so there is literally no trade-off when compared with using multiple Entities.

    - ECS is not "performance by default"

    Unity's ECS neither claims to be "performance by default" too. Unity DOTS (ECS + Jobs + Burst) is "performance by default", ECS is just a key point here as it makes it easier to use Jobs + Burst with it and make everything work in parallel, which brings another point...

    - It is not common to have multiple of the exact same object in a game, thus parallelism is not actually an advantage

    On Unity's DOTS, you can have different archetypes being updated at the same time, as different systems can run in parallel as long as they don't touch the same components, so the AI can decide which path or action on the next frame to take while the game is rendering and stuff are being animated. So, there is an actual advantage in using Unity DOTS even for games that have only 1000 or even 100 objects being updated every frame.

    - Kruger Engine

    While the implementation itself is far different from Unity's (having pros and cons on both sides), the general idea can easily be brought into Unity's DOTS.

    The local system's logic stuff is very easily replicable on Unity by using specific tags components for each system that are a "local system", and then create authoring components to make specific "local systems" run in specific entities. I think that Unity's conversion workflow resolves most of the issues he points out about the need to manage different grouping of components for different situations.

    The transform updating every time a change is made to the local transform is easily achievable by calling World.GetExistingSystem<TransformSystemGroup>().Update() after you touch something that needs to be updated right away (or before something that needs to have stuff updated right away), while still taking advantage of multi-threading easily. But I don't see the need to do that most of the time, as usually delaying stuff one frame is not that bad in most use-cases.

    - My opinion on ECS

    My way of thinking has always been more ECS-friendly than GO-Component friendly, but Unity's ECS is still lacking a lot of features (like at least half of what we already have in GO world) while mixing GO with ECS is too confusing if you don't know all the gotchas. My hopes are that it all will get improved over time and in some years from now working with DOTS (both pure and mixed with GOs) will be at least as easy as working with GO only.
     
    Sarkahn, GliderGuy and laurentlavigne like this.
  12. velenrendlich

    velenrendlich

    Joined:
    Jan 22, 2018
    Posts:
    215
    I personally dislike it when someone criticizes an architecture, and instead of going and making a game with what they think it's right and prove that their claim, they also go and make their engine again which nobody will use, since even that person didn't battle test it with making an actual game.
     
    Thygrrr likes this.
unityunity