Search Unity

Difficulty Writing a System to Filter Entities. Please Help.

Discussion in 'Entity Component System' started by PublicEnumE, Aug 11, 2019.

  1. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Please help. I've been really struggling to find out a good way to do this in DOTS:
    • This game is about cats.
    • There are 'Cat' entities, and 'Owner' entities.
    • Cat entities are transform children of their Owners.
    • Cat entities come in different archetypes, with different trait-based components. One type of Cat may have the components: "Playful", "LongTail", Pointy Ears", for example. Other types of Cats may have different components.
    • Owners are the same way. One example Owner archetype might have the components: "Tall", "Old", "Kind". Other Owners may have different components.
    At times throughout the game, I need to perform an action on certain types of cats. For example, I might give a toy to all "Playful" cats who have "Kind" owners.

    To find all the appropriate cats, I create a "CatFilterCommand" entity. It has a collection components which are required for the Cats, and well as for their Owners. For example:

    CatFilterCommand:
    Type: Toy
    Cat Component Requirements: "Playful"
    Owner Component Requirements: "Kind"
    I've been trying to write a JobComponentSystem to do this filtering, but I keep running into problems.

    Here's the closest I've gotten:

    Code (CSharp):
    1. using Unity.Burst;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Transforms;
    6.  
    7. namespace CatGameRuntime
    8. {
    9.     [UpdateInGroup(typeof(CatAssignmentSystemGroup))]
    10.     public class FindCatTargetsSystem : JobComponentSystem
    11.     {
    12.         [BurstCompile]
    13.         struct Job : IJob
    14.         {
    15.             [WriteOnly]
    16.             [NativeDisableParallelForRestriction]
    17.             public DynamicBuffer<CatTarget> targetBuffer;
    18.  
    19.             [ReadOnly]
    20.             [DeallocateOnJobCompletion]
    21.             public NativeArray<Entity> ownerEntities;
    22.  
    23.             [ReadOnly]
    24.             [DeallocateOnJobCompletion]
    25.             public NativeArray<Entity> catEntities;
    26.  
    27.             [ReadOnly]
    28.             [DeallocateOnJobCompletion]
    29.             public NativeArray<Parent> catParents;
    30.  
    31.             public void Execute()
    32.             {
    33.                 // Each Cat's parent entity will also be its Owner.
    34.                 // Check to see if each cat's parent entity also appears in the list of qualifying owner entities. If so, then add this Cat to the targetBuffer. It has passed all tests.
    35.                 for (int i = 0; i < catParents.Length; i++)
    36.                 {
    37.                     Parent parent = catParents[i];
    38.  
    39.                     for (int iOwner = 0; iOwner < ownerEntities.Length; iOwner++)
    40.                     {
    41.                         Entity ownerEntity = ownerEntities[iOwner];
    42.  
    43.                         if (ownerEntity == parent.Value)
    44.                         {
    45.                             Entity catEntity = catEntities[i];
    46.  
    47.                             CatTarget catTarget = new CatTarget
    48.                             {
    49.                                 target = catEntity
    50.                             };
    51.  
    52.                             targetBuffer.Add(catTarget);
    53.                             break;
    54.                         }
    55.                     }
    56.                 }
    57.             }
    58.         }
    59.  
    60.         protected override JobHandle OnUpdate(JobHandle inputDependencies)
    61.         {
    62.             // Get all Cat Filter Command entities
    63.             EntityQuery CatFilterCommandEntityQuery = GetEntityQuery(
    64.                 ComponentType.ReadOnly<CatFilterCommand>(),
    65.                 ComponentType.ReadOnly<OwnerComponentRequirement>(),
    66.                 ComponentType.ReadOnly<CatComponentRequirement>(),
    67.                 ComponentType.ReadWrite<OwnerTarget>(),
    68.                 ComponentType.ReadWrite<CatTarget>());
    69.  
    70.             NativeArray<Entity> catFilterCommandEntities = CatFilterCommandEntityQuery.ToEntityArray(Allocator.TempJob);
    71.             BufferFromEntity<OwnerComponentRequirement> ownerComponentRequirementBuffers = GetBufferFromEntity<OwnerComponentRequirement>();
    72.             BufferFromEntity<CatComponentRequirement> catComponentRequirementBuffers = GetBufferFromEntity<CatComponentRequirement>();
    73.             BufferFromEntity<OwnerTarget> ownerTargetBuffers = GetBufferFromEntity<OwnerTarget>();
    74.             BufferFromEntity<CatTarget> catTargetBuffers = GetBufferFromEntity<CatTarget>();
    75.  
    76.             NativeList<JobHandle> jobHandles = new NativeList<JobHandle>(catFilterCommandEntities.Length, Allocator.Temp);
    77.  
    78.             // for each Cat Filter Command, find all cats and owners which match the component requirements.
    79.             for (int i = 0; i < catFilterCommandEntities.Length; i++)
    80.             {
    81.                 NativeArray<Entity> ownerTargets;
    82.                 NativeArray<Entity> catTargets;
    83.                 NativeArray<Parent> catTargetParents;
    84.  
    85.                 Entity catFilterCommandEntity = catFilterCommandEntities[i];
    86.                 DynamicBuffer<OwnerComponentRequirement> ownerComponentRequirementBuffer = ownerComponentRequirementBuffers[catFilterCommandEntity];
    87.                 DynamicBuffer<CatComponentRequirement> catComponentRequirementBuffer = catComponentRequirementBuffers[catFilterCommandEntity];
    88.                 DynamicBuffer<OwnerTarget> ownerTargetBuffer = ownerTargetBuffers[catFilterCommandEntity];
    89.                 DynamicBuffer<CatTarget> catTargetBuffer = catTargetBuffers[catFilterCommandEntity];
    90.  
    91.                 // cat targets & parents
    92.                 ComponentType[] catComponentRequirementTypes = new ComponentType[catComponentRequirementBuffer.Length + 2];
    93.  
    94.                 for (int iCatComponentRequirement = 0; iCatComponentRequirement < catComponentRequirementBuffer.Length; iCatComponentRequirement++)
    95.                 {
    96.                     CatComponentRequirement catComponentRequirement = catComponentRequirementBuffer[iCatComponentRequirement];
    97.  
    98.                     catComponentRequirementTypes[iCatComponentRequirement] = catComponentRequirement.componentType;
    99.                 }
    100.  
    101.                 catComponentRequirementTypes[catComponentRequirementBuffer.Length] = ComponentType.ReadOnly<Cat>();
    102.                 catComponentRequirementTypes[catComponentRequirementBuffer.Length + 1] = ComponentType.ReadOnly<Parent>();
    103.  
    104.                 EntityQuery catTargetQuery = GetEntityQuery(catComponentRequirementTypes);
    105.  
    106.                 catTargets = catTargetQuery.ToEntityArray(Allocator.TempJob);
    107.                 catTargetParents = catTargetQuery.ToComponentDataArray<Parent>(Allocator.TempJob);
    108.  
    109.                 // owner targets
    110.                 ComponentType[] ownerComponenetRequirementTypes = new ComponentType[ownerComponentRequirementBuffer.Length + 1];
    111.  
    112.                 for (int iOwnerComponentRequirement = 0; iOwnerComponentRequirement < ownerComponentRequirementBuffer.Length; iOwnerComponentRequirement++)
    113.                 {
    114.                     OwnerComponentRequirement ownerComponentRequirement = ownerComponentRequirementBuffer[iOwnerComponentRequirement];
    115.  
    116.                     ownerComponenetRequirementTypes[iOwnerComponentRequirement] = ownerComponentRequirement.componentType;
    117.                 }
    118.  
    119.                 ownerComponenetRequirementTypes[ownerComponentRequirementBuffer.Length] = ComponentType.ReadOnly<Owner>();
    120.  
    121.                 EntityQuery ownerTargetQuery = GetEntityQuery(ownerComponenetRequirementTypes);
    122.  
    123.                 ownerTargets = ownerTargetQuery.ToEntityArray(Allocator.TempJob);
    124.  
    125.                 // send the qualifying cats and qualifying owners into a job, to do the rest of the work.
    126.                 // since EntityQueries can only be created on the main thread, this is the earliest possible time to send this work to a Job.
    127.                 Job job = new Job
    128.                 {
    129.                     targetBuffer = catTargetBuffer,
    130.                     ownerEntities = ownerTargets,
    131.                     catEntities = catTargets,
    132.                     catParents = catTargetParents
    133.                 };
    134.  
    135.                 JobHandle jobHandle = job.Schedule(inputDependencies);
    136.  
    137.                 jobHandles.Add(jobHandle);
    138.             }
    139.  
    140.             JobHandle combinedJobHandles = JobHandle.CombineDependencies(jobHandles.AsArray());
    141.  
    142.             jobHandles.Dispose();
    143.  
    144.             catFilterCommandEntities.Dispose();
    145.  
    146.             return combinedJobHandles;
    147.         }
    148.     }
    149. }
    This works correctly! But it throws a 'Previously Schedules Job' error:

    Code (CSharp):
    1. InvalidOperationException: The previously scheduled job FindCatTargetsSystem:Job writes to the NativeArray Job.targetBuffer. You must call JobHandle.Complete() on the job FindCatTargetsSystem:Job, before you can read from the NativeArray safely.
    Can anyone explain why this error is being thrown? I'm assigning a unique "targetBuffer" to each Job. I'm also using the [NativeDisableParallelForRestriction] attribute. Is this one of those cases where Unity is being overly cautious, because it can't know that there isn't actually a race condition?

    Alternatively, if you have a better idea for how to do this filtering, I'd really appreciate your insight!

    Please keep in mind that I will always need to filter by both the Cat's and the Owner's components.

    Thank you for any help. :)
     
    Last edited: Aug 11, 2019
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    You're scheduling your jobs in parallel but using the same array in each - this is not safe and the system is telling you.

    This is a huge mess, I just woke up but if I find some free time today I'll try give some advise on writing this better.
     
  3. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    If you know a better way to do this filtering, I would love to know it. Thank you.

    Please double check this. I believe it isn’t actually true. A different DynamicBuffer is being assigned to each Job. Though I can see how Unity would have a hard time detecting that.
     
    Last edited: Aug 12, 2019
  4. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I @tertle. So I’ve come back to find that you didn’t actually know a better way to do this. Is that right?
     
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    I've just been busy today and haven't had time to really look into it yet sorry..
     
  6. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    By looking at your problem i would create a shared component with the types for filtering both Cat and owner and would build the query from that passing it to a job where i would collect the matching entities.
    Something like this:
    Code (CSharp):
    1. namespace CatGameRuntime {
    2.  
    3.   public struct Owner : IComponentData { }
    4.   public struct Cat : IComponentData { }
    5.  
    6.   // Traits
    7.   public struct Playfull : IComponentData { }
    8.   public struct Kind : IComponentData { }
    9.   public struct Nasty : IComponentData { }
    10.  
    11.   public struct CatFilter : ISharedComponentData, IEquatable<CatFilter> {
    12.     public ComponentType[] OwnerTraits;
    13.     public ComponentType[] CatTraits;
    14.  
    15.     public bool Equals(CatFilter other) {
    16.       return (OwnerTraits?.SequenceEqual(other.OwnerTraits ?? Enumerable.Empty<ComponentType>()) ?? OwnerTraits == other.OwnerTraits) &&
    17.         (CatTraits?.SequenceEqual(other.CatTraits ?? Enumerable.Empty<ComponentType>()) ?? CatTraits == other.CatTraits);
    18.     }
    19.  
    20.     public override int GetHashCode() {
    21.       var ownerTraitsHashcode = OwnerTraits?.Length ?? 0;
    22.       if (OwnerTraits != null)
    23.         for (int i = 0; i < OwnerTraits.Length; ++i)
    24.           ownerTraitsHashcode = unchecked(ownerTraitsHashcode * 17 + OwnerTraits[i].GetHashCode());
    25.  
    26.       var catTraitsHashcode = CatTraits?.Length ?? 0;
    27.       if (CatTraits != null)
    28.         for (int i = 0; i < CatTraits.Length; ++i)
    29.           catTraitsHashcode = unchecked(catTraitsHashcode * 17 + CatTraits[i].GetHashCode());
    30.       return unchecked(ownerTraitsHashcode * 17 + catTraitsHashcode); ;
    31.     }
    32.   }
    33.  
    34.   public class FindCatTargetsSystem : JobComponentSystem {
    35.  
    36.     NativeQueue<Entity> m_result;
    37.  
    38.     protected override void OnCreate() {
    39.       base.OnCreate();
    40.       m_result = new NativeQueue<Entity>(Allocator.Persistent);
    41.     }
    42.  
    43.     protected override void OnDestroy() {
    44.       base.OnDestroy();
    45.       m_result.Dispose();
    46.     }
    47.  
    48.     [BurstCompile]
    49.     struct FilterJob : IJobForEachWithEntity<Cat, Parent> {
    50.       [DeallocateOnJobCompletion]
    51.       [ReadOnly]
    52.       public NativeArray<Entity> Owners;
    53.       [WriteOnly]
    54.       public NativeQueue<Entity>.ParallelWriter CatsResult;
    55.  
    56.       public void Execute(Entity entity, int index, [ReadOnly]ref Cat _, [ReadOnly]ref Parent parent) {
    57.         if (Owners.Contains(parent.Value))
    58.           CatsResult.Enqueue(entity);
    59.       }
    60.     }
    61.  
    62.     struct PrintJob : IJob {
    63.       [ReadOnly]
    64.       public NativeQueue<Entity> Values;
    65.  
    66.       public void Execute() {
    67.         if (Values.Count > 0) {
    68.           var log = "Query result: ";
    69.           while (Values.TryDequeue(out var item))
    70.             log += $"\n{item}";
    71.           Debug.Log(log);
    72.         }
    73.       }
    74.     }
    75.  
    76.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    77.       m_result.Clear();
    78.       var filters = new List<CatFilter>();
    79.       EntityManager.GetAllUniqueSharedComponentData(filters);
    80.       EntityManager.DestroyEntity(EntityManager.CreateEntityQuery(typeof(CatFilter)));
    81.       foreach (var filter in filters) {
    82.         if (filter.Equals(default))
    83.           continue;
    84.         var catsQuery = GetEntityQuery(filter.CatTraits.Union(new ComponentType[] { ComponentType.ReadOnly<Cat>(), ComponentType.ReadOnly<Parent>() }).ToArray());
    85.         var matchingOwners = GetEntityQuery(filter.OwnerTraits.Union(new ComponentType[] { ComponentType.ReadOnly<Owner>() }).ToArray())
    86.           .ToEntityArray(Allocator.TempJob);
    87.  
    88.         inputDeps = new FilterJob {
    89.           Owners = matchingOwners,
    90.           CatsResult = m_result.AsParallelWriter()
    91.         }.Schedule(catsQuery, inputDeps);
    92.       }
    93.       inputDeps = new PrintJob {
    94.         Values = m_result
    95.       }.Schedule(inputDeps);
    96.       return inputDeps;
    97.     }
    98.   }
    99. }

    For testing the system:
    Code (CSharp):
    1.  
    2. public class TestSystem : ComponentSystem {
    3.   protected override void OnUpdate() {
    4.     if (Input.GetKeyDown(KeyCode.Alpha1))
    5.       EntityManager.AddSharedComponentData(
    6.         EntityManager.CreateEntity(),
    7.         new CatFilter {
    8.           OwnerTraits = Array.Empty<ComponentType>(),
    9.           CatTraits = new ComponentType[] { typeof(Nasty) }
    10.         });
    11.  
    12.     if (Input.GetKeyDown(KeyCode.Alpha2))
    13.       EntityManager.AddSharedComponentData(
    14.         EntityManager.CreateEntity(),
    15.         new CatFilter {
    16.           OwnerTraits = new ComponentType[] { typeof(Nasty) },
    17.           CatTraits = new ComponentType[] { typeof(Kind) }
    18.         });
    19.  
    20.     if (Input.GetKeyDown(KeyCode.Alpha3))
    21.       EntityManager.AddSharedComponentData(
    22.         EntityManager.CreateEntity(),
    23.         new CatFilter {
    24.           OwnerTraits = new ComponentType[] { typeof(Kind) },
    25.           CatTraits = new ComponentType[] { typeof(Playfull), typeof(Kind) }
    26.         });
    27.  
    28.   }
    29. }
    30.  
    31. public class BootStrap : ICustomBootstrap {
    32.   public List<Type> Initialize(List<Type> systems) {
    33.     var entityManager = World.Active.EntityManager;
    34.     var owner1 = entityManager.CreateEntity(typeof(Owner), typeof(Kind));
    35.     var owner2 = entityManager.CreateEntity(typeof(Owner), typeof(Nasty));
    36.     var owner3 = entityManager.CreateEntity(typeof(Owner));
    37.  
    38.     var cat1 = entityManager.CreateEntity(typeof(Cat), typeof(Playfull));
    39.     var cat2 = entityManager.CreateEntity(typeof(Cat), typeof(Playfull), typeof(Kind));
    40.     var cat3 = entityManager.CreateEntity(typeof(Cat), typeof(Kind));
    41.     var cat4 = entityManager.CreateEntity(typeof(Cat), typeof(Nasty));
    42.     var cat5 = entityManager.CreateEntity(typeof(Cat), typeof(Playfull), typeof(Nasty));
    43.     var cat6 = entityManager.CreateEntity(typeof(Cat), typeof(Playfull));
    44.  
    45.     entityManager.AddComponentData(cat1, new Parent { Value = owner1 });
    46.     entityManager.AddComponentData(cat2, new Parent { Value = owner1 });
    47.     entityManager.AddComponentData(cat3, new Parent { Value = owner2 });
    48.     entityManager.AddComponentData(cat4, new Parent { Value = owner3 });
    49.     entityManager.AddComponentData(cat5, new Parent { Value = owner3 });
    50.     entityManager.AddComponentData(cat6, new Parent { Value = owner3 });
    51.     return systems;
    52.   }
    53. }
     
    PublicEnumE likes this.
  7. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Thanks, @GilCat! There are several tools here I wasn't aware of, like
    GetAllUniqueSharedComponentData()
    , and the ability to passing in a Native collection when scheduling an IJobForEachWithEntity. That second one is something I was really hurting for here. I figured I couldn't limit which entities an IJobForEach was iterating over.

    There's so much here that will help me. I really appreciate it. :)

    One question: You have 'm_result' here as a member of the System itself. Ultimately, I'll need to have a separate collection like this for each filter query. And the members of that collection will need to end up in a DynamicBuffer on the entity that has the CatFilter component (in my code, I don't destroy those entities).

    ^ How would you go about doing this?

    Also, how are you calling Contains() on a NativeArray<T>?
     
    Last edited: Aug 12, 2019
  8. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    As many know on these boards I'm a huge hater of SCD for a lot of cases so I have to provide you an alternative. Also what you were doing seemed like a huge mess to me. Trying to filter on so many components, so many loops etc.

    So instead lets filtering on those components and simply use an enum and put it all on a single multi thread job with no filtering, no shared component data. The entire system update then just looks like this.

    Code (CSharp):
    1.  
    2. protected override JobHandle OnUpdate(JobHandle handle)
    3. {
    4.     return new FindCatTargetsJob
    5.         {
    6.             Cats = this.catQuery.ToEntityArray(Allocator.TempJob),
    7.             CatComponents = this.catQuery.ToComponentDataArray<Cat>(Allocator.TempJob),
    8.             CatParents = this.catQuery.ToComponentDataArray<Parent>(Allocator.TempJob),
    9.             OwnerComponents = this.GetComponentDataFromEntity<Owner>(true),
    10.         }
    11.         .Schedule(this.commandQuery, handle);
    12. }
    13.  
    Full code

    Code (CSharp):
    1.  
    2. namespace CatGameRuntime
    3. {
    4.     using System;
    5.     using Unity.Burst;
    6.     using Unity.Collections;
    7.     using Unity.Entities;
    8.     using Unity.Jobs;
    9.     using Unity.Transforms;
    10.  
    11.     public struct CatTarget : IBufferElementData
    12.     {
    13.         public Entity Target;
    14.     }
    15.  
    16.     public struct CatFilterCommand : IComponentData
    17.     {
    18.         public CatAttribute CatRequirement;
    19.         public OwnerAttribute OwnerRequirement;
    20.     }
    21.  
    22.     [Flags]
    23.     public enum CatAttribute
    24.     {
    25.         Playful,
    26.         LongTail,
    27.         PointyEars
    28.     }
    29.  
    30.     [Flags]
    31.     public enum OwnerAttribute
    32.     {
    33.         Tall,
    34.         Old,
    35.         Kind
    36.     }
    37.  
    38.     public struct Cat : IComponentData
    39.     {
    40.         public CatAttribute Attribute;
    41.     }
    42.  
    43.     public struct Owner : IComponentData
    44.     {
    45.         public OwnerAttribute Attribute;
    46.     }
    47.  
    48.     public class FindCatTargetsSystem : JobComponentSystem
    49.     {
    50.         private EntityQuery catQuery;
    51.         private EntityQuery commandQuery;
    52.  
    53.         protected override void OnCreate()
    54.         {
    55.             // Specify command just so we can require it for update
    56.             this.commandQuery = this.GetEntityQuery(
    57.                 ComponentType.ReadOnly<CatFilterCommand>(),
    58.                 ComponentType.ReadWrite<CatTarget>());
    59.  
    60.             this.catQuery = this.GetEntityQuery(ComponentType.ReadOnly<Cat>(), ComponentType.ReadOnly<Parent>());
    61.  
    62.             this.RequireForUpdate(this.commandQuery);
    63.             this.RequireForUpdate(this.catQuery);
    64.         }
    65.  
    66.         protected override JobHandle OnUpdate(JobHandle handle)
    67.         {
    68.             return new FindCatTargetsJob
    69.                 {
    70.                     Cats = this.catQuery.ToEntityArray(Allocator.TempJob),
    71.                     CatComponents = this.catQuery.ToComponentDataArray<Cat>(Allocator.TempJob),
    72.                     CatParents = this.catQuery.ToComponentDataArray<Parent>(Allocator.TempJob),
    73.                     OwnerComponents = this.GetComponentDataFromEntity<Owner>(true),
    74.                 }
    75.                 .Schedule(this.commandQuery, handle);
    76.         }
    77.  
    78.         [BurstCompile]
    79.         private struct FindCatTargetsJob : IJobForEach_BC<CatTarget, CatFilterCommand>
    80.         {
    81.             [DeallocateOnJobCompletion]
    82.             [ReadOnly]
    83.             public NativeArray<Entity> Cats;
    84.  
    85.             [DeallocateOnJobCompletion]
    86.             [ReadOnly]
    87.             public NativeArray<Cat> CatComponents;
    88.  
    89.             [DeallocateOnJobCompletion]
    90.             [ReadOnly]
    91.             public NativeArray<Parent> CatParents;
    92.  
    93.             [ReadOnly]
    94.             public ComponentDataFromEntity<Owner> OwnerComponents;
    95.  
    96.             public void Execute(DynamicBuffer<CatTarget> catTargets, [ReadOnly] ref CatFilterCommand filterCommand)
    97.             {
    98.                 for (var i = 0; i < this.Cats.Length; i++)
    99.                 {
    100.                     var cat = this.CatComponents[i];
    101.  
    102.                     if (!cat.Attribute.HasFlag(filterCommand.CatRequirement))
    103.                     {
    104.                         continue;
    105.                     }
    106.  
    107.                     var parentEntity = this.CatParents[i];
    108.  
    109.                     var parent = this.OwnerComponents[parentEntity.Value];
    110.  
    111.                     if (!parent.Attribute.HasFlag(filterCommand.OwnerRequirement))
    112.                     {
    113.                         continue;
    114.                     }
    115.  
    116.                     var catTarget = new CatTarget
    117.                     {
    118.                         Target = this.Cats[i],
    119.                     };
    120.  
    121.                     catTargets.Add(catTarget);
    122.  
    123.                     break; // is this intended? original brief said "might give a toy to all "Playful" cats who have "Kind" owners"
    124.                 }
    125.             }
    126.         }
    127.     }
    128. }
    129.  
    *** UNTESTED - just a proof of concept, never ran the code and I might have some logic that doesn't match your original intent as I may have misunderstood slightly but should be close and should be pretty obvious what I attempted.

    -edit-

    by the way, you could still totally use a PlayFul component etc as you likely want to have completely separate systems to for each behaviour. I find filtering this way much easier though and if cats/owners behaviour changed over time I'd probably have some system that automatically maintained the enum matching the components on the cat/owners.

    -edit2-

    also to be fair, i didn't answer your question since my answer was do not filter :confused:
     
    Last edited: Aug 13, 2019
  9. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Contains() is a NativeArray extension that exists in Unity.Collections namespace.

    For that you must then copy the values in the NativeQueue into a Dynamic buffer.
    Here is an update:
    Code (CSharp):
    1.  
    2. namespace CatGameRuntime {
    3.  
    4.   public struct Owner : IComponentData { }
    5.   public struct Cat : IComponentData { }
    6.  
    7.   // Traits
    8.   public struct Playfull : IComponentData { }
    9.   public struct Kind : IComponentData { }
    10.   public struct Nasty : IComponentData { }
    11.  
    12.   public struct CatFilter : ISharedComponentData, IEquatable<CatFilter> {
    13.     public ComponentType[] OwnerTraits;
    14.     public ComponentType[] CatTraits;
    15.  
    16.     public bool Equals(CatFilter other) {
    17.       return (OwnerTraits?.SequenceEqual(other.OwnerTraits ?? Enumerable.Empty<ComponentType>()) ?? OwnerTraits == other.OwnerTraits) &&
    18.         (CatTraits?.SequenceEqual(other.CatTraits ?? Enumerable.Empty<ComponentType>()) ?? CatTraits == other.CatTraits);
    19.     }
    20.  
    21.     public override int GetHashCode() {
    22.       var ownerTraitsHashcode = OwnerTraits?.Length ?? 0;
    23.       if (OwnerTraits != null)
    24.         for (int i = 0; i < OwnerTraits.Length; ++i)
    25.           ownerTraitsHashcode = unchecked(ownerTraitsHashcode * 17 + OwnerTraits[i].GetHashCode());
    26.  
    27.       var catTraitsHashcode = CatTraits?.Length ?? 0;
    28.       if (CatTraits != null)
    29.         for (int i = 0; i < CatTraits.Length; ++i)
    30.           catTraitsHashcode = unchecked(catTraitsHashcode * 17 + CatTraits[i].GetHashCode());
    31.       return unchecked(ownerTraitsHashcode * 17 + catTraitsHashcode); ;
    32.     }
    33.   }
    34.  
    35.   public struct CatFilterResult : IBufferElementData , IEquatable<CatFilterResult> {
    36.     public Entity Value;
    37.  
    38.     public bool Equals(CatFilterResult other) {
    39.       return Value == other.Value;
    40.     }
    41.  
    42.     public override int GetHashCode() {
    43.       return Value.GetHashCode();
    44.     }
    45.   }
    46.  
    47.   public class FindCatTargetsSystem : JobComponentSystem {
    48.  
    49.     NativeQueue<Entity> m_result;
    50.     EntityQuery m_filterQuery;
    51.     int m_filterOrderVersion;
    52.  
    53.     protected override void OnCreate() {
    54.       base.OnCreate();
    55.       m_result = new NativeQueue<Entity>(Allocator.Persistent);
    56.       m_filterQuery = GetEntityQuery(typeof(CatFilter));
    57.       // This should unique entity
    58.       // Unfortunately there is no Singleton for Dynamic buffers
    59.       var resultHolder = EntityManager.CreateEntity(typeof(CatFilterResult));
    60. #if UNITY_EDITOR
    61.       EntityManager.SetName(resultHolder, "Cat Filter Result");
    62. #endif
    63.     }
    64.  
    65.     protected override void OnDestroy() {
    66.       base.OnDestroy();
    67.       m_result.Dispose();
    68.     }
    69.  
    70.     [BurstCompile]
    71.     struct ClearNativeQueue<T> : IJob where T : struct {
    72.  
    73.       public NativeQueue<T> Source;
    74.       public void Execute() {
    75.         Source.Clear();
    76.       }
    77.     }
    78.  
    79.     [BurstCompile]
    80.     struct ClearDynamicBuffer<T> : IJobForEach_B<T>
    81.       where T : struct, IBufferElementData {
    82.       public void Execute(DynamicBuffer<T> buffer) {
    83.         buffer.Clear();
    84.       }
    85.     }
    86.  
    87.     [BurstCompile]
    88.     struct FilterJob : IJobForEachWithEntity<Cat, Parent> {
    89.       [DeallocateOnJobCompletion]
    90.       [ReadOnly]
    91.       public NativeArray<Entity> Owners;
    92.       [WriteOnly]
    93.       public NativeQueue<Entity>.ParallelWriter CatsResult;
    94.  
    95.       public void Execute(Entity entity, int index, [ReadOnly]ref Cat _, [ReadOnly]ref Parent parent) {
    96.         if (Owners.Contains(parent.Value))
    97.           CatsResult.Enqueue(entity);
    98.       }
    99.     }
    100.  
    101.     [BurstCompile]
    102.     struct CopyFilterResults : IJobForEach_B<CatFilterResult> {
    103.       [ReadOnly]
    104.       public NativeQueue<Entity> Entities;
    105.  
    106.       public void Execute(DynamicBuffer<CatFilterResult> result) {
    107.         while (Entities.TryDequeue(out var entity)) {
    108.           var item = new CatFilterResult { Value = entity };
    109.           // Avoid duplicates
    110.           if (!result.AsNativeArray().Contains(item))
    111.             result.Add(item);
    112.         }
    113.       }
    114.     }
    115.  
    116.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    117.       // Cache the filter order version so we only query when there is a change
    118.       if (m_filterQuery.GetCombinedComponentOrderVersion() == m_filterOrderVersion)
    119.         return inputDeps;  
    120.  
    121.       var filters = new List<CatFilter>();
    122.       EntityManager.GetAllUniqueSharedComponentData(filters);
    123.       EntityManager.DestroyEntity(EntityManager.CreateEntityQuery(typeof(CatFilter)));
    124.       m_filterOrderVersion = m_filterQuery.GetCombinedComponentOrderVersion();
    125.  
    126.       inputDeps = new ClearDynamicBuffer<CatFilterResult>()
    127.         .Schedule(this, inputDeps);
    128.  
    129.       var resultParallelWriter = m_result.AsParallelWriter();
    130.  
    131.       foreach (var filter in filters) {
    132.         if (filter.Equals(default))
    133.           continue;
    134.         var catsQuery = GetEntityQuery(filter.CatTraits.Union(new ComponentType[] { ComponentType.ReadOnly<Cat>(), ComponentType.ReadOnly<Parent>() }).ToArray());
    135.         var matchingOwners = GetEntityQuery(filter.OwnerTraits.Union(new ComponentType[] { ComponentType.ReadOnly<Owner>() }).ToArray())
    136.           .ToEntityArray(Allocator.TempJob);
    137.  
    138.         // When multiple filtering at once combine filtering to run them all in parallel
    139.         inputDeps = JobHandle.CombineDependencies(
    140.           inputDeps,
    141.           new FilterJob {
    142.             Owners = matchingOwners,
    143.             CatsResult = resultParallelWriter,
    144.           }.Schedule(catsQuery, inputDeps));
    145.       }
    146.  
    147.       inputDeps = new CopyFilterResults {
    148.         Entities = m_result
    149.       }.Schedule(this, inputDeps);
    150.  
    151.       inputDeps = new ClearNativeQueue<Entity> {
    152.         Source = m_result
    153.       }.Schedule(inputDeps);
    154.  
    155.       return inputDeps;
    156.     }
    157.   }
    158. }
    159.  
    There be only one entity with CatFilterResult dynamic buffer.
    You can access the result in any other system with a IJobForEach_B<CatFilterResult>
    Also when you issue a query this buffer will be cleared.
    You can combine several queries at once and the result will be merged in the CatFilterResult buffer.
    Combined queries:
    Code (CSharp):
    1.       EntityManager.AddSharedComponentData(
    2.         EntityManager.CreateEntity(),
    3.         new CatFilter {
    4.           OwnerTraits = Array.Empty<ComponentType>(),
    5.           CatTraits = new ComponentType[] { typeof(Nasty) }
    6.         });
    7.       EntityManager.AddSharedComponentData(
    8.         EntityManager.CreateEntity(),
    9.         new CatFilter {
    10.           OwnerTraits = new ComponentType[] { typeof(Nasty) },
    11.           CatTraits = new ComponentType[] { typeof(Kind) }
    12.         });
    @tertle approach is also completely valid but i don't see why we shouldn't take advantage of SCD.
    I've tested this code, it works as expected and it's garbage free...
     
  10. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    I apologize in advance if anything I say here sounds harsh, it's not meant to be I just come off blunt sometimes and really appreciate you and others posting advice and helping people on these forums, especially if it differs to my own as I do not claim that my approach is best or even right and you learn a lot by having different opinions.

    However I don't know how it works as expected, it actually just errors if you have burst and safety checks on.

    To start with this job doesn't work and throws errors if safety system is on

    Code (CSharp):
    1.     [BurstCompile]
    2.     struct CopyFilterResults : IJobForEach_B<CatFilterResult> {
    3.       [ReadOnly]
    4.       public NativeQueue<Entity> Entities;
    5.  
    6.       public void Execute(DynamicBuffer<CatFilterResult> result) {
    7.         while (Entities.TryDequeue(out var entity)) {
    8.           var item = new CatFilterResult { Value = entity };
    9.           // Avoid duplicates
    10.           if (!Enumerable.Contains(result.AsNativeArray(), item))
    11.             result.Add(item);
    12.         }
    13.       }
    14.     }
    Because Entities can't be ReadOnly as you're dequeuing and the safety system will throw an error so you need to remove ReadOnly. Also now you're writing in a threaded job so need to use ScheduleSingle to keep it on a single thread.

    Second is

    Code (CSharp):
    1. [BurstCompile]
    2. struct CopyFilterResults : IJobForEach_B<CatFilterResult> {
    3.  
    4.   public NativeQueue<Entity> Entities;
    5.  
    6.   public void Execute(DynamicBuffer<CatFilterResult> result) {
    7.     while (Entities.TryDequeue(out var entity)) {
    8.       var item = new CatFilterResult { Value = entity };
    9.       // Avoid duplicates
    10.       if (!Enumerable.Contains(result.AsNativeArray(), item))
    11.         result.Add(item);
    12.     }
    13.   }
    14. }
    This line throws exceptions when burst is turned on.
    if (!Enumerable.Contains(result.AsNativeArray(), item))

    This can be fixed with
    if (!result.AsNativeArray().Contains(item)) // Unity.Collections.NativeArrayExtensions

    After changing these the system seems to work fine for me, however then comes the next problem.

    How is

    Code (CSharp):
    1.  
    2. new List<CatFilter>();
    3. filter.CatTraits.Union(new ComponentType[] { ComponentType.ReadOnly<Cat>(), ComponentType.ReadOnly<Parent>() }).ToArray()
    4. filter.OwnerTraits.Union(new ComponentType[] { ComponentType.ReadOnly<Owner>() }).ToArray()
    not allocating every frame? You are creating a bunch of new managed objects.
    I just checked and it allocates about 2KB of garbage for me every frame with 2 filters.
    For 100 filters that skyrockets up to 105KB of garbage.

    Anyway, even if it it works it's exactly nice or scaling if you ask me. Why don't I like it?

    Imagine you have 100 cat filters. That's 200 (and a few more) entity queries in a single system, 100 array allocations, 100 IJobs with overhead (and a few more) and all this is being done on the main thread. On my machine, this locks my main thread for 14ms just scheduling these jobs and setting this up.

    upload_2019-8-13_22-33-0.png

    Now imagine you have 1000.

    upload_2019-8-13_22-33-56.png

    It's not exactly a performant approach.
     
    Last edited: Aug 13, 2019
  11. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    I don't have any errors and safety system is on.
    That's not the job i've posted and mine is burstable:
    Code (CSharp):
    1.     [BurstCompile]
    2.     struct CopyFilterResults : IJobForEach_B<CatFilterResult> {
    3.       [ReadOnly]
    4.       public NativeQueue<Entity> Entities;
    5.  
    6.       public void Execute(DynamicBuffer<CatFilterResult> result) {
    7.         while (Entities.TryDequeue(out var entity)) {
    8.           var item = new CatFilterResult { Value = entity };
    9.           // Avoid duplicates
    10.           if (!result.AsNativeArray().Contains(item))
    11.             result.Add(item);
    12.         }
    13.       }
    14.     }
    Right but i'm only writing to one entity anyway.
    And then again it also happens in you job here:

    Code (CSharp):
    1. [BurstCompile]
    2.         private struct FindCatTargetsJob : IJobForEach_BC<CatTarget, CatFilterCommand>
    3.         {
    4.             [DeallocateOnJobCompletion]
    5.             [ReadOnly]
    6.             public NativeArray<Entity> Cats;
    7.             [DeallocateOnJobCompletion]
    8.             [ReadOnly]
    9.             public NativeArray<Cat> CatComponents;
    10.             [DeallocateOnJobCompletion]
    11.             [ReadOnly]
    12.             public NativeArray<Parent> CatParents;
    13.             [ReadOnly]
    14.             public ComponentDataFromEntity<Owner> OwnerComponents;
    15.             public void Execute(DynamicBuffer<CatTarget> catTargets, [ReadOnly] ref CatFilterCommand filterCommand)
    16.             {
    17.                 for (var i = 0; i < this.Cats.Length; i++)
    18.                 {
    19.                     var cat = this.CatComponents[i];
    20.                     if (!cat.Attribute.HasFlag(filterCommand.CatRequirement))
    21.                     {
    22.                         continue;
    23.                     }
    24.                     var parentEntity = this.CatParents[i];
    25.                     var parent = this.OwnerComponents[parentEntity.Value];
    26.                     if (!parent.Attribute.HasFlag(filterCommand.OwnerRequirement))
    27.                     {
    28.                         continue;
    29.                     }
    30.                     var catTarget = new CatTarget
    31.                     {
    32.                         Target = this.Cats[i],
    33.                     };
    34.                     catTargets.Add(catTarget);
    35.                     break; // is this intended? original brief said "might give a toy to all "Playful" cats who have "Kind" owners"
    36.                 }
    37.             }
    Because of this:
    Code (CSharp):
    1.       if (m_filterQuery.GetCombinedComponentOrderVersion() == m_filterOrderVersion)
    2.         return inputDeps;  
    It is not running every frame.
    Yes it allocates when a query is issued but only at that frame.
    Haven't done performance test but i believe your numbers.
    Just trying to help and offer other options here. :)
     
  12. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Hmm that's weird, I don't know why it copied in Enumerable.Contains into my IDE probably some auto complete importing namespaces ____ weird. Ignore that part.

    That said I don't know why you aren't getting errors on the NativeQueue. When you have ReadOnly you can't do Dequeue because Dequene modifies the Queue so you should be getting errors... I'm a little stumped.

    I liked your early out. Like I said not trying to tear you down or anything, I like differing opinions as you learn a lot from them and there are certainly cases where this solution is ideal and to be honest, most people don't need to scale that much performance I'm just a bit of a performance junky so it's my go to thing.
     
    Last edited: Aug 13, 2019
  13. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    To be honest i don't know why it doesn't throw me an error. If i remove the [ReadOnly] it throws me an error stating that the container must be [ReadOnly]. So i suppose it is a bug.
    NOTE: I was running it with Entities 0.1.0, switched to 0.1.1 and now it throws me the error. Just replace [ReadOnly] with [NativeDisableParallelForRestriction]. This implies that only one DynamicBuffer CatFilterResult (there is no Singleton for IBufferElement) will ever exist, otherwise there will be a race condition here.

    I know you are not tearing me down. As i said i was showing one way of doing what was asked.
    This forum is really the main source of learning material regarding DOTS, personally i've learned a lot from this forum and from a lot of posts of you and other users that are usually around. We are always learning from each other :)
     
  14. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    Here is my last update on this with some caching to avoid GC.
    Now it can run queries without any GC (on all frames).
    Tested with 100 cat filters every frame on 10000 Owners each with 1 cat (20000 total) with main thread time of 9ms.
    Code (CSharp):
    1.  
    2. namespace CatGameRuntime {
    3.  
    4.   public struct Owner : IComponentData { }
    5.   public struct Cat : IComponentData { }
    6.  
    7.   // Traits
    8.   public struct Playfull : IComponentData { }
    9.   public struct Kind : IComponentData { }
    10.   public struct Nasty : IComponentData { }
    11.  
    12.   public struct CatFilter : ISharedComponentData, IEquatable<CatFilter> {
    13.  
    14.     ComponentType[] m_ownerTraits;
    15.  
    16.     public ComponentType[] OwnerTraits {
    17.       get => m_ownerTraits;
    18.       set => m_ownerTraits = value.Union(new ComponentType[] { ComponentType.ReadOnly<Owner>() }).ToArray();
    19.     }
    20.  
    21.     ComponentType[] m_catTraits;
    22.  
    23.     public ComponentType[] CatTraits {
    24.       get => m_catTraits;
    25.       set => m_catTraits = value.Union(new ComponentType[] { ComponentType.ReadOnly<Cat>(), ComponentType.ReadOnly<Parent>() }).ToArray();
    26.     }
    27.  
    28.     public bool Equals(CatFilter other) {
    29.       return (OwnerTraits?.SequenceEqual(other.OwnerTraits ?? Enumerable.Empty<ComponentType>()) ?? OwnerTraits == other.OwnerTraits) &&
    30.         (CatTraits?.SequenceEqual(other.CatTraits ?? Enumerable.Empty<ComponentType>()) ?? CatTraits == other.CatTraits);
    31.     }
    32.  
    33.     public override int GetHashCode() {
    34.       var ownerTraitsHashcode = OwnerTraits?.Length ?? 0;
    35.       if (OwnerTraits != null)
    36.         for (int i = 0; i < OwnerTraits.Length; ++i)
    37.           ownerTraitsHashcode = unchecked(ownerTraitsHashcode * 17 + OwnerTraits[i].GetHashCode());
    38.  
    39.       var catTraitsHashcode = CatTraits?.Length ?? 0;
    40.       if (CatTraits != null)
    41.         for (int i = 0; i < CatTraits.Length; ++i)
    42.           catTraitsHashcode = unchecked(catTraitsHashcode * 17 + CatTraits[i].GetHashCode());
    43.       return unchecked(ownerTraitsHashcode * 17 + catTraitsHashcode); ;
    44.     }
    45.   }
    46.  
    47.   public struct CatFilterResult : IBufferElementData , IEquatable<CatFilterResult> {
    48.     public Entity Value;
    49.  
    50.     public bool Equals(CatFilterResult other) {
    51.       return Value == other.Value;
    52.     }
    53.  
    54.     public override int GetHashCode() {
    55.       return Value.GetHashCode();
    56.     }
    57.   }
    58.  
    59.   public class FindCatTargetsSystem : JobComponentSystem {
    60.  
    61.     NativeQueue<Entity> m_result;
    62.     EntityQuery m_filterQuery;
    63.     int m_filterOrderVersion;
    64.     List<CatFilter> m_filters = new List<CatFilter>();
    65.  
    66.     protected override void OnCreate() {
    67.       base.OnCreate();
    68.       m_result = new NativeQueue<Entity>(Allocator.Persistent);
    69.       m_filterQuery = GetEntityQuery(typeof(CatFilter));
    70.       // This should unique entity
    71.       // Unfortunately there is no Singleton for Dynamic buffers
    72.       var resultHolder = EntityManager.CreateEntity(typeof(CatFilterResult));
    73. #if UNITY_EDITOR
    74.       EntityManager.SetName(resultHolder, "Cat Filter Result");
    75. #endif
    76.     }
    77.  
    78.     protected override void OnDestroy() {
    79.       base.OnDestroy();
    80.       m_result.Dispose();
    81.     }
    82.  
    83.     [BurstCompile]
    84.     struct ClearNativeQueue<T> : IJob where T : struct {
    85.  
    86.       public NativeQueue<T> Source;
    87.       public void Execute() {
    88.         Source.Clear();
    89.       }
    90.     }
    91.  
    92.     [BurstCompile]
    93.     struct ClearDynamicBuffer<T> : IJobForEach_B<T>
    94.       where T : struct, IBufferElementData {
    95.       public void Execute(DynamicBuffer<T> buffer) {
    96.         buffer.Clear();
    97.       }
    98.     }
    99.  
    100.     [BurstCompile]
    101.     struct FilterJob : IJobForEachWithEntity<Cat, Parent> {
    102.       [DeallocateOnJobCompletion]
    103.       [ReadOnly]
    104.       public NativeArray<Entity> Owners;
    105.       [WriteOnly]
    106.       public NativeQueue<Entity>.ParallelWriter CatsResult;
    107.  
    108.       public void Execute(Entity entity, int index, [ReadOnly]ref Cat _, [ReadOnly]ref Parent parent) {
    109.         if (Owners.Contains(parent.Value))
    110.           CatsResult.Enqueue(entity);
    111.       }
    112.     }
    113.  
    114.     [BurstCompile]
    115.     struct CopyFilterResults : IJobForEach_B<CatFilterResult> {
    116.       [NativeDisableParallelForRestriction]
    117.       public NativeQueue<Entity> Entities;
    118.  
    119.       public void Execute(DynamicBuffer<CatFilterResult> result) {
    120.         while (Entities.TryDequeue(out var entity)) {
    121.           var item = new CatFilterResult { Value = entity };
    122.           // Avoid duplicates
    123.           if (!result.AsNativeArray().Contains(item))
    124.             result.Add(item);
    125.         }
    126.       }
    127.     }
    128.  
    129.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    130.       // Cache the filter order version so we only query when there is a change
    131.       if (m_filterQuery.GetCombinedComponentOrderVersion() == m_filterOrderVersion)
    132.         return inputDeps;    
    133.      
    134.       EntityManager.GetAllUniqueSharedComponentData(m_filters);
    135.       EntityManager.DestroyEntity(m_filterQuery);
    136.       m_filterOrderVersion = m_filterQuery.GetCombinedComponentOrderVersion();
    137.  
    138.       inputDeps = new ClearDynamicBuffer<CatFilterResult>()
    139.         .Schedule(this, inputDeps);
    140.  
    141.       var resultParallelWriter = m_result.AsParallelWriter();
    142.  
    143.       foreach (var filter in m_filters) {
    144.         if (filter.Equals(default))
    145.           continue;
    146.         var catsQuery = GetEntityQuery(filter.CatTraits);
    147.         var matchingOwners = GetEntityQuery(filter.OwnerTraits)
    148.           .ToEntityArray(Allocator.TempJob);
    149.  
    150.         // When multiple filtering at once combine filtering to run them all in parallel
    151.         inputDeps = JobHandle.CombineDependencies(
    152.           inputDeps,
    153.           new FilterJob {
    154.             Owners = matchingOwners,
    155.             CatsResult = resultParallelWriter,
    156.           }.Schedule(catsQuery, inputDeps));
    157.       }
    158.  
    159.       m_filters.Clear();
    160.       inputDeps = new CopyFilterResults {
    161.         Entities = m_result
    162.       }.Schedule(this, inputDeps);
    163.  
    164.       inputDeps = new ClearNativeQueue<Entity> {
    165.         Source = m_result
    166.       }.Schedule(inputDeps);
    167.  
    168.       return inputDeps;
    169.     }
    170.   }
    171. }
    172.  
    @PublicEnumE Hope you have learnt some good stuff on this thread :)
     
  15. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Indeed, I've learned so much from this thread already! Thank you @tertle and @GilCat, so much for pouring your time into it.

    I have in mind a slightly different solution from what the two of you wrote that I'd like to post later tonight (unfortunately I can't code during the day). It's most similar to @tertle's, but uses EntityArchetypes to test for component types, instead of enums.

    If you have a chance when that's up, please take a look at it. If you find code small, please let me know. Cheers!
     
  16. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    Yeah, I think there is merit in keeping those attributes as IComponentData, especially if you want to query them later. For the FilterCommand, you can utilize the TypeIndex (from TypeManager). For example:

    Code (CSharp):
    1. public struct CatFilterCommand : IComponentData { }
    2.  
    3. public struct CatFilterAttribute : IBufferElementData {
    4.     public static implicit operator int(CatFilterAttribute e) { return e.typeIndex; }
    5.     public static implicit operator CatFilterAttribute(int e) { return new CatFilterAttribute { typeIndex = (byte)e }; }
    6.  
    7.     public byte typeIndex;
    8. }
    9.  
    10. public struct OwnerFilterAttribute : IBufferElementData {
    11.     public static implicit operator int(OwnerFilterAttribute e) { return e.typeIndex; }
    12.     public static implicit operator OwnerFilterAttribute(int e) { return new OwnerFilterAttribute { typeIndex = (byte)e }; }
    13.  
    14.     public byte typeIndex;
    15. }
    For the Job, you'll then need 2 chunk arrays for Cat and Owner and use chunk.Archetype.GetComponentTypes() for the filtering.

    To be as performant as tertle's enum implementation, you can have a DynamicBuffer on each Cat and Owner to keep track of all the attributes' TypeIndexes. Then the filtering Job become the same as tertle's except you are using DB's instead of enums.
     
  17. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Thanks, @Creepgin!

    Using the ComponentType type index is an option. Though with respect to both you and @tertle, the main reason I don't want to use them (or enums) is the regular maintenance they would require. If a new Component is ever added to a Cat or Owner, I would need to follow up with one or more systems to update the CatFilterAttribute buffer. Same for Owner components. Keeping them in sync adds work, complexity, and could possibly impact performance.

    In the case of enums, I would also need to make an enum for each Component type, which may be beyond 32 or 64 values.

    My thinking is: There's already a structure in Unity ECS which records which ComponentTypes an entity has: EntityArchetype. As soon as I get home, I'll post what I have. Please see what you think - I believe the code I wrote this morning works, but I don't know enough about Unity ECS to guess about its performance at a large scale.
     
  18. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Ok, here's my current approach. If you have time, please take a look and tell me what you think:

    Notable features:
    • Uses DynamicBuffers to hold the requirements for both Cats and Owners (since there can be a variable number of each, for each command).
    • Collects EntityArchetypes for each Cat and Owner. Passes NativeArrays of those into the Job. Tests those Archetypes for the required components.

    Code (CSharp):
    1. namespace CatGameRuntime
    2. {
    3.     // Search Command Components
    4.     public struct CatTarget : IBufferElementData
    5.     {
    6.         public Entity Target;
    7.     }
    8.  
    9.     public struct CatComponentRequirement : IBufferElementData
    10.     {
    11.         public ComponentType componentType;
    12.     }
    13.  
    14.     public struct OwnerComponentRequirement : IBufferElementData
    15.     {
    16.         public ComponentType componentType;
    17.     }
    18.  
    19.     // Cat & Owner Components
    20.     public struct Cat : IComponentData
    21.     {
    22.     }
    23.  
    24.     public struct Owner : IComponentData
    25.     {
    26.     }
    27.  
    28.     // Trait Components
    29.     public struct Kind : IComponentData
    30.     {
    31.     }
    32.  
    33.     public struct Nasty : IComponentData
    34.     {
    35.     }
    36.  
    37.     public struct Curious : IComponentData
    38.     {
    39.     }
    40.  
    41.     public struct Bored : IComponentData
    42.     {
    43.     }
    44.  
    45.     // Filtering System
    46.     public class FindCatTargetsSystem : JobComponentSystem
    47.     {
    48.         private EntityQuery catQuery;
    49.         private EntityQuery commandQuery;
    50.  
    51.         protected override void OnCreate()
    52.         {
    53.             this.commandQuery = this.GetEntityQuery(
    54.                 ComponentType.ReadOnly<CatComponentRequirement>(),
    55.                 ComponentType.ReadOnly<OwnerComponentRequirement>(),
    56.                 ComponentType.ReadWrite<CatTarget>());
    57.  
    58.             this.catQuery = this.GetEntityQuery(ComponentType.ReadOnly<Cat>(), ComponentType.ReadOnly<Parent>());
    59.  
    60.             this.RequireForUpdate(this.commandQuery);
    61.             this.RequireForUpdate(this.catQuery);
    62.         }
    63.  
    64.         protected override JobHandle OnUpdate(JobHandle handle)
    65.         {
    66.             NativeArray<Entity> cats = catQuery.ToEntityArray(Allocator.TempJob);
    67.             NativeArray<Parent> catParents = catQuery.ToComponentDataArray<Parent>(Allocator.TempJob);
    68.             NativeArray<EntityArchetype> catArchetypes = new NativeArray<EntityArchetype>(cats.Length, Allocator.TempJob);
    69.             NativeArray<EntityArchetype> ownerArchetypes = new NativeArray<EntityArchetype>(cats.Length, Allocator.TempJob);
    70.  
    71.             for (int i = 0; i < cats.Length; i++)
    72.             {
    73.                 Entity cat = cats[i];
    74.                 ArchetypeChunk catArchetypChunk = EntityManager.GetChunk(cat);
    75.                 catArchetypes[i] = catArchetypChunk.Archetype;
    76.  
    77.                 Parent catParent = catParents[i];
    78.                 ArchetypeChunk ownerArchetypeChunk = EntityManager.GetChunk(catParent.Value);
    79.                 ownerArchetypes[i] = ownerArchetypeChunk.Archetype;
    80.             }
    81.  
    82.             catParents.Dispose();
    83.  
    84.             return new FindCatTargetsJob
    85.             {
    86.                 cats = cats,
    87.                 catArchetypes = catArchetypes,
    88.                 ownerArchetypes = ownerArchetypes
    89.             }
    90.             .Schedule(this.commandQuery, handle);
    91.         }
    92.  
    93.         [BurstCompile]
    94.         private struct FindCatTargetsJob : IJobForEach_BBB<CatTarget, CatComponentRequirement, OwnerComponentRequirement>
    95.         {
    96.             [DeallocateOnJobCompletion]
    97.             [ReadOnly]
    98.             public NativeArray<Entity> cats;
    99.  
    100.             [DeallocateOnJobCompletion]
    101.             [ReadOnly]
    102.             public NativeArray<EntityArchetype> catArchetypes;
    103.  
    104.             [DeallocateOnJobCompletion]
    105.             [ReadOnly]
    106.             public NativeArray<EntityArchetype> ownerArchetypes;
    107.  
    108.             public void Execute(DynamicBuffer<CatTarget> catTargets, [ReadOnly] DynamicBuffer<CatComponentRequirement> catComponentRequirements, [ReadOnly] DynamicBuffer<OwnerComponentRequirement> ownerComponentRequirements)
    109.             {
    110.                 for (int i = 0; i < cats.Length; i++)
    111.                 {
    112.                     // test for required Cat components
    113.                     EntityArchetype catArchetype = catArchetypes[i];
    114.                     NativeArray<ComponentType> catComponentTypes = catArchetype.GetComponentTypes();
    115.  
    116.                     bool catRequirementsMet = true;
    117.                     for(int iRequirement = 0; iRequirement < catComponentRequirements.Length; iRequirement++)
    118.                     {
    119.                         CatComponentRequirement catComponentRequirement = catComponentRequirements[iRequirement];
    120.  
    121.                         if (!catComponentTypes.Contains(catComponentRequirement.componentType))
    122.                         {
    123.                             catRequirementsMet = false;
    124.                             break;
    125.                         }
    126.                     }
    127.  
    128.                     if(!catRequirementsMet)
    129.                     {
    130.                         continue;
    131.                     }
    132.        
    133.                     // test for required Owner components
    134.                     EntityArchetype ownerArchetype = ownerArchetypes[i];
    135.                     NativeArray<ComponentType> ownerComponentTypes = ownerArchetype.GetComponentTypes();
    136.  
    137.                     bool ownerRequirementsMet = true;
    138.                     for (int iRequirement = 0; iRequirement < ownerComponentRequirements.Length; iRequirement++)
    139.                     {
    140.                         OwnerComponentRequirement ownerComponentRequirement = ownerComponentRequirements[iRequirement];
    141.  
    142.                         if (!ownerComponentTypes.Contains(ownerComponentRequirement.componentType))
    143.                         {
    144.                             ownerRequirementsMet = false;
    145.                         }
    146.                     }
    147.  
    148.                     if (!ownerRequirementsMet)
    149.                     {
    150.                         continue;
    151.                     }
    152.  
    153.                     // tests passed. Add Cat to catTargets buffer
    154.                     Entity cat = cats[i];
    155.  
    156.                     CatTarget catTarget = new CatTarget
    157.                     {
    158.                         Target = cats[i]
    159.                     };
    160.  
    161.                     catTargets.Add(catTarget);
    162.                 }
    163.             }
    164.         }
    165.     }
    166. }
    This works (tested). But it doesn't scale well at all. Currently, each filter command is iterating over the data of every single cat, no matter what.

    With a test of 10,000 cats, and 100 search commands, I'm averaging 10fps.

    The enum suggestion also has this scaling problem. As a performance junkie, what do you say, @tertle?

    Thank you guy so much for your time and advice.
     
    Last edited: Aug 14, 2019
  19. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    This is a really cool technique for doing dynamic runtime chunk filtering without GC. Thanks for sharing this clever approach!

    An optimization which will eliminate most if not all of your performance problems is instead of catQuery.ToEntityArray, use catQuery.CreateArchetypeChunkArray. Then you can do your componentType filtering per chunk rather than per cat entity.
     
    PublicEnumE likes this.
  20. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Brilliant suggestion @DreamingImLatios, thank you!

    Agh. Wait, I'm not sure it will work in this case, since not all Cats in a chunk will have the same Owner. At some level, I'll still need to iterate over each individual Cat to see if it's Owner has the required Components. :(

    Is my assumption correct? Tell me I'm missing something. :)

    P.S. ...I suppose I could use the chunk to filter out which entities I eventually iterate over...
     
    Last edited: Aug 14, 2019
  21. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Unfortunately I have no internet today so can't load this and have a look just yet but it does appear to be a lot nicer than what you had.

    I love that you're taking the varying advice and building a system that fits your projects requirements. This is important as you're really the only one who intimately knows the retirements and other features you'll need to support. You'll always build something that works better (great advise for anyone on here.)

    As for performance, as I'm not able to bench it yet my gut says that this

    Code (CSharp):
    1. for (int i = 0; i < cats.Length; i++)
    2.             {
    3.                 Entity cat = cats[i];
    4.                 ArchetypeChunk catArchetypChunk = EntityManager.GetChunk(cat);
    5.                 catArchetypes[i] = catArchetypChunk.Archetype;
    6.                 Parent catParent = catParents[i];
    7.                 ArchetypeChunk ownerArchetypeChunk = EntityManager.GetChunk(catParent.Value);
    8.                 ownerArchetypes[i] = ownerArchetypeChunk.Archetype;
    9.             }
    Is probably a bit of a bottleneck. It'd be nice to figure out a way to move that to a job and / or cache it.

    Another thing I might suggest, again without looking too closely as in on my phone, is chunk instead of array iteration in the job and figuring out an early out.

    And as contrary as this is for me to say, you might be able to achieve ever better optimizations with early outs using some mild grouping with scd, but I'd need to properly look at code to suggest anything (I dislike scd for solving problems in general, instead I think it should be used for optimizations)
     
    Last edited: Aug 14, 2019
    PublicEnumE likes this.
  22. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    Ok, I've implemented @DreamingImLatios 's advice, and the performance has improved exponentially. With 10,000 cats and 1000 search commands, I'm not averaging 80 fps.

    Code (CSharp):
    1. namespace CatGameRuntime
    2. {
    3.     // Filter Command Components
    4.     public struct CatTarget : IBufferElementData
    5.     {
    6.         public Entity Target;
    7.     }
    8.  
    9.     public struct CatComponentRequirement : IBufferElementData
    10.     {
    11.         public ComponentType componentType;
    12.     }
    13.  
    14.     public struct OwnerComponentRequirement : IBufferElementData
    15.     {
    16.         public ComponentType componentType;
    17.     }
    18.  
    19.     // Cat & Owner Components
    20.     public struct Cat : IComponentData
    21.     {
    22.     }
    23.  
    24.     public struct Owner : IComponentData
    25.     {
    26.     }
    27.  
    28.     // Trait Components
    29.     public struct Kind : IComponentData
    30.     {
    31.     }
    32.  
    33.     public struct Nasty : IComponentData
    34.     {
    35.     }
    36.  
    37.     public struct Curious : IComponentData
    38.     {
    39.     }
    40.  
    41.     public struct Bored : IComponentData
    42.     {
    43.     }
    44.  
    45.     // Filtering System
    46.     public class FindCatTargetsSystem : JobComponentSystem
    47.     {
    48.         private EntityQuery catQuery;
    49.         private EntityQuery commandQuery;
    50.  
    51.         protected override void OnCreate()
    52.         {
    53.             this.commandQuery = this.GetEntityQuery(
    54.                 ComponentType.ReadOnly<CatComponentRequirement>(),
    55.                 ComponentType.ReadOnly<OwnerComponentRequirement>(),
    56.                 ComponentType.ReadWrite<CatTarget>());
    57.  
    58.             this.catQuery = this.GetEntityQuery(ComponentType.ReadOnly<Cat>(), ComponentType.ReadOnly<Parent>());
    59.  
    60.             this.RequireForUpdate(this.commandQuery);
    61.             this.RequireForUpdate(this.catQuery);
    62.         }
    63.  
    64.         protected override JobHandle OnUpdate(JobHandle handle)
    65.         {
    66.             ArchetypeChunkEntityType entityType = GetArchetypeChunkEntityType();
    67.             ArchetypeChunkComponentType<Parent> parentType = GetArchetypeChunkComponentType<Parent>();
    68.             NativeArray<ArchetypeChunk> catChunks = catQuery.CreateArchetypeChunkArray(Allocator.TempJob);
    69.             NativeArray<Parent> catParents = catQuery.ToComponentDataArray<Parent>(Allocator.TempJob);
    70.  
    71.             NativeHashMap<Entity, EntityArchetype> archetypesByOwner = new NativeHashMap<Entity, EntityArchetype>(catParents.Length, Allocator.TempJob);
    72.  
    73.             for (int i = 0; i < catParents.Length; i++)
    74.             {
    75.                 Parent catParent = catParents[i];
    76.                 ArchetypeChunk ownerArchetypeChunk = EntityManager.GetChunk(catParent.Value);
    77.  
    78.                 archetypesByOwner[catParent.Value] = ownerArchetypeChunk.Archetype;
    79.             }
    80.  
    81.             catParents.Dispose();
    82.  
    83.             return new FindCatTargetsJob
    84.             {
    85.                 entityType = entityType,
    86.                 parentType = parentType,
    87.                 catChunks = catChunks,
    88.                 archetypesByOwner = archetypesByOwner
    89.             }
    90.             .Schedule(commandQuery, handle);
    91.         }
    92.  
    93.         [BurstCompile]
    94.         private struct FindCatTargetsJob : IJobForEach_BBB<CatTarget, CatComponentRequirement, OwnerComponentRequirement>
    95.         {
    96.             [ReadOnly]
    97.             public ArchetypeChunkEntityType entityType;
    98.  
    99.             [ReadOnly]
    100.             public ArchetypeChunkComponentType<Parent> parentType;
    101.  
    102.             [DeallocateOnJobCompletion]
    103.             [ReadOnly]
    104.             public NativeArray<ArchetypeChunk> catChunks;
    105.  
    106.             [ReadOnly]
    107.             public NativeHashMap<Entity, EntityArchetype> archetypesByOwner;
    108.  
    109.             public void Execute(DynamicBuffer<CatTarget> catTargets, [ReadOnly] DynamicBuffer<CatComponentRequirement> catComponentRequirements, [ReadOnly] DynamicBuffer<OwnerComponentRequirement> ownerComponentRequirements)
    110.             {
    111.                 for (int i = 0; i < catChunks.Length; i++)
    112.                 {
    113.                     ArchetypeChunk catChunk = catChunks[i];
    114.  
    115.                     // test chunk for required components
    116.                     NativeArray<ComponentType> catComponentTypes = catChunk.Archetype.GetComponentTypes();
    117.  
    118.                     bool catRequirementsMet = true;
    119.                     for(int iRequirement = 0; iRequirement < catComponentRequirements.Length; iRequirement++)
    120.                     {
    121.                         CatComponentRequirement catComponentRequirement = catComponentRequirements[iRequirement];
    122.  
    123.                         if (!catComponentTypes.Contains(catComponentRequirement.componentType))
    124.                         {
    125.                             catRequirementsMet = false;
    126.                             break;
    127.                         }
    128.                     }
    129.  
    130.                     if(!catRequirementsMet)
    131.                     {
    132.                         continue;
    133.                     }
    134.  
    135.                     // get cat entity array
    136.                     NativeArray<Entity> cats = catChunk.GetNativeArray(entityType);
    137.                     NativeArray<Parent> parents = catChunk.GetNativeArray<Parent>(parentType);
    138.  
    139.                     for(int iCat = 0; iCat < cats.Length; iCat++)
    140.                     {
    141.                         // test for required Owner components
    142.                         Parent parent = parents[iCat];
    143.                         EntityArchetype ownerArchetype = archetypesByOwner[parent.Value];
    144.                         NativeArray<ComponentType> ownerComponentTypes = ownerArchetype.GetComponentTypes();
    145.  
    146.                         bool ownerRequirementsMet = true;
    147.                         for (int iRequirement = 0; iRequirement < ownerComponentRequirements.Length; iRequirement++)
    148.                         {
    149.                             OwnerComponentRequirement ownerComponentRequirement = ownerComponentRequirements[iRequirement];
    150.  
    151.                             if (!ownerComponentTypes.Contains(ownerComponentRequirement.componentType))
    152.                             {
    153.                                 ownerRequirementsMet = false;
    154.                             }
    155.                         }
    156.  
    157.                         if (!ownerRequirementsMet)
    158.                         {
    159.                             continue;
    160.                         }
    161.  
    162.                         // tests passed. Add Cat to catTargets buffer
    163.                         CatTarget catTarget = new CatTarget
    164.                         {
    165.                             Target = cats[iCat]
    166.                         };
    167.  
    168.                         catTargets.Add(catTarget);
    169.                     }
    170.                 }
    171.             }
    172.         }
    173.     }
    174. }
    Now there's only one problem, and it's something I don't yet know how to solve: I'm passing a NativeHashMap into my Job. NativeHasMaps don't support the [DeallocateOnJobCompletion] attribute. So right now that allocation is leaking everywhere.

    I get the sense people have been running into this problem for quite some time now. What's the best way to fix it?
     
    Last edited: Aug 15, 2019
  23. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Ooo just got internet back, the joys of a keyboard again.

    Easy solution, don't allocate/deallocate it every frame keep it persistent.

    Code (CSharp):
    1. protected override void OnCreate()
    2. {
    3.     this.archetypesByOwner = new NativeHashMap<Entity, EntityArchetype>(128, Allocator.Persistent);
    4.     // ...
    5. }
    6.  
    7. protected override void OnDestroy()
    8. {
    9.     this.archetypesByOwner.Dispose();
    10.     // ...
    11. }
    12.  
    13. protected override JobHandle OnUpdate(JobHandle handle)
    14. {
    15.     // ...
    16.     this.archetypesByOwner.Clear();
    17.     if (this.archetypesByOwner.Capacity < catParents.Length)
    18.     {
    19.         this.archetypesByOwner.Capacity = catParents.Length
    20.     }
    21.     // ...
    22.  
     
  24. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    That did it. Here's the final code, for reference:

    Code (CSharp):
    1. namespace CatGameRuntime
    2. {
    3.     // Filter Command Components
    4.     public struct CatTarget : IBufferElementData
    5.     {
    6.         public Entity Target;
    7.     }
    8.  
    9.     public struct CatComponentRequirement : IBufferElementData
    10.     {
    11.         public ComponentType componentType;
    12.     }
    13.  
    14.     public struct OwnerComponentRequirement : IBufferElementData
    15.     {
    16.         public ComponentType componentType;
    17.     }
    18.  
    19.     // Cat & Owner Components
    20.     public struct Cat : IComponentData
    21.     {
    22.     }
    23.  
    24.     public struct Owner : IComponentData
    25.     {
    26.     }
    27.  
    28.     // Trait Components
    29.     public struct Kind : IComponentData
    30.     {
    31.     }
    32.  
    33.     public struct Nasty : IComponentData
    34.     {
    35.     }
    36.  
    37.     public struct Curious : IComponentData
    38.     {
    39.     }
    40.  
    41.     public struct Bored : IComponentData
    42.     {
    43.     }
    44.  
    45.     // Filtering System
    46.     public class FindCatTargetsSystem : JobComponentSystem
    47.     {
    48.         private EntityQuery catQuery;
    49.         private EntityQuery commandQuery;
    50.         private NativeHashMap<Entity, EntityArchetype> archetypesByOwner;
    51.  
    52.         protected override void OnCreate()
    53.         {
    54.             this.catQuery = this.GetEntityQuery(ComponentType.ReadOnly<Cat>(), ComponentType.ReadOnly<Parent>());
    55.  
    56.             this.commandQuery = this.GetEntityQuery(
    57.                 ComponentType.ReadOnly<CatComponentRequirement>(),
    58.                 ComponentType.ReadOnly<OwnerComponentRequirement>(),
    59.                 ComponentType.ReadWrite<CatTarget>());
    60.  
    61.             this.RequireForUpdate(this.catQuery);
    62.             this.RequireForUpdate(this.commandQuery);
    63.  
    64.             archetypesByOwner = new NativeHashMap<Entity, EntityArchetype>(128, Allocator.Persistent);
    65.         }
    66.  
    67.         protected override JobHandle OnUpdate(JobHandle handle)
    68.         {
    69.             ArchetypeChunkEntityType entityType = GetArchetypeChunkEntityType();
    70.             ArchetypeChunkComponentType<Parent> parentType = GetArchetypeChunkComponentType<Parent>();
    71.             NativeArray<ArchetypeChunk> catChunks = catQuery.CreateArchetypeChunkArray(Allocator.TempJob);
    72.             NativeArray<Parent> catParents = catQuery.ToComponentDataArray<Parent>(Allocator.TempJob);
    73.  
    74.             archetypesByOwner.Clear();
    75.             if (this.archetypesByOwner.Capacity < catParents.Length)
    76.             {
    77.                 this.archetypesByOwner.Capacity = catParents.Length;
    78.             }
    79.  
    80.             for (int i = 0; i < catParents.Length; i++)
    81.             {
    82.                 Parent catParent = catParents[i];
    83.                 ArchetypeChunk ownerArchetypeChunk = EntityManager.GetChunk(catParent.Value);
    84.  
    85.                 archetypesByOwner[catParent.Value] = ownerArchetypeChunk.Archetype;
    86.             }
    87.  
    88.             catParents.Dispose();
    89.  
    90.             return new FindCatTargetsJob
    91.             {
    92.                 entityType = entityType,
    93.                 parentType = parentType,
    94.                 catChunks = catChunks,
    95.                 archetypesByOwner = archetypesByOwner
    96.             }
    97.             .Schedule(commandQuery, handle);
    98.         }
    99.  
    100.         protected override void OnDestroy()
    101.         {
    102.             archetypesByOwner.Dispose();
    103.         }
    104.  
    105.         [BurstCompile]
    106.         private struct FindCatTargetsJob : IJobForEach_BBB<CatTarget, CatComponentRequirement, OwnerComponentRequirement>
    107.         {
    108.             [ReadOnly]
    109.             public ArchetypeChunkEntityType entityType;
    110.  
    111.             [ReadOnly]
    112.             public ArchetypeChunkComponentType<Parent> parentType;
    113.  
    114.             [DeallocateOnJobCompletion]
    115.             [ReadOnly]
    116.             public NativeArray<ArchetypeChunk> catChunks;
    117.  
    118.             [ReadOnly]
    119.             public NativeHashMap<Entity, EntityArchetype> archetypesByOwner;
    120.  
    121.             public void Execute(DynamicBuffer<CatTarget> catTargets, [ReadOnly] DynamicBuffer<CatComponentRequirement> catComponentRequirements, [ReadOnly] DynamicBuffer<OwnerComponentRequirement> ownerComponentRequirements)
    122.             {
    123.                 for (int i = 0; i < catChunks.Length; i++)
    124.                 {
    125.                     ArchetypeChunk catChunk = catChunks[i];
    126.  
    127.                     // test chunk for required components
    128.                     NativeArray<ComponentType> catComponentTypes = catChunk.Archetype.GetComponentTypes();
    129.  
    130.                     bool catRequirementsMet = true;
    131.                     for(int iRequirement = 0; iRequirement < catComponentRequirements.Length; iRequirement++)
    132.                     {
    133.                         CatComponentRequirement catComponentRequirement = catComponentRequirements[iRequirement];
    134.  
    135.                         if (!catComponentTypes.Contains(catComponentRequirement.componentType))
    136.                         {
    137.                             catRequirementsMet = false;
    138.                             break;
    139.                         }
    140.                     }
    141.  
    142.                     if(!catRequirementsMet)
    143.                     {
    144.                         continue;
    145.                     }
    146.  
    147.                     // get cat entity array
    148.                     NativeArray<Entity> cats = catChunk.GetNativeArray(entityType);
    149.                     NativeArray<Parent> parents = catChunk.GetNativeArray<Parent>(parentType);
    150.  
    151.                     for(int iCat = 0; iCat < cats.Length; iCat++)
    152.                     {
    153.                         // test for required Owner components
    154.                         Parent parent = parents[iCat];
    155.                         EntityArchetype ownerArchetype = archetypesByOwner[parent.Value];
    156.                         NativeArray<ComponentType> ownerComponentTypes = ownerArchetype.GetComponentTypes();
    157.  
    158.                         bool ownerRequirementsMet = true;
    159.                         for (int iRequirement = 0; iRequirement < ownerComponentRequirements.Length; iRequirement++)
    160.                         {
    161.                             OwnerComponentRequirement ownerComponentRequirement = ownerComponentRequirements[iRequirement];
    162.  
    163.                             if (!ownerComponentTypes.Contains(ownerComponentRequirement.componentType))
    164.                             {
    165.                                 ownerRequirementsMet = false;
    166.                             }
    167.                         }
    168.  
    169.                         if (!ownerRequirementsMet)
    170.                         {
    171.                             continue;
    172.                         }
    173.  
    174.                         // tests passed. Add Cat to catTargets buffer
    175.                         CatTarget catTarget = new CatTarget
    176.                         {
    177.                             Target = cats[iCat]
    178.                         };
    179.  
    180.                         catTargets.Add(catTarget);
    181.                     }
    182.                 }
    183.             }
    184.         }
    185.     }
    186. }
    I can't thank you folks enough for your help. This thread has made me a better ecs coder, going forward.

    Plus, I got to use the variable name, "catChunks".

    Have a good night/morning! :)
     
    Last edited: Aug 15, 2019
    esc_marcin likes this.
  25. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,759
    Oh just 1 more thing since I suggested it.

    You can clear your native hash map in a job instead of main thread (as clear can be a bit slow on large hash maps.)
    I have a generic job for this that I use.

    Code (CSharp):
    1. namespace BovineLabs.Common.Jobs
    2. {
    3.     using System;
    4.     using Unity.Burst;
    5.     using Unity.Collections;
    6.     using Unity.Jobs;
    7.  
    8.     /// <summary>
    9.     /// Clear a <see cref="NativeMultiHashMap{TKey,TValue}"/>.
    10.     /// </summary>
    11.     /// <typeparam name="TKey">The key type.</typeparam>
    12.     /// <typeparam name="TValue">The value type.</typeparam>
    13.     [BurstCompile]
    14.     public struct ClearNativeMultiHashMapJob<TKey, TValue> : IJob
    15.         where TKey : struct, IEquatable<TKey>
    16.         where TValue : struct
    17.     {
    18.         /// <summary>
    19.         /// The map to clear.
    20.         /// </summary>
    21.         public NativeMultiHashMap<TKey, TValue> Map;
    22.  
    23.         /// <inheritdoc />
    24.         public void Execute()
    25.         {
    26.             this.Map.Clear();
    27.         }
    28.     }
    29. }
    Even have an extension method for it

    Code (CSharp):
    1.  
    2. namespace BovineLabs.Common.Extensions
    3. {
    4.     using System;
    5.     using BovineLabs.Common.Collections;
    6.     using BovineLabs.Common.Jobs;
    7.     using Unity.Collections;
    8.     using Unity.Collections.LowLevel.Unsafe;
    9.     using Unity.Jobs;
    10.  
    11.     /// <summary>
    12.     /// The NativeMultiHashMap Extensions.
    13.     /// </summary>
    14.     public static class NativeMultiHashMapExtensions
    15.     {
    16.         /// <summary>
    17.         /// Create a clear job for <see cref="NativeMultiHashMap{TKey,TValue}"/>.
    18.         /// </summary>
    19.         /// <param name="hashMap">The hash map.</param>
    20.         /// <param name="handle">The dependency handle.</param>
    21.         /// <typeparam name="TKey">The key type.</typeparam>
    22.         /// <typeparam name="TValue">The value type.</typeparam>
    23.         /// <returns>The handle to the job.</returns>
    24.         public static JobHandle ClearInJob<TKey, TValue>(this NativeMultiHashMap<TKey, TValue> hashMap, JobHandle handle)
    25.             where TKey : struct, IEquatable<TKey>
    26.             where TValue : struct
    27.         {
    28.             return new ClearNativeMultiHashMapJob<TKey, TValue>
    29.                 {
    30.                     Map = hashMap,
    31.                 }
    32.                 .Schedule(handle);
    33.         }
    34.  
    so you can just call

    return this.archetypesByOwner.ClearInJob(findCatTargetsJobHandle);


    at the end of frame (or start with input handle)
     
    esc_marcin likes this.
  26. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I spent some time optimizing this system.

    Since the job checks the archetype of each Cat chunk (and since multiple chunks may have the same archetype), that meant the same archetypes were being tested multiple times. Potentially many times.

    I added caching, to store previously tested archetypes. The code below checks against the cached collections to see if a chunk's archetype has been previously accepted or rejected. That actually did add ~5fps at higher volumes (10,000-100,000 cats).

    Code (CSharp):
    1. namespace CatGameRuntime
    2. {
    3.     // Filter Command Components
    4.     public struct OwnerTarget : IBufferElementData, IEquatable<OwnerTarget>
    5.     {
    6.         public Entity target;
    7.  
    8.         public bool Equals(OwnerTarget other)
    9.         {
    10.             return other.target == this.target;
    11.         }
    12.  
    13.         public override int GetHashCode()
    14.         {
    15.             return base.GetHashCode();
    16.         }
    17.     }
    18.  
    19.     public struct CatTarget : IBufferElementData
    20.     {
    21.         public Entity target;
    22.     }
    23.  
    24.     public struct CatComponentRequirement : IBufferElementData
    25.     {
    26.         public ComponentType componentType;
    27.     }
    28.  
    29.     public struct OwnerComponentRequirement : IBufferElementData
    30.     {
    31.         public ComponentType componentType;
    32.     }
    33.  
    34.     // Cat & Owner Components
    35.     public struct Cat : IComponentData
    36.     {
    37.     }
    38.  
    39.     public struct Owner : IComponentData
    40.     {
    41.     }
    42.  
    43.     // Trait Components
    44.     public struct Kind : IComponentData
    45.     {
    46.     }
    47.  
    48.     public struct Nasty : IComponentData
    49.     {
    50.     }
    51.  
    52.     public struct Curious : IComponentData
    53.     {
    54.     }
    55.  
    56.     public struct Bored : IComponentData
    57.     {
    58.     }
    59.  
    60.     // Filtering System
    61.     public class FindCatTargetsSystem : JobComponentSystem
    62.     {
    63.         private EntityQuery catQuery;
    64.         private EntityQuery commandQuery;
    65.         private NativeHashMap<Entity, EntityArchetype> archetypesByOwner;
    66.  
    67.         protected override void OnCreate()
    68.         {
    69.             this.catQuery = this.GetEntityQuery(ComponentType.ReadOnly<Cat>(), ComponentType.ReadOnly<Parent>());
    70.  
    71.             this.commandQuery = this.GetEntityQuery(
    72.                 ComponentType.ReadOnly<CatComponentRequirement>(),
    73.                 ComponentType.ReadOnly<OwnerComponentRequirement>(),
    74.                 ComponentType.ReadOnly<OwnerTarget>(),
    75.                 ComponentType.ReadWrite<CatTarget>());
    76.  
    77.             this.RequireForUpdate(this.catQuery);
    78.             this.RequireForUpdate(this.commandQuery);
    79.  
    80.             archetypesByOwner = new NativeHashMap<Entity, EntityArchetype>(128, Allocator.Persistent);
    81.         }
    82.  
    83.         protected override JobHandle OnUpdate(JobHandle handle)
    84.         {
    85.             ArchetypeChunkEntityType entityType = GetArchetypeChunkEntityType();
    86.             ArchetypeChunkComponentType<Parent> parentType = GetArchetypeChunkComponentType<Parent>();
    87.             NativeArray<ArchetypeChunk> catChunks = catQuery.CreateArchetypeChunkArray(Allocator.TempJob);
    88.             NativeArray<Parent> catParents = catQuery.ToComponentDataArray<Parent>(Allocator.TempJob);
    89.  
    90.             archetypesByOwner.Clear();
    91.             if (this.archetypesByOwner.Capacity < catParents.Length)
    92.             {
    93.                 this.archetypesByOwner.Capacity = catParents.Length;
    94.             }
    95.  
    96.             for (int i = 0; i < catParents.Length; i++)
    97.             {
    98.                 Parent catParent = catParents[i];
    99.                 ArchetypeChunk ownerArchetypeChunk = EntityManager.GetChunk(catParent.Value);
    100.  
    101.                 archetypesByOwner[catParent.Value] = ownerArchetypeChunk.Archetype;
    102.             }
    103.  
    104.             catParents.Dispose();
    105.  
    106.             return new FindCatTargetsJob
    107.             {
    108.                 entityType = entityType,
    109.                 parentType = parentType,
    110.                 catChunks = catChunks,
    111.                 archetypesByOwner = archetypesByOwner
    112.             }
    113.             .Schedule(commandQuery, handle);
    114.         }
    115.  
    116.         protected override void OnDestroy()
    117.         {
    118.             archetypesByOwner.Dispose();
    119.         }
    120.  
    121.         [BurstCompile]
    122.         private struct FindCatTargetsJob : IJobForEach_BBBB<CatTarget, OwnerTarget, CatComponentRequirement, OwnerComponentRequirement>
    123.         {
    124.             [ReadOnly]
    125.             public ArchetypeChunkEntityType entityType;
    126.  
    127.             [ReadOnly]
    128.             public ArchetypeChunkComponentType<Parent> parentType;
    129.  
    130.             [DeallocateOnJobCompletion]
    131.             [ReadOnly]
    132.             public NativeArray<ArchetypeChunk> catChunks;
    133.  
    134.             [ReadOnly]
    135.             public NativeHashMap<Entity, EntityArchetype> archetypesByOwner;
    136.  
    137.             public void Execute(DynamicBuffer<CatTarget> catTargets, [ReadOnly] DynamicBuffer<OwnerTarget> ownerTargets, [ReadOnly] DynamicBuffer<CatComponentRequirement> catComponentRequirements, [ReadOnly] DynamicBuffer<OwnerComponentRequirement> ownerComponentRequirements)
    138.             {
    139.                 NativeList<EntityArchetype> approvedCatArchetypes = new NativeList<EntityArchetype>(catChunks.Length, Allocator.Temp);
    140.                 NativeList<EntityArchetype> rejectedCatArchetypes = new NativeList<EntityArchetype>(catChunks.Length, Allocator.Temp);
    141.                 NativeList<EntityArchetype> approvedOwnerArchetypes = new NativeList<EntityArchetype>(archetypesByOwner.Length, Allocator.Temp);
    142.                 NativeList<EntityArchetype> rejectedOwnerArchetypes = new NativeList<EntityArchetype>(archetypesByOwner.Length, Allocator.Temp);
    143.  
    144.                 for (int i = 0; i < catChunks.Length; i++)
    145.                 {
    146.                     ArchetypeChunk catChunk = catChunks[i];
    147.  
    148.                     // test for existing archetype rejection
    149.                     if(rejectedCatArchetypes.Contains(catChunk.Archetype))
    150.                     {
    151.                         continue;
    152.                     }
    153.                     // test for existing archetype approval
    154.                     else if (!approvedCatArchetypes.Contains(catChunk.Archetype))
    155.                     {
    156.                         // test chunk for required components
    157.                         NativeArray<ComponentType> catComponentTypes = catChunk.Archetype.GetComponentTypes();
    158.  
    159.                         bool catRequirementsMet = true;
    160.                         for (int iRequirement = 0; iRequirement < catComponentRequirements.Length; iRequirement++)
    161.                         {
    162.                             CatComponentRequirement catComponentRequirement = catComponentRequirements[iRequirement];
    163.  
    164.                             if (!catComponentTypes.Contains(catComponentRequirement.componentType))
    165.                             {
    166.                                 catRequirementsMet = false;
    167.                                 break;
    168.                             }
    169.                         }
    170.  
    171.                         if (catRequirementsMet)
    172.                         {
    173.                             approvedCatArchetypes.Add(catChunk.Archetype);
    174.                         }
    175.                         else
    176.                         {
    177.                             rejectedCatArchetypes.Add(catChunk.Archetype);
    178.                             continue;
    179.                         }
    180.                     }
    181.                
    182.                     // get cat entity array
    183.                     NativeArray<Entity> cats = catChunk.GetNativeArray(entityType);
    184.                     NativeArray<Parent> parents = catChunk.GetNativeArray<Parent>(parentType);
    185.                     NativeArray<OwnerTarget> OwnerTargetArray = ownerTargets.AsNativeArray();
    186.  
    187.                     for (int iCat = 0; iCat < cats.Length; iCat++)
    188.                     {
    189.                         Parent parent = parents[iCat];
    190.  
    191.                         if (ownerTargets.Length > 0)
    192.                         {
    193.                             // test for owner membership
    194.                             OwnerTarget ownerTarget = new OwnerTarget
    195.                             {
    196.                                 target = parent.Value
    197.                             };
    198.  
    199.                             if(!OwnerTargetArray.Contains(ownerTarget))
    200.                             {
    201.                                 continue;
    202.                             }
    203.                         }
    204.                         else
    205.                         {
    206.                             // find owner archetypes
    207.                             EntityArchetype ownerArchetype = archetypesByOwner[parent.Value];
    208.  
    209.                             // test for existing archetype rejection
    210.                             if(rejectedOwnerArchetypes.Contains(ownerArchetype))
    211.                             {
    212.                                 continue;
    213.                             }
    214.                             // test for existing archetype approval
    215.                             else if (!approvedOwnerArchetypes.Contains(ownerArchetype))
    216.                             {
    217.                                 // test for required Owner components
    218.                                 NativeArray<ComponentType> ownerComponentTypes = ownerArchetype.GetComponentTypes();
    219.  
    220.                                 bool ownerRequirementsMet = true;
    221.                                 for (int iRequirement = 0; iRequirement < ownerComponentRequirements.Length; iRequirement++)
    222.                                 {
    223.                                     OwnerComponentRequirement ownerComponentRequirement = ownerComponentRequirements[iRequirement];
    224.  
    225.                                     if (!ownerComponentTypes.Contains(ownerComponentRequirement.componentType))
    226.                                     {
    227.                                         ownerRequirementsMet = false;
    228.                                     }
    229.                                 }
    230.  
    231.                                 if(ownerRequirementsMet)
    232.                                 {
    233.                                     approvedOwnerArchetypes.Add(ownerArchetype);
    234.                                 }
    235.                                 else
    236.                                 {
    237.                                     rejectedOwnerArchetypes.Add(ownerArchetype);
    238.                                     continue;
    239.                                 }
    240.                             }
    241.                         }
    242.  
    243.                         // tests passed. Add Cat to catTargets buffer
    244.                         CatTarget catTarget = new CatTarget
    245.                         {
    246.                             target = cats[iCat]
    247.                         };
    248.  
    249.                         catTargets.Add(catTarget);
    250.                     }
    251.                 }
    252.             }
    253.         }
    254.     }
    255. }
    It would be great if I could get a list of all archetypes covered by an EntityQuery, rather than just chunks. But so far I haven't found a way to do that.

    If you know of a way, please do tell.
     
    Last edited: Aug 17, 2019
  27. Singtaa

    Singtaa

    Joined:
    Dec 14, 2010
    Posts:
    492
    If only they had a
    ArchetypeFromEntity
    API... I don't know about the performance implications of that, but it would certainly greatly reduce the boilerplate.
     
    PublicEnumE likes this.