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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

How to ForEach transform the data to other unrelated entity?

Discussion in 'Entity Component System' started by 5argon, Nov 30, 2019.

  1. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,554
    In 0.2 I see the concise Entities.ForEach is shaping to be the "default" The problem I usually have is that ForEach is a data transformation. It is only able to work on itself or from-to any other components on itself.

    Concretely I would like to have a different write target that I can bring to ForEach while maintaining concise syntax while maintaining dependency chain.

    For example in this task I want the code to say "for each" component Value, read its `value` and add them up to `summation` value in component Collected that is on an another singleton entity. This work do not need to be parallel addition into a single target, it just need to be on thread and add each one in sequentially. Practical use case such as UI that looks to gather data from Entity in the world to cache and display it.

    Here's how it would be concisely, but however cannot be burst compiled or cannot be on thread.

    Code (CSharp):
    1.  
    2. protected override JobHandle OnUpdate(JobHandle inputDeps)
    3. {
    4.     var singleton = GetSingletonEntity<Collected>();
    5.     Entities.ForEach((in Value a) =>
    6.      {
    7.          Collected b = EntityManager.GetComponentData<Collected>(singleton);
    8.          b.summation += a.value;
    9.          EntityManager.SetComponentData<Collected>(singleton, b);
    10.      }).WithoutBurst().Run();
    11.     return default;
    12. }
    13.  
    If I want it to be on thread, burst compiled, ForEach, and not breaking the dependency chain, it would have to be something like capturing the archetype chunk array into the ForEach so the modification go into the database "live". With ToComponentDataArray (which seems to be a bit cleaner), the copy back step will have to be done right after the job.

    Code (CSharp):
    1.  
    2. EntityQuery singletonQuery;
    3. protected override void OnCreate()
    4. {
    5.     base.OnCreate();
    6.     singletonQuery = GetEntityQuery(ComponentType.ReadOnly<Collected>());
    7. }
    8.  
    9. protected override JobHandle OnUpdate(JobHandle inputDeps)
    10. {
    11.     var acctB = GetArchetypeChunkComponentType<Collected>(isReadOnly: false);
    12.     var aca = singletonQuery.CreateArchetypeChunkArray(Allocator.TempJob);
    13.     var jobHandle = Entities.ForEach((in Value a) =>
    14.     {
    15.         var na = aca[0].GetNativeArray(acctB);
    16.         var previousData = na[0];
    17.         previousData.summation += a.value;
    18.         na[0] = previousData;
    19.     }).Schedule(inputDeps);
    20.     return jobHandle;
    21. }
    22.  
    Is it possible to currently code something shorter like (made-up code) :

    Code (CSharp):
    1.  
    2. EntityQuery singletonQuery;
    3. protected override void OnCreate()
    4. {
    5.     base.OnCreate();
    6.     singletonQuery = GetEntityQuery(ComponentType.ReadOnly<Collected>());
    7. }
    8.  
    9. protected override JobHandle OnUpdate(JobHandle inputDeps)
    10. {
    11.     var jobHandle = Entities.ForEach((in Value a) =>
    12.     {
    13.         var previousData = cda[0];
    14.         previousData.summation += a.value;
    15.         cda[0] = previousData;
    16.     }).WithComponentDataArray(singletonQuery, out var cda, isReadOnly: false).Schedule(inputDeps);
    17.     return jobHandle;
    18. }
    19.  
    So I can have any additional linearized array of data inside ForEach generated from any other unrelated query that is writable, and the job knows how to tie it into its job handle.

    *test file with 2 passed test about 2 approaches above
     

    Attached Files:

  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    I haven't had the chance to try it yet, but can you not capture ComponentDataFromEntity?

    As for replacing IJFE.ScheduleSingle, I'm not sure yet.
     
  3. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,554
    Using CDFE will cause ForEach to complain that it doesn't support parallel writing. That made me realize ForEach is parallel only since it assume each entry is independent. By introducing this outside entity it is not the case anymore.

    Now that ForEach cannot be forced to ScheduleSingle I think the right way to collect *then* do something else without completing the job in OnUpdate, would be Job.WithCode? But in that case well, it's not the "ForEach" wording and we are back to the old boiler plate of having to prep things into it. I don't know but it sounds like anti pattern.
     
  4. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,554
    Here's my next best take I could think of :

    - On thread but sequentially. Do not have to complete.
    - The job chain know about complete dep chain
    - Use the new 0.2 method without declaring a job
    - ToComponentDataArray with job handle
    - Assignment job is Job.WithCode so it won't parallel. Allow CDFE, get singleton entity and capture into job.
    - Need WithDeallocateOnJobCompletion instead, so it add back a bit more thing to be aware of?

    Code (CSharp):
    1. EntityQuery valuesQuery;
    2. protected override void OnCreate()
    3. {
    4.     base.OnCreate();
    5.     valuesQuery = GetEntityQuery(ComponentType.ReadOnly<Value>());
    6. }
    7.  
    8. protected override JobHandle OnUpdate(JobHandle inputDeps)
    9. {
    10.     var singletonEntity = GetSingletonEntity<Collected>();
    11.     var cdfe = GetComponentDataFromEntity<Collected>(isReadOnly: false);
    12.     var cda = valuesQuery.ToComponentDataArray<Value>(Allocator.TempJob, out var tcdaHandle);
    13.     return Job.WithCode(() =>
    14.     {
    15.         var value = cdfe[singletonEntity];
    16.         for (int i = 0; i < cda.Length; i++)
    17.         {
    18.             value.summation += cda[i].value;
    19.         }
    20.         cdfe[singletonEntity] = value;
    21.     }).WithDeallocateOnJobCompletion(cda).Schedule(tcdaHandle);
    22. }
     
  5. Lucas-Meijer

    Lucas-Meijer

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    175
    We will add support to concisely read/write components on other entities in a ForEach. Well also have .Run() .Schedule() and .ScheduleParallel()