Search Unity

Job to have entity follow position of another entity causes InvalidOperationException

Discussion in 'Entity Component System' started by andoco, Feb 11, 2019.

  1. andoco

    andoco

    Joined:
    Feb 24, 2014
    Posts:
    2
    I have a Follow component and system to allow the position of one entity to be set to the position of an entity being followed:

    Code (CSharp):
    1. public struct Follow : IComponentData
    2. {
    3.     public Entity target;
    4. }
    5.  
    6. public class FollowSystem : JobComponentSystem
    7. {
    8.     struct FollowJob : IJobProcessComponentDataWithEntity<Follow, Position>
    9.     {
    10.         [ReadOnly] public ComponentDataFromEntity<Position> Position;
    11.  
    12.         public void Execute(Entity entity, int index, [ReadOnly] ref Follow follow, ref Position position)
    13.         {
    14.             if (follow.target != Entity.Null && Position.Exists(follow.target))
    15.             {
    16.                 var targetPos = Position[follow.target];
    17.                 position.Value = targetPos.Value;
    18.             }
    19.         }
    20.     }
    21.  
    22.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    23.     {
    24.         var job = new FollowJob
    25.         {
    26.             Position = GetComponentDataFromEntity<Position>()
    27.         };
    28.  
    29.         return job.Schedule(this, inputDeps);
    30.     }
    31. }
    But when I attempt to run this with one entity set to follow another, I get the exception:

    What is the cause of this exception, and is it possible to keep the following entity's position set to the target's in this way?

    As a workaround I'm using an EndFrameBarrier command buffer instead:

    Code (CSharp):
    1. public class FollowSystem : JobComponentSystem
    2. {
    3.     struct FollowJob : IJobProcessComponentDataWithEntity<Follow>
    4.     {
    5.         public EntityCommandBuffer.Concurrent Ecb;
    6.         [ReadOnly] public ComponentDataFromEntity<Position> Position;
    7.  
    8.         public void Execute(Entity entity, int index, [ReadOnly] ref Follow follow)
    9.         {
    10.             if (follow.target != Entity.Null && Position.Exists(follow.target))
    11.             {
    12.                 var targetPos = Position[follow.target];
    13.                 Ecb.SetComponent(index, entity, targetPos);
    14.             }
    15.         }
    16.     }
    17.  
    18.     [Inject] private EndFrameBarrier endFrameBarrier;
    19.  
    20.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    21.     {
    22.         var job = new FollowJob
    23.         {
    24.             Ecb = endFrameBarrier.CreateCommandBuffer().ToConcurrent(),
    25.             Position = GetComponentDataFromEntity<Position>()
    26.         };
    27.  
    28.         return job.Schedule(this, inputDeps);
    29.     }
    30. }
    31.  
    Is this a better approach than trying to set the Position component value inside the job?

    Thanks.
     
  2. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    451
    The cause of this issue is in [ReadOnly] permission for Position component array. You have to add ReadOnly attribute for ComponentDataFromEntity, but then you can't write to position array.
    Beside your solution with ECB you could split it to two jobs and create temporary hashmap. In the first job populate HashMap with entity->targetPos pairs. In the second job read from this hash map and write to position component, but I do not know if it is a better solution then yours.
     
  3. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    You can't have a job executing on Position and have a field ComponentDataFromEntity<Position> because they will result in the same array (aliasing).
    You can already to that following with the Attach component but if you for some reason want to do this follow system you should read from the LocalToWorld and not the Position.

    Code (CSharp):
    1.   struct FollowJob : IJobProcessComponentDataWithEntity<Follow, Position> {
    2.     [ReadOnly]
    3.     public ComponentDataFromEntity<LocalToWorld> LocalToWorldFromEntity;
    4.  
    5.     public void Execute(Entity entity, int index, [ReadOnly] ref Follow follow, [WriteOnly]ref Position position) {
    6.       if (follow.target != Entity.Null && LocalToWorldFromEntity.Exists(follow.target)) {
    7.         var targetPos = LocalToWorldFromEntity[follow.target].Value.c3.xyz;
    8.         position.Value = targetPos;
    9.       }
    10.     }
    11.   }
     
    FelixKahle and sseba like this.
  4. andoco

    andoco

    Joined:
    Feb 24, 2014
    Posts:
    2
    Using Attach sounds like the best approach in general but it doesn't seem to work when trying to attach a GameObjectEntity (on the scene Camera in this case) to some other pure ECS entity.

    Thanks for the other explanations on why the aliasing is occurring. It makes more sense now.
     
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Should work fine, you just need the one of the hybrid link components, either CopyTransfromFromGameObject or CopyTransformToGameObject to sync them.

    -edit-

    actually from memory CopyTransformToGameObjectSystem does not take into account parents so this will probably not work if your child is an GameObject.
     
    Last edited: Feb 14, 2019
  6. GilCat

    GilCat

    Joined:
    Sep 21, 2013
    Posts:
    676
    I also use that a lot to attach Unity UI elements (Monobehaviours) to pure ECS elements.
    That will do the trick.