Search Unity

Options for Entity interaction - Unite Copenhagen - Follow Example

Discussion in 'Data Oriented Technology Stack' started by MattInContext, Oct 10, 2019.

  1. MattInContext

    MattInContext

    Joined:
    Oct 5, 2018
    Posts:
    8
    I watched the "Options for Entity Interaction" talk from Unite Copenhagen 2019.


    The first example was the follower and leader example. The main concept of the talk was how to support entities to interact i.e. a Follower entity moves toward the Leader entity. The example was showing how to use ComponentDataFromEnity.

    After walking through the component data pseudo code was shown looping over each 'Follower' to update the velocity toward the 'Leader' Position.
    Video Time: 14:25

    The actual code was then shown to be this:
    Video Time: 19:45

    struct FollowLeaderJob : IJobForEach<Leader, Position, Velocity>
    {
    [ReadOnly] public ComponentDataFromEntity<Position> PositionData;
    public void Execute([ReadOnly] ref Leader leader, [ReadOnly] ref Position position, ref Velocity velocity)
    {
    if (!PositionData.Exists(leader.Entity))
    return;

    Position leaderPosition = PositionData[leader.Entity];
    velocity.Value += 0.1f * (leaderPosition.Value - position.Value);
    }
    }


    The ComponentDataFromEntity was given to the Job in the OnUpdate by using 'GetComponentDataFromEntity<Position>(true)'
    Video Time: 20:01
    ---------------

    I don't believe the pseudo code and the 'FollowLeaderJob' code match.

    1. IJobForEach<Leader, Position, Velocity> finds the entities that have a Leader component on it, not the Follower component. Shouldn't this be IJobForEach<Follower, Position, Velocity> ?

    2. I am not sure how the Leader ComponentData is defined but, a member variable on the Leader would need to be the Entity to support 'leader.Entity'. Correct?

    3. Can someone show the correct way to set this up?
    How do I use a IJobForEach walking the Followers and still get hold of the Leader Entity?

    I have attempted to have the Job hold onto a NativeArray<Entity> using a 'Leader' query in the OnUpdate but, that seems to leak even if I Dispose of the array at the end of the job.


    Thank you.
     
  2. sschoener

    sschoener

    Joined:
    Aug 18, 2014
    Posts:
    59
    I think it is mostly an issue of naming. A follower entity will have three components:
    1. Position
    2. Velocity
    3. Leader
    Arguably, that last component should not be called Leader but FollowLeader or Follower. The Leader component might look like this:
    Code (CSharp):
    1. struct Leader : IComponentData {
    2.     // This is the leader entity, but this component will be stored for each follower.
    3.     // Each follower thus stores who its leader is.
    4.     public Entity Entity;
    5. }
    Does this help? :)
     
    pal_trefall likes this.
  3. MattInContext

    MattInContext

    Joined:
    Oct 5, 2018
    Posts:
    8
    Yes, that does make sense. I think where I get hung up is the creation of the Leader component and how to get and hold onto the Entity.

    I have tried to query for it during the Job creation and pass it to the job which resulted in the NativeArray not cleaning up properly.

    Also I have stored the Entity I want followed after conversion in a reachable class used during the creation of the Leader component.

    Any suggestions on strategies on accessing other entities than the entity being iterated over in a IJobForEach?
     
  4. sschoener

    sschoener

    Joined:
    Aug 18, 2014
    Posts:
    59
    This depends on your setup, but at some point you will need to have both entities at hand to set it up. Something like:
    Code (CSharp):
    1. void OnCreate() {
    2.     var followerEntity = EntityManager.CreateEntity();
    3.     var leaderEntity = EntityManager.CreateEntity();
    4.     EntityManager.AddComponentData(followerEntity, new Leader {
    5.         Entity = leaderEntity
    6.     };
    7. }
    I'm not sure I'm following what exactly you did. If you post a code snippet, I can take a look at it and see what is going wrong.

    That really depends on what you want to do. You could query for all entities with a specific component before you start the job, store them in a NativeArray<Entity> and then use that in the job. Again, if you post a specific piece of code I'd be willing to take a look at it :)
     
  5. MattInContext

    MattInContext

    Joined:
    Oct 5, 2018
    Posts:
    8
    Here is an example that ends up producing memory leaks. I am sure I am not handling the NativeArray<Entities> correctly. Without the NativeArray<Entity> LeaderEntities variable, I use an entity the Follower component is holding onto which is stored off when the Leader component is created.

    Code (CSharp):
    1. public class FollowerSystem : JobComponentSystem
    2. {
    3.     private EntityQuery _leaderQuery;
    4.     protected override void OnCreate()
    5.     {
    6.         _leaderQuery = EntityManager.CreateEntityQuery(typeof(Leader), typeof(Translation));
    7.     }
    8.  
    9.     [BurstCompile]
    10.     struct FollowerSystemJob : IJobForEach<Follower, Translation, Velocity>
    11.     {
    12.         [ReadOnly] public ComponentDataFromEntity<Translation> LeaderTranslation;
    13.         [ReadOnly] public NativeArray<Entity> LeaderEntities;
    14.        
    15.         public void Execute([ReadOnly]ref Follower follower, [ReadOnly] ref Translation translation, ref Velocity velocity)
    16.         {
    17.             var leaderEntity = LeaderEntities[0]; // follower.LeaderentityToFollow
    18.  
    19.            
    20.             if (!LeaderTranslation.HasComponent(leaderEntity))
    21.             {
    22.                 return;
    23.             }
    24.  
    25.             var leaderTranslation = LeaderTranslation[leaderEntity];
    26.             var deltaVectorFull = leaderTranslation.Value - translation.Value;
    27.             if (math.length(deltaVectorFull) < 0.1f)
    28.             {
    29.                 velocity.Value = new float3(0f, 0f, 0f);
    30.             }
    31.             else
    32.             {
    33.  
    34.                 velocity.Value = normalize(deltaVectorFull) * follower.Speed;
    35.             }
    36.  
    37.         }
    38.     }
    39.  
    40.     protected override JobHandle OnUpdate(JobHandle inputDependencies)
    41.     {
    42.         var leaderEntities = _leaderQuery.ToEntityArray(Allocator.TempJob);
    43.         var job = new FollowerSystemJob()
    44.         {
    45.             LeaderEntities = leaderEntities,
    46.             LeaderTranslation = GetComponentDataFromEntity<Translation>(true)
    47.         };
    48.         var handle =  job.Schedule(this, inputDependencies);
    49.         return handle;
    50.     }
    51. }
    Appreciate the help.
     
  6. sschoener

    sschoener

    Joined:
    Aug 18, 2014
    Posts:
    59
    Your specific problem here is that you do not deallocate the LeaderEntities array correctly. Whenever you call a function that takes an Allocator and returns a native container like NativeArray, it is your responsibility to free the storage allocated for the container. You can in general either to this manually by calling Dispose on the container, use a using block to make it automated, or use a special attribute, DeallocateOnJobCompletion. In the specific case of an IJobForEach you need to go for the third solution since you absolutely need the array to live for as long as the job is running. You can do this by editing the LeaderEntities declaration to look like this:
    Code (CSharp):
    1. [DeallocateOnJobCompletion][ReadOnly] public NativeArray<Entity> LeaderEntities;
    I'm not 100% sure I understand what you are trying to do, but if you certainly know that there is exactly one single leader in existence, then you can use JobComponentSystem.GetSingletonEntity<T>() to query for the single entity and not start the job at all ;)
     
    Last edited: Oct 10, 2019
  7. MattInContext

    MattInContext

    Joined:
    Oct 5, 2018
    Posts:
    8
    That worked. I had seen that attribute before and did not try that option. Perfect.
    As for what I am doing, I am just testing out ideas and concepts and how to operate on multiple entities as cleanly as possible. Thank you very much for the help.
     
  8. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,516
    Note: except Allocator.Temp, which you shouldn’t deallocate manually.
     
  9. sschoener

    sschoener

    Joined:
    Aug 18, 2014
    Posts:
    59
    Hm, while it might be technically true that you do not strictly need to deallocate them (temporary allocations are freed automatically, at least in 2019.2, and there are no warnings if you do not manually deallocate), it doesn't hurt to deallocate temporary allocations manually. I think that for learning purposes a clear rule is preferable, such as: Call Dispose whenever you pass in an Allocator label. It will make it easier to learn when to dispose and when not to and ultimately lead to fewer bugs.

    Also, I'm not sure the behavior is fully intentional. According to the documentation for Allocator.Temp (emphasis mine):
    See here, particularly the section titled "NativeContainer Allocator". So yes, in my opinion you absolutely should dispose temporary allocations :)

    Did you have any particular case in mind where manually deallocating a temporary allocation causes problems?
     
  10. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,516
    Yep it was problem in past if you tried to deallocate Temp allocated container.
    https://forum.unity.com/threads/shared-component-entity-query-in-jobs.729092/#post-4865909
     
  11. sschoener

    sschoener

    Joined:
    Aug 18, 2014
    Posts:
    59
    Thank you, @eizenhorn :) I dug up the thread that pointed out this problem and it seems that calling Dispose on temporary allocations was a problem in jobs with Unity 2019.1.

    Edit: I'm running 2019.3b6 and Burst 1.1.2; I cannot reproduce the problem. I can Dispose allocations from Allocator.Temp without any problems from jobs that run in Burst. I'm sticking to always deallocate as the docs say :)
     
    Last edited: Oct 11, 2019
  12. francois85

    francois85

    Joined:
    Aug 11, 2015
    Posts:
    677
    On a side note, I seems like random access is unavoidable with GetComponentDataFromEntity. If I have a large amount of entities that need to access other entity component data what is my alternative ?
     
  13. Maxi77

    Maxi77

    Joined:
    Mar 3, 2015
    Posts:
    15
    I am wondering the same.