Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

IJobForEach across two different archetypes with one to many relationship

Discussion in 'Entity Component System' started by JakHussain, Jul 9, 2019.

  1. JakHussain

    JakHussain

    Joined:
    Oct 20, 2016
    Posts:
    318
    I have two entity archetypes (tables) with a one to many relationship between them.The first has each entity contain a dynamic buffer of radiation values. The second has each entity contain component data to store things like the peak value of the radiation associated to it and a radiation index component used as a sort of foreign key in order to maintain the one to many association.

    As far as I understand, I don't want to be using a component system because I don't want to run this job in start or update but instead based on some event somewhere else in my app for a monobehaviour to respond to and therefore schedule and complete this job.

    I have code that works but it doesn't burst compile because of my use of the entity manager in a job and I'm really unsure about whether my approach is even very performant in the first place.

    Code (CSharp):
    1. [BurstCompile]
    2. public struct PeaksCalculationJob : IJobForEach<RadiationIndex, Peak, PeakIndex, Cummulative>
    3. {
    4.     [ReadOnly] public NativeArray<Entity> simulationSphere;
    5.  
    6.     public void Execute (ref RadiationIndex radiationIndex, ref Peak peak, ref PeakIndex peakIndex, ref Cummulative cummulative)
    7.     {
    8.         // managed code that doesn't burst compile
    9.         EntityManager entityManager = World.Active.EntityManager;
    10.         Entity simulationSurface = simulationSphere[radiationIndex.value];
    11.         DynamicBuffer<Radiation> radiationOnThisSurface = entityManager.GetBuffer<Radiation> (simulationSurface);
    12.  
    13.         // what I want to achieve
    14.         for (short i = 0; i < radiationOnThisSurface.Length; i++)
    15.         {
    16.             if (peak.value < radiationOnThisSurface[i])
    17.             {
    18.                 peak.value = radiationOnThisSurface[i];
    19.                 peakIndex.value = i;
    20.             }
    21.  
    22.             cummulative.value += radiationOnThisSurface[i];
    23.         }
    24.  
    25.         cummulative.value /= 1000;
    26.     }
    27. }
    How can I improve this code/approach and ensure I'm squeezing out as much performance as I can. What rookie mistakes am I making here?
     
  2. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    You can't directly access the EntityManager or DynamicBuffers from another thread. If you need to make structural changes (Add/Remove components or entities) you have to pass in an EntityCommandBuffer which will create a sync point and cannot be BurstCompiled right now, see the samples for examples of that.

    If you just need to access a dynamic buffer you need to pass in a BufferFromEntity to the job, which you do with GetBufferFomEntity when you're creating the job:
    Code (CSharp):
    1.     struct WriteToBuffer : IJobForEach<Data>
    2.     {
    3.         [NativeDisableParallelForRestriction]
    4.         public BufferFromEntity<BufferType> bufferFromEntity;
    5.  
    6.         public void Execute(ref Data c0)
    7.         {
    8.             var buffer = bufferFromEntity[someEntity];
    9.             // ...
    10.         }
    11.     }
    12.  
    13.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    14.     {
    15.         inputDeps = new WriteToBuffer
    16.         {
    17.             bufferFromEntity = GetBufferFromEntity<BufferType>(false),
    18.         }.Schedule(this, inputDeps);
    19.  
    20.         return inputDeps;
    21.     }
    You need the [NativeDisableParallelForRestriction] attribute to be able to write concurrently to the buffer from multiple threads. Just make sure you know what you're doing and aren't trying to write to the same index from different threads. If you can't accommodate that you can use .ScheduleSingle to force your job to run on a single thread.
     
  3. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    From what I get out of your description you can have all that in just one Entity. That entity would have buffer Radiation and components Peak, PeakIndex, and Cumulative then you just run a IJobFoeach_BCCC reading from Radiation and writing on the other components. And that will be BurstCompiled.
     
  4. JakHussain

    JakHussain

    Joined:
    Oct 20, 2016
    Posts:
    318
    Hi @GilCat, this used to be the case for the project but the radiation arrays would end up repeating across a lot of the data. Our memory usage on just the pure numbers alone was reaching 10gb. So we want to reduce repeating data by storing the Radiation Buffer on separate entities and then accessing the right one by the using the RadiationIndex componentdata. This is the context behind my one to many relationship.

    The code @Sarkahn posted seems like an ideal solution from what it looks like. Would that implementation work with the burst compiler?
     
  5. Sarkahn

    Sarkahn

    Joined:
    Jan 9, 2013
    Posts:
    440
    Yes, as long as you're not using an EntityCommandBuffer (which will work eventually, but not yet) it will benefit from Burst.