Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Simple Example for Accessing Data on another Component

Discussion in 'Entity Component System' started by pakfront, Apr 30, 2019.

  1. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    I'm looking through the HelloECS and Boid example project and am having a hard time finding a simple example of this common operation. Could someone familiar with ECS in the current version (0.0.12) provide some example code on how to do this?

    I have several hundred agents:
    public struct Agent : IComponentData { public int  unit; }

    each of whom has an index to look up information from one of several Units:
    public struct Unit : IComponentData { public float3 target; }

    to get their current target and move towards it.

    How do assign the index so I can reliably point to another entity?
    How do I actually do that lookup in a job?
    Is there a way to look up the unit and get the data directly using the entity index?

    Or do I have to have a job that runs before that copies data from unit to agent?

    Or is this best done with a SharedComponent where the Unit and its Agents all have the same value stored in a shared component? Then I can do all the work in a single Chunk somehow?


    A simple example would be greatly appreciated.
     
    Last edited: Apr 30, 2019
  2. Guedez

    Guedez

    Joined:
    Jun 1, 2012
    Posts:
    825
    Are Agent and Unit on the same Entity? If so, it's trivial, look up ForEach or chunk iteration
    If Unit and Agent are on different entites, it can get complicated, and I don't think you can take advantage of the high performance access if you end up practically doing random access using EntityManager.GetComponentData<Unity>(units_entity)

    What you can do, is toss every Agent that is part of a Unit into the same chunk using ISharedComponentData
    Do random access for the Unit's information you want to get (unit's target), then use ForEach on the Agent's by setting a filter
    I have no idea how to do the above in a performant way, so I can't really offer a code sample for it. I just know it is probably possible.
    A possibly not performant way is as follows:
    Code (CSharp):
    1. public class Example : ComponentSystem {
    2.     public struct UnitReference : ISharedComponentData{
    3.         Entity reference;
    4.     }
    5.    
    6.     public ComponentGroup helperGroup;
    7.  
    8.     public void OnCreateManager() {
    9.         helperGroup = GetComponentGroup(typeof(UnitReference));
    10.     }
    11.  
    12.     public void OnUpdate() {
    13.         ForEach((Entity e, ref Unit unit) => {
    14.             helperGroup.SetFilter(new UnitReference(){reference=e});
    15.             ForEach((ref Agent agent) => {
    16.                 agent.target = unit.target;
    17.             }, helperGroup);
    18.         });
    19.     }
    20. }
    The good part is that there's no random access, the bad part is that this is always taking all units and setting all agent's targets to their units' targets every cycle, rather than only when needed. This was also not tested and made on notepad++, so it could even have syntax errors

    I am not really proficient in using the Job systems, but the ForEach -> Job conversion shouldn't be too hard in theory
     
    pakfront likes this.
  3. Ahlundra

    Ahlundra

    Joined:
    Apr 13, 2017
    Posts:
    47
    you can do a query before the job and send the information you need to it before it starts
    i'm still learning on how to do that but it would be something like this

    the handle
    Code (CSharp):
    1.  protected override JobHandle OnUpdate(JobHandle inputDeps)
    2.     {
    3.         var query = new EntityQueryDesc { All = new ComponentType[] { typeof(AnimalDB)} };
    4.         var entityQuery = GetEntityQuery(query);
    5.  
    6.  
    7.         //Job job = new Job() { lookup = GetBufferFromEntity<Buff_TestChange>(), lista = Struct_Test.lista, database = entityQuery.CreateArchetypeChunkArray(Allocator.TempJob)};
    8.         Job job = new Job() { lookup = GetBufferFromEntity<Buff_TestChange>(), database = entityQuery.ToEntityArray(Allocator.TempJob), rand = new Unity.Mathematics.Random(), randme = (uint)System.DateTime.Now.Millisecond };
    9.         return job.Schedule(this, inputDeps);
    10.     }
    and the job
    Code (CSharp):
    1.    private struct Job : IJobForEachWithEntity<Animal, Tag_BusterDynamic_Test>
    2.     {
    3.         [NativeDisableParallelForRestriction]
    4.         public BufferFromEntity<Buff_TestChange> lookup;
    5.  
    6.         //[NativeDisableParallelForRestriction]
    7.         //public NativeList<Struct_ListTest> lista;
    8.  
    9.         [DeallocateOnJobCompletion]
    10.         [NativeDisableParallelForRestriction]
    11.         public NativeArray<Entity> database;
    12.  
    13.         public Unity.Mathematics.Random rand;
    14.  
    15.         public uint randme;
    16.  
    17.         public void Execute (Entity ent, int index, ref Animal tag, ref Tag_BusterDynamic_Test tag2)
    18.         {
    19.             rand.InitState(randme * 100);
    20.             var buffer = lookup[database[0]];
    21.             tag.id = buffer[rand.NextInt(buffer.Length)].id;
    22.          
    23.          
    24.         }
    25.     }
    this works for buffer, you could save your entities positions inside a buffer and query them to the job, after that use the lookup to get their position and do your code

    just throwing it there so you know there is a way to send data to a job. There surely is a better way to do that for your use-case, this code was just me testing some random things =p

    and i'm pretty sure there is a way to find a single entity by its ID[you should not do that as entities are removed new ones will take up the old ID] and to get components instead of buffer

    edit:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Entities;
    5. using Unity.Jobs;
    6. using Unity.Burst;
    7. using Unity.Collections;
    8. using Unity.Mathematics;
     
    pakfront likes this.
  4. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    Thanks, those are both interesting. I suspect there is an established pattern for this though. I am getting glimpses in the Boid example and HybridRenderer, but it's too complex for me to pull apart and experiment with.

    Naively, using ISharedComponentData to set and access a target float3 once per chunk seemed like a good place to start, but looking through the ECS Samples and the HybridRenderSystem I only see it used in main-thread ComponentSystem. I don't see any examples of using this in a JobComponentSystem. Can I access it? How do I access it? Here are my attempts so far to read it, any advice is appreciated. After I figure that out I'll attempt to set it.

    Code (CSharp):
    1.  
    2. [BurstCompile]
    3. struct TargetPositionJob : IJobChunk
    4. {
    5.    public float DeltaTime;
    6.  
    7.     public ArchetypeChunkComponentType<Rotation> RotationType;
    8.     [ReadOnly] public ArchetypeChunkComponentType<Translation> TranslationType;
    9.     [ReadOnly] public ArchetypeChunkSharedComponentType<TargetPosition> TargetPositionType;
    10.  
    11.       public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    12.       {
    13.                 var chunkRotations = chunk.GetNativeArray(RotationType);
    14.                 var chunkTranslations = chunk.GetNativeArray(TranslationType);
    15.  
    16.                 // wrong
    17.                 // var chunkTargetPosition = chunk.GetChunkComponentData(TargetPositionType);
    18.  
    19.                 // can get shadedINdex, but can't figure out how to get at data
    20.                 // int sharedIndex = chunk.GetSharedComponentIndex(TargetPositionType);
    21.                 // var chunkTargetPositions = chunk.GetNativeArray(TargetPositionType);            
    22.                 // float3 target = chunkTargetPositions(sharedIndex);
    23.  
    24.                 // requires entity manager as second arg...
    25.                 // var chunkTargetPosition = chunk.GetSharedComponentData(TargetPositionType);
    26.                 // float3 target = chunkTargetPosition.Value;
    27.            
    28.                 // placeholder until I figure it out
    29.                 float3 target = new float3 (1,1,1);
    30.  
    31.                 for (var i = 0; i < chunk.Count; i++)
    32.                 {
    ok, I see there is a long discussion here https://forum.unity.com/threads/how-to-read-sharedcomponentdata-in-ijobchunk.639838/ that, if true in the current version, means my assumptions are incorrect and that SharedComponentData is not accessible from IJobChunk. So I'm at a loss... how do I pass data from one set of entities to another?
     
    Last edited: May 2, 2019
  5. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    OK, so I'm probably thinking about this all wrong. Is this a better?

    public struct Unit : IComponentData { }

    public struct Agent : IComponentData { }

    public struct UnitId : IComponentData { public int Value;}


    create a system that has a
    NativeMultiHashMap<int,float3> unitPositions;

    in that system create a IJobForEach called UnitJob that iterates over all the units <Unit,UnitId,[ReadOnly]Translation> and puts their translation value in the unitPositions using UnitId.Value as the key.
    in the same system create another, later job called AgentJob that iterates over <Agent,Rotation, [ReadOnly]UnitId,...> and uses that UnitId.Value to lookup into the same unitPositions and do it's LookAt calcs there,

    If there is a calculation common to each Agent that shares a unit should I do that in UnitJob and store it in a Native collection or is there a better way to do it via chunking?
     
    Last edited: May 2, 2019
  6. Ahlundra

    Ahlundra

    Joined:
    Apr 13, 2017
    Posts:
    47
    with my limited knowledge, at least for now, that's how I would go with it. It may be slower to search for the right id having to go through every unit but considering it would be able to run in parallel, I think it would still be faster than using only the main thread

    of course there should be a better way to do that, but I cant help you with that yet ;x
     
  7. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    I got it working, here's the gist: https://gist.github.com/pakfront/1877e2384541a32a49410ee6268e26d1

    In brief, a Unit and its Agents all have UnitIds with equal value. In addition, each agent in a unit has a SharedComponentData with the same value. This triggers chunking, which I will use later to do some expensive operations per for all the agents in the unit.
    In the AgentSystem, a job runs over Units to copy Translation into a NativeHashMap keyed with that unitId.
    A second job then runs where each Agent uses the unitId.Value to lookup into the NativeHashMap and get the target position.

    Lesson - despite the name don't use SharedComponentData to share component data. It's a grouping mechanism only (i think.)

    Lesson - NativeHashMap returns false from TryAdd if the key already exists. So I have to Clear it before each tick.

    Question - for the NativeHashMap, I have it as an instance variable on the JobSystem and Clear() it at the start of each update. In this performant within ECS? Should it be created inside OnUpdate and Disposed each tick?

    Question - for NativeCollections with TempJob allocation, where do you Dispose them? Doing it inside OnUpdate obviously breaks, since the scheduled job has not been run yet.
     
  8. Ahlundra

    Ahlundra

    Joined:
    Apr 13, 2017
    Posts:
    47
    for the last question that's what [DeallocateOnJobCompletion] does ;p

    unless for some reason you cant use it?
     
    cort_of_unity likes this.
  9. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Why not just use IJobForEach and ComponentDataFromEntity<> to look up the referenced entities data inside the job.

    As far as i can see you dont need a nativehashmap or IJobChunk or a shared component here.
     
    pakfront likes this.
  10. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    @Joachim_Ante, thanks, that is much simpler! I cribbed it from here: https://forum.unity.com/threads/how...romentity-without-inject.587857/#post-3924478

    Should I be filtering that
    Targets = GetComponentDataFromEntity<Translation>()
    somehow or is it OK to pass entire collection of Translations (of which there will probably be many)

    Code (CSharp):
    1. namespace UnitAgent
    2. {
    3.    [Serializable] public struct Agent : IComponentData { public Entity Unit; }
    4.  
    5.     public class AgentSystem : JobComponentSystem
    6.     {
    7.         [BurstCompile]
    8.         struct RotationJob : IJobForEach<Rotation, Translation, Agent >
    9.         {
    10.             [ReadOnly] public ComponentDataFromEntity<Translation> Targets;
    11.             public void Execute(ref Rotation rotation, [ReadOnly] ref Translation translation, [ReadOnly] ref Agent agent)
    12.             {
    13.                 // float3 target = new float3(0,0,0);
    14.                 Entity e = agent.Unit;
    15.                 float3 target = Targets[e].Value;
    16.                 float3 heading = target - translation.Value;
    17.                 heading.y = 0;
    18.                 rotation.Value = quaternion.LookRotation(heading, math.up());              
    19.             }
    20.         }
    21.  
    22.         protected override JobHandle OnUpdate(JobHandle inputDependencies)
    23.         {
    24.             var job = new RotationJob()
    25.             {
    26.                 Targets = GetComponentDataFromEntity<Translation>()
    27.             };
    28.  
    29.             return job.Schedule(this, inputDependencies);
    30.         }
    31.     }
    32. }
     
  11. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    The code you pasted is fine.

    GetComponentDataFromEntity<Translation>(true); would be better it implies to the system that it will only read Translation, enabling more parallelism.
     
    FROS7 and pakfront like this.
  12. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    Thanks, based on your help I've gotten a bit further on understanding the docs and implementing code.

    Right now I'm working on the "finding nearest/best enemy." I have two or more teams. If an agent is not on my team, it's an enemy. Is this an appropriate place to use a SharedComponentData (SCD) and Chunks?

    I'm thinking I could
    [Serializable] public struct TeamMembership : ISharedComponentData { public int Team;}

    then using chunks, I could iterate over each team separately and somehow pass filtered collection of all Entities that have TeamMembership that does not equal the current TeamMembership.Team. I'm not sure how and where to build that array though, so I could use some guidance there.

    Is this an appropriate way to do this or am I better off just using a regular Component for Team and IJobForEach with an internal loop that iterates over all Entites with a Team and just 'continues' if my.Team == other.Team?
    In this case, what is the best way to pass in a collection of all entities with a Team component to an IJobForEach that is already iterating over that same collection?
     
    Last edited: May 7, 2019
  13. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    OK, I got the second technique working, a single system with one job to fill arrays, a second job to find closest. It's expensive, are better ways to approach it?
    /edit I see that the Boids example has a space partioned way of doing this. I will explore that, but short of that level of optimization, is there a more efficient way to do this brute force method?

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Burst;
    6. using Unity.Mathematics;
    7. using Unity.Transforms;
    8. using UnityEngine;
    9.  
    10. namespace UnitAgent
    11. {
    12.     [UpdateBefore(typeof(AgentSystem))]
    13.     public class CombatSystem : JobComponentSystem
    14.     {
    15.         private EntityQuery m_CombatGroup;
    16.  
    17.         protected override void OnCreateManager()
    18.         {
    19.             // Cached access to a set of ComponentData based on a specific query
    20.             m_CombatGroup = GetEntityQuery(
    21.                 ComponentType.ReadOnly<Combat>(),
    22.                 ComponentType.ReadOnly<Team>(),
    23.                 ComponentType.ReadOnly<Translation>());
    24.         }
    25.  
    26.         [BurstCompile]
    27.         struct FillArrays : IJobForEachWithEntity<Combat, Team, Translation>
    28.         {
    29.             public NativeArray<float3> Positions;
    30.             public NativeArray<int> Teams;
    31.  
    32.             public void Execute(Entity entity, int index, [ReadOnly] ref Combat combat, [ReadOnly] ref Team team, [ReadOnly] ref Translation translation)
    33.             {
    34.                 Positions[index] = translation.Value;
    35.                 Teams[index] = team.Value;
    36.             }
    37.         }
    38.  
    39.  
    40.         [BurstCompile]
    41.         struct FindOpponent : IJobForEach<Combat, Team, Translation>
    42. {           [DeallocateOnJobCompletion] [ReadOnly] public NativeArray<float3> OtherPositions;
    43.             [DeallocateOnJobCompletion] [ReadOnly] public NativeArray<int> OtherTeams;
    44.  
    45.             public void Execute([ReadOnly] ref Combat combat, [ReadOnly] ref Team team, [ReadOnly] ref Translation translation)
    46.             {
    47.                 float3 position = translation.Value;
    48.                 int teamIndex = team.Value;
    49.  
    50.  
    51.                 int nearestPositionIndex = -1;
    52.                 float nearestDistance = float.MaxValue;
    53.  
    54.                 for (int i = 1; i < OtherPositions.Length; i++)
    55.                 {
    56.                     if ( OtherTeams[i] == teamIndex) continue;
    57.  
    58.                     var targetPosition = OtherPositions[i];
    59.                     var distance       = math.lengthsq(position-targetPosition);
    60.                     bool nearest        = distance < nearestDistance;
    61.                     nearestDistance = math.select(nearestDistance, distance, nearest);
    62.                     nearestPositionIndex = math.select(nearestPositionIndex, i, nearest);
    63.                 }
    64.  
    65.                 // nearestDistance = math.sqrt(nearestDistance);
    66.                 if (nearestPositionIndex > -1)
    67.                 {
    68.                     combat.Position = OtherPositions[nearestPositionIndex];
    69.                 } else {
    70.                     //temp debug value
    71.                     combat.Position = new float3(-1,-1,-1);
    72.  
    73.                 }
    74.             }
    75.         }
    76.  
    77.         protected override JobHandle OnUpdate(JobHandle inputDependencies)
    78.         {
    79.             var agentCount  = m_CombatGroup.CalculateLength();
    80.              var otherPositions = new NativeArray<float3>(agentCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    81.               var otherTeams = new NativeArray<int>(agentCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
    82.  
    83.             var fillArraysJob = new FillArrays()
    84.             {
    85.                 Positions = otherPositions,
    86.                 Teams = otherTeams
    87.             };
    88.      
    89.             var fillArraysJobHandle = fillArraysJob.Schedule(this, inputDependencies);
    90.  
    91.  
    92.             var findOpponentJob = new FindOpponent()
    93.             {
    94.                 OtherPositions = otherPositions,
    95.                 OtherTeams = otherTeams
    96.             };
    97.  
    98.             return findOpponentJob.Schedule(this, fillArraysJobHandle);
    99.         }
    100.     }
    101. }
    102.  
     
    Last edited: May 8, 2019
  14. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    This is exactly the use case for ISharedComponentData. You would then schedule a job for each team with the appropriate filters to do chunk iteration to get the enemies only. That'll remove the "continue" branch in your inner loop which will help your branch predictor immensely.

    You might be able to get a small speed-up checking four OtherPositions at a time. You can do a cmin of the square distances and then compare the distances against the cmin result to get a bool4 mask with the minimum distance of the four.

    But really, spatial partitioning is the only way you are going to get the speedup you are looking for.
     
  15. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    Excellent, that is quite interesting. I think there is an example of this in Boids, if I understand correctly it's dynamically building a filter for each 'flock' of boids.
    Code (CSharp):
    1.             EntityManager.GetAllUniqueSharedComponentData(m_UniqueTypes);
    2. ...
    3.             for (int typeIndex = 1; typeIndex < m_UniqueTypes.Count; typeIndex++)
    4.             {
    5.                 var settings = m_UniqueTypes[typeIndex];
    6.                 m_BoidGroup.SetFilter(settings);
    Though I don't understand one thing - if all the values two SCDs are identical, are they merged into a single group/SCD or not?
     
    Last edited: May 8, 2019
  16. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    Unity does not allow you two have two separate but identical SCDs stored in a world. So yes the index can be the same for multiple chunks pointing to the exact same SCD.
     
  17. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    Working on this, and it's not clear to me how to dynamically create a collection of entities that have an SCD that does not match a given value. Is this possible? Would I create n*(n-1) jobs like this:
    for A in teams:
    for B in teams:
    if A == B: continue
    else: createJob where A looks at B

    this seems hard to do safely in mutliple threads, but it does move the continue out of the actual Job.
     
    Last edited: May 8, 2019
  18. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    ultimately, you will need spatial partitioning / broad phase to optimize performance.

    To keep things simple, I would not use SCD but simple tags, i.e. "TeamA" and "TeamB" -> now you have 2 separate Archetypes / groups, where you do this:

    1) Create Array of relevant data from Team B
    teamBTransforms = [Array of Transforms of TeamB] via to component data array
    teamBEntities = [Array of Entities of TeamB] via to entity array
    note: This will have an initial copy cost / could be avoided by chunk iteration or random access via CDFE, but since you loop through this a lot I think this is OK in your case.

    2) IJobParallelForEach looping through teamA
    - find closest --- closestIndex = teamBTransform by looping through teamBTransforms
    - set target to closest --- target = teamBEntities[closestIndex]
     
  19. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Really we don't have enough context to provide the answer of what will work well. What's the scale, how many enemies and how spread out are there, what's best/worst case as far as density. Are the groupings dynamic? etc..

    You can use chunk iteration inside most job types including IJobForEach. That's pretty flexible for inner loops over the same groups.

    Actually for this use case we have factions which is close to what you want, dynamic groups basically. I maintain a lookup map using NativeHashMap<int2,byte>. Key is a faction id pair, value is the standing. Everything has a faction id.

    If your groups are static or at least not a lot, then just maintaining separate lists for those might work well. You pay a one time cost for updating those to save possible many more iterations of filtering out entities.

    Spatial partitioning may or may not make a big impact depending on the numbers involved and if targets tend to cluster a lot.

    Just so much here is context specific. Like for all we know you are working with say 100 agents, in which case just use burst and almost anything will work.
     
  20. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    You can combine queries, so you would have a query for each team and then you would create a combined team for all the teams except the team in question. However, there's another way:

    While you can't access an SCD in a job, you can access the SCD's index from a chunk in a job. And that's enough to tell that two chunks belong to different teams. So you'd end up with something like this in pseudocode:
    Code (CSharp):
    1. IJobForEach
    2.     for each chunk in chunks
    3.         if chunk team SCD index == active team SCD index
    4.             continue
    5.         for each entity in chunk
    6.             do normal processing
     
    pakfront likes this.
  21. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
  22. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Manual iteration isn't tied to a specific job type. Note that example is passing the chunk length to Schedule, that's why the chunk index is the value in Execute. In IJobForEach you just iterate over the chunks yourself.
     
  23. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    Thanks, but it's not clear to me how I access 'the chunks' in the Execute of an IJobForEach.
    Is it already available or do I have to construct it in OnUpdate with CreateArchetypeChunkArray and pass it to my job like I would in the IJobParallelFor example?
    If the latter, doesn't that require creation of that chunk array twice (once by me and once by the wrapper?)

    /edit Is IJobForEach just a helper that creates the equivalent of an IJobParrallelFor or is there a performance advantage to using IJobForEach?
     
    Last edited: May 8, 2019
  24. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    It's just a wrapper. IJobParallelFor is actually the most efficient. Both IJobParallelFor and IJobChunk will let you do what you want to do with just one job and no filtering (just grab and compare SCD indices from the chunks). IJobForEach was an illustrative middle step to help you wrap your mind around chunk iteration.
     
  25. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    Thanks, that's very good to know. I'm finding IParallelFor actually easier to understand than the wrapper.
    How expensive is ArcheTypeChunk.GetNativeArray()?
    Currently I'm putting it in my outer loop so I only do it once per otherChunk. This also moves the continue higher, which I'm assuming helps the predictor, true? (btw there will be less than a dozen Teams/chunks and hundreds to low thousands of agents.)
    Code (CSharp):
    1.  
    2. IJobParallelForExecute:
    3. for otherChunk in Chunks
    4.     if otherChunk SCD index == thisChunk SCD index
    5.        continue
    6.     otherChunk translations = GetNativeArray(TranslationType)
    7.     for agent in thisChunk
    8.        for otherAgent in otherChunk
    9.           do stuff
    10.  
    but certain operations would be simpler if it's really cheap, then I could it in an inner loop
    Code (CSharp):
    1.  
    2. for agent in thisChunk
    3.     for otherChunk in Chunks
    4.        if otherChunk SCD index == thisChunk SCD index
    5.           continue
    6.        otherChunk translations = GetNativeArray(TranslationType)
    7.        for otherAgent in otherChunk
    8.              do stuff
    9.  
    ... looking at this again it seems wrong to be doing getnativearray inside the loop at all. Would it be better to preallocate and populate these in the OnUpdate, which iirc is run on the main thread?
     
    Last edited: May 10, 2019
  26. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    Out of curiosity, why did you not follow the tocomponentdataarray approach, I outlined earlier? Given your recent post with the number in each group, you want to call tocomponentdataarray on the group with lower count
     
  27. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    @sndgan Your post looked useful, but it assumed a predetermined number of teams, each with their own component tag, correct? That may be the correct answer for the problem in the long run, but I'm trying to wrap my head around more generic ways of doing it.
     
  28. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,993
    GetNativeArray is cheap, but I suspect your first approach might exhibit better cache behavior. Only way to know for sure though is to profile it. The second approach is a lot more intuitive, so I would go with that since in the future you'll probably want to use some sort of spatial partitioning anyways.
     
  29. sngdan

    sngdan

    Joined:
    Feb 7, 2014
    Posts:
    1,131
    @pakfront yes, it assumed a predetermined number of teams.

    Too keep this flexible, filtering by SCD (say int teamnumber) is a sound approach. But then what, how generic do you want to keep it? What is the ultimate output you want? NMHM<teampair,collidingentitypairs>

    I would do it simple first, optimize it and when required generalize it.
     
  30. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    Probably not optimal, but this is what I came up with for the moment:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Burst;
    6. using Unity.Mathematics;
    7. using Unity.Transforms;
    8. using UnityEngine;
    9.  
    10. namespace UnitAgent
    11. {
    12.     [DisableAutoCreation]
    13.     [UpdateBefore(typeof(SubordinateSystem))]
    14.     public class FindOpponentSystem : JobComponentSystem
    15.     {
    16.         [BurstCompile]
    17.         struct FindOpponent : IJobParallelFor
    18.         {
    19.             [DeallocateOnJobCompletion] public NativeArray<ArchetypeChunk> Chunks;
    20.             public ArchetypeChunkComponentType<Opponent> OpponentType;
    21.             [ReadOnly] public ArchetypeChunkComponentType<Translation> TranslationType;
    22.             [ReadOnly] public ArchetypeChunkSharedComponentType<Team> TeamType;
    23.  
    24.             public void Execute(int chunkIndex)
    25.             {
    26.                 var chunk = Chunks[chunkIndex];
    27.                 var chunkOpponent = chunk.GetNativeArray(OpponentType);
    28.                 var chunkTranslation = chunk.GetNativeArray(TranslationType);
    29.                 int chunkTeamIndex = chunk.GetSharedComponentIndex(TeamType);
    30.                 var instanceCount = chunk.Count;
    31.  
    32.                 // initialize
    33.                 for (int i = 0; i < instanceCount; i++)
    34.                 {
    35.                     chunkOpponent[i] =  new Opponent
    36.                     {
    37.                         DistanceSq = float.MaxValue
    38.                     };
    39.                 }
    40.  
    41.                 for (int c = 0; c < Chunks.Length; c++)
    42.                 {
    43.                     var otherChunk = Chunks[c];
    44.                     // don't test against your own Team/chunk
    45.                     int otherChunkTeamIndex = otherChunk.GetSharedComponentIndex(TeamType);
    46.                     if (otherChunkTeamIndex == chunkTeamIndex) continue;
    47.  
    48.                     var otherTranslations = otherChunk.GetNativeArray(TranslationType);
    49.  
    50.                     for (int i = 0; i < instanceCount; i++)
    51.                     {
    52.                         float3 position = chunkTranslation[i].Value;
    53.                         // Get from previous pass
    54.                         float nearestDistanceSq = chunkOpponent[i].DistanceSq;
    55.                         int nearestPositionIndex = -1;
    56.  
    57.                         for (int j = 0; j < otherChunk.Count; j++)
    58.                         {
    59.                             var targetPosition = otherTranslations[j].Value;
    60.                             var distance = math.lengthsq(position - targetPosition);
    61.                             bool nearest = distance < nearestDistanceSq;
    62.                             nearestDistanceSq = math.select(nearestDistanceSq, distance, nearest);
    63.                             nearestPositionIndex = math.select(nearestPositionIndex, j, nearest);
    64.                         }
    65.  
    66.                         if (nearestPositionIndex > -1)
    67.                         {
    68.                             // Debug.Log("Found nearest chunk["+chunkIndex+"]: otherChunk["+c+"]"+nearestPositionIndex);
    69.                             chunkOpponent[i] =  new Opponent
    70.                             {
    71.                                 DistanceSq = nearestDistanceSq,
    72.                                 Position = otherTranslations[nearestPositionIndex].Value
    73.                             };
    74.                         }
    75.                         //TODO possibly remove a tag if none found
    76.                     }
    77.                 }
    78.             }
    79.         }
    80.  
    81.     [BurstCompile]
    82.     // this will have more filters in the future, such as Pursue and 'not Flee'
    83.     struct SetGoal : IJobForEach<Opponent, GoalMoveTo>
    84.     {
    85.         public void Execute([ReadOnly] ref Opponent opponent, ref GoalMoveTo goal)
    86.         {
    87.             goal.Position = opponent.Position;
    88.         }
    89.     }
    90.  
    91.  
    92.         EntityQuery m_group;
    93.  
    94.         protected override void OnCreate()
    95.         {
    96.             var query = new EntityQueryDesc
    97.             {
    98.                 All = new ComponentType[] { typeof(Opponent), ComponentType.ReadOnly<Team>(), ComponentType.ReadOnly<Translation>(), ComponentType.ReadOnly<Unit>() }
    99.             };
    100.  
    101.             m_group = GetEntityQuery(query);
    102.         }
    103.  
    104.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    105.         {
    106.             var opponentType = GetArchetypeChunkComponentType<Opponent>();
    107.             var translationType = GetArchetypeChunkComponentType<Translation>(true);
    108.             var teamType = GetArchetypeChunkSharedComponentType<Team>();
    109.             var chunks = m_group.CreateArchetypeChunkArray(Allocator.TempJob);
    110.  
    111.             var findOpponentJob = new FindOpponent
    112.             {
    113.                 Chunks = chunks,
    114.                 OpponentType = opponentType,
    115.                 TranslationType = translationType,
    116.                 TeamType = teamType
    117.             };
    118.             var findOpponentJobHandle = findOpponentJob.Schedule(chunks.Length, 32, inputDeps);
    119.  
    120.             var setGoalJob = new SetGoal();
    121.             return setGoalJob.Schedule(this,findOpponentJobHandle);
    122.         }
    123.     }
    124. }
    125.  
     
    Last edited: May 17, 2019
    Shinyclef likes this.