Search Unity

Official Introducing the DOTS Best Practices guide (0.16, 0.17)

Discussion in 'Entity Component System' started by SteveM_Unity, Feb 9, 2021.

Thread Status:
Not open for further replies.
  1. SteveM_Unity

    SteveM_Unity

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    41
    My name’s Steve. I’m a Senior Developer Relations Engineer in Unity’s Professional Services team. This means that I work with a lot of different Unity customers, and a big part of my job involves seeing how Unity is used in “real world” projects and digging into the performance implications of those use-cases.

    Over the past two years, I have worked with a number of customers who were building DOTS projects, and saw some of these projects experience poor performance in comparison to what we know DOTS is capable of. One common thread was for developers to have difficulty stepping outside of the object-oriented mindset and truly understanding what data-oriented design is, what it’s trying to achieve, and why. Several problems arose from focusing on designing around the interactions between systems instead of focusing on designing the data itself. Several other problems arose from the performance characteristics of certain DOTS features such as structural changes not being well-understood or documented in sufficient detail.

    Part of my job is helping customers overcome these sorts of performance challenges. Another part of my job is to find ways to disseminate what I learn during those engagements to help a wider range of Unity users be successful themselves. Our team regularly give talks on optimization at Unite conferences, and we write technical blog posts and Best Practice Guides. The teams I worked with who were finding it difficult to get the performance they needed from DOTS were comprised of very smart people who I felt just hadn’t stumbled across the right documentation to help them. They needed DOTS Best Practices guide. So I wrote one.

    You can find it here:
    https://learn.unity.com/course/dots-best-practices


    It’s aimed at programmers who are using DOTS or thinking of using DOTS to improve the runtime performance of their projects. The guide is split into three main sections:
    • Part 1 links to some additional learning resources and summarizes the key principles of Data-Oriented Design.
    • Part 2 walks you through the process of designing your data structures for maximum efficiency, including some dos and don’ts of DOD
    • Part 3 takes you step-by-step through how to implement systems which will process your data in the most efficient way possible, and it’s broken down into four subsections:
      • Fundamental things to consider when writing DOTS code
      • How to manage your data transformation pipeline to minimise sync points and structural changes
      • Techniques for minimising cache misses
      • Advice for getting the best out of Burst, the Unity.Mathematics package, and SIMD optimisations
    I hope you find the guide useful, and I’d be happy to hear any feedback.
     
  2. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Very cool, I'm just skimming over it and I've already learned a few new things! This table from part 3.2.4 is really low resolution though, very hard to read.
     
  3. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Also this part from section 3.2.3 is using a command buffer system, but I'm not sure it's actually doing anything?

    Code (CSharp):
    1. public class AnimatorRef : IComponentData { public Animator animator; }
    2.  
    3. public struct AIState : IComponentData { public CharacterActivity activity; };
    4.  
    5. [UpdateInGroup(typeof(SimulationSystemGroup))]
    6. public class AIStateSystem : SystemBase
    7. {
    8.     protected override void OnUpdate()
    9.     {
    10.         var bufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    11.         Entities.ForEach((ref AIState animAIState) =>
    12.         {
    13.             animAIState.activity = SomeComplexStateMachineLogic();
    14.         }).ScheduleParallel();
    15.          
    16.         bufferSystem.AddJobHandleForProducer(Dependency);
    17.     }
    18. }
    19.  
    20. [UpdateInGroup(typeof(PostSimulationSystemGroup))]
    21. public class AnimatorRefSystem : SystemBase
    22. {
    23.     protected override void OnUpdate()
    24.     {
    25.         Entities.WithoutBurst().ForEach((AnimatorRef animatorRef, in AIState aiState) =>
    26.         {
    27.             animatorRef.animator.SetInteger("State", (int)aiState.activity);
    28.         }).Run();
    29.     }
    30. }  
    Correct me if I'm wrong but since AnimatorRefSystem is already set to update in a group that's after SimulationSystemGroup and it includes the AIState as an "in" component it should already ensure the original job completes shouldn't it? I'm confused what the purpose of the bufferSystem is here.
     
    scarface117 likes this.
  4. iamarugin

    iamarugin

    Joined:
    Dec 17, 2014
    Posts:
    883


    This image from the guide is not readable.
    Also it is also better to use World.GetExistingSystem instead of GetOrCreateSystem for standard command buffer systems (and cache them at OnCreate), because they guaranteed to be created at that point.
     
    MNNoxMortem likes this.
  5. Ashkan_gc

    Ashkan_gc

    Joined:
    Aug 12, 2009
    Posts:
    1,124
    I read this and We are using ECS and DOTS for more than 8 months on a project and I read unity projects and used it on a now shelved project for few months when still foreach only worked in single threaded component systems. I think this is really good and can be turned into a guide for the manual.

    What I learned after this long time. using UpdateWithSingleton to enable/disable systems
    the SIMD example was nice despite i knew how to do it

    What I would still love to see. How to allow users to extend my systems. examples like jobs which you can give to physics system (ITriggerEvents is a good example). Generic structs implementing interfaces (like what NetCode has)

    ..
     
  6. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    987
    I like it. Lots of good information on Part 3. Non beginners can skip part 1 and 2.
     
  7. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    232
    This is really great info and helped me finally figure out a lot of things(like the correct way to dispose of nativearrays). Thanks a lot for this, and I'm hoping there will be lots more info like this.
     
  8. jdtec

    jdtec

    Joined:
    Oct 25, 2017
    Posts:
    302
    I thought part 3 was great too. Fleshed out my understanding for somethings.
     
  9. SteveM_Unity

    SteveM_Unity

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    41
    Hi all. Thanks for the great feedback, and apologies for the slightly delayed responses to some of the points made here.

    A few of the images got reduced in size during the publishing process. I'm told that the Learn team are working on a redesign for some of the content (including this guide, when the changes are rolled out) which will include the ability to view all images at full resolution. In the short term I've asked if the table image from Part 3.2.4 can be included as a "Material" in that section of the guide, the same way that the OOP vs DOD diagram in Part 1 is (there's a link at the top of the section which takes you to a full resolution version of that image). That should be happening fairly soon, but in the meantime I'll attach a copy of the full res table image in case anyone here needs to see it.
     

    Attached Files:

  10. SteveM_Unity

    SteveM_Unity

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    41
    You're not wrong :). I think that was a habit I picked up from the days before SystemBase and nice automated dependency tracking. The ForEach that is Run() in the second system has a dependency on the job that's scheduled from the first system, so that job will be completed when the second system executes, if it hasn't already completed at that point.

    I will make sure that the references to the Entity Command Buffer System are removed from that code snippet the next time the guide is updated. Thank you, and well spotted!

    Glad you like it! I don't imagine every part of the guide to be useful to every reader, but I thought parts 1 and 2 were important because I've seen very smart teams start using DOTS whilst misunderstanding what data-oriented design is, what it's trying to achieve, and how. Stepping out of the OOP paradigm can be difficult or scary for developers who may have never done it before, so I wanted to provide resources to help people fully understand what DOD is about and how to focus on designing data rather than systems.
     
    FlavioIT, Egad_McDad, DrBoum and 13 others like this.
  11. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Very informative, especially the SMID part. I'm already happy about the performance of my code but it's great to see that I still have room for improvements :D
    Thanks for taking the time to make such great resource.
     
    adammpolak and Krajca like this.
  12. twaananen

    twaananen

    Joined:
    Jul 24, 2017
    Posts:
    23
    Awesome guide, I've been hoping for something like this to show up, thank you!
    I've been doing almost everything in Entities.Foreach including structural changes, and I have been using a lot of GetComponentDataFromEntity, for example looking up stored input and movement from other entities. So I have a lot of rethinking to do.

    What I would like to see is some more examples in Part3. Also I'm wondering if there is a recommended way to go about denormalization. For example, I'm going to break down my massive heavy entities into smaller functional entities. Many of those will need data about the user input or the last speed or health of the unit. Straight forward way could be to iterate over LinkedEntityGroup/NativeArray/Buffer from the parent/related entity, check if they have the target component and set its value. But what is the cost of this and is there a better way? I loved the informative performance comparisons on ways to do structural changes, but what about setting data on other entities?

    Also, I would love to see similar Best Practice courses for the other dots packages, like physics, netcode etc.
     
    Egad_McDad likes this.
  13. SteveM_Unity

    SteveM_Unity

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    41
    This is going to sound like a cop-out answer, but I don't think there is any particular piece of advice about this which will be applicable to all (or even most) situations. I think it depends heavily on your data. So you take a piece of data (health, for example), and you ask yourself:
    • How often is this likely to change for a single "grouped entity" (one of your big entities which has been broken down into smaller ones to avoid chunk fragmentation)? Every frame? Twice a second? Once every few seconds? Minutes?
    • How many of my "grouped entities" are there updating in the world, typically? How many chunks do they occupy?
    • In a grouped entity, how many sub-entities actually need to access the current health value?
    • How often do each of those entities need to access it?
    Your aim is to minimise cache misses. That's why you're considering breaking your "heavy entities" down into "grouped entities" in the first place: to pack more of those into fewer chunks, and to avoid the cache misses if you have to do operations which iterate over multiple chunks on a single thread. If you understand the size of your data, you should be able to calculate (even roughly) how much more data you can pack into a chunk for a given archetpye, and so how many cache misses you'll avoid in the systems that operate on that data when you do so.

    But now you have the problem that the data you want to access isn't always in a component on the same sub-entity that you're processing. Do you just hold on to an Entity reference, go look up the data and live with the cache miss, or should you denormalize by duplicating that data to the sub-entity so it can avoid the cache miss? Depends on how often you need to access that data. If you decide to denormalize, you now have the problem of deciding which copy of that data is the "definitive" version you'll treat as the one source of truth, and also the problem of keeping them in sync - a process which will inevitably involve cache misses. So you need to figure out how many cache misses, and how often, and compare that to the cache misses you're saving by breaking down the entity.

    Maybe you need to think about HOW to break down the heavy entity - like, maybe this shared data defines how you split the original entity up. So you make a sub-entity of all the components that need to access health a lot, another entity of components that are all interested in speed... And you have that one system that wants to access health AND speed...

    My point is, all of this work is about minimizing cache misses. For your data design and predicted data access patterns, you need to figure out which approach will achieve that.

    I'd like to see those too. Right now I don't think I'm the person to write those, because I haven't used them enough, and also the APIs and performance characteristics of those packages perhaps haven't started to settle down in the way that it feels like the Entities package is beginning to. I'm sure they'll come at some point in the future, but I wouldn't want to speculate on when.
     
  14. Per-Morten

    Per-Morten

    Joined:
    Aug 23, 2019
    Posts:
    119
    Really cool to see this guide popping up, hoping for more stuff like this in the future. :)

    There's one thing I think is rather strongly implied (please correct me if I'm wrong) in the "Data Design" section and potentially "Key principles" section that could perhaps be a bit more explicit. When I read "5. Consider the most common use-cases, and optimize for those" I get the understanding that "I should store data A and data B together, because they are most commonly accessed together in my systems", or, "I should split data A and data B apart, because one system only needs data A, and but these two other systems only need data B.".

    However, the thing I think is also implied in the secion that could be a bit more explicit is the "How should I structure my code based on what I know/can learn about the values in the data".
    Like, I'm not explicitly getting this understanding "I should put data A in this location, and data B in this other location, because, even though A and B are of the same type, and could be processed in the same manner, I know that the value of A is part of a 90% situation where I can detect a pattern and look up a precomputed result, while B is in the 10% situation where I need to do a heavy calculation"

    To try and highlight what I mean. When I read: "Structure the data to make the most common operations have the most efficient access", I think that I should put data A and data B together, because the most common, and only operation, if I don't know anything about the values in my data, is the heavy calculation. However, if I had read for instance: "Structure the data to make processing of the most common situations have the most efficient access", I might have been more encouraged to inspect the values of my data and learned about the 90%/10% situation that was there. Towards the end of the paragraph the optimization for the common case is mentioned, but at that point I'm so primed for only thinking about "operations", that I would probably also tie "case" to operation, rather than potentially common values.

    My intention isn't to be super nit-picky on specific wording, rather that the whole "inspect, understand and take advantage of your understanding/knowledge of the data your processing" part of DOD was what I struggled with the most when I started learning about DOD. It wasn't until I did my master thesis on DOD and actually got to analyse literature and talk to practitioners that I properly understood it. I think this is partly because that aspect was the most implied aspect in the literature I surveyed. So, I think it would make the best practices guide even better if that was more explicit.
     
  15. SteveM_Unity

    SteveM_Unity

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    41
    @Per-Morten , I'm having some difficulty understanding what you're trying to say exactly. Is there some concrete example of a situation where you think the wording in the guide might lead people to make decisions that are bad for performance?

    I definitely think that "inspect, understand and take advantage of your understanding/knowledge of the data you're processing" is a key part of what I'm trying to communicate in the Data Design section. Are you saying that the guide lacks nuance in that it glosses over branching logic based on the values of the data itself? The worksheets that are linked in that section are perhaps a bit simplified in that they consider the quantity of a given type of data and its read/write frequency, but doesn't really discuss what happens if different values of the data require different processing paths with different CPU costs.

    If that's what you're saying, I think you're right that the guide is an oversimplification in those cases, but in my experience I haven't found those cases to be particularly common, and not necessarily even a problem (unless the CPU branch prediction was failing and/or the branch was stopping good SIMD compilation in a performance-critical job). And if/when that problem does come up, there are a number of fairly simple ways to solve it. One possible solution that immediately springs to mind would be to combine all of the data of the same type together, but write a system that checks the buffer for the 10% case and adds a tag component to the entities with components that fall into that case, so that EntityQueries can choose to filter for the 90% or 10% case based on the absence or presence of the tag, or ignore the tag entirely to process the whole data set.

    I agree that you need to understand your data well to consider such designs. I think that by the time you're making these kind of optimizations, it's hopefully because you already do understand your data well enough. I can't right now think of an effective way to modify the guide to prepare people for the idea that they might find themselves making decisions like this without increasing the complexity of the guide. I'll consider this some more.
     
    MINORLIFE likes this.
  16. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    Watershed moment in DOTS history @Steve_McGreal !

    I think everyone using ECS has been hoping for a "Unity-recommended approach" and generally I think the vibe has been "it is changing so it is not worth the effort". This clearly took a huge level of effort and I personally appreciate you doing it instead of pushing it off because "ECS is evolving".

    Maybe it would be a good idea to add this guide to the front page of ECS documentation: https://docs.unity3d.com/Packages/com.unity.entities@0.17/manual/index.html ? This is really a terrific resource and I think getting it in front of as many devs interested in ECS couldn't hurt

    Thank you!
     
    daniilkorotin, Egad_McDad and pcg like this.
  17. Per-Morten

    Per-Morten

    Joined:
    Aug 23, 2019
    Posts:
    119
    Sorry for the confusing post, reading it again I see I clearly hadn't structured my thoughts as well as I thought. I'll try to be more direct.

    No, I think the guide is really, really, good in its current form. I was trying to suggest that it might be improved further if the "value inspection" aspect of DOD was a bit more explicit in the Data Design sections.

    Yes, that's partly what I'm saying, but I wasn't only thinking about branching logic, but rather a bit more general. I'll give two examples.

    1. I once worked on a solution for mapping a name (string) to an object in the game world at runtime. The name was supposed to be unique, however objects with the same name could exist, and in that case we needed to return those objects as well. This was implemented with a hashmap that mapped the name to a list containing all the entities representing objects with that name in the game world (this was not DOTS ECS, we had an internal very primitive entities implementation). I was trying to optimize the creation of this hashmap and noticed in the profiler that a lot of time was spent allocating memory for the lists containing the entities. Wanting to reduce all that time on allocation I asked myself "how often do we actually have multiple objects with the same name?". So I found some representative game worlds and checked. The vast majority of the time a name mapped only to 1 object, sometimes 2, and very rarely 3 or more. So we were creating lists that would essentially hold 1 32-bit integer most of the time. The fix was to create a custom struct that embedded 2 entities (alignment requirements allowed me to get the second entity for free) in the struct and put any additional entities in a list, so we only allocated memory for a list if we had 3 or more entities. This reduced creation time, and also improved the lookup time, since we now usually only needed 1 memory lookup, rather than 2.

    2. I was toying around with an optimization for getting better index buffer compression on file that I learned about from a GDC presentation on the Insomniac Spider-Man game. Essentially it was about storing indices as a delta from the previous index in order to get more repeating patterns and therefore better compression. After changing my index buffers to being deltas I wrote them to file, looked through them and noticed that for a lot of my meshes I could use smaller data types to represent the deltas, because the deltas were so small that they never underflowed or overflowed 8-bit or 16-bit integers, allowing me to save some space when I wrote my meshes to file.

    Neither in these examples, nor the 90%/10% case is it necessarily hard to spot the optimization potential if you've picked up on the "I should inspect and understand the values in my data" idea of DOD. However, if you haven't and you're still a bit stuck in the "I'm going to write generic code doing abstract computation" mindset, that I, and probably others, is/was, then looking for distributions, patterns, or common values, etc. in the data isn't necessarily obvious. That would make the code less generic and less abstract, so it might not be something that you have experience with or even consider thinking about. Due to the "generic abstract" mindset, I don't think I would have inferred the value inspection aspect of DOD from the Data Design section. I think I would have "only" picked up on the type, quantity, and read/write frequency part, as the value inspection isn't as directly spelled out as those.

    I don't really have any good ideas either. It might also be that this line of thinking is obvious to most people already and that me from 2-3 years ago is a minority. Additionally, the examples I came up with, other than the 90%/10%, case isn't really DOTS Entities focused either, so it might be out of the scope of the best practices guide, particularly if you said that in your experience those 90%/10% aren't really common. It just sort of stood out to me when I read the best practices guide the first time that I'm unsure if I would have picked up on the value inspection aspect.
     
    MINORLIFE and Egad_McDad like this.
  18. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    451
    Very nice guide. I will try a datasheet approach on my current project. It would be nice if in the future there was some concrete example/case study. Something like this: datasheet -> implementation with common mistakes -> fixing common mistakes -> optimisation.
     
    DevViktoria and adammpolak like this.
  19. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    I agree, despite the guide was great at explaining the technical caveats of DOTS, but there are only a few good examples/case studies. Especially in the second part. I have a feeling that we get "think about data, design it well", but there was too little on how to do it properly. i.e. Breakout was a great example and I'm looking for more in quantity and complexity.
     
  20. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
    Even though I knew most of this stuff already, I did not know 'all' of it, and I was very happy to have the rest validated rather than "I hope I'm doing this right". Guide gives me confidence with my knowledge so far, reminds me of some important things, and teaches me some new things. Very appreciated.
     
  21. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    Yeah I learn this way too. When I first stared with Unity/Dots a month ago I jumped in with a tutorial off Udemy. Now that I can finally code/understand a little bit, I'm working through the docs :) Great to have high quality technical information introduced.
     
    adammpolak likes this.
  22. SteveM_Unity

    SteveM_Unity

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    41
    Thank you all again for your kind words. I'm really happy that people are finding this useful :)

    Another quite long (sorry) post responding to some specific comments.

    Just to follow up on this, the full resolution table image is now linked under the "Materials" header at the top of part 3.2. This is a temporary measure until the new format for best practice guides is rolled out on learn, which should allow for clicking on images to see full-res versions.

    This is absolutely the plan. I'm not able to update the 0.17 package documentation myself, but I'll speak to the people who are looking into that. We should be adding a link to unity.com/dots as well.

    I think that it's a good idea to add something which explicitly says that as well as thinking about the quantity and access frequency of a particular piece of data, people should think about what values the data can take and how that might affect the runtime logic. I will think on this further, see if I can come up with any concrete examples of this in ECS/DOTS, and potentially include a short discussion of this in a future version of the guide.

    For what it's worth, the reason it's not already called out in the guide is mainly because it didn't come up in any of the customer engagements I've been involved in. Clearly I haven't been involved in every DOTS project ever, so the guide is by definition incomplete - but as you can see, it got pretty long, and I had to choose some way to filter what would go into it, and what wouldn't. I chose to focus on advice that came from solving problems I've seen with my own eyes, because I felt that that would paint a "realistic" view of what DOTS usage looks like in real projects rather than being something hypothetical.

    I thought exactly the same as I was writing the guide. One of our internal DOTS training programs for engineers involves spending a few days picking a sample from the DOTS training samples repo and converting it from OOP to DOTS. I think there could be a good tutorial series to accompany the best practice guide that takes one of these samples (or a new one: perhaps Beach Ball Simulator), perhaps implements a "worst practices" version of it in DOTS to begin with, then takes you through the process of doing the data design properly and applying the advice from the guide one step at at time to end up with a performant implementation.

    Such a thing will take me some time to put together though. I'm an engineer, not a full-time documentation person. So don't expect it any time soon, but I do think it's a good idea.
     
  23. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    225
    Excellent work!! Two things I'd like to comment about:

    First, I feel some of the performance recommendations could be wrongly understood by novice programmers. For example on the frustum culling code, it says the version with a break is (always) slower. This might be true for this particular use case or in a pure per loop-iteration basis but at a more abstract level things could be different.
    TLDR: make extra sure nobody takes use case specific optimizations as some sort of general "best practice".

    Second. The sheer amount of unity/api specific things u have to known for using DOTS (on top of the already a little bit more complicated DOD) contradicts some PR campaigns that proposed DOTS as the get-go "performance by default" use-everywhere approach to game development. (a lot of custom attributes, built in systems and their order/dependencies, special data structures, "burstability", what can be scheduled as parallel or not, etc, etc, etc)
    TLDR: it would be nice to have a "Part 0 - How to decide if DOTS is for me / my game"
     
    FlavioIT, Egad_McDad and FilmBird like this.
  24. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    No offense (seriously) but novice programmers have no business using DOTS. Unity should make this clear. I know people get outraged when they think elitism is going on, but really, The prerequisites should be clearly laid out:
    1. Basic Unity knowledge
    2. Intermediate to advanced c# programming and system architecture
    3. Intermediate experience with debugging multithreaded applications
    4. intermediate C# Unsafe/Pointers experience
    5. The prereqs laid out in Part 1 and 2 of this training class (great reads regardless of experience!)
    6. Ability and enthusiasm to dig for answers: in the fourms, in the docs, in samples.
     
  25. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    225
    I agree, but remember my second point, some PR talks made it look it was for everyone.
    Plus, I'd personally recommend even stricter pre-requisites.
     
  26. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
    I haven't had to dig into pointers much before DOTS, and even then it's been limited. I wouldn't call it a prerequisite to have pointers experience, but it certainly would help...
     
    Egad_McDad, xVergilx and FilmBird like this.
  27. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    715
    I think that 1, 2 and 6 are enough IMO. I learned C# by myself and while DOTS was at first an inconvenience (I mean the first versions of the package with the [Inject] tag and no booleans were weird to me), it quickly became something I'm comfortable with. In the end DOTS makes it easier to control the order of things and as your project grow, it becomes easier to manage than a typical Monobehaviour project.

    One thing that isn't mentioned enough in the docs however is that nobody should feel forced to use Jobs. Many, many things can run faster in a Burst Run() than scheduling a job. Scheduling itself takes time and it is not negligeable.
     
    NotaNaN, hippocoder and charleshendry like this.
  28. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    451
    But no one is forced to use Jobs on other threads than main. You can just run all your jobs on main thread with burst.
     
  29. Kolyasisan

    Kolyasisan

    Joined:
    Feb 2, 2015
    Posts:
    397
    But I want performance. It's Unity's problem if the sheer scheduling of simple jobs is substantially longer than their execution. If the jobs are small enough/the game never has an absurd amount of entities, then what's even the point of Unity's ECS? Right now, there is barely any performance to be had, and I hope it will improve. Some things can be outright faster in C#'s traditional OOP land.

    If main-thread ECS is not performant at the lowest end, then it doesn't even matter if it's crazy-performant in the upper stratosphere overall, because such cases will inevitably spawn tons of systems, each of them contributing to massive overhead. That's why I will also never stop advocating for bringing some OOP paradigms into ECS, just to make gameplay logic easier and (ironically) straightforward. Class components are already heading into the right direction for that.
     
    Last edited: Feb 19, 2021
    FlavioIT and Lukas_Kastern like this.
  30. Krajca

    Krajca

    Joined:
    May 6, 2014
    Posts:
    347
    How's main thread not performant while you can use burst and run()? Burst alone should give you major boost.
     
  31. Kolyasisan

    Kolyasisan

    Joined:
    Feb 2, 2015
    Posts:
    397
    Not when there is a large number of systems. There's still a noticeable overhead due to job struct setup and bursted code invoke.
     
  32. T2RKUS

    T2RKUS

    Joined:
    Jul 13, 2015
    Posts:
    4
    What are the best practices for looking up data based on component data? For example say I wanted to get an entity based on a vector3 position or similar.. should I have a system/method called positionLookupSystemRef.GetEntity(pos) and keep track of the position being added/removed/updated with map/dictionary/suitable collection holding that data in the system? How should I go about doing this?
     
    Egad_McDad likes this.
  33. SteveM_Unity

    SteveM_Unity

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    41
    I think the core thing to remember with DOD is that there is no "one size fits all" best practice information that works in all circumstances. You have to understand your data and your data access patterns before you can choose the best approach.

    • What EntityQueries/filters are available to narrow the search for the types of entity you are looking for? If you're looking for a needle (an entity in a particular position) in a haystack (all possible relevant entities), how big is your haystack?
    • How many needles? How entities do you need to look up in this way?
    • How many of these types of queries do you need to make per frame? Or per second?
    • How many different positions are you interested in?
    • Are you looking for actual specific positions, or is it more like a radius check?
    • How often do you expect the entities that you need to look up will move? If an entity is in a particular position this frame, how likely is it that it will be in the same position next frame?
    • Do you have (or could you have) some kind of spatial partitioning in the world to "bucket" entities of interest according to their position and accelerate searches? A simple voxel grid, a KD-tree, octree, etc.
    The optimal answer will depend on the answers to these questions.

    That said, as a general observation, you should probably be specifying positions in terms of float3 rather than Vector3, and you should be aware that because of the inherent cache misses involved in data structures like maps and dictionaries, it's entirely possible that those data structures might be significantly slower than a brute-force linear search.
     
  34. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    The take away from this I have is that there's a considerable amount of gotchas that can and should probably be handled by Rider or even Visual Studio's hints. I've programmed for a long time but my brain is absolutely stuffed at this point and I don't think I'll have the energy to keep up with the growing list of nuanced performance do's and don'ts.

    As a game developer I have to write shaders, regular C# and if I want to achieve more on lower end hardware, DOTS. It's for this reason I don't think I can program in DOTS since I have so many other jobs to do. It seems to be the domain of teams.

    Unless all this extra fine-grained and somewhat necessary information can be automated or applied as I work rather than have me hunt for things I don't know exist or not. This isn't the case for regular programming.

    Basically saying I'd love to be involved but it feels too punishing for a lot of indies if they've also got a game on top of it to make, or have other roles to take care of too.

    Ultimately I think DOTS will have to come to the programmers rather than expecting (most) programmers to go to DOTS. For teams, tool or feature designers and enterprise, this is never a problem. Just my feedback.
     
  35. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    I'm totally with hippocoder here. OOP has developed patterns and some "good style". So it is "easy" to deduce how things should be organized. For Unity's ECS such "rules" and know-how does not exist (publicly) yet. This best practices guide is a good start but it must be expanded alot to be a reference for developers who try so solve a certain problem or have a certain question. Currently up to date information is scattered in too many places to be useful IMO.

    On the other hand, maybe DOTS/ECS has to be seen as a more low level programming type of thing. Like when C++ developers code some methods in assembly to squeeze out the maximum performance. So it is natural that you have to make other considerations about your code than someone who just want to "make it work". So if UT want's to make ECS "mainstream" there should be more effort towards teaching their users how to do ECS right. Currently it seems to be outright necessary to implement all different variants of how to do a single thing in ECS and then profile them to pick one. As the post of Steve_McGreal above indicates.
    For me this is a bit "scary". Maybe this is what software engineers are supposed to do. But what about "normal" people, plain programmers, hobby developers, ambitious designers? I mean it's perfectly fine when using ECS requires a PhD in computer science. But from official posts the expectation was raised, that it will be almost as user friendly as plain Monobehaviors. But I don't see ECS usability close to it, and I also don't see a way for it to reach this goal. So must I change my expectations or is it just a question of time until we get there? As I still see the potential and find ECS quite tempting. But I have a hard time learning it. Not the DOD part, thats fine I think. But how I can translate what I want to do into Unity API calls to make it work as intended. And not doing one of the many "pitfalls" mentioned in the guide.
    Maybe this is just an "experience thing" and comes naturally when we have used ECS for some years in different projects. But "trial and error" is not a good way to learn IMO. And some guidance on this way would be greatly apreciated.
     
    MINORLIFE likes this.
  36. SenseEater

    SenseEater

    Joined:
    Nov 28, 2014
    Posts:
    84
    At the risk of grossly generalizing things , i am gonna just say it. ECS is not rocket science. Some of you guys are making it look way too complicated than it really is. Just focus on fundamentals. You certainly don't need PhD in Compute Science to understand basics of DOTS and get rolling with it.

    Plus Monobehavior / GameObjects are perfectly fine if you are more fluent and comfortable with classic Unity. Unity is constantly receiving QoL and Performance updates outside DOTS too if that's a concern.
     
    MINORLIFE, NotaNaN, Vacummus and 2 others like this.
  37. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    With normal Unity we already have multiple ways of doing the same thing, some prefer an Event-based approach, some use MVC pattern, others use MVVM, others just put everything on Update and let the chaos reigns... There was never a "one size fits all"-way to develop your code and DOTS was never meant to change that. Each project, and each feature within a project, will have specific needs, there is no way to change that.

    But back on the topic, I loved this guide as it feels - and should keep that way - focused in teaching "What do I need to know to start using Unity DOTS?". One thing I surely would like to see there in the future is more about other DOTS packages, like DOTS Animation, Data Flow Graph, Tiny, etc... But I understand that it was too much stuff to put in the first version.
     
  38. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    232
    Yeah I'm mostly useless at programming and I can get my around DOTS relatively ok(apart from the syntax issues and figuring out how exactly to format the code). Its feels way easier than OOP for me to understand. It reduces problems straight down to their roots(the data), there's no abstractions and obstructions. I could never figure out nor get my head around inheritance and polymorphism, and all the other stuff, and why they were used so much. DOTS just deals with data its simple, helps me understand the problems and get to solving it as quickly as possible, and I am better able to understand how the underlying hardware uses that data and then I can optimise it, for that hardware(its agnostic enough that the same code should be optimal for most hardware). Which is kind of crazy I never would have thought I would have been able to make highly optimised multithreaded code before.

    You just have to be patient and willing to learn new things.
     
    MINORLIFE, Neonage and SenseEater like this.
  39. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    225
    While I agree it is not rocket science, I think the specific/api knowledge (and its caveats) u need to know to use are quite complex.

    Let's take an example from the guide:

    Code (CSharp):
    1. [UpdateInGroup(typeof(SimulationSystemGroup))]
    2. public class AIStateSystem : SystemBase
    3. {
    4.     protected override void OnUpdate()
    5.     {
    6.         var bufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    7.         Entities.ForEach((ref AIState animAIState) =>
    8.         {
    9.             animAIState.activity = SomeComplexStateMachineLogic();
    10.         }).ScheduleParallel();
    11.        
    12.         bufferSystem.AddJobHandleForProducer(Dependency);
    13.     }
    14. }  
    There u have a custom attribute u need to know with a param u need to understand.
    Then classic OOP inheritance.
    Then a classic OOP method override.
    Then u need to call a singleton to get (or create?) some special kind of command buffer.
    Then create a ForEach (job?) with a lambda.
    Then Schedule the job as parallel or not.
    Finally call some function on the buffer and pass "Dependency" (where does it come from?)

    Citing the tutorial:
    Code (CSharp):
    1. Note that this code uses the PostSimulationSystemGroup from the previous section of this guide, and ensures that the AnimatorRefSystem updates as a part of it. This is because the job in AnimatorRefSystem must run on the main thread, but should only run after the job scheduled in AIStateSystem has completed. Currently the best way to express this dependency between a ....
    Am I supposed to know all of that above? for each of the built in systems? on top of the regular Unity APIs?

    Maybe not rocket science but it seems like quite a lot to swallow.
     
  40. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    As noted above, that example is not a great one, the buffer isn't actually doing anything. But you're not wrong - the API is a lot to swallow and there are definitely some arcane things that only become clearer with practice - like how dependency resolution works, or shared resources, or even how to do things that are normally simple in OOP, like work with transform hierarchies.

    The documentation and samples are currently light years ahead of where they were a year ago though, I honestly feel like it's a pretty good time for people to jump in and start learning. Just don't expect it to be easy.
     
  41. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    715
    Your example is not fair. You would need to compare starting and managing jobs and their dependencies in Monobehaviour versus DOTS.
    • [UpdateInGroup(typeof(SimulationSystemGroup))] is not required. Everything runs in simulation by default.
    • Overriding is done automatically for you in most code editor
    • Dependency management is automatic too for simple systems
    • You do not need a command buffer.
    • You do not need to Schedule a job.
    You could start by learning the basics of Entities.ForEach.Run(), which is quite easy. Then learn your way to jobs and benefit from a lot of performance gain without touching much of your syntax.

    I do mostly UI stuff and I can see a lot of benefits in this kind of the syntax just by reading the thing:

    Code (CSharp):
    1.  public class HealthBarUpdateSystem : SystemBase
    2.     {
    3.         protected override void OnUpdate()
    4.         {
    5.             var health = GetSingleton<Health>();
    6.            
    7.             Entities
    8.                 .WithoutBurst()
    9.                 .ForEach((HealthBar healthBar) =>{
    10.                     healthBar.Image.fillAmount = health.Normalized;
    11.                 }).Run();
    12.         }
    13.     }
     
  42. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    The way I see it, this is not that different than needing to learn the specific API that "normal" Unity or any other engine has. When you were learning Unity you needed to learn why Start and Update were being called, the purpose of the Awake, LateUpdate and FixedUpdate methods, why having a Rigidbody on the parent made the OnCollisionXXX methods never fire on your object, why you would use the RequireComponent, ExecuteAlways or DefaultExecutionOrder attributes, etc... Not to mention the headache of dealing with multi-threading in usual Unity, as the majority of the API is not thread-safe.

    With DOTS, they are trying to make the Authoring side as close as possible from what we are used to already (we == existing Unity users), but the Runtime is a completely new framework (that as a bonus gives you multi-threading ready-to-go), so keep in mind that while trying to learn it.
     
    MINORLIFE and SteveM_Unity like this.
  43. SteveM_Unity

    SteveM_Unity

    Unity Technologies

    Joined:
    Nov 21, 2017
    Posts:
    41
    Yes, I overcomplicated the code sample that's being discussed - my bad, it will be fixed. The kind of code/workflow you've described here is very close to how I like to write DOTS systems. You can absolutely write non-Burst compiled, main thread code as a first pass to test that it works, then parallelize and Burst-compile it with relatively small code changes once you're satisfied that the basic algorithm is working. Although personally I would always want to add an attribute to a system to specify a group to update in and a system to update before/after, because I think letting ECS make those decisions for you is giving up too much control over your data transformation pipeline.

    In any case, I don't think basic DOTS code is particularly hard to read, and has got considerably easier to read with the ForEach lambdas and automatic dependency management, compared to the days of hand-constructing every job. Heavily optimized DOTS code looks more complex, but I don't think it's that much more complex. System code is quite malleable as long as the data you're feeding to it is designed with the goal of minimising cache misses for what you anticipate to be the most common access patterns.

    I know that the DOTS team are really serious about usability and are aiming to get as close as possible to MonoBehaviour in terms of ease of use in the future. There's still some way to go between what we have now and that goal, but I think things are definitely moving in the right direction. ECS code is much easier to write now than it was a year, or two, ago.
     
    MINORLIFE and JesOb like this.
  44. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    715
    So that is clear I really enjoy that the samples and documentation are going somewhat in-depth. I did not really like the boid / fish demo as it did not make any sense to me, but the newest Tiny samples and the best practice guide are really welcomed.
     
    MINORLIFE likes this.
  45. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    225
    Well even in your example, just to change a float u need to know about "burstability" (to know u need to use the WirhtoutBurst). Then u need to know about schedulling (parallel, non-parallel, main-thread (run)). And not to be an ass but u forgot the "ref" on the for each param so the system won't actually do anything (so u need to know about ref).
    That on top of having to know all the regular unity elements/api (the Image class, etc).

    With monobehaviour u could simply override Update() and update a reference to your healthbar that is set automatically by unity. I'd say that's quite simpler.

    Again, I'm not saying DOTS is rocket science, nor it is particularly more complicated than any other Entity-System API . I'm actually using it myself in a limited manner.

    My point was it IS more complicated/bigger than regular MonoBehavior and not necessarily a better option for all use cases so having a chapter on the tutorial to help devs decide if DOTs is appropriate or not for their game would be very helpful.
    (Remember some past PR wanted us to buy DOTs as the holy grail that should be used in all and any game to ever be.)
     
    amisner2k likes this.
  46. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    In the example they're using a reference type (a ui image) - as silly as it sounds you don't use "ref" or "in" when capturing a reference type in a foreach query - you just pass by value since it's already a reference type and there's no "copying" happening. That's also why they are using WithoutBurst/Run - reference types aren't supported in burst or jobs.

    All that is to say people in this thread talking about how confusing the API can be - especially if you're not super familiar with C# already - are completely correct. But it's just something you get used to as you learn the framework. And all of this is in fact covered in the manual, but it's buried and not always in obvious places.
     
    Last edited: Feb 24, 2021
    MINORLIFE likes this.
  47. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    715
    • Do not write WithoutBurst and the console will tell you you should; you'll also learn that Burst is a good thing.
    • I did not forget "ref", you do not need it for Monobehaviours. Even if you needed it, the console would tell you and then you'll learn about the advantage of it
    • You seem to forget that in Monobehaviour you also have to know about Awake, Start, Update, Coroutines, Destroy, garbage collection, [Serializable] and [SerializeField] attributes and so on.
    • You seem to forget that Image is part of Unity UI and you need to know about that if you wish to make UI in Monobehaviour.
    I remember people complaining when Unity stopped maintaining Unity Script in favor of C#. "Too complicated", some said. Here we are again I guess ;)
     
    Last edited: Feb 24, 2021
  48. TieSKey

    TieSKey

    Joined:
    Apr 14, 2011
    Posts:
    225
    Doesn't using a reference type defeat the purpose of the entire ECS framework??, did I miss something? (as in, u are doing the same random memory access as in OOP just writing it different).

    Idk what old crappy scripting languages has to do with this topic :p
    I'm just saying ECS is not a panacea fix-em-all that should be mindlessly used for any and all projects. The same way all other API/paradigms aren't either. The only problem here is the old Unity PR ... "mistakes".
     
  49. alexandre-fiset

    alexandre-fiset

    Joined:
    Mar 19, 2012
    Posts:
    715
    You could evaluate complex AI on jobs and write back to an Animator. Then when the DOTS Animation package will be ready, rewire things up to it. Same with rendering, same with UI. Granularity of code has a lot of advantages for testing and maintenance.

    Also, it obviously should not be used mindlessly as this is a preview package.
     
    mariandev, FilmBird and SenseEater like this.
  50. TheOtherMonarch

    TheOtherMonarch

    Joined:
    Jul 28, 2012
    Posts:
    867
     
    Last edited: Feb 25, 2021
Thread Status:
Not open for further replies.