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

"The container does not support parallel writing." While I'm not writing anything.

Discussion in 'Entity Component System' started by illinar, Dec 27, 2019.

  1. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    857
    Just made a class to profile the HashMap:
    Code (CSharp):
    1. using Unity.Entities;
    2. using Unity.Jobs;
    3. using Unity.Collections;
    4. using Unity.Mathematics;
    5.  
    6. public class HashTestSystem : JobComponentSystem
    7. {
    8.     [ReadOnly] public NativeHashMap<int3, Entity> HashMap;
    9.  
    10.     protected override void OnCreate()
    11.     {
    12.         var e = EntityManager.CreateEntity(typeof(HashtableTest));
    13.         HashMap = new Unity.Collections.NativeHashMap<Unity.Mathematics.int3, Entity>(10000, Allocator.Persistent);
    14.         for (int x = 0; x < 100; x++)
    15.         {
    16.             for (int y = 0; y < 100; y++)
    17.             {
    18.                 for (int z = 0; z < 100; z++)
    19.                 {
    20.                     HashMap.Add(new int3(x, y, z), e);
    21.                 }
    22.             }
    23.         }
    24.     }
    25.  
    26.     protected override JobHandle OnUpdate(JobHandle handle)
    27.     {
    28.         var hm = HashMap;
    29.         Entities.ForEach((ref HashtableTest t) =>
    30.         {
    31.             for (int x = 0; x < 1; x++)
    32.             {
    33.                 for (int y = 0; y < 1000; y++)
    34.                 {
    35.                     for (int z = 0; z < 1000; z++)
    36.                     {
    37.                         t.b = hm.TryGetValue(new int3(x, y, z), out var e);
    38.                     }
    39.                 }
    40.             }
    41.         }).WithName("HASH_TEST").Schedule(handle);
    42.  
    43.         return handle;
    44.     }
    45. }
    46.  
    47. public struct HashtableTest : IComponentData
    48. {
    49.     public bool b;
    50. }
    51.  
    Getting error:
    InvalidOperationException: <>c__DisplayClass_HASH_TEST.Data.hm is not declared [ReadOnly] in a IJobParallelFor job. The container does not support parallel writing. Please use a more suitable container type. 


    As you can see I'm not writing to the container.
     
  2. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,554
    The job activates a write mode for all captured containers unless you add .WithReadOnly(hm), or wait for ScheduleSingle support for ForEach and let the write mode on for hash map.

    [ReadOnly] you put doesn't work because it isn't part of internal job fields. (imagine public NativeHashMap<int3, Entity> hm; declared automatically which connects with whats captured to lambda, without [ReadOnly]) If you have a custom struct with NativeHashMap inside then you put [ReadOnly] inside this struct then capturing [ReadOnly] works.
     
    victor_apihtin and illinar like this.
  3. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    857
    Right, thank you. Someone already told me that before and I forgot.
     
  4. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    857
    What about this though:


    var matrices = GetComponentDataFromEntity<LocalToWorld>(true);


    It is explicitly read only. Shouldn't it just work? It throws the same error message.

    Might be different error. It complains that the container is not ReadOnly.

    Code (CSharp):
    1. protected override JobHandle OnUpdate(JobHandle handle)
    2.     {
    3.         var matrices = GetComponentDataFromEntity<LocalToWorld>(true);
    4.  
    5.         handle = Entities.ForEach((ref Translation translation, in FollowHorizontalPositopn transform) =>
    6.         {
    7.             var pos = matrices[transform.Entity].Position;
    8.             translation.Value = new float3(pos.x, translation.Value.y, pos.z);
    9.         }).Schedule(handle);
    10.  
    11.         return handle;
    12.     }
    InvalidOperationException: <>c__DisplayClass_OnUpdate_LambdaJob0.Data.matrices is not declared [ReadOnly] in a IJobParallelFor job. The container does not support parallel writing. Please use a more suitable container type.
     
  5. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,554
    I remembered the same thing happen in IJob/IJobForEach/IJobChunk since long ago that I must add [ReadOnly] to match what I use at OnUpdate.
     
  6. paul-figiel

    paul-figiel

    Joined:
    Feb 9, 2017
    Posts:
    9
    I have the same problem too, I had to go back to the old scripting API (without using Entities.ForEach) to make this use case work.
     
  7. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Does it work if you do this?
    Code (csharp):
    1.  
    2.         var matrices = GetComponentDataFromEntity<LocalToWorld>(true);
    3.  
    4.         handle = Entities.
    5.           WithReadOnly(matrices).
    6.           WithNativeDisableParallelForRestriction(matrices).
    7.           ForEach((ref Translation translation, in FollowHorizontalPositopn transform) =>
    8.         {
    9.             var pos = matrices[transform.Entity].Position;
    10.             translation.Value = new float3(pos.x, translation.Value.y, pos.z);
    11.         }).Schedule(handle);
    12.  
    The safety system would complain if you tried to use CDFE in IJobForeach too, you would need to either ScheduleSingle or turn off the parallel restrictions.

    Edit: Actually you shouldn't even need the NativeDisableParallelForRestriction call as long as you're using WithReadOnly, it should support parallel reading.
     
    Last edited: Jan 13, 2020
  8. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Internally GetComponentDataFromEntity uses an entity query to initialize entity access. The query needs to know whether it's accessing as read-only or not. You still need to tell any jobs you pass it to that you're explicitly accessing it as read-only.
     
  9. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    857
    Of course I added WithoutRestrictions, it works, just a bit weird that I have to do it.

    Code (CSharp):
    1.         handle = Entities.ForEach((ref Translation translation, in FollowHorizontalPositopn transform) =>
    2.         {
    3.             var pos = matrices[transform.Entity].Position;
    4.             translation.Value = new float3(pos.x, translation.Value.y, pos.z);
    5.         }).WithNativeDisableContainerSafetyRestriction(matrices).Schedule(handle);
    WithReadOnly(matrices) wasn't necessary I didn't even know about that one. Turns out either one works. I'm using
    WithReadOnly() now. Thanks.

    The (true) argument is telling that to the query if so..

    var matrices = GetComponentDataFromEntity<LocalToWorld>(true);


    If you do the same line and then pass it to a manually created job wouldn't it work?

    It seems like those are two separate APIs and one of them will be deprecated. Or not..
     
    Last edited: Jan 13, 2020
  10. elcionap

    elcionap

    Joined:
    Jan 11, 2016
    Posts:
    138
    Nop. You always had to declare permissions in the job, manually created or not, even if the container already has any permission already. If you don't, the permission will be the default (write) and your job will not run parallel with another job reading the same component.

    []'s
     
  11. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    857
    Mmm.. Okay, where is container's (bool readonly) used then?
     
  12. elcionap

    elcionap

    Joined:
    Jan 11, 2016
    Posts:
    138
    To mark the container as readonly for safe checks only and to track internal dependencies in the ECS. For example, calling Complete to dependencies of a ComponentSystem or a JobComponentSystem with AlwaysSynchronizeSystem.

    []'s
     
    illinar likes this.
  13. KillHour

    KillHour

    Joined:
    Oct 25, 2015
    Posts:
    47
    Just FYI, the latest documentation is wrong in this exact way:

    https://docs.unity3d.com/Packages/com.unity.entities@0.10/manual/ecs_lookup_data.html

    Code (CSharp):
    1. public struct BufferData : IBufferElementData
    2. {
    3.     public float Value;
    4. }
    5. public class BufferLookupSystem : SystemBase
    6. {
    7.     protected override void OnUpdate()
    8.     {
    9.         BufferFromEntity<BufferData> buffersOfAllEntities
    10.             = this.GetBufferFromEntity<BufferData>(true);
    11.  
    12.         Entities
    13.             .ForEach((ref Rotation orientation,
    14.             in LocalToWorld transform,
    15.             in Target target) =>
    16.             {
    17.                 // Check to make sure the target Entity still exists
    18.                 if (!buffersOfAllEntities.Exists(target.entity))
    19.                     return;
    20.  
    21.                 // Get a reference to the buffer
    22.                 DynamicBuffer<BufferData> bufferOfOneEntity =
    23.                     buffersOfAllEntities[target.entity];
    24.  
    25.                 // Use the data in the buffer
    26.                 float avg = 0;
    27.                 for (var i = 0; i < bufferOfOneEntity.Length; i++)
    28.                 {
    29.                     avg += bufferOfOneEntity[i].Value;
    30.                 }
    31.                 if (bufferOfOneEntity.Length > 0)
    32.                     avg /= bufferOfOneEntity.Length;
    33.             })
    34.             .ScheduleParallel();
    35.     }
    36. }