Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

SystemBase ForEach System.IndexOutOfRangeException: Index {0} error

Discussion in 'Entity Component System' started by Ksanone, Oct 8, 2021.

  1. Ksanone

    Ksanone

    Joined:
    Feb 7, 2015
    Posts:
    39
    Hi all, any help with the following would be appreciated. The code below runs fine to find the nearest target<TargetTag> to assign to entities<NeedsTargetTag>, but when the number of <NeedsTargetTag> entities exceeds the number of targets I get the error

    "
    System.IndexOutOfRangeException: Index {0} is out of restricted IJobParallelFor range [{1}...{2}] in ReadWriteBuffer.
    Thrown from job: FindNearestTargetSystem.<>c__DisplayClass_Compare_Agent_And_Target_Positions
    "

    From what I have googled it seems like
    - it may be fixed by making some element ReadOnly but I haven't been able to figure it out
    - some misunderstanding about how ScheduleParallel works
    - some error in my logic


    Code (CSharp):
    1.  
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Mathematics;
    5. using Unity.Transforms;
    6.  
    7. public class FindNearestTargetSystem : SystemBase
    8. {
    9.     private EntityCommandBufferSystem _ecbSystem;
    10.  
    11.     private EntityQuery _targetsQuery;
    12.     private EntityQuery _agentsQuery;
    13.  
    14.     protected override void OnCreate()
    15.     {
    16.         base.OnCreate();
    17.         _ecbSystem = World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>();
    18.     }
    19.  
    20.     protected override void OnUpdate()
    21.     {
    22.         var buffer = _ecbSystem.CreateCommandBuffer().AsParallelWriter();
    23.  
    24.         // make a native array - its length is decided by how many agents we have in our entity query
    25.         var agentsQueryCount = _agentsQuery.CalculateEntityCount();
    26.  
    27.         var targetEntities  = new NativeArray<Entity>(agentsQueryCount, Allocator.TempJob);
    28.         var targetPositions = new NativeArray<float3>(agentsQueryCount, Allocator.TempJob);
    29.  
    30.         // used to temporarily store the distance between an agent and possible targets
    31.         var distances = new NativeArray<float>(agentsQueryCount, Allocator.TempJob);
    32.         var agentPositions = new NativeArray<float3>(agentsQueryCount, Allocator.TempJob);
    33.  
    34.         // get all AGENT positions
    35.         // set a default high/infinity value for distance check
    36.         Entities
    37.             .WithName("Get_Agent_Positions")
    38.             .WithStoreEntityQueryInField(ref _agentsQuery)
    39.             .WithAll<TargetData>()
    40.             .WithAll<NeedsTargetTag>()
    41.             .ForEach((Entity e, int entityInQueryIndex, in Translation translation) =>
    42.             {
    43.                 agentPositions[entityInQueryIndex] = translation.Value;
    44.                 distances[entityInQueryIndex] = float.PositiveInfinity;
    45.             })
    46.             .ScheduleParallel();
    47.  
    48.         // nested For loops to find the closest target
    49.         // compare all target positions with all agent positions to find the shortest distances for each pair
    50.         // TODO - Performance can be enhanced by use of grid check for large maps
    51.         Entities
    52.             .WithName("Compare_Agent_And_Target_Positions")
    53.             .WithAll<TargetTag>()
    54.             .WithStoreEntityQueryInField(ref _targetsQuery)
    55.             .WithReadOnly(agentPositions)
    56.             .ForEach((Entity targetEntity, int entityInQueryIndex, in Translation targetPos) =>
    57.             {
    58.                 // compare all target pos to all agent pos
    59.                 for (var i = 0; i < agentPositions.Length; i++)
    60.                 {
    61.                     // calculate the distance between target and agent
    62.                     float distance = distances[i];
    63.                     var newDistance = math.distance(targetPos.Value, agentPositions[i]);
    64.  
    65.                     // if the distance is smaller then store this and the associated target and target position
    66.                     if (newDistance < distance)
    67.                     {
    68.                         distances[i] = newDistance;
    69.                         targetEntities[i] = targetEntity;
    70.                         targetPositions[i] = targetPos.Value;
    71.                     }
    72.                 }
    73.             })
    74.             .WithDisposeOnCompletion(distances)
    75.             .WithDisposeOnCompletion(agentPositions)
    76.             .ScheduleParallel();
    77.  
    78.         // assign the data values obtained from the above For loops
    79.         Entities
    80.             .WithName("Assign_Closest_Target_Position")
    81.             .ForEach((Entity e, int entityInQueryIndex, ref TargetData targetData, ref NeedsTargetTag needsTargetTag) =>
    82.             {
    83.                 targetData.TargetPosition = targetPositions[entityInQueryIndex];
    84.                 targetData.Target = targetEntities[entityInQueryIndex];
    85.                 buffer.RemoveComponent<NeedsTargetTag>(entityInQueryIndex, e);
    86.             })
    87.             .WithDisposeOnCompletion(targetEntities)
    88.             .WithDisposeOnCompletion(targetPositions)
    89.             .ScheduleParallel();
    90.  
    91.         _ecbSystem.AddJobHandleForProducer(Dependency);
    92.  
    93.     }
    94. }
    95.  
     
  2. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    I would assume that is not the full error message. Usually it tells you to disable Burst and enable Full Stack Traces to get the full error stack. it will be much easier to tell what's going on then.

    Im not sure i understand why you write every AgentPosition into a nativeArray before comparing them to TargetPositions. Couldn't you do it all in one job? I handle those situations by iterating over the AgentChunks with an IJobChunk and then passing the TargetChunks into this job. In the Job i then manually iterate over the TargetChunks for each entity in the AgentChunks. No idea if that is the most optimal way but i like to get everything off the mainthread.

    The easiest solution that is a bit worse because of random memory access would be to just use GetComponent<LocalToWorld>(targetentity). It's still fast since it's essentially just a hashmap lookup and should be totally fine if you don't have to check thousands of potential targets. Then again you wouldn't need to allocate and free memory.
     
    Last edited: Oct 8, 2021
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    My guess is that it is complaining about line 68, and rightly so. That's a real race condition. If you want to write your logic that way, you should use Schedule instead of ScheduleParallel. Otherwise you need to rewrite that logic to ensure that multiple entities don't reach that conditional logic and try writing to that array index at the same time.
     
    MaNaRz likes this.
  4. Ksanone

    Ksanone

    Joined:
    Feb 7, 2015
    Posts:
    39
    Hey thanks for the considered reply, you might be right about using the chunk way. I will try the GetComponent way as well and benchmark them all. My goal was just to do it a "dumb dumb" way to start, like 1+1=2 level.

    The full error code without Burst enabled is
    "
    IndexOutOfRangeException: Index 1 is out of restricted IJobParallelFor range [0...0] in ReadWriteBuffer.
    ReadWriteBuffers are restricted to only read & write the element at the job index. You can use double buffering strategies to avoid race conditions due to reading & writing in parallel to the same elements from a job.
    Unity.Collections.NativeArray`1[T].FailOutOfRangeError (System.Int32 index) (at <88f69663e9a64d00b2091dd8dfd4d38f>:0)
    Unity.Collections.NativeArray`1[T].CheckElementReadAccess (System.Int32 index) (at <88f69663e9a64d00b2091dd8dfd4d38f>:0)
    Unity.Collections.NativeArray`1[T].get_Item (System.Int32 index) (at <88f69663e9a64d00b2091dd8dfd4d38f>:0)
    FindNearestTargetSystem+<>c__DisplayClass_Compare_Agent_And_Target_Positions.IterateEntities (Unity.Entities.ArchetypeChunk& chunk, FindNearestTargetSystem+<>c__DisplayClass_Compare_Agent_And_Target_Positions+LambdaParameterValueProviders+Runtimes& runtimes) (at <e5e2430c5c3c4ac5b3502cb2ca670e60>:0)
    FindNearestTargetSystem+<>c__DisplayClass_Compare_Agent_And_Target_Positions.Execute (Unity.Entities.ArchetypeChunk chunk, System.Int32 chunkIndex, System.Int32 firstEntityIndex) (at <e5e2430c5c3c4ac5b3502cb2ca670e60>:0)
    Unity.Entities.JobChunkExtensions+JobChunkProducer`1[T].ExecuteInternal (Unity.Entities.JobChunkExtensions+JobChunkWrapper`1[T]& jobWrapper, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/IJobChunk.cs:386)
    Unity.Entities.JobChunkExtensions+JobChunkProducer`1[T].Execute (Unity.Entities.JobChunkExtensions+JobChunkWrapper`1[T]& jobWrapper, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, Unity.Jobs.LowLevel.Unsafe.JobRanges& ranges, System.Int32 jobIndex) (at Library/PackageCache/com.unity.entities@0.17.0-preview.42/Unity.Entities/IJobChunk.cs:353)
    "
     
  5. Ksanone

    Ksanone

    Joined:
    Feb 7, 2015
    Posts:
    39
    Thanks for looking at the code.

    So the problem is that two or more threads are trying to read and write to the distances NativeArray?
     
  6. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    Yes. Multiple threads try to set the same Index in the distance NativeArray in parrallel because you allocate it on the main thread just once for all your entities. To get around it without rewriting your logic you can either give every targetEntity its own DynamicBuffer which could store all it's own distances to every Agent or use .Schedule() as @DreamingImLatios suggested.
     
    Ksanone likes this.