Search Unity

Question InvalidOperationException: The previously scheduled job writes to NA you must call Complete

Discussion in 'Entity Component System' started by MNNoxMortem, Sep 6, 2020.

  1. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Code (CSharp):
    1. InvalidOperationException: The previously scheduled job OriginShiftSystem:<>c__DisplayClass_CopyPositions writes to the Unity.Collections.NativeArray`1[Unity.Mathematics.float3] <>c__DisplayClass_CopyPositions.JobData.positions. You must call JobHandle.Complete() on the job OriginShiftSystem:<>c__DisplayClass_CopyPositions, before you can read from the Unity.Collections.NativeArray`1[Unity.Mathematics.float3] safely.
    I am trying to replicate the structure from the Entities.ForEach documentation (https://docs.unity3d.com/Packages/com.unity.entities@0.14/manual/ecs_entities_foreach.html) and run into this error.

    This is the actual simple system

    Code (CSharp):
    1. [UpdateBefore(typeof(BuildPhysicsWorld))]
    2.     public class OriginShiftSystem : SystemBase
    3.     {
    4.         private EntityQuery query;
    5.         private EndSimulationEntityCommandBufferSystem endSimulationEcbSystem;
    6.  
    7.         protected override void OnCreate()
    8.         {
    9.             base.OnCreate();
    10.             // Find the ECB system once and store it for later usage
    11.             endSimulationEcbSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    12.         }
    13.  
    14.         protected override void OnUpdate()
    15.         {
    16.             var count = query.CalculateEntityCount();
    17.             var positions = new NativeArray<float3>(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    18.             Entities.ForEach((int entityInQueryIndex, ref Translation position, in RequiresOriginShift requiresOriginShift) =>
    19.                 {
    20.                     positions[entityInQueryIndex] = position.Value;
    21.                 })
    22.              .WithStoreEntityQueryInField(ref query)
    23.              .WithName("CopyPositions")
    24.              .ScheduleParallel();
    25.            
    26.  
    27.             // We do not need the centroid (https://en.wikipedia.org/wiki/Centroid)
    28.             // and therefore can be more numerically stable by using a bound-center (max-min)/2f
    29.             // as we do not care for mass, but only for the extremes!
    30.             var minMax = new NativeArray<float3>(2, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); // 0:min, 1:max
    31.             minMax[0] = positions[1];
    32.             minMax[1] = positions[1];
    33.             Job.WithCode(() =>
    34.                     {
    35.                         for (var i = 1; i < positions.Length; i++)
    36.                         {
    37.                             minMax[0] = math.min(minMax[0], positions[i]);
    38.                             minMax[1] = math.max(minMax[1], positions[i]);
    39.                         }
    40.                     }
    41.                 )
    42.              .WithName("MinMax")
    43.              .Schedule();
    44.  
    45.             // Calculate bounds-center
    46.             var origin = new NativeArray<float3>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); // 0:min, 1:max
    47.             Job.WithCode(() => { origin[0] = (minMax[1] - minMax[0]) / 2f; })
    48.              .WithName("CalculateOrigin")
    49.              .Schedule();
    50.  
    51.             // Origin-Shift
    52.             EntityCommandBuffer.ParallelWriter ecb = endSimulationEcbSystem.CreateCommandBuffer().AsParallelWriter();
    53.             Entities.ForEach((Entity entity, int entityInQueryIndex, ref Translation position, in RequiresOriginShift requiresOriginShift) =>
    54.                 {
    55.                     position = new Translation
    56.                     {
    57.                         Value = position.Value - origin[0]
    58.                     };
    59.                     ecb.RemoveComponent<RequiresOriginShift>(entityInQueryIndex, entity);
    60.                 })
    61.              .WithStoreEntityQueryInField(ref query)
    62.              .WithName("OriginShift")
    63.              .ScheduleParallel();
    64.         }
    65.     }
    That does nothing fancy:
    1. Copy position
    2. Calculate AABB.Min/Max
    3. Calculate AABB.center
    4. Set position = position - AABB.center
     
  2. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,709
    Your position array is being written to and read from at potentially the same time. You need to establish dependency or complete the previous job
     
  3. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    I thought the dependencies of jobs in a system are automatically combined as serial dependencies as in the linked Job.WithCode example where the NativeArray<float> randomNumbers is written to in Job.WithCode 1 and read from in Job.WithCode 2.

    But even when I do it explitly it complains, now about
    Code (CSharp):
    1. InvalidOperationException: The previously scheduled job OriginShiftSystem:<>c__DisplayClass_CopyPositions writes to the ComponentTypeHandle<Unity.Transforms.Translation> <>c__DisplayClass_CopyPositions.JobData._lambdaParameterValueProviders.forParameter_position._typeHandle. You are trying to schedule a new job Jobs:CheckStaticBodyChangesJob, which reads from the same ComponentTypeHandle<Unity.Transforms.Translation> (via CheckStaticBodyChangesJob.JobData.PositionType). To guarantee safety, you must include OriginShiftSystem:<>c__Di
    ... which sadly also is truncated...
    Code (CSharp):
    1. protected override void OnUpdate()
    2.         {
    3.             var count = query.CalculateEntityCount();
    4.             var positions = new NativeArray<float3>(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    5.             JobHandle h1 = Entities.ForEach((int entityInQueryIndex, ref Translation position, in RequiresOriginShift requiresOriginShift) =>
    6.                 {
    7.                     positions[entityInQueryIndex] = position.Value;
    8.                 })
    9.              .WithStoreEntityQueryInField(ref query)
    10.              .WithName("CopyPositions")
    11.              .ScheduleParallel(Dependency);
    12.            
    13.          
    14.  
    15.             // We do not need the centroid (https://en.wikipedia.org/wiki/Centroid)
    16.             // and therefore can be more numerically stable by using a bound-center (max-min)/2f
    17.             // as we do not care for mass, but only for the extremes!
    18.             var minMax = new NativeArray<float3>(2, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); // 0:min, 1:max
    19.             minMax[0] = positions[1];
    20.             minMax[1] = positions[1];
    21.             var h2 = Job.WithCode(() =>
    22.                     {
    23.                         for (var i = 1; i < positions.Length; i++)
    24.                         {
    25.                             minMax[0] = math.min(minMax[0], positions[i]);
    26.                             minMax[1] = math.max(minMax[1], positions[i]);
    27.                         }
    28.                     }
    29.                 )
    30.              .WithName("MinMax")
    31.              .Schedule(h1);
    32.  
    33.             // Calculate bounds-center
    34.             var origin = new NativeArray<float3>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); // 0:min, 1:max
    35.             var h3 = Job.WithCode(() => { origin[0] = (minMax[1] - minMax[0]) / 2f; })
    36.              .WithName("CalculateOrigin")
    37.              .Schedule(h2);
    38.  
    39.             // Origin-Shift
    40.             EntityCommandBuffer.ParallelWriter ecb = endSimulationEcbSystem.CreateCommandBuffer().AsParallelWriter();
    41.             var h4 = Entities.ForEach((Entity entity, int entityInQueryIndex, ref Translation position, in RequiresOriginShift requiresOriginShift) =>
    42.                 {
    43.                     position = new Translation
    44.                     {
    45.                         Value = position.Value - origin[0]
    46.                     };
    47.                     ecb.RemoveComponent<RequiresOriginShift>(entityInQueryIndex, entity);
    48.                 })
    49.              .WithStoreEntityQueryInField(ref query)
    50.              .WithName("OriginShift")
    51.              .ScheduleParallel(h3);
    52.          
    53.             h4.Complete();
    54.         }
     
    Last edited: Sep 6, 2020
  4. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,709
    I’ve been handeljng my dependency manually but the doc say this:

    https://docs.unity3d.com/Packages/com.unity.entities@0.7/manual/ecs_job_dependencies.html


    Note that the system Dependency property does not track the dependencies that a job might have on data passed through NativeArrays or other similar containers. If you write a NativeArray in one job, and read that array in another, you must manually add the JobHandle of the first job as a dependency of the second (typically by using JobHandle.CombineDependencies).
     
  5. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    @francois85 I have done exactly that in the updated example above, but likely edited it after you had written your answer :). it still complains, and sadly trunactes the error message.
     
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    You are reading from the positions array in the system AFTER you schedule you're job that is writing to them.
     
    florianhanke likes this.
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,685
    No. If this NA part of jobs which in dependency chain - you shouldn't. And in his case, everything is fine with dependencies as they tracked by Dependency just fine and chain builds correctly. The problem only in immediate reading inside the system on main thread from positions array as @tertle already said.
    @MNNoxMortem Move
    Code (CSharp):
    1. minMax[0] = positions[1];
    2. minMax[1] = positions[1];
    Inside
    Job.WithCode
    right before
    for
    loop, it doesn't change your logic, but will resolve potential race condition about which safety system complaining
     
    Last edited: Sep 6, 2020
    MNNoxMortem and florianhanke like this.
  8. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Oh god, I feel a bit embarrassed as how obvious this was. Thank you so much. I was so much focused on other stuff that this slipped through completly.

    and worse it was initialized wrong as well.
     
  9. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,709
    I missed it too lol
     
    MNNoxMortem likes this.