Search Unity

Question Same parallel writer for different scheduled jobs?

Discussion in 'Entity Component System' started by Cell-i-Zenit, Nov 17, 2020.

  1. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    Hi,

    I have a system with 2 jobs, which use an entity command buffer. These two jobs can run parallel.

    Now unity is telling me that i cannot use the same entity command buffer for these two jobs, so i thought i create a parallel writer and just assign a different index to them:

    job A does this:

    parallelWriter.SetComponent(0, etc);


    job B does this:

    parallelWriter.SetComponent(1, etc);


    still doesnt work.

    Is there a flag i need to set somewhere? I know that the parallel writer is intended to be used inside the same
    parallelized job, but i see no problem why i cannot do the parallelization over two jobs.
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,770
    Show us your jobs. What is exact error message?
    btw.
    parallelWriter.SetComponent(0, etc);
    You should pass job index into ecb.SetComponent, not constant value 0, or 1.
    You need also data component and typically entity as well.
     
  3. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,684
    For parallel jobs create a separate buffer
    Code (CSharp):
    1.  
    2. private EntityQuery _query;
    3.  
    4. protected override void OnCreateSimulation()
    5. {
    6.     var archetype = EntityManager.CreateArchetype(ComponentType.ReadOnly<TestType>());
    7.     EntityManager.CreateEntity(archetype, 100);
    8.    
    9.     _query = GetEntityQuery(new EntityQueryDesc()
    10.     {
    11.         All  = new[] {ComponentType.ReadOnly<TestType>()},
    12.         None = new[] {ComponentType.ReadOnly<TestType2>()}
    13.     });
    14. }
    15.  
    16. [BurstCompile]
    17. private struct SomeJob : IJobFor
    18. {
    19.     [ReadOnly]
    20.     public NativeSlice<Entity> Targets;
    21.  
    22.     public EntityCommandBuffer.ParallelWriter Buffer;
    23.  
    24.     public void Execute(int index)
    25.     {
    26.         Buffer.AddComponent<TestType2>(index, Targets[index]);
    27.     }
    28. }
    29.  
    30. protected override void OnUpdateSimulation()
    31. {
    32.     var bufferFirst  = BeginSimulation.CreateCommandBuffer().AsParallelWriter();
    33.     var bufferSecond = BeginSimulation.CreateCommandBuffer().AsParallelWriter();
    34.  
    35.     var entities = _query.ToEntityArrayAsync(Allocator.TempJob, out var entitiesHandle);
    36.    
    37.     Dependency = JobHandle.CombineDependencies(Dependency, entitiesHandle);
    38.    
    39.     var handleFirst = new SomeJob()
    40.     {
    41.         Buffer  = bufferFirst,
    42.         Targets = entities.Slice(0, entities.Length / 2)
    43.     }.ScheduleParallel(entities.Length / 2, 8, Dependency);
    44.    
    45.     var handleSecond = new SomeJob()
    46.     {
    47.         Buffer  = bufferSecond,
    48.         Targets = entities.Slice(entities.Length / 2, entities.Length / 2)
    49.     }.ScheduleParallel(entities.Length / 2, 8, Dependency);
    50.    
    51.     Dependency = JobHandle.CombineDependencies(handleFirst, handleSecond);
    52.     Dependency = entities.Dispose(Dependency);
    53.    
    54.     Debug.Log("End schedule");
    55. }
    56.  
     
    frankfringe likes this.
  4. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    I thought it was clear, but anyway here is the code in reduced form:

    Code (CSharp):
    1.      
    2.       var parallelWriter = commandBuffer.AsParallelWriter();
    3.       var handle1 = Entities.WithName("job1")
    4.         .ForEach((in Data data) =>
    5.         {
    6.             parallelWriter.AddComponent<Disabled>(0,data.data);
    7.         })
    8.         .Schedule(Dependency);
    9.  
    10.       var handle2 = Entities.WithName("job2")
    11.         .ForEach((in Data data) =>
    12.         {
    13.           parallelWriter.SetComponent(1, data.data, new Translation {Value = float3.zero});    
    14.         })
    15.         .Schedule(Dependency);
    16.  
    17.       Dependency = JobHandle.CombineDependencies(handle1, handle2);
    I dont understand why i need to create two parallel buffers here, and provide each of them to the job.

    If the parallel buffer is intended to be used for parallel stuff, and we provide an index to write in parallel to the writer, why cant i do it like this?
     
  5. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,684
    Because it's not safe in case of determinism I suppose. You should provide a correct index to buffer while you inside job (not 0-1 which you put) in case of
    ForEach
    it's
    entityInQueryIndex
    argument which out of box guarantee unique index in case of parallel scheduling when it will be executed on different threads it guarantees no one will write to the same index (sortKey) in parallel producing non-deterministic result if used proper value (
    entityInQueryIndex
    ). And by default safety system prevents you from using the same buffer in two separate jobs as they can't guarantee safety in this case as
    entityInQueryIndex
    can and will duplicate in different jobs.
     
    Last edited: Nov 18, 2020
  6. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,770
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,684
  8. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,770
    Yep, but I am pretty sure, it is still valid. Entities iterations later, same approach applies.
    At least is an official reference.
    I can not see similar page, including entities command buffer for newer packages.

    Alternatively can look here.
    https://reeseschultz.com/spawning-prefabs-with-unity-ecs/
     
  9. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    Guys, you dont understand.

    I KNOW how i am supposed to use the parallel writer.

    Lets take an example to make it clear.

    So this is correct?
    1. Create parallel buffer << main thread
    2. provide in job << main thread
    3. job chooses int numbers via thread count
    4. job adds int numbers + data to buffers

    But this is not correct?
    1. Create parallel buffer
    2. provide in TWO jobs
    3. we just provide our own numbers
    4. jobs add int numbers + data to buffers

    Note that in each of these steps, Step 3 and 4 happen in different threads.

    So why is it "safe" in method 1, but not in method 2?

    I just see no technical reason why method 2 should not work.
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    (3) is incorrect. The integer is based on the deterministic item index and has nothing to do with the thread count.

    The safety system considers a parallel job as a single job with parallel write capabilities. The safety system never allows a container to be written to from more than one job at a time.

    There's also zero benefit of trying to use a single ECB.ParallelWriter over using an ECB (not parallel) for each job. You aren't saving any memory nor are you gaining batching benefits doing what you are trying to do. If ECB playback is a performance bottleneck for you, that's a different problem I can help you with.
     
    Antypodish likes this.