Search Unity

Using minimum and maximum array indices in IJobParallelFor

Discussion in 'Entity Component System' started by chanfort, Oct 9, 2018.

  1. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    [Custom update project inspired by discussions in this thread can be found here: https://github.com/chanfort/UnityECSCustomUpdate]

    I noticed that examples provide usage of IJobParallelFor only with the maximum array index, when scheduling the job. I.e. in the standard approach we use:

    Code (CSharp):
    1. job.Schedule(group.Length, 64, inputDeps);
    The first parameter here group.Length allows to specify the length of the array to process. If it's set to N, then only elements up to N-1 are processed and from N to group.Length are not.

    I was interested if there is a way also to use the lower boundary, i.e. the code above would be something like that:

    Code (CSharp):
    1. job.Schedule(imin, imax, 64, inputDeps);
    In such case if group.Length = 1000, and we provide imin=200, imax=500, then only elements between indices 200 and 500 would be processed. Other elements 0 - 199 and 501 - 999 would be skipped and parallel calculations would be running only on 300 elements.

    So is there such a way to use both min and max limits?
     
    Last edited: Jan 24, 2021
  2. slim_trunks

    slim_trunks

    Joined:
    Dec 31, 2017
    Posts:
    41
    You could use a
    NativeSlice
    of that array with start and length specified and iterate over that.
    Or if that is not a possibility you could simply pass the min and max indices into the job and then ignore all indices outside that range in the Execute method.
    Although it would be nice to have an API method for the second version for sure (avoiding branching would also benefit performance/Burst I guess)
     
  3. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So I think the second approach would break the parallel balancing. Let’s say we run over the same 1000 elements and do calculations for 200-500 elements. Let’s also take that there are total number of 4 threads running for this job, which represents 4 processor cores. Let’s also assume that calculations are running on the following cores:

    Core1: 0 - 250
    Core2: 251 - 500
    Core3: 501 - 750
    Core4: 751 - 999

    Now, calculations would be always running on cores 1 and 2. But cores 3 and 4 would only be checking if statements, which would be always false. The final utilisation would be:

    Core1: 50/250 = 20%
    Core2: 100%
    Core3: 0%
    Core4: 0%

    So probably the best solution in this case would be to use that NativeSlice. But how do we actually use NativeSlice in order to take it from NativeArray or ComponentDatArray between min and max indices?
     
  4. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    from NativeArray, you can do
    array.Slice(start, count)


    from CDA, you should not filter by index range anyways because elements can be reordered between updates, invalidating the indices
     
  5. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So it seems that I have some issues with converting ComponentDataArrays to NativeArrays (needed for NativeSlice). However, I managed to sort this in a different way and would like to share what is this all about.

    So I was looking into the problem on defining a custom update method, which would run calculations at a lower frequency than the main update. So a simple movement system could look like that:

    Code (CSharp):
    1. using System;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Burst;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7. using UnityEngine;
    8.  
    9. public class PositionSpeedSystem : JobComponentSystem
    10. {
    11.     [BurstCompile]
    12.     struct PositionSpeedPosition : IJobParallelFor
    13.     {
    14.         public float dt;
    15.         public ComponentDataArray<Speed> speeds;
    16.         public ComponentDataArray<Position> positions;
    17.         public int updateOrder;
    18.         public int updateFrequency;
    19.  
    20.         public void Execute(int i)
    21.         {
    22.             if((i + updateOrder) % updateFrequency == 0)
    23.             {
    24.                 float3 prevPosition = positions[i].Value;
    25.                 float speed = speeds[i].Value;
    26.              
    27.                 positions[i] = new Position
    28.                 {
    29.                     Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    30.                 };
    31.             }
    32.          
    33.         }
    34.     }
    35.  
    36.     struct Group
    37.     {
    38.         public ComponentDataArray<Speed> speeds;
    39.         public ComponentDataArray<Position> pos;
    40.         public readonly int Length;
    41.     }
    42.  
    43.     [Inject] private Group m_Group;
    44.  
    45.  
    46.     int updateOrder = 0;
    47.     int updateFrequency = 10;
    48.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    49.     {
    50.         updateOrder++;
    51.         if(updateOrder >= updateFrequency)
    52.         {
    53.             updateOrder = 0;
    54.         }
    55.      
    56.         var job = new PositionSpeedPosition();
    57.         job.speeds = m_Group.speeds;
    58.         job.positions = m_Group.pos;
    59.         job.dt = Time.deltaTime;
    60.         job.updateOrder = updateOrder;
    61.         job.updateFrequency = updateFrequency;
    62.         return job.Schedule(m_Group.Length, 64, inputDeps);
    63.     }
    64. }
    65.  
    Here every entity in m_Group is updated not every single update but every 10th update. This basically allows us to reduce calculations where they can run at lower frequency than main update (similar like coroutines). The low frequency updates can be handy for units which are, for example, far away from camera, behind the camera, sending data over the network or just running less important calculations.

    This way calculations also seems to be well parallel balanced as it is always running every 10th element. So I imagine that the same logic applies across all threads and we won't have the situation where one thread would be running 100% and the the remaining 9 threads idle on empty if statement.

    I also like that this way it does not need to copy data to temporary arrays. But this may come at a cost that there is if statement, while NativeSlice would allow not to use it. So it would be quite interesting to compare this technique with NativeSlice.
     
  6. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    that will:
    1) fetch the whole array while you only use 10% of that, resulting in 10x increased cache misses (and branching inside the job)
    2) entity order can change between updates, so you will have entities that are not updated while others are updated more often.

    i'd split them with ISharedComponentData instead
     
  7. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Hmm, interesting. So how do you split it using ISharedComponentData ? I looked inside the entities package and ISharedComponentDataArray do not have any methods which would convert to NativeArray or get NativeSlice. Do you have some examples in mind?
     
  8. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    you filter outside the job with ComponentGroup.SetFilter(...). [Inject] api don't support that
    or you use chunk iteration, get the relevant index from the scd array, and skip chunks that don't have it
     
  9. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So, unfortunately, tests show that the way I used with if checks inside job is much faster than using filtering. Here are all scripts to reproduce the checks.

    PositionSystem.cs

    Code (CSharp):
    1. using System;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Burst;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7. using UnityEngine;
    8.  
    9. public class PositionSpeedSystem : JobComponentSystem
    10. {
    11.     [BurstCompile]
    12.     struct PositionSpeedPosition : IJobParallelFor
    13.     {
    14.         public float dt;
    15.         public ComponentDataArray<Speed> speeds;
    16.         public ComponentDataArray<Position> positions;
    17.         public int updateOrder;
    18.         public int updateFrequency;
    19.  
    20.         public void Execute(int i)
    21.         {
    22.             if((i + updateOrder) % updateFrequency == 0)
    23.             {
    24.                 float3 prevPosition = positions[i].Value;
    25.                 float speed = speeds[i].Value;
    26.                
    27.                 positions[i] = new Position
    28.                 {
    29.                     Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    30.                 };
    31.             }
    32.            
    33.         }
    34.     }
    35.    
    36.     [BurstCompile]
    37.     struct PositionSpeedPositionFilter : IJobParallelFor
    38.     {
    39.         public float dt;
    40.         public ComponentDataArray<Speed> speeds;
    41.         public ComponentDataArray<Position> positions;
    42.         public int updateFrequency;
    43.  
    44.         public void Execute(int i)
    45.         {
    46.             float3 prevPosition = positions[i].Value;
    47.             float speed = speeds[i].Value;
    48.            
    49.             positions[i] = new Position
    50.             {
    51.                 Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    52.             };          
    53.         }
    54.     }
    55.    
    56.     private ComponentGroup m_Group;
    57.    
    58.     protected override void OnCreateManager()
    59.     {
    60.         m_Group = GetComponentGroup(typeof(Position), typeof(Speed), typeof(UpdateOrder));
    61.        
    62.         GameObject prefab = PrefabHolder.GetActive().prefab;
    63.         int numberToSpawn = PrefabHolder.GetActive().numberToSpawn;
    64.        
    65.         for(int i=0; i<numberToSpawn; i++)
    66.         {
    67.             Entity entity = EntityManager.Instantiate(prefab);
    68.             UpdateOrder updateOrder = EntityManager.GetSharedComponentData<UpdateOrder>(entity);
    69.            
    70.             updateOrder.Value = i % updateFrequency;
    71.            
    72.             EntityManager.SetSharedComponentData(entity, updateOrder);
    73.         }
    74.     }
    75.    
    76.     int updateOrder = 0;
    77.     int updateFrequency = 10;
    78.     bool useFilter = false;
    79.    
    80.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    81.     {
    82.         if(Input.GetKeyDown(KeyCode.A))
    83.         {
    84.             useFilter = !useFilter;
    85.             m_Group.ResetFilter();
    86.             Debug.Log("Using filtering mode: " + useFilter);
    87.         }
    88.        
    89.        
    90.         updateOrder++;
    91.         if(updateOrder >= updateFrequency)
    92.         {
    93.             updateOrder = 0;
    94.         }
    95.        
    96.         var positions = m_Group.GetComponentDataArray<Position>();
    97.         var speeds = m_Group.GetComponentDataArray<Speed>();
    98.        
    99.         if(useFilter){
    100.             m_Group.ResetFilter();
    101.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    102.            
    103.             var jobF = new PositionSpeedPositionFilter();
    104.             jobF.speeds = speeds;
    105.             jobF.positions = positions;
    106.             jobF.dt = Time.deltaTime;
    107.             jobF.updateFrequency = updateFrequency;
    108.             return jobF.Schedule(positions.Length, 64, inputDeps);
    109.         }
    110.        
    111.         var job = new PositionSpeedPosition();
    112.         job.speeds = speeds;
    113.         job.positions = positions;
    114.         job.dt = Time.deltaTime;
    115.         job.updateOrder = updateOrder;
    116.         job.updateFrequency = updateFrequency;
    117.         return job.Schedule(positions.Length, 64, inputDeps);
    118.        
    119.     }
    120. }
    121.  
    SpeedComponent.cs

    Code (CSharp):
    1. using System;
    2. using Unity.Entities;
    3.  
    4. [Serializable]
    5. public struct Speed : IComponentData
    6. {
    7.     public float Value;
    8. }
    9.  
    10. public class SpeedComponent : ComponentDataWrapper<Speed> { }
    UpdateOrderComponent.cs

    Code (CSharp):
    1. using System;
    2. using Unity.Entities;
    3.  
    4. [Serializable]
    5. public struct UpdateOrder : ISharedComponentData
    6. {
    7.     public float Value;
    8. }
    9.  
    10. public class UpdateOrderComponent : SharedComponentDataWrapper<UpdateOrder> { }
    PrefabHolder.cs

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Entities;
    5. using Unity.Transforms;
    6.  
    7. public class PrefabHolder : MonoBehaviour {
    8.    
    9.     public static PrefabHolder active;
    10.    
    11.     public GameObject prefab;
    12.     public int numberToSpawn = 1000000;
    13.    
    14.     public static PrefabHolder GetActive(){
    15.         if(PrefabHolder.active == null){
    16.             PrefabHolder.active = UnityEngine.Object.FindObjectOfType<PrefabHolder>();
    17.         }
    18.         return PrefabHolder.active;
    19.     }
    20.  
    21. }
    22.  
    The PrefabHolder.cs is added to a single game object in the scene. The prefab itself is saved in assets folder with attached GameObjectEntity, Position, Speed and UpdateOrder components with values as it can be seen in the screenshot below.

    Screenshot 2018-10-13 at 18.42.59.png

    The entity debugger window shows that when we are in filtering mode with 1 million entities running, the PositionSystem is running under 1.3 ms and is the most heavy system in the list. Pressing "A" on runtime it switches between filtered and old (not filtered) modes. So when in not filtered mode, PositionSystem runs with 0.05 ms (positions in the entities inspector are updating as they should be). Do I miss something in such implementation or it just tells that filtering is inefficient ?
     
  10. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    you are
    Code (CSharp):
    1.         var positions = m_Group.GetComponentDataArray<Position>();
    2.         var speeds = m_Group.GetComponentDataArray<Speed>();
    BEFORE setting the filter. this will get you unfiltered arrays.

    ps. with chink archetype APIs, you can get full control over the filtering
     
  11. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    If you remove line 85 from PositionSystem.cs, which says m_Group.ResetFilter(), the non-filtering job would run as slow as filtered job after flipping at least one time to filtered mode and back. This means that non-filtered mode would be running with previously set filter even if in update it is not setting anywhere. And this means that poor performance is coming from the inside the job, no matter how we do outside the job directly in OnUpdate.
     
  12. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    then it seems that filtering each frame is slow, can you try to debug with a constant filter? then you can either have a group for each value, or switch to chunk iteration (
    ArchetypeChunkArray
    /
    ArchetypeChunkComponentType<T>
    /etc...)
     
  13. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Hmm, will check these ones. I also found a good article online where it was compared ForEachFilter/SetFilter/Chunk Iteration cases in details:
    https://gametorrahod.com/unity-ecs-why-and-how-to-use-the-foreachfilter-setfilter-5e464bb5bdb2

    Need to read through it. However, I am getting a bit skeptical about filtering performance after these tests, i.e. not sure if it can bring performance 1.3 / 0.05 = 26 times as the non-filtering test shows. Filtering also requires shared component just to store the update id and takes additional memory.

    I am now curious when the order in ComponentDataArray is changing? Maybe only when new instances are added or removed since visual inspection of updated positions seems to be correct ? Could be quite easy test to store previous index and add counter to find how many instances changed their indices between updates :)
     
  14. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    shared component is stored per chunk, not per entity.

    data is reordered every time you add or remove a component, that includes components you don't care but are part of the archetype

    for the setFilter slowdown wrt. unfiltered iteration, it may be a bug
     
  15. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So I investigated a bit further. It seems like the bad performance occurs only when copying in positions and speeds array, i.e. through these lines (and only when filter is set on m_Group):

    Code (CSharp):
    1. var positions = m_Group.GetComponentDataArray<Position>();
    2. var speeds = m_Group.GetComponentDataArray<Speed>();
    The full code with tests is here:

    Code (CSharp):
    1. using System;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Burst;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7. using UnityEngine;
    8.  
    9. public class PositionSpeedSystem : JobComponentSystem
    10. {
    11.     [BurstCompile]
    12.     struct PositionSpeedPosition : IJobParallelFor
    13.     {
    14.         public float dt;
    15.         public ComponentDataArray<Speed> speeds;
    16.         public ComponentDataArray<Position> positions;
    17.         public int updateOrder;
    18.         public int updateFrequency;
    19.  
    20.         public void Execute(int i)
    21.         {
    22.             if((i + updateOrder) % updateFrequency == 0)
    23.             {
    24.                 float3 prevPosition = positions[i].Value;
    25.                 float speed = speeds[i].Value;
    26.                
    27.                 positions[i] = new Position
    28.                 {
    29.                     Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    30.                 };
    31.             }
    32.            
    33.         }
    34.     }
    35.    
    36.     [BurstCompile]
    37.     struct PositionSpeedPositionFilter : IJobParallelFor
    38.     {
    39.         public float dt;
    40.         public ComponentDataArray<Speed> speeds;
    41.         public ComponentDataArray<Position> positions;
    42.         public int updateFrequency;
    43.  
    44.         public void Execute(int i)
    45.         {
    46.             float3 prevPosition = positions[i].Value;
    47.             float speed = speeds[i].Value;
    48.            
    49.             positions[i] = new Position
    50.             {
    51.                 Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    52.             };          
    53.         }
    54.     }
    55.  
    56.     [BurstCompile]
    57.     struct PositionSpeedPositionComponentJob : IJobProcessComponentData<Speed, Position>
    58.     {
    59.         public float dt;
    60.         public int updateFrequency;
    61.  
    62.         public void Execute(ref Speed speed, ref Position position)
    63.         {
    64.             position.Value = position.Value + new float3(0,1,0) * speed.Value * dt * updateFrequency;
    65.         }
    66.     }
    67.    
    68.     private ComponentGroup m_Group;
    69.    
    70.     protected override void OnCreateManager()
    71.     {
    72.         m_Group = GetComponentGroup(typeof(Position), typeof(Speed), typeof(UpdateOrder));
    73.        
    74.         GameObject prefab = PrefabHolder.GetActive().prefab;
    75.         int numberToSpawn = PrefabHolder.GetActive().numberToSpawn;
    76.        
    77.         for(int i=0; i<numberToSpawn; i++)
    78.         {
    79.             Entity entity = EntityManager.Instantiate(prefab);
    80.             UpdateOrder updateOrder = EntityManager.GetSharedComponentData<UpdateOrder>(entity);
    81.            
    82.             updateOrder.Value = i % updateFrequency;
    83.            
    84.             EntityManager.SetSharedComponentData(entity, updateOrder);
    85.         }
    86.     }
    87.    
    88.     int updateOrder = 0;
    89.     int updateFrequency = 10;
    90.     int mode = 0;
    91.    
    92.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    93.     {
    94.         if(Input.GetKeyDown(KeyCode.A))
    95.         {
    96.             mode = 0;
    97.             m_Group.ResetFilter();
    98.             Debug.Log("Using filtering mode: " + mode);
    99.         }
    100.         else if(Input.GetKeyDown(KeyCode.B))
    101.         {
    102.             mode = 1;
    103.             m_Group.ResetFilter();
    104.             Debug.Log("Using filtering mode: " + mode);
    105.         }
    106.         else if(Input.GetKeyDown(KeyCode.C))
    107.         {
    108.             mode = 2;
    109.             m_Group.ResetFilter();
    110.             m_Group.SetFilter(new UpdateOrder { Value = 0 });
    111.             Debug.Log("Using filtering mode: " + mode);
    112.         }
    113.         else if(Input.GetKeyDown(KeyCode.D))
    114.         {
    115.             mode = 3;
    116.             m_Group.ResetFilter();
    117.             Debug.Log("Using filtering mode: " + mode);
    118.         }
    119.         else if(Input.GetKeyDown(KeyCode.E))
    120.         {
    121.             mode = 4;
    122.             m_Group.ResetFilter();
    123.             Debug.Log("Using filtering mode: " + mode);
    124.         }
    125.        
    126.        
    127.         updateOrder++;
    128.         if(updateOrder >= updateFrequency)
    129.         {
    130.             updateOrder = 0;
    131.         }
    132.        
    133.        
    134.        
    135.        
    136.         JobHandle jobHandle = inputDeps;
    137.  
    138.         if(mode == 0)
    139.         {
    140.             var positions = m_Group.GetComponentDataArray<Position>();
    141.             var speeds = m_Group.GetComponentDataArray<Speed>();
    142.             var job = new PositionSpeedPosition();
    143.             job.speeds = speeds;
    144.             job.positions = positions;
    145.             job.dt = Time.deltaTime;
    146.             job.updateOrder = updateOrder;
    147.             job.updateFrequency = updateFrequency;
    148.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    149.         }
    150.         else if(mode == 1)
    151.         {
    152.             m_Group.ResetFilter();
    153.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    154.             var positions = m_Group.GetComponentDataArray<Position>();
    155.             var speeds = m_Group.GetComponentDataArray<Speed>();
    156.            
    157.             var job = new PositionSpeedPositionFilter();
    158.             job.speeds = speeds;
    159.             job.positions = positions;
    160.             job.dt = Time.deltaTime;
    161.             job.updateFrequency = updateFrequency;
    162.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    163.         }
    164.         else if(mode == 2)
    165.         {  
    166.             var positions = m_Group.GetComponentDataArray<Position>();
    167.             var speeds = m_Group.GetComponentDataArray<Speed>();  
    168.             var job = new PositionSpeedPositionFilter();
    169.             job.speeds = speeds;
    170.             job.positions = positions;
    171.             job.dt = Time.deltaTime;
    172.             job.updateFrequency = updateFrequency;
    173.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    174.         }
    175.         else if(mode == 3)
    176.         {  
    177.             m_Group.ResetFilter();
    178.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    179.  
    180.             var positions = m_Group.GetComponentDataArray<Position>();
    181.             var speeds = m_Group.GetComponentDataArray<Speed>();
    182.            
    183.             var job = new PositionSpeedPositionComponentJob()
    184.             {
    185.                 dt = Time.deltaTime,
    186.                 updateFrequency = updateFrequency
    187.             };
    188.            
    189.             jobHandle = job.Schedule(this, inputDeps);
    190.         }
    191.         else if(mode == 4)
    192.         {  
    193.             m_Group.ResetFilter();
    194.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    195.  
    196.  
    197.             var job = new PositionSpeedPositionComponentJob()
    198.             {
    199.                 dt = Time.deltaTime,
    200.                 updateFrequency = updateFrequency
    201.             };
    202.            
    203.             jobHandle = job.Schedule(this, inputDeps);
    204.         }
    205.        
    206.         return jobHandle;
    207.     }
    208. }
    209.  
    So this can be tested by comparing mode 3 with 4 where the only difference is that in mode 3 is copying arrays and in mode 4 is not. I suspect copying is taking a long time.

    Also noticed that mode 3 and 4 does not work as it should as filters are not used in the job somehow.

    So I guess the next option would be to test with archetype chunks. Are there any examples of how filtering is passed through these archetype chunks?
     
  16. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
  17. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So I tried to implement chunk iteration but got somehow stuck. Here is what I currently have (mode 5)

    Code (CSharp):
    1. using System;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Burst;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7. using Unity.Collections;
    8. using UnityEngine;
    9.  
    10. public class PositionSpeedSystem : JobComponentSystem
    11. {
    12.     [BurstCompile]
    13.     struct PositionSpeedPosition : IJobParallelFor
    14.     {
    15.         public float dt;
    16.         public ComponentDataArray<Speed> speeds;
    17.         public ComponentDataArray<Position> positions;
    18.         public int updateOrder;
    19.         public int updateFrequency;
    20.  
    21.         public void Execute(int i)
    22.         {
    23.             if((i + updateOrder) % updateFrequency == 0)
    24.             {
    25.                 float3 prevPosition = positions[i].Value;
    26.                 float speed = speeds[i].Value;
    27.                
    28.                 positions[i] = new Position
    29.                 {
    30.                     Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    31.                 };
    32.             }
    33.            
    34.         }
    35.     }
    36.    
    37.     [BurstCompile]
    38.     struct PositionSpeedPositionFilter : IJobParallelFor
    39.     {
    40.         public float dt;
    41.         public ComponentDataArray<Speed> speeds;
    42.         public ComponentDataArray<Position> positions;
    43.         public int updateFrequency;
    44.  
    45.         public void Execute(int i)
    46.         {
    47.             float3 prevPosition = positions[i].Value;
    48.             float speed = speeds[i].Value;
    49.            
    50.             positions[i] = new Position
    51.             {
    52.                 Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    53.             };          
    54.         }
    55.     }
    56.  
    57.     [BurstCompile]
    58.     struct PositionSpeedPositionComponentJob : IJobProcessComponentData<Speed, Position>
    59.     {
    60.         public float dt;
    61.         public int updateFrequency;
    62.  
    63.         public void Execute(ref Speed speed, ref Position position)
    64.         {
    65.             position.Value = position.Value + new float3(0,1,0) * speed.Value * dt * updateFrequency;
    66.         }
    67.     }
    68.    
    69.     [BurstCompile]
    70.     struct PositionSpeedPositionChunkIteration : IJobParallelFor
    71.     {
    72.         public float dt;
    73.         [ReadOnly] public NativeArray<ArchetypeChunk> chunks;
    74.         [NativeDisableParallelForRestriction] public ArchetypeChunkComponentType<Position> positions;
    75.        
    76.         public void Execute(int chunkIndex)
    77.         {
    78.             var chunk = chunks[chunkIndex];
    79.             var chunkCount = chunk.Count;
    80.             var chunkPositions = chunk.GetNativeArray(positions);
    81.            
    82.             for(int i=0; i<chunkCount; i++)
    83.             {
    84.                 float3 prevPosition = chunkPositions[i].Value;
    85.                
    86.                 chunkPositions[i] = new Position
    87.                 {
    88.                     Value = prevPosition + new float3(0,1,0) * 1 * dt * 1
    89.                 };        
    90.             }
    91.         }
    92.     }
    93.    
    94.     private ComponentGroup m_Group;
    95.    
    96.     protected override void OnCreateManager()
    97.     {
    98.         m_Group = GetComponentGroup(typeof(Position), typeof(Speed), typeof(UpdateOrder));
    99.        
    100.         GameObject prefab = PrefabHolder.GetActive().prefab;
    101.         int numberToSpawn = PrefabHolder.GetActive().numberToSpawn;
    102.        
    103.         for(int i=0; i<numberToSpawn; i++)
    104.         {
    105.             Entity entity = EntityManager.Instantiate(prefab);
    106.             UpdateOrder updateOrder = EntityManager.GetSharedComponentData<UpdateOrder>(entity);
    107.            
    108.             updateOrder.Value = i % updateFrequency;
    109.            
    110.             EntityManager.SetSharedComponentData(entity, updateOrder);
    111.         }
    112.     }
    113.    
    114.     int updateOrder = 0;
    115.     int updateFrequency = 10;
    116.     int mode = 0;
    117.    
    118.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    119.     {
    120.         if(Input.GetKeyDown(KeyCode.A))
    121.         {
    122.             mode = 0;
    123.             m_Group.ResetFilter();
    124.             Debug.Log("Using filtering mode: " + mode);
    125.         }
    126.         else if(Input.GetKeyDown(KeyCode.B))
    127.         {
    128.             mode = 1;
    129.             m_Group.ResetFilter();
    130.             Debug.Log("Using filtering mode: " + mode);
    131.         }
    132.         else if(Input.GetKeyDown(KeyCode.C))
    133.         {
    134.             mode = 2;
    135.             m_Group.ResetFilter();
    136.             m_Group.SetFilter(new UpdateOrder { Value = 0 });
    137.             Debug.Log("Using filtering mode: " + mode);
    138.         }
    139.         else if(Input.GetKeyDown(KeyCode.D))
    140.         {
    141.             mode = 3;
    142.             m_Group.ResetFilter();
    143.             Debug.Log("Using filtering mode: " + mode);
    144.         }
    145.         else if(Input.GetKeyDown(KeyCode.E))
    146.         {
    147.             mode = 4;
    148.             m_Group.ResetFilter();
    149.             Debug.Log("Using filtering mode: " + mode);
    150.         }
    151.         else if(Input.GetKeyDown(KeyCode.F))
    152.         {
    153.             mode = 5;
    154.             m_Group.ResetFilter();
    155.             Debug.Log("Using filtering mode: " + mode);
    156.         }
    157.        
    158.        
    159.         updateOrder++;
    160.         if(updateOrder >= updateFrequency)
    161.         {
    162.             updateOrder = 0;
    163.         }
    164.        
    165.        
    166.        
    167.        
    168.         JobHandle jobHandle = inputDeps;
    169.  
    170.         if(mode == 0)
    171.         {
    172.             var positions = m_Group.GetComponentDataArray<Position>();
    173.             var speeds = m_Group.GetComponentDataArray<Speed>();
    174.             var job = new PositionSpeedPosition();
    175.             job.speeds = speeds;
    176.             job.positions = positions;
    177.             job.dt = Time.deltaTime;
    178.             job.updateOrder = updateOrder;
    179.             job.updateFrequency = updateFrequency;
    180.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    181.         }
    182.         else if(mode == 1)
    183.         {
    184.             m_Group.ResetFilter();
    185.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    186.             var positions = m_Group.GetComponentDataArray<Position>();
    187.             var speeds = m_Group.GetComponentDataArray<Speed>();
    188.            
    189.             var job = new PositionSpeedPositionFilter();
    190.             job.speeds = speeds;
    191.             job.positions = positions;
    192.             job.dt = Time.deltaTime;
    193.             job.updateFrequency = updateFrequency;
    194.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    195.         }
    196.         else if(mode == 2)
    197.         {  
    198.             var positions = m_Group.GetComponentDataArray<Position>();
    199.             var speeds = m_Group.GetComponentDataArray<Speed>();  
    200.             var job = new PositionSpeedPositionFilter();
    201.             job.speeds = speeds;
    202.             job.positions = positions;
    203.             job.dt = Time.deltaTime;
    204.             job.updateFrequency = updateFrequency;
    205.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    206.         }
    207.         else if(mode == 3)
    208.         {  
    209.             m_Group.ResetFilter();
    210.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    211.  
    212.             var positions = m_Group.GetComponentDataArray<Position>();
    213.             var speeds = m_Group.GetComponentDataArray<Speed>();
    214.            
    215.             var job = new PositionSpeedPositionComponentJob()
    216.             {
    217.                 dt = Time.deltaTime,
    218.                 updateFrequency = updateFrequency
    219.             };
    220.            
    221.             jobHandle = job.Schedule(this, inputDeps);
    222.         }
    223.         else if(mode == 4)
    224.         {  
    225.             m_Group.ResetFilter();
    226.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    227.  
    228.  
    229.             var job = new PositionSpeedPositionComponentJob()
    230.             {
    231.                 dt = Time.deltaTime,
    232.                 updateFrequency = updateFrequency
    233.             };
    234.            
    235.             jobHandle = job.Schedule(this, inputDeps);
    236.         }
    237.         else if(mode == 5)
    238.         {
    239.             var query = new EntityArchetypeQuery
    240.             {
    241.                 Any = Array.Empty<ComponentType>(),
    242.                 None = Array.Empty<ComponentType>(),
    243.                 All = new ComponentType[] {typeof(Position)}
    244.             };
    245.             var chunks = EntityManager.CreateArchetypeChunkArray(query, Allocator.Temp);
    246.             var positions = EntityManager.GetArchetypeChunkComponentType<Position>(true);
    247.             var job = new PositionSpeedPositionChunkIteration
    248.             {
    249.                 chunks = chunks,
    250.                 positions = positions
    251.             };
    252.             jobHandle = job.Schedule(chunks.Length, 64, inputDeps);
    253.             jobHandle.Complete();
    254.             chunks.Dispose();
    255.         }
    256.        
    257.         return jobHandle;
    258.     }
    259. }
    260.  
    Problems arise as in documentation it was saying to use chunk.StartIndex. When I put this into code it says that ArchetypeChunk does not have StartIndex. Furthermore I noticed that GetNativeSlice() method also does not exist in ArchetypeChunk. But I found that there is GetNativeArray() method instead (I assume that it was renamed :) ). Because chunk.StartIndex does not exist, I can't use the values NativeArray as shown in the example. Instead I tried using directly positions array and update values there.

    The outcome depends on what attributes are used. If I use both [ReadOnly], editor just freezes when entering mode=5. If I use [NativeDisableParallelForRestriction] it complains that it should be marked as readonly.

    So I was wondering how to get around this problem here?

    And another thing is that I can't find how filters and shared components would be related with chunks. But that's the next question once the above problem is solved.
     
  18. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    You can get all unique shared component datas and their indices, thus you know which data depends on index, SCD is per chunk basis, not per entity, chunk has GetSharedComponentIndex thus you can check in job if this chunk is your specific SCD and can operate with them.
    StartIndex not exists anymore.

    Because you get ACCT as readonly
    var positions = EntityManager.GetArchetypeChunkComponentType<Position>(true); // <---- by default argument is false
     
  19. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So I corrected this and also did a bit of cleanup. The code runs correctly now and positions are being updated as intended. But there are some performance surprises coming up and before going to SCD it's interesting to do some comparison between different modes. Firstly here is the code:

    Code (CSharp):
    1. using System;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Burst;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7. using Unity.Collections;
    8. using UnityEngine;
    9.  
    10. public class PositionSpeedSystem : JobComponentSystem
    11. {
    12.     [BurstCompile]
    13.     struct PositionSpeedPosition : IJobParallelFor
    14.     {
    15.         public float dt;
    16.         public ComponentDataArray<Speed> speeds;
    17.         public ComponentDataArray<Position> positions;
    18.         public int updateOrder;
    19.         public int updateFrequency;
    20.  
    21.         public void Execute(int i)
    22.         {
    23.             if((i + updateOrder) % updateFrequency == 0)
    24.             {
    25.                 float3 prevPosition = positions[i].Value;
    26.                 float speed = speeds[i].Value;
    27.            
    28.                 positions[i] = new Position
    29.                 {
    30.                     Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    31.                 };
    32.             }
    33.        
    34.         }
    35.     }
    36.  
    37.     [BurstCompile]
    38.     struct PositionSpeedPositionFilter : IJobParallelFor
    39.     {
    40.         public float dt;
    41.         public ComponentDataArray<Speed> speeds;
    42.         public ComponentDataArray<Position> positions;
    43.         public int updateFrequency;
    44.  
    45.         public void Execute(int i)
    46.         {
    47.             float3 prevPosition = positions[i].Value;
    48.             float speed = speeds[i].Value;
    49.        
    50.             positions[i] = new Position
    51.             {
    52.                 Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    53.             };      
    54.         }
    55.     }
    56.  
    57.     [BurstCompile]
    58.     struct PositionSpeedPositionComponentJob : IJobProcessComponentData<Speed, Position>
    59.     {
    60.         public float dt;
    61.         public int updateFrequency;
    62.  
    63.         public void Execute(ref Speed speed, ref Position position)
    64.         {
    65.             position.Value = position.Value + new float3(0,1,0) * speed.Value * dt * updateFrequency;
    66.         }
    67.     }
    68.  
    69.     [BurstCompile]
    70.     struct PositionSpeedPositionChunkIteration : IJobParallelFor
    71.     {
    72.         public float dt;
    73.         public NativeArray<ArchetypeChunk> chunks;
    74.         public ArchetypeChunkComponentType<Position> positions;
    75.         [ReadOnly] public ArchetypeChunkComponentType<Speed> speeds;
    76.    
    77.         public void Execute(int chunkIndex)
    78.         {
    79.             var chunk = chunks[chunkIndex];
    80.             var chunkCount = chunk.Count;
    81.             var chunkPositions = chunk.GetNativeArray(positions);
    82.             var chunkSpeeds = chunk.GetNativeArray(speeds);
    83.        
    84.             for(int i=0; i<chunkCount; i++)
    85.             {
    86.                 float3 prevPosition = chunkPositions[i].Value;
    87.                 float3 speed = chunkSpeeds[i].Value;
    88.            
    89.                 chunkPositions[i] = new Position
    90.                 {
    91.                     Value = prevPosition + new float3(0,1,0) * speed * dt * 1
    92.                 };    
    93.             }
    94.         }
    95.     }
    96.  
    97.     private ComponentGroup m_Group;
    98.  
    99.     protected override void OnCreateManager()
    100.     {
    101.         m_Group = GetComponentGroup(typeof(Position), typeof(Speed), typeof(UpdateOrder));
    102.    
    103.         GameObject prefab = PrefabHolder.GetActive().prefab;
    104.         int numberToSpawn = PrefabHolder.GetActive().numberToSpawn;
    105.    
    106.         for(int i=0; i<numberToSpawn; i++)
    107.         {
    108.             Entity entity = EntityManager.Instantiate(prefab);
    109.             UpdateOrder updateOrder = EntityManager.GetSharedComponentData<UpdateOrder>(entity);
    110.        
    111.             updateOrder.Value = i % updateFrequency;
    112.        
    113.             EntityManager.SetSharedComponentData(entity, updateOrder);
    114.         }
    115.     }
    116.  
    117.     int updateOrder = 0;
    118.     int updateFrequency = 10;
    119.     int mode = 0;
    120.  
    121.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    122.     {
    123.         if(Input.GetKeyDown(KeyCode.A))
    124.         {
    125.             mode = 0;
    126.             m_Group.ResetFilter();
    127.             Debug.Log("Using filtering mode: " + mode);
    128.         }
    129.         else if(Input.GetKeyDown(KeyCode.B))
    130.         {
    131.             mode = 1;
    132.             m_Group.ResetFilter();
    133.             Debug.Log("Using filtering mode: " + mode);
    134.         }
    135.         else if(Input.GetKeyDown(KeyCode.C))
    136.         {
    137.             mode = 2;
    138.             m_Group.ResetFilter();
    139.             m_Group.SetFilter(new UpdateOrder { Value = 0 });
    140.             Debug.Log("Using filtering mode: " + mode);
    141.         }
    142.         else if(Input.GetKeyDown(KeyCode.D))
    143.         {
    144.             mode = 3;
    145.             m_Group.ResetFilter();
    146.             Debug.Log("Using filtering mode: " + mode);
    147.         }
    148.         else if(Input.GetKeyDown(KeyCode.E))
    149.         {
    150.             mode = 4;
    151.             m_Group.ResetFilter();
    152.             Debug.Log("Using filtering mode: " + mode);
    153.         }
    154.         else if(Input.GetKeyDown(KeyCode.F))
    155.         {
    156.             mode = 5;
    157.             m_Group.ResetFilter();
    158.             Debug.Log("Using filtering mode: " + mode);
    159.         }
    160.    
    161.    
    162.         updateOrder++;
    163.         if(updateOrder >= updateFrequency)
    164.         {
    165.             updateOrder = 0;
    166.         }
    167.    
    168.    
    169.    
    170.    
    171.         JobHandle jobHandle = inputDeps;
    172.  
    173.         if(mode == 0)
    174.         {
    175.             var positions = m_Group.GetComponentDataArray<Position>();
    176.             var speeds = m_Group.GetComponentDataArray<Speed>();
    177.             var job = new PositionSpeedPosition();
    178.             job.speeds = speeds;
    179.             job.positions = positions;
    180.             job.dt = Time.deltaTime;
    181.             job.updateOrder = updateOrder;
    182.             job.updateFrequency = updateFrequency;
    183.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    184.         }
    185.         else if(mode == 1)
    186.         {
    187.             m_Group.ResetFilter();
    188.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    189.             var positions = m_Group.GetComponentDataArray<Position>();
    190.             var speeds = m_Group.GetComponentDataArray<Speed>();
    191.        
    192.             var job = new PositionSpeedPositionFilter();
    193.             job.speeds = speeds;
    194.             job.positions = positions;
    195.             job.dt = Time.deltaTime;
    196.             job.updateFrequency = updateFrequency;
    197.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    198.         }
    199.         else if(mode == 2)
    200.         {
    201.             var positions = m_Group.GetComponentDataArray<Position>();
    202.             var speeds = m_Group.GetComponentDataArray<Speed>();
    203.             var job = new PositionSpeedPositionFilter();
    204.             job.speeds = speeds;
    205.             job.positions = positions;
    206.             job.dt = Time.deltaTime;
    207.             job.updateFrequency = updateFrequency;
    208.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    209.         }
    210.         else if(mode == 3)
    211.         {
    212.             m_Group.ResetFilter();
    213.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    214.  
    215.             var positions = m_Group.GetComponentDataArray<Position>();
    216.             var speeds = m_Group.GetComponentDataArray<Speed>();
    217.        
    218.             var job = new PositionSpeedPositionComponentJob()
    219.             {
    220.                 dt = Time.deltaTime,
    221.                 updateFrequency = updateFrequency
    222.             };
    223.        
    224.             jobHandle = job.Schedule(this, inputDeps);
    225.         }
    226.         else if(mode == 4)
    227.         {
    228.             m_Group.ResetFilter();
    229.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    230.  
    231.  
    232.             var job = new PositionSpeedPositionComponentJob()
    233.             {
    234.                 dt = Time.deltaTime,
    235.                 updateFrequency = updateFrequency
    236.             };
    237.        
    238.             jobHandle = job.Schedule(this, inputDeps);
    239.         }
    240.         else if(mode == 5)
    241.         {
    242.             var query = new EntityArchetypeQuery
    243.             {
    244.                 Any = Array.Empty<ComponentType>(),
    245.                 None = Array.Empty<ComponentType>(),
    246.                 All = new ComponentType[] {typeof(Position), typeof(Speed)}
    247.             };
    248.        
    249.             var chunks = EntityManager.CreateArchetypeChunkArray(query, Allocator.Temp);
    250.             var positions = EntityManager.GetArchetypeChunkComponentType<Position>(false);
    251.             var speeds = EntityManager.GetArchetypeChunkComponentType<Speed>(false);
    252.        
    253.             var job = new PositionSpeedPositionChunkIteration
    254.             {
    255.                 dt = Time.deltaTime,
    256.                 chunks = chunks,
    257.                 positions = positions,
    258.                 speeds = speeds
    259.             };
    260.        
    261.             jobHandle = job.Schedule(chunks.Length, 64, inputDeps);
    262.             jobHandle.Complete();
    263.             chunks.Dispose();
    264.         }
    265.    
    266.         return jobHandle;
    267.     }
    268. }
    269.  
    The times for different modes for PositionSpeed system are:

    mode 0 : <0.05 ms
    mode 1 : 1.3 ms
    mode 2 : 1.3 ms
    mode 3 : 1.3 ms
    mode 4 : 0.07 ms
    mode 5 : 3.4 ms

    The interesting bit here is the comparison between modes 4 and 5. They both are doing the same thing, i.e. updating positions every frame. However, chunk iteration appears to be 3.4 / 0.07 ~ 50 times slower than mode 4 (IJobProcessComponentData). Do I still miss something here?
     
    Last edited: Oct 29, 2018
  20. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    IJobProcessComponentData is Chunk Iteration and Component group under hood.

    In mode 5 you freeze main thread calling Complete, you immediatly wait untill job done.

    Also you can use IJobChunk in this case
     
  21. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So I just managed to get IJobChunk working as in mode 6:

    Code (CSharp):
    1. using System;
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Burst;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7. using Unity.Collections;
    8. using UnityEngine;
    9.  
    10. public class PositionSpeedSystem : JobComponentSystem
    11. {
    12.     [BurstCompile]
    13.     struct PositionSpeedPosition : IJobParallelFor
    14.     {
    15.         public float dt;
    16.         public ComponentDataArray<Speed> speeds;
    17.         public ComponentDataArray<Position> positions;
    18.         public int updateOrder;
    19.         public int updateFrequency;
    20.  
    21.         public void Execute(int i)
    22.         {
    23.             if((i + updateOrder) % updateFrequency == 0)
    24.             {
    25.                 float3 prevPosition = positions[i].Value;
    26.                 float speed = speeds[i].Value;
    27.              
    28.                 positions[i] = new Position
    29.                 {
    30.                     Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    31.                 };
    32.             }
    33.          
    34.         }
    35.     }
    36.  
    37.     [BurstCompile]
    38.     struct PositionSpeedPositionFilter : IJobParallelFor
    39.     {
    40.         public float dt;
    41.         public ComponentDataArray<Speed> speeds;
    42.         public ComponentDataArray<Position> positions;
    43.         public int updateFrequency;
    44.  
    45.         public void Execute(int i)
    46.         {
    47.             float3 prevPosition = positions[i].Value;
    48.             float speed = speeds[i].Value;
    49.          
    50.             positions[i] = new Position
    51.             {
    52.                 Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    53.             };        
    54.         }
    55.     }
    56.  
    57.     [BurstCompile]
    58.     struct PositionSpeedPositionComponentJob : IJobProcessComponentData<Speed, Position>
    59.     {
    60.         public float dt;
    61.         public int updateFrequency;
    62.  
    63.         public void Execute(ref Speed speed, ref Position position)
    64.         {
    65.             position.Value = position.Value + new float3(0,1,0) * speed.Value * dt * updateFrequency;
    66.         }
    67.     }
    68.  
    69.     [BurstCompile]
    70.     struct PositionSpeedPositionChunkIteration : IJobParallelFor
    71.     {
    72.         public float dt;
    73.         public NativeArray<ArchetypeChunk> chunks;
    74.         public ArchetypeChunkComponentType<Position> positions;
    75.         [ReadOnly] public ArchetypeChunkComponentType<Speed> speeds;
    76.      
    77.         public void Execute(int chunkIndex)
    78.         {
    79.             var chunk = chunks[chunkIndex];
    80.             var chunkCount = chunk.Count;
    81.             var chunkPositions = chunk.GetNativeArray(positions);
    82.             var chunkSpeeds = chunk.GetNativeArray(speeds);
    83.          
    84.             for(int i=0; i<chunkCount; i++)
    85.             {
    86.                 float3 prevPosition = chunkPositions[i].Value;
    87.                 float3 speed = chunkSpeeds[i].Value;
    88.              
    89.                 chunkPositions[i] = new Position
    90.                 {
    91.                     Value = prevPosition + new float3(0,1,0) * speed * dt * 1
    92.                 };      
    93.             }
    94.         }
    95.     }
    96.  
    97.     [BurstCompile]
    98.     struct PositionSpeedPositionIJobChunk : IJobChunk
    99.     {
    100.         public float dt;
    101.         public ArchetypeChunkComponentType<Position> positions;
    102.         [ReadOnly] public ArchetypeChunkComponentType<Speed> speeds;
    103.      
    104.         public void Execute(ArchetypeChunk chunk, int chunkIndex)
    105.         {
    106.             var chunkCount = chunk.Count;
    107.             var chunkPositions = chunk.GetNativeArray(positions);
    108.             var chunkSpeeds = chunk.GetNativeArray(speeds);
    109.          
    110.             for(int i=0; i<chunkCount; i++)
    111.             {
    112.                 float3 prevPosition = chunkPositions[i].Value;
    113.                 float3 speed = chunkSpeeds[i].Value;
    114.              
    115.                 chunkPositions[i] = new Position
    116.                 {
    117.                     Value = prevPosition + new float3(0,1,0) * speed * dt * 1
    118.                 };      
    119.             }
    120.         }
    121.     }
    122.  
    123.     [BurstCompile]
    124.     struct PositionSpeedPositionIJobChunkShared : IJobChunk
    125.     {
    126.         public float dt;
    127.         public ArchetypeChunkComponentType<Position> positions;
    128.         [ReadOnly] public ArchetypeChunkComponentType<Speed> speeds;
    129.         [ReadOnly] public ArchetypeChunkSharedComponentType<UpdateOrder> updateOrder;
    130.      
    131.         public void Execute(ArchetypeChunk chunk, int chunkIndex)
    132.         {
    133.             var chunkCount = chunk.Count;
    134.             var chunkPositions = chunk.GetNativeArray(positions);
    135.             var chunkSpeeds = chunk.GetNativeArray(speeds);
    136.             var chunkUpdateOrderSharedIndex = chunk.GetSharedComponentIndex(updateOrder);
    137.          
    138. //             var updateOrderData = EntityManager.GetSharedComponentData<UpdateOrder>(chunkUpdateOrderSharedIndex);
    139.          
    140.             for(int i=0; i<chunkCount; i++)
    141.             {
    142.                 float3 prevPosition = chunkPositions[i].Value;
    143.                 float3 speed = chunkSpeeds[i].Value;
    144.              
    145.                 chunkPositions[i] = new Position
    146.                 {
    147.                     Value = prevPosition + new float3(0,1,0) * speed * dt * 1
    148.                 };      
    149.             }
    150.         }
    151.     }
    152.  
    153.  
    154.  
    155.  
    156.  
    157.     private ComponentGroup m_Group;
    158.  
    159.     protected override void OnCreateManager()
    160.     {
    161.         m_Group = GetComponentGroup(typeof(Position), typeof(Speed), typeof(UpdateOrder));
    162.      
    163.         GameObject prefab = PrefabHolder.GetActive().prefab;
    164.         int numberToSpawn = PrefabHolder.GetActive().numberToSpawn;
    165.      
    166.         for(int i=0; i<numberToSpawn; i++)
    167.         {
    168.             Entity entity = EntityManager.Instantiate(prefab);
    169.             UpdateOrder updateOrder = EntityManager.GetSharedComponentData<UpdateOrder>(entity);
    170.          
    171.             updateOrder.Value = i % updateFrequency;
    172.          
    173.             EntityManager.SetSharedComponentData(entity, updateOrder);
    174.         }
    175.     }
    176.  
    177.     int updateOrder = 0;
    178.     int updateFrequency = 10;
    179.     int mode = 0;
    180.  
    181.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    182.     {
    183.         if(Input.GetKeyDown(KeyCode.A))
    184.         {
    185.             mode = 0;
    186.             m_Group.ResetFilter();
    187.             Debug.Log("Using filtering mode: " + mode);
    188.         }
    189.         else if(Input.GetKeyDown(KeyCode.B))
    190.         {
    191.             mode = 1;
    192.             m_Group.ResetFilter();
    193.             Debug.Log("Using filtering mode: " + mode);
    194.         }
    195.         else if(Input.GetKeyDown(KeyCode.C))
    196.         {
    197.             mode = 2;
    198.             m_Group.ResetFilter();
    199.             m_Group.SetFilter(new UpdateOrder { Value = 0 });
    200.             Debug.Log("Using filtering mode: " + mode);
    201.         }
    202.         else if(Input.GetKeyDown(KeyCode.D))
    203.         {
    204.             mode = 3;
    205.             m_Group.ResetFilter();
    206.             Debug.Log("Using filtering mode: " + mode);
    207.         }
    208.         else if(Input.GetKeyDown(KeyCode.E))
    209.         {
    210.             mode = 4;
    211.             m_Group.ResetFilter();
    212.             Debug.Log("Using filtering mode: " + mode);
    213.         }
    214.         else if(Input.GetKeyDown(KeyCode.F))
    215.         {
    216.             mode = 5;
    217.             m_Group.ResetFilter();
    218.             Debug.Log("Using filtering mode: " + mode);
    219.         }
    220.         else if(Input.GetKeyDown(KeyCode.G))
    221.         {
    222.             mode = 6;
    223.             m_Group.ResetFilter();
    224.             Debug.Log("Using filtering mode: " + mode);
    225.         }
    226.         else if(Input.GetKeyDown(KeyCode.H))
    227.         {
    228.             mode = 7;
    229.             m_Group.ResetFilter();
    230.             Debug.Log("Using filtering mode: " + mode);
    231.         }
    232.      
    233.      
    234.         updateOrder++;
    235.         if(updateOrder >= updateFrequency)
    236.         {
    237.             updateOrder = 0;
    238.         }
    239.      
    240.      
    241.      
    242.      
    243.         JobHandle jobHandle = inputDeps;
    244.  
    245.         if(mode == 0)
    246.         {
    247.             var positions = m_Group.GetComponentDataArray<Position>();
    248.             var speeds = m_Group.GetComponentDataArray<Speed>();
    249.             var job = new PositionSpeedPosition();
    250.             job.speeds = speeds;
    251.             job.positions = positions;
    252.             job.dt = Time.deltaTime;
    253.             job.updateOrder = updateOrder;
    254.             job.updateFrequency = updateFrequency;
    255.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    256.         }
    257.         else if(mode == 1)
    258.         {
    259.             m_Group.ResetFilter();
    260.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    261.             var positions = m_Group.GetComponentDataArray<Position>();
    262.             var speeds = m_Group.GetComponentDataArray<Speed>();
    263.          
    264.             var job = new PositionSpeedPositionFilter();
    265.             job.speeds = speeds;
    266.             job.positions = positions;
    267.             job.dt = Time.deltaTime;
    268.             job.updateFrequency = updateFrequency;
    269.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    270.         }
    271.         else if(mode == 2)
    272.         {
    273.             var positions = m_Group.GetComponentDataArray<Position>();
    274.             var speeds = m_Group.GetComponentDataArray<Speed>();
    275.             var job = new PositionSpeedPositionFilter();
    276.             job.speeds = speeds;
    277.             job.positions = positions;
    278.             job.dt = Time.deltaTime;
    279.             job.updateFrequency = updateFrequency;
    280.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    281.         }
    282.         else if(mode == 3)
    283.         {
    284.             m_Group.ResetFilter();
    285.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    286.  
    287.             var positions = m_Group.GetComponentDataArray<Position>();
    288.             var speeds = m_Group.GetComponentDataArray<Speed>();
    289.          
    290.             var job = new PositionSpeedPositionComponentJob()
    291.             {
    292.                 dt = Time.deltaTime,
    293.                 updateFrequency = updateFrequency
    294.             };
    295.          
    296.             jobHandle = job.Schedule(this, inputDeps);
    297.         }
    298.         else if(mode == 4)
    299.         {
    300.             m_Group.ResetFilter();
    301.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    302.  
    303.  
    304.             var job = new PositionSpeedPositionComponentJob()
    305.             {
    306.                 dt = Time.deltaTime,
    307.                 updateFrequency = updateFrequency
    308.             };
    309.          
    310.             jobHandle = job.Schedule(this, inputDeps);
    311.         }
    312.         else if(mode == 5)
    313.         {
    314.             var query = new EntityArchetypeQuery
    315.             {
    316.                 Any = Array.Empty<ComponentType>(),
    317.                 None = Array.Empty<ComponentType>(),
    318.                 All = new ComponentType[] {typeof(Position), typeof(Speed)}
    319.             };
    320.          
    321.             var chunks = EntityManager.CreateArchetypeChunkArray(query, Allocator.Temp);
    322.             var positions = EntityManager.GetArchetypeChunkComponentType<Position>(false);
    323.             var speeds = EntityManager.GetArchetypeChunkComponentType<Speed>(false);
    324.          
    325.             var job = new PositionSpeedPositionChunkIteration
    326.             {
    327.                 dt = Time.deltaTime,
    328.                 chunks = chunks,
    329.                 positions = positions,
    330.                 speeds = speeds
    331.             };
    332.          
    333.             jobHandle = job.Schedule(chunks.Length, 64, inputDeps);
    334.             jobHandle.Complete();
    335.             chunks.Dispose();
    336.         }
    337.         else if(mode == 6)
    338.         {
    339.             var query = GetComponentGroup(new EntityArchetypeQuery()
    340.             {
    341.                 Any = Array.Empty<ComponentType>(),
    342.                 None = Array.Empty<ComponentType>(),
    343.                 All = new ComponentType[] {ComponentType.Create<Position>(), ComponentType.ReadOnly<Speed>()}
    344.             });
    345.          
    346.             var job = new PositionSpeedPositionIJobChunk()
    347.             {
    348.                 dt = Time.deltaTime,
    349.                 positions = GetArchetypeChunkComponentType<Position>(false),
    350.                 speeds = GetArchetypeChunkComponentType<Speed>(true),
    351.             };
    352.             jobHandle = job.Schedule(query, inputDeps);
    353.         }
    354.         else if(mode == 7)
    355.         {
    356.             var query = GetComponentGroup(new EntityArchetypeQuery()
    357.             {
    358.                 Any = Array.Empty<ComponentType>(),
    359.                 None = Array.Empty<ComponentType>(),
    360.                 All = new ComponentType[] {ComponentType.Create<Position>(), ComponentType.ReadOnly<Speed>()}
    361.             });
    362.          
    363.             var job = new PositionSpeedPositionIJobChunkShared()
    364.             {
    365.                 dt = Time.deltaTime,
    366.                 positions = GetArchetypeChunkComponentType<Position>(false),
    367.                 speeds = GetArchetypeChunkComponentType<Speed>(true),
    368.                 updateOrder = GetArchetypeChunkSharedComponentType<UpdateOrder>()
    369.             };
    370.             jobHandle = job.Schedule(query, inputDeps);
    371.         }
    372.      
    373.         return jobHandle;
    374.     }
    375. }
    376.  
    The interesting bit is that in mode 5 jobHandle.Complete() can't be removed as chunks needs to be disposed. Removing jobHandle.Complete() throws errors. Mode 6 works like a charm with positions updated correctly and the frame time dropped to 0.05 ms.

    The next step is SCD and accessing it through shared component index. I started to set the example in mode 7. chunk.GetSharedComponentIndex is providing the index. But what I would need is to access the actual UpdateOrder data which this index represents, and read Value stored inside. I tried to use EntityManager.GetSharedComponentData but I am getting issues as I can't pass EntityManager in the job (as it's reference type). I noticed that GetSharedComponentData can be used outside the job. But when scheduling IJobChunk I don't have access to chunk index outside the job in the main thread. Is there a way to solve this?
     
  22. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    [DeallocateOnJobCompletion][ReadOnly] public NativeArray<ArchetypeChunk> chunks;
     
  23. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    https://github.com/Unity-Technologi...ta-from-archetypechunkgetsharedcomponentindex

    1. SCD is for grouping, filtering and other stuff like that.
    2. Can't be Bursted.
    You can get list of unique values mapped with indeces, know that you can do what you want.
     
  24. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So I am glad to announce that the desired performance is now achieved. Here is the code with several additional modes:

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Burst;
    6. using Unity.Mathematics;
    7. using Unity.Transforms;
    8. using Unity.Collections;
    9. using UnityEngine;
    10.  
    11. public class PositionSpeedSystem : JobComponentSystem
    12. {
    13.     [BurstCompile]
    14.     struct PositionSpeedPosition : IJobParallelFor
    15.     {
    16.         public float dt;
    17.         public ComponentDataArray<Speed> speeds;
    18.         public ComponentDataArray<Position> positions;
    19.         public int updateOrder;
    20.         public int updateFrequency;
    21.  
    22.         public void Execute(int i)
    23.         {
    24.             if((i + updateOrder) % updateFrequency == 0)
    25.             {
    26.                 float3 prevPosition = positions[i].Value;
    27.                 float speed = speeds[i].Value;
    28.                
    29.                 positions[i] = new Position
    30.                 {
    31.                     Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    32.                 };
    33.             }
    34.            
    35.         }
    36.     }
    37.    
    38.     [BurstCompile]
    39.     struct PositionSpeedPositionFilter : IJobParallelFor
    40.     {
    41.         public float dt;
    42.         public ComponentDataArray<Speed> speeds;
    43.         public ComponentDataArray<Position> positions;
    44.         public int updateFrequency;
    45.  
    46.         public void Execute(int i)
    47.         {
    48.             float3 prevPosition = positions[i].Value;
    49.             float speed = speeds[i].Value;
    50.            
    51.             positions[i] = new Position
    52.             {
    53.                 Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    54.             };          
    55.         }
    56.     }
    57.  
    58.     [BurstCompile]
    59.     struct PositionSpeedPositionComponentJob : IJobProcessComponentData<Speed, Position>
    60.     {
    61.         public float dt;
    62.         public int updateFrequency;
    63.  
    64.         public void Execute(ref Speed speed, ref Position position)
    65.         {
    66.             position.Value = position.Value + new float3(0,1,0) * speed.Value * dt * updateFrequency;
    67.         }
    68.     }
    69.    
    70.     [BurstCompile]
    71.     struct PositionSpeedPositionChunkIteration : IJobParallelFor
    72.     {
    73.         public float dt;
    74.         [ReadOnly] public NativeArray<ArchetypeChunk> chunks;
    75.         public ArchetypeChunkComponentType<Position> positions;
    76.         [ReadOnly] public ArchetypeChunkComponentType<Speed> speeds;
    77.        
    78.         public void Execute(int chunkIndex)
    79.         {
    80.             var chunk = chunks[chunkIndex];
    81.             var chunkCount = chunk.Count;
    82.             var chunkPositions = chunk.GetNativeArray(positions);
    83.             var chunkSpeeds = chunk.GetNativeArray(speeds);
    84.            
    85.             for(int i=0; i<chunkCount; i++)
    86.             {
    87.                 float3 prevPosition = chunkPositions[i].Value;
    88.                 float3 speed = chunkSpeeds[i].Value;
    89.                
    90.                 chunkPositions[i] = new Position
    91.                 {
    92.                     Value = prevPosition + new float3(0,1,0) * speed * dt * 1
    93.                 };        
    94.             }
    95.         }
    96.     }
    97.    
    98.     [BurstCompile]
    99.     struct PositionSpeedPositionIJobChunk : IJobChunk
    100.     {
    101.         public float dt;
    102.         public ArchetypeChunkComponentType<Position> positions;
    103.         [ReadOnly] public ArchetypeChunkComponentType<Speed> speeds;
    104.        
    105.         public void Execute(ArchetypeChunk chunk, int chunkIndex)
    106.         {
    107.             var chunkCount = chunk.Count;
    108.             var chunkPositions = chunk.GetNativeArray(positions);
    109.             var chunkSpeeds = chunk.GetNativeArray(speeds);
    110.            
    111.             for(int i=0; i<chunkCount; i++)
    112.             {
    113.                 float3 prevPosition = chunkPositions[i].Value;
    114.                 float3 speed = chunkSpeeds[i].Value;
    115.                
    116.                 chunkPositions[i] = new Position
    117.                 {
    118.                     Value = prevPosition + new float3(0,1,0) * speed * dt * 1
    119.                 };        
    120.             }
    121.         }
    122.     }
    123.    
    124.     [BurstCompile]
    125.     struct PositionSpeedPositionIJobChunkShared : IJobChunk
    126.     {
    127.         public float dt;
    128.         public ArchetypeChunkComponentType<Position> positions;
    129.         [ReadOnly] public ArchetypeChunkComponentType<Speed> speeds;
    130.         [ReadOnly] public ArchetypeChunkSharedComponentType<UpdateOrder> updateOrderACSCT;
    131.         [ReadOnly] public NativeArray<UpdateOrder> updateOrdersNativeArray;
    132.         [ReadOnly] public int updateOrder;
    133.         [ReadOnly] public int updateFrequency;
    134.        
    135.         public void Execute(ArchetypeChunk chunk, int chunkIndex)
    136.         {
    137.             var chunkCount = chunk.Count;
    138.             var chunkPositions = chunk.GetNativeArray(positions);
    139.             var chunkSpeeds = chunk.GetNativeArray(speeds);
    140.             var chunkUpdateOrderSharedIndex = chunk.GetSharedComponentIndex(updateOrderACSCT);
    141.            
    142.             var updateOrderData = updateOrdersNativeArray[chunkUpdateOrderSharedIndex];
    143.            
    144.             if(updateOrderData.Value == updateOrder)
    145.             {
    146.                 for(int i=0; i<chunkCount; i++)
    147.                 {
    148.                     float3 prevPosition = chunkPositions[i].Value;
    149.                     float3 speed = chunkSpeeds[i].Value;
    150.                
    151.                     chunkPositions[i] = new Position
    152.                     {
    153.                         Value = prevPosition + new float3(0,1,0) * speed * dt * updateFrequency
    154.                     };        
    155.                 }
    156.             }
    157.         }
    158.     }
    159.    
    160.    
    161.    
    162.    
    163.    
    164.     private ComponentGroup m_Group;
    165.    
    166.     protected override void OnCreateManager()
    167.     {
    168.         m_Group = GetComponentGroup(typeof(Position), typeof(Speed), typeof(UpdateOrder));
    169.        
    170.         GameObject prefab = PrefabHolder.GetActive().prefab;
    171.         int numberToSpawn = PrefabHolder.GetActive().numberToSpawn;
    172.        
    173.         for(int i=0; i<numberToSpawn; i++)
    174.         {
    175.             Entity entity = EntityManager.Instantiate(prefab);
    176.             UpdateOrder updateOrder = EntityManager.GetSharedComponentData<UpdateOrder>(entity);
    177.            
    178.             updateOrder.Value = i % updateFrequency;
    179.            
    180.             EntityManager.SetSharedComponentData(entity, updateOrder);
    181.         }
    182.     }
    183.    
    184.     int updateOrder = 0;
    185.     int updateFrequency = 10;
    186.     int mode = 0;
    187.     static NativeArray<ArchetypeChunk> chunksMode5;
    188.     static NativeArray<UpdateOrder> updateOrdersNativeArray;
    189.     static List<UpdateOrder> updateOrdersList = new List<UpdateOrder>();
    190.    
    191.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    192.     {
    193.         if(Input.GetKeyDown(KeyCode.A))
    194.         {
    195.             mode = 0;
    196.             m_Group.ResetFilter();
    197.             DisposeNativeArrays();
    198.             Debug.Log("Using mode: " + mode + ". Uses IJobParalelFor with updateOrder and updateFrequency on every entity. < 0.05 ms");
    199.         }
    200.         else if(Input.GetKeyDown(KeyCode.B))
    201.         {
    202.             mode = 1;
    203.             m_Group.ResetFilter();
    204.             DisposeNativeArrays();
    205.             Debug.Log("Using mode: " + mode + ". Uses IJobParalelFor with filtered SCD. Filtering gives bad performance. 1.3 ms");
    206.         }
    207.         else if(Input.GetKeyDown(KeyCode.C))
    208.         {
    209.             mode = 2;
    210.             m_Group.ResetFilter();
    211.             m_Group.SetFilter(new UpdateOrder { Value = 0 });
    212.             DisposeNativeArrays();
    213.             Debug.Log("Using mode: " + mode + ". Same as mode 1 but without setting filter every update. Shows bad perfromance, caused by calling GetComponentDataArray when filter is set. 1.3 ms");
    214.         }
    215.         else if(Input.GetKeyDown(KeyCode.D))
    216.         {
    217.             mode = 3;
    218.             m_Group.ResetFilter();
    219.             DisposeNativeArrays();
    220.             Debug.Log("Using mode: " + mode + ". Uses ComponentJob with artificially calling GetComponentDataArray. Poor performance. 1.3 ms");
    221.         }
    222.         else if(Input.GetKeyDown(KeyCode.E))
    223.         {
    224.             mode = 4;
    225.             m_Group.ResetFilter();
    226.             DisposeNativeArrays();
    227.             Debug.Log("Using mode: " + mode + ". Uses ComponentJob without calling GetComponentDataArray. Good performance. < 0.05 ms");
    228.         }
    229.         else if(Input.GetKeyDown(KeyCode.F))
    230.         {
    231.             mode = 5;
    232.             m_Group.ResetFilter();
    233.             DisposeNativeArrays();
    234.             Debug.Log("Using mode: " + mode + ". Uses Chunk iteration with IJobParallelFor and iterating over all chunks and entities. Poor performance due to chunk request on the main thread. 0.60 ms");
    235.         }
    236.         else if(Input.GetKeyDown(KeyCode.G))
    237.         {
    238.             mode = 6;
    239.             m_Group.ResetFilter();
    240.             DisposeNativeArrays();
    241.             Debug.Log("Using mode: " + mode + ". IJobChunk with processing all entities. Good performance. < 0.05 ms");
    242.         }
    243.         else if(Input.GetKeyDown(KeyCode.H))
    244.         {
    245.             mode = 7;
    246.             m_Group.ResetFilter();
    247.             DisposeNativeArrays();
    248.             Debug.Log("Using mode: " + mode + ". IJobChunk with processing only requested SCD chunks. SCD are refreshed on every update. Good performance. 0.13 ms");
    249.         }
    250.         else if(Input.GetKeyDown(KeyCode.I))
    251.         {
    252.             mode = 8;
    253.             m_Group.ResetFilter();
    254.             RefreshUpdateOrdersLists(true);
    255.             Debug.Log("Using mode: " + mode + ". IJobChunk with processing only requested SCD chunks and under assumption that SCD never change. If SCD change, RefreshUpdateOrdersLists needs to be called manually. Best filtered queries performance. 0.06 ms");
    256.         }
    257.        
    258.        
    259.         updateOrder++;
    260.         if(updateOrder >= updateFrequency)
    261.         {
    262.             updateOrder = 0;
    263.         }
    264.        
    265.        
    266.        
    267.        
    268.         JobHandle jobHandle = inputDeps;
    269.  
    270.         if(mode == 0)
    271.         {
    272.             var positions = m_Group.GetComponentDataArray<Position>();
    273.             var speeds = m_Group.GetComponentDataArray<Speed>();
    274.             var job = new PositionSpeedPosition();
    275.             job.speeds = speeds;
    276.             job.positions = positions;
    277.             job.dt = Time.deltaTime;
    278.             job.updateOrder = updateOrder;
    279.             job.updateFrequency = updateFrequency;
    280.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    281.         }
    282.         else if(mode == 1)
    283.         {
    284.             m_Group.ResetFilter();
    285.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    286.             var positions = m_Group.GetComponentDataArray<Position>();
    287.             var speeds = m_Group.GetComponentDataArray<Speed>();
    288.            
    289.             var job = new PositionSpeedPositionFilter();
    290.             job.speeds = speeds;
    291.             job.positions = positions;
    292.             job.dt = Time.deltaTime;
    293.             job.updateFrequency = updateFrequency;
    294.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    295.         }
    296.         else if(mode == 2)
    297.         {  
    298.             var positions = m_Group.GetComponentDataArray<Position>();
    299.             var speeds = m_Group.GetComponentDataArray<Speed>();  
    300.             var job = new PositionSpeedPositionFilter();
    301.             job.speeds = speeds;
    302.             job.positions = positions;
    303.             job.dt = Time.deltaTime;
    304.             job.updateFrequency = updateFrequency;
    305.             jobHandle = job.Schedule(positions.Length, 64, inputDeps);
    306.         }
    307.         else if(mode == 3)
    308.         {  
    309.             m_Group.ResetFilter();
    310.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    311.  
    312.             var positions = m_Group.GetComponentDataArray<Position>();
    313.             var speeds = m_Group.GetComponentDataArray<Speed>();
    314.            
    315.             var job = new PositionSpeedPositionComponentJob()
    316.             {
    317.                 dt = Time.deltaTime,
    318.                 updateFrequency = updateFrequency
    319.             };
    320.            
    321.             jobHandle = job.Schedule(this, inputDeps);
    322.         }
    323.         else if(mode == 4)
    324.         {  
    325.             m_Group.ResetFilter();
    326.             m_Group.SetFilter(new UpdateOrder { Value = updateOrder % updateFrequency });
    327.  
    328.  
    329.             var job = new PositionSpeedPositionComponentJob()
    330.             {
    331.                 dt = Time.deltaTime,
    332.                 updateFrequency = updateFrequency
    333.             };
    334.            
    335.             jobHandle = job.Schedule(this, inputDeps);
    336.         }
    337.         else if(mode == 5)
    338.         {
    339.             var query = new EntityArchetypeQuery
    340.             {
    341.                 Any = Array.Empty<ComponentType>(),
    342.                 None = Array.Empty<ComponentType>(),
    343.                 All = new ComponentType[] {typeof(Position), typeof(Speed)}
    344.             };
    345.            
    346.             DisposeNativeArrays();
    347.            
    348.             chunksMode5 = EntityManager.CreateArchetypeChunkArray(query, Allocator.Temp);          
    349.             var positions = EntityManager.GetArchetypeChunkComponentType<Position>(false);
    350.             var speeds = EntityManager.GetArchetypeChunkComponentType<Speed>(true);
    351.            
    352.             var job = new PositionSpeedPositionChunkIteration
    353.             {
    354.                 dt = Time.deltaTime,
    355.                 chunks = chunksMode5,
    356.                 positions = positions,
    357.                 speeds = speeds
    358.             };
    359.            
    360.             jobHandle = job.Schedule(chunksMode5.Length, 64, inputDeps);
    361.         }
    362.         else if(mode == 6)
    363.         {
    364.             var query = GetComponentGroup(new EntityArchetypeQuery()
    365.             {
    366.                 Any = Array.Empty<ComponentType>(),
    367.                 None = Array.Empty<ComponentType>(),
    368.                 All = new ComponentType[] {ComponentType.Create<Position>(), ComponentType.ReadOnly<Speed>()}
    369.             });
    370.            
    371.             var job = new PositionSpeedPositionIJobChunk()
    372.             {
    373.                 dt = Time.deltaTime,
    374.                 positions = GetArchetypeChunkComponentType<Position>(false),
    375.                 speeds = GetArchetypeChunkComponentType<Speed>(true),
    376.             };
    377.             jobHandle = job.Schedule(query, inputDeps);
    378.         }
    379.         else if(mode == 7)
    380.         {
    381.             var query = GetComponentGroup(new EntityArchetypeQuery()
    382.             {
    383.                 Any = Array.Empty<ComponentType>(),
    384.                 None = Array.Empty<ComponentType>(),
    385.                 All = new ComponentType[] {ComponentType.Create<Position>(), ComponentType.ReadOnly<Speed>()}
    386.             });
    387.            
    388.             RefreshUpdateOrdersLists(false);
    389.            
    390.             var job = new PositionSpeedPositionIJobChunkShared()
    391.             {
    392.                 dt = Time.deltaTime,
    393.                 positions = GetArchetypeChunkComponentType<Position>(false),
    394.                 speeds = GetArchetypeChunkComponentType<Speed>(true),
    395.                 updateOrderACSCT = GetArchetypeChunkSharedComponentType<UpdateOrder>(),
    396.                 updateOrdersNativeArray = updateOrdersNativeArray,
    397.                 updateOrder = updateOrder,
    398.                 updateFrequency = updateFrequency
    399.             };
    400.             jobHandle = job.Schedule(query, inputDeps);
    401.         }
    402.         else if(mode == 8)
    403.         {
    404.             var query = GetComponentGroup(new EntityArchetypeQuery()
    405.             {
    406.                 Any = Array.Empty<ComponentType>(),
    407.                 None = Array.Empty<ComponentType>(),
    408.                 All = new ComponentType[] {ComponentType.Create<Position>(), ComponentType.ReadOnly<Speed>()}
    409.             });
    410.            
    411.             var job = new PositionSpeedPositionIJobChunkShared()
    412.             {
    413.                 dt = Time.deltaTime,
    414.                 positions = GetArchetypeChunkComponentType<Position>(false),
    415.                 speeds = GetArchetypeChunkComponentType<Speed>(true),
    416.                 updateOrderACSCT = GetArchetypeChunkSharedComponentType<UpdateOrder>(),
    417.                 updateOrdersNativeArray = updateOrdersNativeArray,
    418.                 updateOrder = updateOrder,
    419.                 updateFrequency = updateFrequency
    420.             };
    421.             jobHandle = job.Schedule(query, inputDeps);
    422.         }
    423.        
    424.        
    425.         return jobHandle;
    426.     }
    427.    
    428.     public void RefreshUpdateOrdersLists(bool usePersistentAllocation)
    429.     {
    430.         DisposeNativeArrays();
    431.        
    432.         updateOrdersList.Clear();
    433.         EntityManager.GetAllUniqueSharedComponentData<UpdateOrder>(updateOrdersList);
    434.        
    435.         if(usePersistentAllocation)
    436.         {
    437.             updateOrdersNativeArray = new NativeArray<UpdateOrder>(updateOrdersList.Count, Allocator.Persistent);
    438.         }
    439.         else{
    440.             updateOrdersNativeArray = new NativeArray<UpdateOrder>(updateOrdersList.Count, Allocator.Temp);
    441.         }
    442.        
    443.         for(int i=0; i<updateOrdersList.Count; i++)
    444.         {
    445.             updateOrdersNativeArray[i] = updateOrdersList[i];
    446.         }
    447.     }
    448.    
    449.     public static void DisposeNativeArrays()
    450.     {
    451.         if(chunksMode5.IsCreated)
    452.         {
    453.             chunksMode5.Dispose();
    454.         }
    455.         if(updateOrdersNativeArray.IsCreated)
    456.         {
    457.             updateOrdersNativeArray.Dispose();
    458.         }
    459.     }
    460. }
    461.  
    So far modes 7 and 8 have the fastest throughput when filtered queries are processed using SCD. Mode 7 is slightly slower than 8 as mode 8 benefits from the assumption that SCD does not change. If SCD changes while on mode 8, it needs to call RefreshUpdateOrdersLists immediately.

    I was also a bit surprised to find that EntityManager.GetAllUniqueSharedComponentData takes lists as input rather than returning native array.

    Thanks again for these discussions, which helped to get all 8 modes running and any feedback / improvements are welcome. I will also make a git repo for a full project and drop a link soon so everyone can easily clone and use these tests.