Search Unity

Monobehaviour with Native Containers or DynamicBuffer?

Discussion in 'Entity Component System' started by Abbrew, Feb 5, 2019.

  1. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I'm trying to do things the ECS way, but I've encountered a scenario where it may be better to use Monobehaviour. I get that you should try to store all data on entities, not systems, so to store lists of data one may use Dynamic Buffers. However, in my use case there should be constant time lookup, so storing that data in a NativeHashMap inside of a Monobehaviour attached to an entity seems like the better option. Which choice is more idiomatic?
     
  2. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Constant time lookup in what way? Do you want items in the list in constant time or getting the list in constant time? Can you elaborate on what the lists contain/how you want to use them?

    Neither NativeHashMap or Monobehaviours are blittable so they can't be attached to an entity though components. You can store a NativeMultiHashMap in a system and look up a list of items with an entity key from there, alternatively there is dynamic buffers as you mentioned, neither of those would get list elements in constant time though.

    I haven't seen a nativehashset in the packages yet sadly. Theoretically you could store monobehaviours on a SharedComponent, however this would likely be a bad idea because it would create a new chunk for every unique instance of it and accessing shared components is still unwieldy.
     
    Last edited: Feb 6, 2019
  3. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I am considering using a NativeMultiHashmap to map EnemyMarker structs to a list of SoldierId structs. This is a centralized view of which SoldierIds can see which EnemyMarkers. I also need to map SoldierId structs to a list of EnemyMarker structs. This is a per-soldier view of which EnemyMarkers the SoldierId can see. It's essentially an 1-n relationship. DynamicBuffers and IJobProcessEntity (it may be named something different) can model and utilize this relationship, but a Monobehaviour with NativeMultiHashmap would make it easier to work with the SoldierID and EnemyMarker structs.
     
  4. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    Either 2 MultiHashMaps in a system or buffers would do. What data do you have on your Marker/ID structs? The marker shouldn't contain anything, and the id is a constant value right? I don't think you should be processing those for anything in this context, and just use the entities themselves. I'd lean towards making a buffer "EntitiesInSight"(or whatever you want), attach that to your enemies/soldiers. Then in a system say "LineOfSightSystem" you could:

    • Build 2 NativeMultiHashMaps<Entity, Entity> in a job, one for each relationship.
    • Create 2 ComponentGroups, one that includes a SoldierId component + buffer, and another with EnemyMarker component + buffer.
    • In OnUpdate use ComponentGroup.GetBufferArray/GetEntityArray on both and save the results.
    • Send the Soldier HashMap + Soldier BufferArray to an IJobParallelFor, do the same for Enemies.
    • In the IJobParallelFor, get the buffer and entity from the index parameter, clear the buffer then iterate the hashmap with the entity and fill the buffer again.
    The above jobs should be burst compilable, there are probably more efficient ways to do this system but this is a fairly straightforward way.

    And just because I feel like procrastinating today, something like this:

    Code (CSharp):
    1. [InternalBufferCapacity(8)]
    2. public struct EntitiesInSight : IBufferElementData {
    3.     public Entity Value;
    4. }
    5.  
    6. public class LineOfSightSystem : JobComponentSystem {
    7.     ComponentGroup enemyGroup;
    8.     ComponentGroup soldierGroup;
    9.  
    10.     protected override void OnCreateManager() {
    11.         enemyGroup = GetComponentGroup(typeof(EnemyMarker), typeof(EntitiesInSight));
    12.         soldierGroup = GetComponentGroup(typeof(SoldierId), typeof(EntitiesInSight));
    13.     }
    14.  
    15.     [BurstCompile]
    16.     struct InLineOfSightJob : {//Whatever
    17.         public NativeMultiHashMap<Entity, Entity>.Concurrent EntitiesSeenByEntity;
    18.         //Up to your implementation
    19.     }
    20.  
    21.     [BurstCompile]
    22.     struct UpdateBuffersJob : IJobParallelFor {
    23.         public BufferArray<EntitiesInSight> EntitiesInSightBuffers;
    24.         [ReadOnly]
    25.         public EntityArray Entities;
    26.         [ReadOnly, DeallocateOnJobCompletion]
    27.         public NativeMultiHashMap<Entity, Entity> EntitiesSeenByEntity;
    28.  
    29.         public void Execute(int index) {
    30.             var entity = Entities[index];
    31.             EntitiesInSightBuffers[index].Clear();
    32.             //Fill up buffer again with EntitiesSeenByEntity, maybe with Buffer.AddRange
    33.         }
    34.     }
    35.  
    36.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    37.         var enemiesSeenBySoldier = new NativeMultiHashMap<Entity, Entity>(8, Allocator.TempJob);
    38.         var soldiersSeenByEnemy = new NativeMultiHashMap<Entity, Entity>(8, Allocator.TempJob);
    39.  
    40.         var enemiesSeenBySoldierJob = new InLineOfSightJob {
    41.             EntitiesSeenByEntity = enemiesSeenBySoldier.ToConcurrent()
    42.         }.Schedule(inputDeps)//And whatever your job type is
    43.  
    44.         var soldiersSeenByEnemyJob = new InLineOfSightJob {
    45.             EntitiesSeenByEntity = soldiersSeenByEnemy.ToConcurrent()
    46.         }.Schedule(inputDeps)//And whatever your job type is
    47.  
    48.         var soldierBuffers = soldierGroup.GetBufferArray<EntitiesInSight>();
    49.         var enemyBuffers = enemyGroup.GetBufferArray<EntitiesInSight>();
    50.         var enemyEntities = enemyGroup.GetEntityArray();
    51.         var soldierEntities = soldierGroup.GetEntityArray();
    52.  
    53.         var updateSoldierBuffersJob = new UpdateBuffersJob {
    54.             Entities = soldierEntities,
    55.             EntitiesInSightBuffers = soldierBuffers,
    56.             EntitiesSeenByEntity = enemiesSeenBySoldier
    57.         }.Schedule(soldierEntities.Length, 64, enemiesSeenBySoldierJob);
    58.  
    59.         var updateEnemyBuffersJob = new UpdateBuffersJob {
    60.             Entities = enemyEntities,
    61.             EntitiesInSightBuffers = enemyBuffers,
    62.             EntitiesSeenByEntity = soldiersSeenByEnemy
    63.         }.Schedule(enemyEntities.Length, 64, soldiersSeenByEnemyJob);
    64.         return updateEnemyBuffersJob;
    65.     }
    66. }
     
    Last edited: Feb 7, 2019
    Abbrew likes this.
  5. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    I did a double-take there - LineOfSightSystem already exists in my codebase; it does something slightly different though. Thought you were psychic or something XD. Thank you so much for providing some code. I'll try to take a look into it tonight