Search Unity

Is this the best way to have a job have access to all the data in other entities

Discussion in 'Entity Component System' started by greyhoundgames, Feb 25, 2019.

  1. greyhoundgames

    greyhoundgames

    Joined:
    Jan 24, 2014
    Posts:
    32
    I want to have a job which can scan for things. The data it uses to scan can be out of date(example the target was destroyed the same frame). Based on scanning the web for examples this is the best I have been able to put together. It however feels a bit wasteful as I am copying a ton of data every frame. Is there a better way to do this?

    Basic logic is:
    1) Create a system that schedules a job each frame
    2) Gather all the values we need from other entities and store them in the job
    3) Run the job

    Code (CSharp):
    1. public class UnitSearchSystem : JobComponentSystem
    2. {
    3.   [BurstCompile]
    4.   struct UnitSearchJob : IJobProcessComponentData<Position, UnitSearch>
    5.   {
    6.     [ReadOnly] public float dT;
    7.     [ReadOnly] public Position[] targetPositions;
    8.     [ReadOnly] public UnitIdentity[] targetIdentities;
    9.  
    10.     public void Execute(ref Position pos, [ReadOnly] ref UnitSearch search)
    11.     {
    12.       //do stuff that can see the component datats in the array
    13.     }
    14.   }
    15.  
    16.   private ComponentGroup targetGroup;
    17.  
    18.   protected override void OnCreateManager()
    19.   {
    20.     targetGroup = GetComponentGroup(typeof(Position), typeof(UnitIdentity));
    21.   }
    22.  
    23.   protected override JobHandle OnUpdate(JobHandle inputDeps)
    24.   {
    25.     NativeArray<Entity> tArray = targetGroup.ToEntityArray(Allocator.TempJob);
    26.     Position[] positions = null ;
    27.     UnitIdentity[] identities = null;
    28.     using (NativeArray<Entity> targetEntities = targetGroup.ToEntityArray(Allocator.TempJob))
    29.     {
    30.       positions = new Position[targetEntities.Length];
    31.       identities = new UnitIdentity[targetEntities.Length];
    32.       for (int i = 0; i < targetEntities.Length; i++)
    33.       {
    34.         positions[i] = EntityManager.GetComponentData<Position>(targetEntities[i]);
    35.         identities[i] = EntityManager.GetComponentData<UnitIdentity>(targetEntities[i]);
    36.       }
    37.     }
    38.  
    39.     UnitSearchJob job = new UnitSearchJob()
    40.     {
    41.       dT = Time.deltaTime,
    42.       targetPositions = positions,
    43.       targetIdentities = identities
    44.     };
    45.     return job.Schedule(this, inputDeps);
    46.   }
    47. }
    A follow up question is, say i find a target. Whats the correct way to remember it to quickly look up information about my target while inside a job?
     
    Last edited: Feb 25, 2019
  2. NoDumbQuestion

    NoDumbQuestion

    Joined:
    Nov 10, 2017
    Posts:
    186
    You should change your job to like this
    Code (CSharp):
    1.    
    2.     [BurstCompile]
    3.     struct UnitSearchJob : IJobProcessComponentData<Position, UnitSearch>
    4.     {
    5.         [ReadOnly] public float          dT;
    6.         [DeallocateOnJobCompletion]
    7.         [ReadOnly] public NativeArray<Position>     targetPositions;
    8.         [DeallocateOnJobCompletion]
    9.         [ReadOnly] public NativeArray<UnitIdentity> targetIdentities;
    10.         public void Execute(ref Position pos, [ReadOnly] ref UnitSearch search)
    11.         {
    12.             //do stuff that can see the component datats in the array
    13.         }
    14.     }
    So you dont have to copy new array to job everytime. And it only have pointer to array memory location.
     
  3. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    What is the UnitSearch component and more accurately, what are you supposed to be doing in your job? If UnitSearch contains an entity reference and you want to do something with the components on that entity you can use ComponentDataFromEntity which is like a hashmap<Entity, Component>:
    Code (CSharp):
    1. public class UnitSearchSystem : JobComponentSystem {
    2.     [BurstCompile]
    3.     struct UnitSearchJob : IJobProcessComponentData<Position, UnitSearch> {
    4.         [ReadOnly] public float dT;
    5.         [ReadOnly] public ComponentDataFromEntity<Position> targetPositions;
    6.         [ReadOnly] public ComponentDataFromEntity<UnitIdentity> targetIdentities;
    7.  
    8.         public void Execute(ref Position pos, [ReadOnly] ref UnitSearch search) {
    9.             //do stuff that can see the component datats in the array
    10.         }
    11.     }
    12.  
    13.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    14.         UnitSearchJob job = new UnitSearchJob() {
    15.             dT = Time.deltaTime,
    16.             targetPositions = GetComponentDataFromEntity<Position>(),
    17.             targetIdentities = GetComponentDataFromEntity<UnitIdentity>()
    18.         };
    19.         return job.Schedule(this, inputDeps);
    20.     }
    21. }
    However, if you need to iterate over all components of targetGroup, you can use the new ToComponentDataArray():
    Code (CSharp):
    1. public class UnitSearchSystem : JobComponentSystem {
    2.     private ComponentGroup targetGroup;
    3.  
    4.     protected override void OnCreateManager() {
    5.         targetGroup = GetComponentGroup(typeof(Position), typeof(UnitIdentity));
    6.     }
    7.  
    8.     [BurstCompile]
    9.     struct UnitSearchJob : IJobProcessComponentData<Position, UnitSearch> {
    10.         [ReadOnly] public float dT;
    11.         [ReadOnly, DeallocateOnJobCompletion]
    12.         public NativeArray<Position> targetPositions;
    13.         [ReadOnly, DeallocateOnJobCompletion]
    14.         public NativeArray<UnitIdentity> targetIdentities;
    15.  
    16.         public void Execute(ref Position pos, [ReadOnly] ref UnitSearch search) {
    17.             //do stuff that can see the component datats in the array
    18.         }
    19.     }
    20.  
    21.     protected override JobHandle OnUpdate(JobHandle inputDeps) {
    22.         UnitSearchJob job = new UnitSearchJob {
    23.             dT = Time.deltaTime,
    24.             targetPositions = targetGroup.ToComponentDataArray<Position>(Allocator.TempJob),
    25.             targetIdentities = targetGroup.ToComponentDataArray<UnitIdentity>(Allocator.TempJob)
    26.         };
    27.         return job.Schedule(this, inputDeps);
    28.     }
    29. }
    I'd like to point out that:
    Code (CSharp):
    1. [ReadOnly] public Position[] targetPositions;
    2. [ReadOnly] public UnitIdentity[] targetIdentities;
    Is not valid, C# arrays are reference types, which are not allowed on job structs.
     
  4. greyhoundgames

    greyhoundgames

    Joined:
    Jan 24, 2014
    Posts:
    32
    <Deleted, the next guys answer answered my reply question>
     
    Last edited: Feb 25, 2019
  5. greyhoundgames

    greyhoundgames

    Joined:
    Jan 24, 2014
    Posts:
    32
    It looks like the 2nd one is what I need, all searches able to scan all things with an identity.

    The first thing you posted, the psudo hash table to look up entity data, is it safe to retain enties in your structs so you can look them up late, like myCurrentTarget entity then use what you showed in your first example to pull data out about that guy?
     
  6. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    ComponentDataFromEntity has an .Exists(Entity) method. If the entity you look up does not exist(like a default/unassigned entity in a componentdata) or does not have that component, Exists() returns false.
     
  7. greyhoundgames

    greyhoundgames

    Joined:
    Jan 24, 2014
    Posts:
    32
    Oh also I would like to add how awesome you are to give me this assistance and so quickly. Follow up question:
    Given the example you showed me below, is there any guarantee that targetPositions is the same entity as targetIdentities. I would think not since there could be a different number of things with each. The goal is for my searcher to go across all the targets by position and then if they are close enough, use their identity to decide what to do with them.
    Code (CSharp):
    1.  
    2. targetPositions = targetGroup.ToComponentDataArray<Position>(Allocator.TempJob),
    3. targetIdentities = targetGroup.ToComponentDataArray<UnitIdentity>(Allocator.TempJob)
    Also to your exists question. Im assuming if I did store an Entity on another Entity, that 2nd entity being removed will result in it not existing and there is no chance for another entity to get "its same index in the giant entity array im assuming is out there"

    Actually I may have answered my own question. Because these both come from the same ComponentGroup i can assume that the indexs and counts match up right?
     
    Last edited: Feb 25, 2019
  8. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    They should be the same because of the way memory is laid out. targetGroup(as it is now) includes all entities that have both the Position AND UnitIdentity component which would be their own archetype/chunk/group of chunks. The components in each chunk should be laid out like: [A, A, A, A][B, B, B, B] and whatever entities are in that chunk. When you query an archetype for a component, it iterates over the chunks with that component and includes them in the array. If an entity did not have one of those components it would be a new archetype and therefore stored in a different chunk and wouldn't get picked up by targetGroup. But you're free to do your own testing.
    https://forum.unity.com/threads/ecs-memory-layout.532028/
    https://forum.unity.com/threads/order-of-entities.524241/
    No because when you destroy an entity, the components get destroyed/freed also. You can't modify a chunk(create/destroy entities) while iterating over it, which is why EntityCommandBuffer/PostUpdateCommands exist. It's either in the array/table when OnUpdate comes or not.

    Alternatively, do you mean that if you had a component that has a reference to Entity 0, and created an Entity 1 with that component, would Entity 0 still exist after destroying Entity 1? If that's what you mean, Entity 0 will still exist, an Entity is basically just a number, and being a struct, the value is just copied to the component.
     
    Last edited: Feb 25, 2019
  9. greyhoundgames

    greyhoundgames

    Joined:
    Jan 24, 2014
    Posts:
    32

    Sorry what I meant was Entity 0 has a component called targeting say, and targeting refrences entity 5235 or whatever. Say 5 minutes later entity 5235 is destroyed. If new entities are being created my question was just making sure that the way chunking\memory works the original refrence to that entity cannot point to some new entity that "reuses" his place in memory such that the exists call would return the wrong answer.

    Its also not clear to me how in my code example I would grab the entity in question. IE while itterating these lists such as public NativeArray<UnitIdentity> targetIdentities; if I find one that i want to target, getting the Entity that represents that guy.
     
  10. RecursiveEclipse

    RecursiveEclipse

    Joined:
    Sep 6, 2018
    Posts:
    298
    It's not just an id, there is also version, version + index prevents that from happening.
    https://forum.unity.com/threads/entity-id-index-version.526950/
    https://gametorrahod.com/all-of-the-unitys-ecs-job-system-gotchas-so-far-6ca80d82d19f (search for "Version")

    You can use targetGroup.GetEntityArray(), which returns an array the same length as the other two.
     
    Last edited: Feb 25, 2019
    Antypodish likes this.
  11. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,779
    Yep, entity haw version and ID. You can run entity.Equal ( otherEntity ), to get comparison checks.

    But I would strongly recommend, having removal of entities in Control. What I mean is, if you remove entity, remove all reference pointers to it at the same time.