Search Unity

JobComponentSystem Dependencies

Discussion in 'Entity Component System' started by Allan-Smith, Jul 29, 2018.

  1. Allan-Smith

    Allan-Smith

    Joined:
    Feb 7, 2012
    Posts:
    57
    Hey,

    I have a JobComponentSystem that alters a NativeArray. Then, I have another JobComponentSystem that reads that array. I get an error straight away that I need to add the Job created in the first system as a dependency to the Job created in the second system. Problem is... how? lol I've added [UpdateAfter(typeof(FirstSystem))] but as I expected that is not the way... so I really don't know how to go about it... I mean, in my specific case that I am right now I could merge them and have just one system and that would make things easier, but what if that was not the case? Like, the filtering didn't match and therefore even though both systems use the same FixedArray, they alter different entities?

    Any enlightment is welcome!
    Best,
    Allan
     
  2. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Ya this has bitten me several times over. OnUpdate isn't just passed the jobhandle from the previous system. The job handle is constructed based on what component dependencies the system has. Jobs you schedule that don't use components won't get passed on, not unless they are themselves a dependency of a component related job. And then the systems in question need to have dependencies on the same component data.

    The behavior is documented here, look at the job dependency management section.
     
  3. Allan-Smith

    Allan-Smith

    Joined:
    Feb 7, 2012
    Posts:
    57
    Hmmm... accordingly to that doc I would assume that this would work? System 1 filters like this:


    Code (CSharp):
    1.         private struct FilterData
    2.         {
    3.             public readonly int Length;
    4.             public ComponentDataArray<TemperatureModifierQueueComponent> TemperatureModifierQueueSize;
    5.             public FixedArrayArray<TemperatureModifierQueue> TemperatureModifiersQueue;
    6.             public ComponentDataArray<TemperatureModifierSizeComponent> TemperatureModifierSize;
    7.             public FixedArrayArray<TemperatureModifier> TemperatureModifiers;
    8.         }
    System 2 filters like this:

    Code (CSharp):
    1.         private struct FilterData
    2.         {
    3.             public readonly int Length;
    4.             public ComponentDataArray<TemperatureComponent> Temperature;
    5.             [ReadOnly] public ComponentDataArray<TemperatureModifierSizeComponent> TemperatureModifierSize;
    6.             [ReadOnly] public FixedArrayArray<TemperatureModifier> TemperatureModifiers;
    7.         }
    Yet, when I run, system 2 is trying to schedule the job before system 1, as evidenced by the message
    InvalidOperationException: The previously scheduled job TemperatureModifierJob reads from the NativeArray TemperatureModifierJob.TemperatureModifierSize. You are trying to schedule a new job TemperatureModifierQueueJob, which writes to the same NativeArray (via TemperatureModifierQueueJob.TemperatureModifierSize). To guarantee safety, you must include TemperatureModifierJob as a dependency of the newly scheduled job.


    Btw, on System 2 I am scheduling the job like this:

    Code (CSharp):
    1. return new TemperatureModifierJob
    2.             {
    3.                 Temperature = _entities.Temperature,
    4.                 TemperatureModifierSize = _entities.TemperatureModifierSize,
    5.                 TemperatureModifiers = _entities.TemperatureModifiers
    6.             }.Schedule(_entities.Length, 1, handle);
    Which means, using the handle as dependency, which is what I understood would be the case? I also still have the [UpdateAfter(typeof(System1))] in there but that apparently is not used in any way cause of what you described... but according to the docs, since System1 Reads and Writes to TemperatureModifierSizeComponent, and System2 only reads from it, it should automatically pass it as a dependency in the handle?

    At any rate, thanks for the help,
    Best,
    Allan
     
  4. This error message means when you schedule your TemperatureModifierQueueJob, you have to pass in the JobHandle of the TemperatureModifierJob.

    You can include dependency like this (inputDeps):
    Code (CSharp):
    1. protected override JobHandle OnUpdate([B]JobHandle inputDeps[/B])
    2.    {
    3.        var job = new RotationSpeedRotation() { dt = Time.deltaTime };
    4.        return job.Schedule(this, 64, [B]inputDeps[/B]);
    5.    }
    When you schedule a job and pass in jobhandle, you state that the currently scheduled job will rely on data from the dependency, so when the system actually schedule the job it will make sure that all of the dependencies will run before the current job.

    At least I think based on the information I got from your posts. :)
     
  5. Allan-Smith

    Allan-Smith

    Joined:
    Feb 7, 2012
    Posts:
    57
    Thanks for the reply. In fact, it is the other way around, I'd like the TemperatureModifierJob to be dependant on the TemperatureModifierQueueJob, so I scheduled the QueueJob without a handle specified as dependency, and the TemperatureModifierJob with the handle specified. Basically the one that has read/write access has no dependencies, and the one that has only read access is dependant on the passed handle. However, somehow, for some reason, the job with readonly permission is being declared before the job with read/write access, which gives that error... and I don't know why.
     
  6. Oh I see. Sorry about that, I thought you have missed the order of dependencies but apparently that is the exact problem.
    It may be a bug, because in theory using the inputDeps-style handle passing and the UpdateBefore/UpdateAfter should work in theory.
    Is it possible to post the entire code for these systems? It maybe just a minor mistype which can't be seen in these portions? Or it's a real bug.
     
  7. Allan-Smith

    Allan-Smith

    Joined:
    Feb 7, 2012
    Posts:
    57
    Sure, here it is. In the end I decided to go with a different approach, but at any rate the question is valid for future systems, so, this is what I have:

    First system, which alters the FixedArrayArray<TemperatureModifier> TemperatureModifiers:
    Code (CSharp):
    1.     public class TemperatureModifierToAddJobSystem : JobComponentSystem
    2.     {
    3.         private struct FilterData
    4.         {
    5.             public readonly int Length;
    6.             public ComponentDataArray<TemperatureModifierToAddComponent> TemperatureModifierToAddSize;
    7.             [ReadOnly] public FixedArrayArray<TemperatureModifierToAdd> TemperatureModifiersToAdd;
    8.             public ComponentDataArray<TemperatureModifierSizeComponent> TemperatureModifierSize;
    9.             public FixedArrayArray<TemperatureModifier> TemperatureModifiers;
    10.         }
    11.    
    12.         [Inject] private FilterData _entities;
    13.  
    14.         protected override JobHandle OnUpdate(JobHandle handle)
    15.         {
    16.             return new TemperatureModifierToAddJob
    17.             {
    18.                 TemperatureModifierToAddSize = _entities.TemperatureModifierToAddSize,
    19.                 TemperatureModifiersToAdd = _entities.TemperatureModifiersToAdd,
    20.                 TemperatureModifierSize = _entities.TemperatureModifierSize,
    21.                 TemperatureModifiers = _entities.TemperatureModifiers
    22.             }.Schedule(_entities.Length, 1);
    23.         }
    24.        
    25.         [BurstCompile]
    26.         public struct TemperatureModifierToAddJob : IJobParallelFor
    27.         {
    28.             public ComponentDataArray<TemperatureModifierToAddComponent> TemperatureModifierToAddSize;
    29.             [ReadOnly] public FixedArrayArray<TemperatureModifierToAdd> TemperatureModifiersToAdd;
    30.             public ComponentDataArray<TemperatureModifierSizeComponent> TemperatureModifierSize;
    31.             public FixedArrayArray<TemperatureModifier> TemperatureModifiers;
    32.            
    33.             public void Execute(int index)
    34.             {
    35.                 var toAddSize = TemperatureModifierToAddSize[index];
    36.                 var currentSize = TemperatureModifierSize[index];
    37.                 var currentModifiers = TemperatureModifiers[index];
    38.                 toAddSize.Size = math.min(toAddSize.Size, currentModifiers.Length - currentSize.Size);
    39.                 var toAddQueue = TemperatureModifiersToAdd[index];
    40.                 for (var j = 0; j < toAddSize.Size; j++)
    41.                 {
    42.                     var currentModifier = currentModifiers[currentSize.Size];
    43.                     var toAddModifier = toAddQueue[j];
    44.                     currentModifier.TargetTemperature = toAddModifier.TargetTemperature;
    45.                     currentModifier.TemperatureLerp = toAddModifier.TemperatureLerp;
    46.                     currentModifiers[currentSize.Size] = currentModifier;
    47.                     currentSize.Size++;
    48.                 }
    49.  
    50.                 toAddSize.Size = 0;
    51.                 TemperatureModifierToAddSize[index] = toAddSize;
    52.                 TemperatureModifierSize[index] = currentSize;
    53.             }
    54.         }
    55.     }
    Second system, which in theory depends on the changes from the system above:
    Code (CSharp):
    1.     [UpdateAfter(typeof(TemperatureModifierToAddJobSystem))]
    2.     public class TemperatureModifierSystem : JobComponentSystem
    3.     {
    4.         private struct FilterData
    5.         {
    6.             public readonly int Length;
    7.             public ComponentDataArray<TemperatureComponent> Temperature;
    8.             [ReadOnly] public ComponentDataArray<TemperatureModifierSizeComponent> TemperatureModifierSize;
    9.             [ReadOnly] public FixedArrayArray<TemperatureModifier> TemperatureModifiers;
    10.         }
    11.    
    12.         [Inject] private FilterData _entities;
    13.  
    14.         protected override JobHandle OnUpdate(JobHandle handle)
    15.         {
    16.             return new TemperatureModifierJob
    17.             {
    18.                 Temperature = _entities.Temperature,
    19.                 TemperatureModifierSize = _entities.TemperatureModifierSize,
    20.                 TemperatureModifiers = _entities.TemperatureModifiers
    21.             }.Schedule(_entities.Length, 1, handle);
    22.         }
    23.        
    24.         [BurstCompile]
    25.         public struct TemperatureModifierJob : IJobParallelFor
    26.         {
    27.             public ComponentDataArray<TemperatureComponent> Temperature;
    28.             [ReadOnly] public ComponentDataArray<TemperatureModifierSizeComponent> TemperatureModifierSize;
    29.             [ReadOnly] public FixedArrayArray<TemperatureModifier> TemperatureModifiers;
    30.            
    31.             public void Execute(int index)
    32.             {
    33.                 var component = Temperature[index];
    34.                 var modifierSize = TemperatureModifierSize[index];
    35.                 var modifierArray = TemperatureModifiers[index];
    36.                 for (var i = 0; i < modifierSize.Size; i++)
    37.                 {
    38.                     var modifier = modifierArray[i];
    39.                     component.Value += math.lerp(component.Value, modifier.TargetTemperature, modifier.TemperatureLerp) - component.Value;
    40.                 }
    41.            
    42.                 Temperature[index] = component;
    43.             }
    44.         }
    45.     }
    Might be there is something I am missing there... but when I run this code, I get this:
    InvalidOperationException: The previously scheduled job TemperatureModifierJob reads from the NativeArray TemperatureModifierJob.TemperatureModifierSize. You are trying to schedule a new job TemperatureModifierToAddJob, which writes to the same NativeArray (via TemperatureModifierToAddJob.TemperatureModifierSize). To guarantee safety, you must include TemperatureModifierJob as a dependency of the newly scheduled job.


    Actually I didnt think it made sense at first but... if I add "handle" as a parameter for BOTH systems... so both systems use Schedule(_entities.Length, 1, handle), it works... haha what? None of this makes sense for me =P