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.
  2. Dismiss Notice

Writing subroutines in Unity's DOTS (ECS)

Discussion in 'Entity Component System' started by zardini123, May 9, 2020.

  1. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    What does one have to take in account when writing reusable code that works in all (or most) contexts of the DOTS stack (Burst and Jobs)?

    In this post, I give examples of subroutines I know that exist and are used with the DOTS. With these examples, I ask questions about how they work with the DOTS, how they correctly interface with the Burst compiler, and if subroutines with ECS is even possible.

    Example 1: Unity Mathematics
    The Unity Mathematics package is essentially a collection of subroutines for doing math calculations, and it can be used in Jobs (has no dependencies on the main thread, and works only with bitable types) and with Burst (can be compiled into neat ol' assemblies). The interesting thing about Unity.Mathematics is that it can be used and referenced in any context (multithreaded, main thread, not in ECS), and not have any issues.

    When looking a bit further into how Unity.Mathematics, most (almost all) subroutines have the attribute [MethodImpl(MethodImplOptions.AggressiveInlining)] before it. From some research, I found that this forces the operations to exist inline with the code that calls the subroutine. Is this required to get subroutines to compile with Burst properly?

    Example 2: DOTS Animation
    The new, preview DOTS Animation package has Evaluation subroutines for their DOTS Animation Curves that can be used anywhere, just like with Unity.Mathematics. Even complex data structures (their AnimationCurve) can be transferable to the subroutine using the ref keyword. Do these subroutines get Burst compiled if used in a Burst compilable job?

    Example 3: ECS??
    Now when it comes to subroutines that relate to ECS directly, I have no examples. How can one do operations on the Entity Manager in a reusable subroutine, and have this subroutine be Burst compiled?

    An example of these Entity Manager operations could include finding an entity with a specific component in a LinkedEntityGroup.

    I do understand that using subroutines with Jobs is not straight forward, because data has to be transferred to the Job/thread. Therefore, using subroutines that does operations on the Entity Manager is not transferable between main thread and multi threaded.
     
    Last edited: May 9, 2020
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    No. Just has to be a static method that does not use reference types. Even static generic methods work.

    If Burst successfully compiles the job, everything executed within it along with any methods it calls into (and methods those methods call into and so on) are all compiled with Burst.

    ComponentDataFromEntity is a useful tool.
     
    hippocoder and vildauget like this.
  3. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
  4. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    So is passing an EntityManager to a function a bad idea?

    For example, a LinkedEntityGroup that contains all related entities needed for the operations would be passed to the function. But the function needs to do operations to find if a component exists on any of those linked entities, so then HasComponent is needed.

    Along with that, the supposed function needs to access and set component data on multiple components, therefore needing GetComponentData and SetComponentData on every needed component.

    If passing an EntityManager is a bad idea, would it be ideal to pass a ComponentDataFromEntity instance for each needed component? I could see how that can replace the need of an EntityManager for this example, as one can use Exists for checking if the entity in the LinkedEntityGroup has the component. But for setting component data, I cannot see how the data would update to the Entity Manager. Is the components in the ComponentDataFromEntity structure just pointers to the real components?
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    The main issue is that EntityManager does not really work in Burst and Jobs (that might be changing with the new unmanaged bursted systems, but I wouldn't count on it working yet). Both EntityManager and ComponentDataFromEntity hold a pointer to EntityComponentStore.

    Unfortunately there is no magic data type that works in all situations. Some things break when structural changes are in play, and some things break when attempted to use in Jobs and/or Burst, and some things are just not parallel thread-safe.

    What exactly is this reusable code that requires directly reading and modifying components anyways?
     
    Nyanpas likes this.
  6. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    This is all great information to learn. Thank you!
    I'm working on a physics-body hierarchy tele-porter. I need to iterate over the hierarchy's root's DynamicBuffer<LinkedEntityGroup> to find all physics bodies under that root. For each physics bodies under the root, I have to set its Translation and Rotation components accordingly, to get the entire hierarchy to "tele-port" to a new location without bugs.

    Currently I need to use it in systems, and in non-systems, so it'd be great to have the code be reusable. I'm thinking about using ComponentDataFromEntity for accessing and setting Translation and Rotation components.
     
  7. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    I just found out that ComponentDataFromEntity only exists in ComponentSystemBase. This is a problem because I want to call the function in a MonoBehaviour context, not only in a ComponentSystem. Is there something similar to ComponentDataFromEntity that exists in the Entity Manager?
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    This sounds like an anti-pattern. May I ask why?
     
  9. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    Haha, from that it sounds like I've headed the wrong way :D

    Currently I'm just trying to create the general outline of my game mechanics, so a small portion of my code is MonoBehaviour dependent. That 10% is due to being dependent on UI and level loaded events, and I know it'd take a huge effort to truly actualize this 10% into full ECS. I remember hearing/seeing somewhere that this is of course fine for prototypes, which is what I'm working.


    Though regardless of my context, I've only seen ComponentDataFromEntity used in the context of JobComponentSystem. I've not seen it used for subroutines, nor used in ComponentSystem. In the documentation for ComponentDataFromEntity, it states:

    If you would like to access an entity's components outside of a job, consider using the EntityManager methodsGetComponentData<T>(Entity) and SetComponentData<T>(Entity, T) instead, to avoid the overhead of creating a ComponentDataFromEntity object.
    From this, the only way I can see ComponentDataFromEntity being useful (and not having unnecessary overhead) is in a job context.

    Writing a subroutine that requires a collection of components seems even more confusing to me. One can write a subroutine that takes in individual components easily because jobs and non-jobs has straightforward ways of accessing individual components. But for collections of components, would it just be best to use NativeArrays at this point?
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    1) Use SystemBase, which replaces ComponentSystem and JobComponentSystem.
    2) ComponentDataFromEntity is for component random access in jobs. Use EntityManager outside of jobs.
    3) You can access MonoBehaviours in an Entities.WithoutBurst().ForEach().Run(). That lets you put your code in a system while still working with legacy functionality. This is how cameras, lights, and VFX work in the Hybrid Renderer currently.
     
    zardini123 and Nyanpas like this.
  11. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    This makes a lot of sense. Thank you!

    I also realized that code that iterates on a bunch of entities can be split up into a separate subroutine that works on only one entity. Therefore, I can write two subroutines, one for jobs (using ComponentDataFromEntity) and one for entity manager, that both iterates over data and does the same operations without reusing major code. The job and entity manager subroutine will both call the static single-entity subroutine. Because the subroutine calling the subroutine follows Burst requirements, it will be Burst compiled.