Search Unity

Add or Set Component in Job - Chunk.Has switch vs EntityManager.AddComponent?

Discussion in 'Entity Component System' started by pakfront, May 16, 2019.

  1. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    There does not appear to be an AddOrSetComponent method. The best I could come up with was to use a Has at the chunk level. Is this the best way to handle this?
    Or is it better to use
    EntityManager.AddComponent(query, typeof((GoalMoveTo));

    in the main thread to add the missing component to the entities before running the job, thus skipping the commandbuffer altogether?

    Code (CSharp):
    1.         //  do not burst compile, AddComponent not supported
    2.         // [BurstCompile]
    3.         [RequireComponentTag(typeof(PlayerSelected))]
    4.         struct SetOrAddGoalJob : IJobChunk
    5.         {
    6.             [ReadOnly] public ArchetypeChunkEntityType EntityType;
    7.             [ReadOnly] public ArchetypeChunkComponentType<GoalMoveTo> GoalMoveToType;
    8.             [ReadOnly] public EntityCommandBuffer CommandBuffer;
    9.             [ReadOnly] public float3 ClickLocation;
    10.  
    11.             public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    12.             {
    13.                 var entities = chunk.GetNativeArray(EntityType);
    14.                 if (chunk.Has(GoalMoveToType))
    15.                 {
    16.                     for (var i = 0; i < chunk.Count; i++)
    17.                     {
    18.                         // some per-entity offset from the click location
    19.                         CommandBuffer.SetComponent(entities[i],
    20.                             new GoalMoveTo { Position = ClickLocation + new float3(i,0,chunkIndex) });
    21.                     }
    22.                 }
    23.                 else
    24.                 {
    25.                     for (var i = 0; i < chunk.Count; i++)
    26.                     {
    27.                         // some per-entity offset from the click location
    28.                         CommandBuffer.AddComponent(entities[i],
    29.                             new GoalMoveTo { Position = ClickLocation + new float3(i,0,chunkIndex) });
    30.                     }
    31.                 }
    32.             }
    33.         }
     
  2. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    You have a couple of options here:
    * That's a valid way to handle it, and it works just fine while maintaining determinism.
    * Split it into 2 jobs with 2 queries, one for the set of things that have GoalMoveTo, one that do not and require additions.
    * As above, it into 2 systems with one job each, one system for add, one for set, run them at the same time.

    There's been a recent modification to AddComponent where if the value being assigned is the same as the value that exists, it won't modify the entity to maintain Determinism. There's no plan to add anything like a true "Upsert"/"AddOrSet" as it causes issues with determinism while your current solution doesn't.

    You could probable collapse some of that logic into helper functions to reduce the amount of redundant code, especially for the new position calculation. Just have a helper that returns a GoalMoveTo with the appropriate parameters.

    In the future it may be possible to have optimized lambdas that would simplify a lot of this but that's not a feature that exists yet.
     
    pakfront likes this.
  3. TLRMatthew

    TLRMatthew

    Joined:
    Apr 10, 2019
    Posts:
    65
  4. pakfront

    pakfront

    Joined:
    Oct 6, 2010
    Posts:
    551
    Thanks for the help guys, both very useful. I tried a few approaches and using EntityManager in main thread followed by an IJobForEach ended up being the simplest. In addition, does not use commandbuffers. This may be faster, given these findings:
    https://forum.unity.com/threads/entitymanager-batch-api-vs-commandbuffer.678589/

    I have not profiled it against earlier approach though. Any further pointers appreciated -

    Code (CSharp):
    1. using Unity.Burst;
    2. using UnityEngine;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Mathematics;
    6. using Unity.Collections;
    7.  
    8. namespace UnitAgent
    9. {
    10.     // [DisableAutoCreation]
    11.     [UpdateAfter(typeof(PlayerSelectionSystem))]
    12.     public class PlayerInputSystem : JobComponentSystem
    13.     {
    14.         private Plane groundplane = new Plane(Vector3.up, 0);
    15.  
    16.         private EntityQuery m_PlayerSelectedNoGoal;
    17.  
    18.         protected override void OnCreate()
    19.         {
    20.             m_PlayerSelectedNoGoal = GetEntityQuery( new EntityQueryDesc
    21.                {
    22.                    None = new ComponentType[] { typeof(GoalMoveTo) },
    23.                    All = new ComponentType[] { ComponentType.ReadOnly<PlayerSelected>() }
    24.                });
    25.         }
    26.  
    27.         [BurstCompile]
    28.         [RequireComponentTag(typeof(PlayerSelected))]
    29.         struct SetGoalJob : IJobForEach<GoalMoveTo>
    30.         {
    31.             [ReadOnly] public float3 ClickLocation;
    32.  
    33.             public void Execute(ref GoalMoveTo goalMoveTo)
    34.             {
    35.                 goalMoveTo.Position = ClickLocation; // + some offset
    36.             }
    37.         }
    38.  
    39.         protected override JobHandle OnUpdate(JobHandle inputDeps)
    40.         {
    41.             if (!Input.GetMouseButtonDown(1)) return inputDeps;
    42.  
    43.             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    44.  
    45.             float enter = 0.0f;
    46.  
    47.             if (!groundplane.Raycast(ray, out enter)) return inputDeps;
    48.  
    49.             Vector3 clickLocation = ray.GetPoint(enter);
    50.             Debug.Log("PlayerInputSystem clickLocation "+clickLocation);
    51.  
    52.             EntityManager.AddComponent(m_PlayerSelectedNoGoal, typeof(GoalMoveTo));
    53.  
    54.             var job = new SetGoalJob
    55.             {
    56.                 ClickLocation = (float3)clickLocation
    57.             };
    58.             return job.Schedule(this, inputDeps);
    59.         }
    60.     }
    61. }
     
    Last edited: May 17, 2019
  5. TLRMatthew

    TLRMatthew

    Joined:
    Apr 10, 2019
    Posts:
    65
    This looks like how I would have approached it based on my current understanding, so if you figure out any further improvements I'll be keen to hear them too!