Search Unity

ECS and Job System explainer

Discussion in 'Entity Component System' started by BrianWill, Jun 7, 2018.

  1. BrianWill

    BrianWill

    Joined:
    Oct 10, 2014
    Posts:
    38
    I'm working on an ECS and Job System explainer:

    Intro
    ECS
    Job System

    It's pieced together from the existing docs and videos, as well as some experimenting with toy examples, reading the ECS package code a bit, and pure speculation. I think what I have so far is mostly accurate, but I'm sure there are errors, and I'm not yet covering every part of the API's. I plan to make videos covering these topics, but I'd like to get the written explainer correct beforehand.

    What I'm trying to do is give a highly structured explanation with key concrete details. In particular:

    - explain ECS and the Job System thoroughly but separately before explaining how to use them together
    - explain pure ECS thoroughly before hybrid ECS
    - explain how the safety checks work, especially where exactly they are performed and under what conditions they are triggered (the docs and videos too often imply hand-wavey magic)
    - where injection is just a convenience, defer covering it as a postscript (similar problem of seeming too magical)

    I read the docs and watched all the videos at least twice, but a few important points weren't clear:

    - Some variant of 'jobs only work with their own data copies' is said at several points, strongly implying that jobs somehow work only with copies of native containers and entity component iterators. This, of course, sounded very wrong, but for a while I tried to vaguely imagine some automatic system that somehow consolidated each job's data into one source of truth after they finish, or something like that. What should be said is that 'a job only has access to a copy of its struct', but native container and entity component iterator fields have pointers into global native memory, and so jobs do NOT only have access to just their own copies of data (which of course is why the safety checks are necessary).
    - How is it known which systems touch which components? For a long time I could only assume it had something to do with injection or introspection of the class. Turns out it's actually from a system's GetComponentGroup calls. This is mentioned at one point in the docs, but I don't think it's mentioned in the videos, and it's a key point worth repeating when talking about the safety checks.
    - In the job docs, it says "Don’t try to update NativeArray contents". This wrongly implies a job cannot mutate a NativeArray. What it should say is simply that, until C# 7 ref return support is added, operators like ++ and += can't be properly overloaded to mutate NativeArrays like you expect, so for now you have to use = instead.
     
  2. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    I can answer #1 at least.

    Technically a Job struct Does have it's own "copy" of all of it's data.

    All Native Containers are handles to data (and access sentinels if safety checks are on) and are structs. Jobs cannot have references as fields. All structs are copied by value (unless passed by ref). When you schedule a job, the struct is copied again into a queue for execution. So it's "technically correct" to say a job has a copy of the data given to it. That being said, it should be made clear that the memory Native Containers handles point to are somewhere on the heap, and thus "access" can be shared between jobs.
     
  3. BrianWill

    BrianWill

    Joined:
    Oct 10, 2014
    Posts:
    38
    @recursive Right, but I think some of the language used in places suggests some kind of *full* job isolation wherein data only goes in and out through pre- and post- execute copy operations. In fact, I'd say jobs definitely *can* have references in their fields, but only in the form of native containers and component group iterators. Those things themselves are copied by value, but they contain unsafe pointers.
     
  4. Afonso-Lage

    Afonso-Lage

    Joined:
    Jul 8, 2012
    Posts:
    70
    First of all, thanks for sharing this explainer, it is amazing. I learn a lot by reading it. Bellow I quote some points mostly on ECS, because i'm not that good with Job Systems, yet.

    Please note that I'm not guru, so I may be wrong on some points.

    Since you are teaching new comers, maybe you should not say this, which may confuse'em. Components should not have any logic, the only methods that they should contain is getters, setters or any convenience method to transform/validade the data it contains. This can be hard to distinguish at begining.

    I may be mistaken, but I don't think the allocator type has any relation with allocation speed, since all uses malloc internally (which is fast). This flag is used by the safety checker to ensure, or at least try to, there is no memory leak. If you allocate a Temp memory but after 1 frame it is not dispose, it will blame you. The same for TempJob, but TempJob allow the auto dispose at the end of the job. Allocator.Persistent there is no dispose check, since it's persistent, so it is the most dangerous of'em all.

    Here I think is a good case where you can reinforce system injections. In this case, there can be a InventorySystem which holds a NativeHashMap with entity as key and a NativeArray (or NativeList) as value, with all items of that player. So when we are on CraftSystem and we need to check if player has the item X, just get the InventorySystem trough injection and ask if player has item X.

    I think you may add more info based on @5argon answer here.
     
    starikcetin and recursive like this.
  5. BrianWill

    BrianWill

    Joined:
    Oct 10, 2014
    Posts:
    38
    I got around to those videos. I found several awkward points arise in talking about ECS if the Job System hasn't already been covered, so I reordered the Job System before ECS.

    the Job System (30 min)
    ECS (27 min)
    using the Job System with ECS (10 min)

    I'd next like to go through some examples that use the stock components and systems to show how to render and such, and then after that demonstrate hybrid. I'll also try to work in a few pieces I haven't yet covered, like shared components and some of the more recent additions.

    @Afonso-Lage I'm quite sure Temp and TempJob are faster than Persistent. As the docs describe, Persistent is just a wrapper for malloc, but Temp and TempJob are said to be faster. (I'd guess that Temp just sequentially allocates from a chunk and simply resets to the beginning every frame because Temp allocations don't live beyond the end of frame, but maybe it's something more complicated.)
     
    friflo likes this.
  6. Afonso-Lage

    Afonso-Lage

    Joined:
    Jul 8, 2012
    Posts:
    70
    Yeah, I saw it also on docs. But I really don't get how they can have a Temp allocation faster than malloc. I don't think they maintain a memory pool for Temp allocations.

    Maybe the keyword is the lifespan and thread-safety, because they say on docs:

    Since safety-check and thread-safe will act on read/write operations, the speed isn't the allocation, but the access? Dunno.
     
  7. TheBiter

    TheBiter

    Joined:
    May 29, 2013
    Posts:
    14
    @BrianWill

    Thank you for making this tutorial with the youtube vids. I generally had an overall decent grasp of how certain things interacted within the new ECS system but having someone put it together in a coherent, to the point, tutorial really helps to solidify and reinforce the understanding. Also it helped me better understand Job Systems too, which I really had no clue beforehand. Many thanks!