Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

IJobChunk and IJobProcessComponentData(WithEntity) and accessing other Entities' data

Discussion in 'Data Oriented Technology Stack' started by The5, Dec 16, 2018.

  1. The5

    The5

    Joined:
    May 26, 2017
    Posts:
    19
    I am currently re-writing my 2D collision system (a simplified version of the NordeusDemos' system) to get myself familiar with the the current/expected ECS "standard".

    My primary reference, so far, is the ECS Package itself.
    The Transform system uses IJobParallelFor for pretty much all it's work.
    This seems to be the most flexible Job to implement.

    The Rendering system already utilizes the convenient IJobProcessComponentData.
    (IJobProcessComponentDataWithEntity is not used but it just seems to also pass you the Entity itself.)
    However, I am not quite sure how to access other Entities' components within those Jobs.
    (I assume IJobProcessComponentData is designed for use-cases where you do not modify any Entities other than the one in the current iteration.)

    IJobChunk is unused so far, but I think it was said to be potentially more performant. I cant seem to find the source for this statement anymore though. Not sure if it was on the sample repository or here in the Forum.
    However, with IJobChunk I am currently also struggling to access other Entities' components.
    E.g. write a new values for both the current entity and the one collision was detected with.

    Code (CSharp):
    1. var job = new HashPositionsJob
    2. {
    3.     CellSize = CONFIG.GRID.CELL_SIZE,
    4.     GridCells = this.GridCells, //is a NativeMultiHashMap<int2,Entity> GridCells;
    5.     EntityType = this.GetArchetypeChunkEntityType(),
    6.     PositionType = this.GetArchetypeChunkComponentType<Position>(true)
    7. };
    Code (CSharp):
    1. public void Execute(ArchetypeChunk chunk, int chunkIndex)
    2. {
    3.     var entities = chunk.GetNativeArray(EntityType);
    4.     var positions = chunk.GetNativeArray(PositionType);
    5.  
    6.     for(int i = 0; i < chunk.Count; i++){  
    7.        Entity selfEntity = entities[i];
    8.        float3 selfPos = positions[i].Value;
    9.        //Hash the position...
    10.        //Retrieve adjacent entities from NativeMultiHashMap via NativeMultiHashMapIterator
    11.        //...
    12.        //Here I would like to access a other entities Position component via the Entity retrieved from the HashMap.
    13.        //In this particular use-case even read-only. But a read/write should be possible?
    14.     }
    15. }
    I suppose I am generally a bit confused, on how to properly access other Entities' components from within those more specialized Jobs.
    The GetComponentDataFromEntity only seems to work if passed to a IJobParallelFor.
    Code (CSharp):
    1. PositionsFromEntity = GetComponentDataFromEntity<Position>(true);
    2.  
    E.g. when attempting to use it with IJobChunk I get the Error: "Two Natvie Arrays may not be the same."
    Which makes sense seeing that I pass it once via
    GetArchetypeChunkComponentType<Position>(false);
    and a second time via
    GetComponentDataFromEntity<Position>(true);
     
    Last edited: Dec 16, 2018
  2. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,362
    If you absolutley shure than you not write and read in to the same position at the same time you can disable safety checks for that container.
     
  3. The5

    The5

    Joined:
    May 26, 2017
    Posts:
    19
    For this particular use case, yes. I do read/write only the current entities position and only read adjacent Entities' positions.
    (Thinking of it, any other usage would be illegal I suppose :p)

    So, is populating a separate [ReadOnly] public ComponentDataFromEntity<Position> PositionsFromEntity; Job member via system.GetComponentDataFromEntity<Position>() the intended way to access per-entity-accessible data inside a IJobChunk?

    Mind to give a short example?
     
  4. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,362
    It’s intended for everywhere if you need get data by entity. Today I’m out of PC, I can provide example from my systems only tomorrow.
     
  5. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,362
    This how I process indicators moving and clustering
    Code (CSharp):
    1.  
    2. [BurstCompile]
    3.     private struct
    4.         ProcessIndicatorsMoving : IJobProcessComponentDataWithEntity<IndicatorComponent, WorldMeshRenderBounds>
    5.     {
    6.         [NativeDisableParallelForRestriction] public ComponentDataFromEntity<Position>        posArray;
    7.         [NativeDisableParallelForRestriction] public ComponentDataFromEntity<HasIndicator>    entities;
    8.         [ReadOnly]                            public BufferFromEntity<IndicatorBufferElement> buffer;
    9.         [ReadOnly]                            public int                                      clusterIndex;
    10.         [ReadOnly]                            public float                                    clusterCellSize;
    11.         public EntityCommandBuffer.Concurrent ecb;
    12.  
    13.         public void Execute(Entity indicatorEntity, int index, [ReadOnly] ref IndicatorComponent res,
    14.             ref WorldMeshRenderBounds bounds)
    15.         {
    16.             if (entities.Exists(res.parentEntity) && entities[res.parentEntity].indicatorEntity == default(Entity))
    17.             {
    18.                 HasIndicator tmp = entities[res.parentEntity];
    19.                 tmp.indicatorEntity        = indicatorEntity;
    20.                 entities[res.parentEntity] = tmp;
    21.             }
    22.  
    23.             if (clusterIndex != 0)
    24.             {
    25.                 var b = buffer[indicatorEntity];
    26.                 if (b.Length > 0)
    27.                 {
    28.                     float xOffset = 0;
    29.                     float zOffset = 0;
    30.                  
    31.                     xOffset = -9f * clusterIndex;
    32.                     zOffset = -3.5f * clusterIndex;
    33.  
    34.                     Position tmpPos = posArray[res.parentEntity];
    35.                     tmpPos.Value.y = tmpPos.Value.y + 4.2f;
    36.                     tmpPos.Value.x =
    37.                         tmpPos.Value.x - tmpPos.Value.x % clusterCellSize + xOffset + clusterCellSize / 2f;
    38.                     tmpPos.Value.z =
    39.                         tmpPos.Value.z - tmpPos.Value.z % clusterCellSize + zOffset - clusterCellSize / 2f;
    40.                     posArray[indicatorEntity] = tmpPos;
    41.                     bounds.Center             = tmpPos.Value;
    42.                 }
    43.             }
    44.             else if (clusterIndex == 0)
    45.             {
    46.                 Position tmpPos = posArray[res.parentEntity];
    47.                 tmpPos.Value.y            = tmpPos.Value.y + 4.2f;
    48.                 posArray[indicatorEntity] = tmpPos;
    49.                 bounds.Center             = tmpPos.Value;
    50.             }
    51.  
    52.             if (!posArray.Exists(res.parentEntity))
    53.             {
    54.                 ecb.DestroyEntity(index, indicatorEntity);
    55.             }
    56.         }
    57.     }
    58.  
     
    Xerioz and racer161 like this.
  6. The5

    The5

    Joined:
    May 26, 2017
    Posts:
    19
    Thank you for sharing your source eizenhorn!

    I got it running yesterday and ultimately was even able to ditch the [NativeDisableParallelForRestriction] again by making both Position Arrays generally [ReadOnly] and writing to a dedicated Velocity component. Writing directly to position was just a temporary "direct" implementation.
    I would also just dump this here for future reference, that one should keep in mind to only write to the Entity that is being iterated over by the Jobs main loop:


    My Collision system still eats ~8ms for 10.000 entities though (with Burst), so I assume I got some design flaws in it. Currently I am still attempting to use IJobChunk for it.

    It seems I should just use IJobProcessComponentData(WithEntities) wherever I can.
    Which is just what Joachim also suggests here:
    Further down that thread the states IJobChunk is recommended too.

    However, I am still curious about Use-Cases where IJobChunk would be the better choice and whether it is generally more performant than IJobParallelFor.
    When would one Choose IJobChunk or IJobParallelFor (aside of exceeding the 4 argument limit of IJobProcessComponentData)?

    EDIT:
    Just tried implementing my Collision Job with IJobChunk VS IJobProcessComponentDataWithEntities. The performance of the Job seems to be the same.
     
    Last edited: Dec 17, 2018
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,362
    In cases where you doing per chunk logic, simple case - skipping some chunks by SCD index,instead of skipping every item in IJobParalleFor by some value. It’s much faster, instead iterating 10k entities you just iterate throug 10 chunks and skips some of them and only iterate entities in right chunks. Less iterating loops, less CPU cost. Of course sometimes thet are equal if you use all chunks.