Search Unity

ComponentType doesn't implement IEquatable

Discussion in 'Entity Component System' started by orionburcham, Jan 13, 2019.

  1. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    Hello! ComponentType doesn't implement the IEquatable interface, which means it can't be used as the key in a NativeHashMap. This:

    NativeMultiHashMap<ComponentType, Entity>


    ...would be invalid.

    What would be some good workarounds for this?
     
    Last edited: Jan 13, 2019
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Hard to provide a workaround when I'm not sure why you would need this? Can you provide an example.
     
  3. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    Sure. I'd like to store a hashmap of entity collections, associated with certain component types. From inside a Job, the user should be able to supply a ComponentType, and receive a native collection of Entities.

    It would be cool to use ComponentGroup/GetComponentGroup() for this, but that would involve managed types.

    There are things one could do to replicate hashmap functionality with NativeLists or or other collection types, but none are as simple or performant. I'm looking for the next best solution. :)
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Without knowing the specifics, I feel like this should be done using chunk iteration as you are kind of just re-implementing the EntitySystem.

    -edit-

    actually is this for some type of observation system? i had a lot of pain implementing something like that.
     
  5. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    Maybe there's a way to make component queries from inside a job that I don't know about? Otherwise, I'm not sure how Chunk iteration would work here. I am using chunk iteration in my systems.
     
    Last edited: Jan 13, 2019
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I made a quick edit and I'm not exactly sure what you're doing, but you'd basically hard code for it.

    For example, let's say you have a job that is responsible for making a tower target a specific type of unit.

    You're query could be

    Code (CSharp):
    1.             this.targetQuery = this.GetComponentGroup(new EntityArchetypeQuery
    2.             {
    3.                 Any = new[]
    4.                 {
    5.                     ComponentType.ReadOnly<Swordsman>(),
    6.                     ComponentType.ReadOnly<Archer>(),
    7.                     ComponentType.ReadOnly<Dragon>(),
    8.  
    9.                 },
    10.             });
    And your job could be.

    Code (CSharp):
    1.         [BurstCompile]
    2.         private struct TargetJob : IJobProcessComponentData<Tower>
    3.         {
    4.             [ReadOnly]
    5.             public NativeArray<ArchetypeChunk> Chunks;
    6.             [ReadOnly]
    7.             public ArchetypeChunkComponentType<Swordsman> SwordsmanType;
    8.             [ReadOnly]
    9.             public ArchetypeChunkComponentType<Archer> ArcherType;
    10.             [ReadOnly]
    11.             public ArchetypeChunkComponentType<Dragon> DragonType;
    12.  
    13.             public void Execute(ref Tower data)
    14.             {
    15.                 for (var index = 0; index < this.Chunks.Length; index++)
    16.                 {
    17.                     var chunk = this.Chunks[index];
    18.  
    19.                     if (data.target == Target.Dragon)
    20.                     {
    21.                         var dragons = chunk.GetNativeArray(this.DragonType);
    22.  
    23.                         if (dragons.Length == 0)
    24.                         {
    25.                             continue;
    26.                         }
    27.  
    28.                         // do something
    29.                     }
    30.  
    31.                     if (data.target == Target.Swordsman)
    32.                     {
    33.                         var swordsman = chunk.GetNativeArray(this.SwordsmanType);
    34.  
    35.                         if (swordsman.Length == 0)
    36.                         {
    37.                             continue;
    38.                         }
    39.  
    40.                         // do something
    41.                     }
    42.  
    43.                     if (data.target == Target.Archer)
    44.                     {
    45.                         var archers = chunk.GetNativeArray(this.ArcherType);
    46.  
    47.                         if (archers.Length == 0)
    48.                         {
    49.                             continue;
    50.                         }
    51.  
    52.                         // do something
    53.                     }
    54.                 }
    55.             }
    56.         }
    57.  
    Just a quickly thrown together example. This obviously only works up to a pointer. When you get too many types it becomes to unmanageable. Instead you'd make a single Unit component and put the type in that.

    -edit-

    You can clean this up a lot with generics though.

    Code (CSharp):
    1.             private bool TryGetTarget<T>(ref Tower data, ArchetypeChunk chunk, ArchetypeChunkComponentType<T> targetsType)
    2.                 where T : struct, IComponentData
    3.             {
    4.                 var targets = chunk.GetNativeArray(targetsType);
    5.  
    6.                 if (targets.Length == 0)
    7.                 {
    8.                     return false;
    9.                 }
    10.  
    11.                 for (var i = 0; i < targets.Length; i++)
    12.                 {
    13.                     // do something
    14.                 }
    15.  
    16.                 return true;
    17.             }
    This could be completely off what you are wanting to do though.
     
    Last edited: Jan 13, 2019
    Derebeyi likes this.
  7. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I apologize, I should have answered the original question as well.

    You can just wrap the ComponentType in another struct.

    Code (CSharp):
    1. public struct ComponentTypeCompare : IEquatable
    2. {
    3.     public ComponentType Type;
    4.  
    5.     // implement equals yourself, comparing ComponentType TypeIndex, AccessMode and BufferCapacity
    6.  
    7.     // override implicit conversion from ComponentType to ComponentTypeCompare for convenience.
    8. }
    And use that as the key
     
    Derebeyi and orionburcham like this.
  8. JooleanLogic

    JooleanLogic

    Joined:
    Mar 1, 2018
    Posts:
    447
    That's an excellent example tertle.
    The chunk already is the collection of entities by component type.
    Plus it provides contiguous data and automatically accounts for addition/removal of entities, neither of which a custom Entity collection will provide.

    Alternate to extra IEquatable components, could you just use ComponentType.TypeIndex as your key? Could even use it as an array index.
     
    orionburcham likes this.
  9. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Yeah that's an excellent point. For this case you probably don't care about matching anything but TypeIndex.
     
  10. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    I was considering that. Might just be the simplest approach. :) Though I'm just going to think on it for a while...as you alluded to, it would be great not to replicate the association of Components with Entities in the first place.

    My end goal is to be able to ask "which entities have this tag" from inside a job, without having to know ahead of time what the tag types might be (that might be defined in a Component, for example).

    Since using ComponentGroups inside of Jobs was off the table, my theoretical implementation would have been a struct type which represented the queryable database. Something like
    public struct EntityTagMap


    Only one system would ever update it, but later Systems could then assign it to Jobs:

    Code (CSharp):
    1. struct MyJob : IJobProcessComponentData<MyComponent>
    2. {
    3.     [ReadOnly]
    4.     public EntityTagMap entityTagMap;
    5.  
    6.     public void Execute(ref MyComponent data)
    7.     {
    8.         NativeArray<Entity> entities = entityTagMap.GetEntitiesWithTag(MyComponent.tagType, Allocator.TempJob);
    9.  
    10.         // ... do work on entities
    11.     }
    12. }
    But that whole approach smells to me. Thankfully it's not something I can even do for another few weeks, but I realized I didn't immediately know how to solve the problem. So I'm just brainstorming. Thanks for the ideas.

    *Fwiw, this queryable database would represent a small subset of possible entities and component types in my game. It's not like it would represent the entire range of archetypes by any stretch.
     
    Last edited: Jan 13, 2019
  11. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    Hi @tertle. Here's a question unrelated to the OP, but something I've been curious about:

    In your frist example, you're calling GetComponentGroup() to retrieve archetypes with Swordsman, Archer, and Dragon components, even though these aren't the Component types you'll be iterating over in the Job.

    Since Unity uses GetComponentGroup() to determine if a System should run, would this system therefore run every update in which there are Swordsmen, Archers, or Dragons, even if there are no 'Tower' Components?
     
  12. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    it should run without towers - but you could change that, by adjusting the query to require the tower (all)
     
  13. orionburcham

    orionburcham

    Joined:
    Jan 31, 2010
    Posts:
    488
    Thank you, but I'm not sure that would make sense in his example. If I understand correctly, his game would involve 'Tower' entities which can target different 'Swordsman', 'Archer', or 'Dragon' entities. There may never be one entity that has both a 'Tower' component and a 'Dragon' component, for example.

    Please correct that if I'm wrong.
     
  14. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Yes the system would always run, but as long as your OnUpdate() code didn't really do anything the overhead would be pretty small.

    One way around this is don't use IJobProcessComponentData<Tower> and instead use IJobChunk and create a tower query. Then at the start of your OnUpdate() you can just do something like this.

    Code (CSharp):
    1.     protected override JobHandle OnUpdate(JobHandle handle)
    2.     {
    3.         if (this.towerQuery.CalculateLength() == 0)
    4.         {
    5.             return handle;
    6.         }
    7.      
    8.         // Create tower targetting job
    9.     }
    I actually run into this a lot, it's a little annoying and I'd love to be able to declare a ComponentGroup as 'required' before a system will run or have some type a system option that can be set to require all component groups instead of any.
     
    orionburcham likes this.
  15. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,154
    did not read carefully - in this case you do as @tertle explained.
    - I use the same approach with early out - the time cost is insignificant
    - I think you can also do something like below

    Code (CSharp):
    1.  
    2. public class ArchtypeTestJobSystemForum : JobComponentSystem
    3. {
    4.     public struct Tower : IComponentData {public float Value;}
    5.     public struct Soldier : IComponentData {public float Value;}
    6.  
    7.     ComponentGroup tower_Group;
    8.     EntityArchetypeQuery soldier_Query;
    9.      
    10.     protected override void OnCreateManager()
    11.     {            
    12.  
    13.         EntityManager.CreateEntity(ComponentType.Create<Tower>());     // comment out and system should not run
    14.         tower_Group = GetComponentGroup(ComponentType.Create<Tower>());
    15.          
    16.         EntityManager.CreateEntity(ComponentType.Create<Soldier>());
    17.         soldier_Query = new EntityArchetypeQuery
    18.         {
    19.             Any =  System.Array.Empty<ComponentType>(),
    20.             None = System.Array.Empty<ComponentType>(),
    21.             All = new ComponentType[] {ComponentType.Create<Soldier>()}
    22.         };
    23.  
    24.     }
    25.  
    26.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    27.     {
    28.          
    29.         var chunks = EntityManager.CreateArchetypeChunkArray (soldier_Query, Allocator.TempJob);
    30.         var entityChunkType = GetArchetypeChunkEntityType ();
    31.         for (int i = 0; i < chunks.Length; i++)
    32.         {
    33.             var entities = chunks[i].GetNativeArray(entityChunkType);
    34.             Debug.Log(entities.Length);
    35.             for (int j = 0; j < entities.Length; j++)
    36.             {
    37.                 // code
    38.             }
    39.         }
    40.  
    41.         chunks.Dispose();
    42.  
    43.         return inputDeps;
    44.     }
    45. }
    46.  
     
  16. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Quoting myself, they just added a in .23

    ComponentSystem.RequireForUpdate(ComponentGroup) for specifiying ComponentGroups that are required for a system to run.)


    method to component systems to support this!
     
    recursive and orionburcham like this.