Search Unity

Question NativeArray in Components readonly in jobs?

Discussion in 'Entity Component System' started by runner78, Nov 19, 2022.

  1. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    With ECS 1.0 you can now have NativeArrays in components, but I have the problem that once filled, i can no longer write to the array in Jobs. i get the error "... has been declared as [ReadOnly] in the job".
    The first time after i allocated the array it works, but not if i trigger the system some time later.

    Components
    Code (CSharp):
    1. public struct ChunkData : IComponentData
    2. {
    3.     public NativeArray<Block> Blocks;
    4. }
    Aspect:
    Code (CSharp):
    1. public readonly partial struct ChunkAspect : IAspect
    2. {
    3.     //...
    4.     private readonly RefRW<ChunkData> m_Data;
    5.     public ref NativeArray<Block> BlocksRO => ref m_Data.ValueRW.Blocks;
    6.  
    7.     public NativeArray<Block> Blocks
    8.     {
    9.         get => m_Data.ValueRO.Blocks;
    10.         set => m_Data.ValueRW.Blocks = value;
    11.      }
    12.  
    13.     public void SetBlock(int index, Block block)
    14.     {
    15.         m_Data.ValueRW.Blocks[index] = block;
    16.     }
    17.  
    18.   //...
    19. }
    System
    Code (CSharp):
    1. [BurstCompile]
    2. public void OnUpdate(ref SystemState state)
    3. {
    4.     //...
    5.     foreach (var chunkAspect in SystemAPI.Query<ChunkAspect>().WithAny<ChunkNeedDataTag>())
    6.     {
    7.         if (chunkAspect.Blocks.Length != Core.ChunkSettings.ChunkBlockCount)
    8.         {
    9.             chunkAspect.Blocks = new NativeArray<Block>(Core.ChunkSettings.ChunkBlockCount, Allocator.Persistent);;
    10.         }
    11.     }
    12.     var job = new FillDataJob {
    13.       Ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter()
    14.     };
    15.     state.Dependency = job.ScheduleParallel(state.Dependency);
    16. }
    Job:
    Code (CSharp):
    1. [BurstCompile]
    2. [WithAny(typeof(ChunkNeedDataTag))]
    3. public partial struct FillDataJob: IJobEntity
    4. {      
    5.     public EntityCommandBuffer.ParallelWriter Ecb;
    6.  
    7.     public void Execute(ref ChunkAspect chunkAspect)
    8.     {
    9.         Ecb.SetComponentEnabled<ChunkNeedDataTag>(index, chunkAspect.Entity, false);
    10.         // Test
    11.         chunkAspect.SetBlock(0, new Block());
    12.     }
    13. }
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    You can add native containers to components but you can't access them in jobs because the safety system can't assign handles into them.

    They're really only useful on singletons and passing them into the job from Update (which in itself is very useful)
     
    Opeth001 and Anthiese like this.
  3. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    Note: I convert the native array to Span<T> inside the job because a method expects it. In the above example, I also tried without Span.

    But now I encountered an strange problem, in another job where i only read from the same array, i convert it to an ReadOnlySpan<T>, here I get the message "..has been declared as [WriteOnly] in the job, but you are reading from it" But if i change the method paramter to Span<T> then i got "... has been declared as [ReadOnly] in the job, but you are writing to it".
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    If you ever see this it is the classic error that means safety has not been set before job ran
     
  5. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    But in my case it is currently that if I only read, the error says that the array is WriteOnly, and if I only write, the array is ReadOnly. I can't access the array at all in the job. I've also tried it with query the component direct instat of the aspect, with the same error.
    In a different test project I tried with unity 2023.1 Alpha, I could easily read-access an array.

    If I find time in the next few days, I'll try to reproduce the problem more precisely with a smaller test project.

    At the moment this is my approach
    1. System generates the array and schedules the job for filling the array. I only have write access to the array in the job if I create the array in the same Frame/OnUpdate. The probem in my first posting.
    2. System schedule a job that generates metadata from the array. Here i use IJob and set the array manually since i also need readonly access to arrays from different entites.
    3. System creates a mesh from the metadata, IJobEntity with the aspect . Here i have no access to the array in the job.
     
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Exactly, as I said above you can't. This is not supported (at least at the moment.)

    The safety system can not inject correct handles into pointers on scheduling which components effectively are. This leaves a default or invalid handle which does not allow any access.

    It's not really a bug, it's simply not supported at this time (or maybe ever though I have heard they are researching possibilities.) The simple repo/test case is this

    Code (CSharp):
    1. public struct TestContainer
    2. {
    3.     public NativeArray<int> TestArray;
    4. }
    5.  
    6. public unsafe struct TestJob : IJob
    7. {
    8.     [NativeDisableUnsafePtrRestriction]
    9.     public TestContainer* Container;
    10.  
    11.     public void Execute()
    12.     {
    13.         this.Container->TestArray[0] = 1; // Read only error
    14.         var b = this.Container->TestArray[0]; // Write only error
    15.     }
    16. }
     
    Last edited: Nov 20, 2022
  7. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    In other words, I have to manually schedule a separate job for each entity like I already do in System 2. A bit problematic with potentially several hundred thousand entities, even if, apart from when initializing, all entities would never be affected at the same time later.
    I also tried with dynamic buffers, but I need dynamic buffers from several other entities in one job, I get different errors (I think it was not allowed to have an alias and simultaneous write access to the same container)

    I think the array access works if you run the job with Run(). But that's not an option in my case.
     
  8. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    Dynamic Buffers are the right tool here, so why don't you share the errors you were getting with those and see if we can help get those resolved?
     
  9. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    Turbo uses a native array in a component in this tutorial and reads from it in a job. That's how I got the idea.
     
  10. davenirline

    davenirline

    Joined:
    Jul 7, 2010
    Posts:
    987
  11. runner78

    runner78

    Joined:
    Mar 14, 2015
    Posts:
    792
    The article is already 2 years old and native array in component data is new in 1.0.
    But I'm now on my 3rd attempt to try it with Dynamic buffer, and this time it seems to work better, at least the filling works as I would like it to, I still have to adjust the mesh system.

    I've thought of a different approach so that I never have to access the data of other entities. How well this works will only become clear over time.
    However, I believe that a function that I would like to have is no longer so easy to implement, namely loading/deallocating the arrays on demand, to save memory. with native array this would be easy without structal changes. Or is there an easy way with dynamic buffers?