Get Entity Component Data from multiple entities inside of a job.

I had this system as an ECS, now I am trying to convert it to a job system. My issue is I was using

World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<Translation>( hasTarget.targetEntity );

But this does not work inside of JobSystem. I am new to this and would appreciate any help. Since this job runs on multiple entities, I am unsure of how to pass multiple Translation data to the job and I dont understand how the job will know which Translation data corresponds to each entity.

    public class UnitMoveToTargetJobSystem : JobComponentSystem
    {
    //[ RequireComponentTag( typeof( HasTarget ) ) ]
    [ ExcludeComponent( typeof( InCombat ) ) ]
    [ BurstCompile ]
    private struct MoveToTargetJob : IJobForEachWithEntity<HasTarget , Translation>
    {
    public EntityCommandBuffer.Concurrent entityCommandBuffer;
    public float dt;
    public bool entityHasTarget;
    public void Execute( Entity unitEntity , int index , ref HasTarget hasTarget , ref Translation translation )
    {
    if ( hasTarget.targetEntity != null )
    {
    Translation targetTranslation = World.DefaultGameObjectInjectionWorld.EntityManager.GetComponentData<Translation>( hasTarget.targetEntity );
    float3 targetDirection = math.normalize( targetTranslation.Value - translation.Value );
    float moveSpeed = 8f;
    translation.Value += targetDirection * moveSpeed * dt;
    if ( math.distance( translation.Value , targetTranslation.Value ) <= 0.3f )
    {
    entityCommandBuffer.AddComponent( index , unitEntity , new InCombat { enemy = hasTarget.targetEntity } );
    entityCommandBuffer.RemoveComponent( index , unitEntity , typeof( HasTarget ) );
    //PostUpdateCommands.AddComponent( unitEntity , new InCombat { enemy = hasTarget.targetEntity } );
    //PostUpdateCommands.RemoveComponent( unitEntity , typeof( HasTarget ) );
    }
    }
    else
    {
    entityCommandBuffer.RemoveComponent( index , unitEntity , typeof( HasTarget ) );
    //PostUpdateCommands.RemoveComponent( unitEntity , typeof( HasTarget ) );
    }
    }
    }
    private EndSimulationEntityCommandBufferSystem endSimulationEntityCommandBufferSystem;
    protected override void OnCreate()
    {
    endSimulationEntityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    base.OnCreate();
    }
    protected override JobHandle OnUpdate( JobHandle inputDeps )
    {
    MoveToTargetJob moveToTargetJob = new MoveToTargetJob
    {
    entityCommandBuffer = endSimulationEntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent() ,
    dt = Time.DeltaTime
    };
    JobHandle jobHandle = moveToTargetJob.Schedule( this , inputDeps );
    endSimulationEntityCommandBufferSystem.AddJobHandleForProducer( jobHandle );
    return jobHandle;
    }
    }

I’m trying the exact same thing right now :slight_smile: If I find a solution, I’ll put a reference in this post (ping me if I forget) :slight_smile:

The other post for reference: DOTS: Get Entity data within job?

1 Like

You can pass the translation data by GetComponentDataFromEntity to a public field of ComponentDataFromentity the Job.
After that you can get the Translation data by using the Entity as an index array. This looks a little bit weird at first but i think this is common usage.

MoveToTargetJob moveToTargetJob = new MoveToTargetJob
{
TranslationData = GetComponentDataFromEntity<Translation>(),
entityCommandBuffer = endSimulationEntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent() ,
dt = Time.DeltaTime
};

and then in the Job

private struct MoveToTargetJob : IJobForEachWithEntity<HasTarget , Translation>
{
public ComponentDataFromEntity<Translation> TranslationData;
public EntityCommandBuffer.Concurrent entityCommandBuffer;
public float dt;
public bool entityHasTarget;
public void Execute( Entity unitEntity , int index , ref HasTarget hasTarget , ref Translation translation )
{
if ( hasTarget.targetEntity != null )
{
Translation targetTranslation = TranslationData[hasTarget.targetEntity];
2 Likes

Thank you very much!

Is ComponentDataFromEntity a list/array then?

Also I receive this error when I try to compile.

InvalidOperationException: The writable NativeArray MoveToTargetJob.Iterator is the same NativeArray as MoveToTargetJob.Data.translationData, two NativeArrays may not be the same (aliasing).

For anyone interested, I’ve gotten it to work by separating the logic to actually move the UnitTranslation into another system. It seems that because I was using ComponentDataFromEntity and a reference to the UnitTranslation in the same job, Unity didn’t like it.

I am not sure if this is the right way to go, but here is my modified code.

public class UnitCalculateMoveDistanceJobSystem : JobComponentSystem
{
    [ ExcludeComponent( typeof( AtTarget ) ) ][ BurstCompile ]
    private struct MoveToTargetJob : IJobForEachWithEntity<HasTarget , MoveAmount>
    {
        [ReadOnly] public ComponentDataFromEntity<Translation> translationData;
        public EntityCommandBuffer.Concurrent entityCommandBuffer;
        public float dt;

        public void Execute( Entity unitEntity , int index , ref HasTarget hasTarget , ref MoveAmount moveAmount )
        {
            if ( hasTarget.targetEntity != null )
            {
                Translation unitTranslation = translationData[ unitEntity ];
                Translation targetTranslation = translationData[ hasTarget.targetEntity ];
                float3 targetDirection = math.normalize( targetTranslation.Value -  unitTranslation.Value );
                float moveSpeed = 8f;

                moveAmount.value = targetDirection * moveSpeed * dt;
               
                // THE LINE OF CODE GIVING THE ERROR
                //unitTranslation.Value += targetDirection * moveSpeed * dt;

                if ( math.distance( unitTranslation.Value , targetTranslation.Value ) <= 0.3f )
                {
                    entityCommandBuffer.AddComponent( index , unitEntity , new AtTarget() );
                   entityCommandBuffer.AddComponent( index , unitEntity , new Remove_HasTarget() ););
                }
            }
        }
    }
    private EndSimulationEntityCommandBufferSystem endSimulationEntityCommandBufferSystem;
    protected override void OnCreate()
    {
        endSimulationEntityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
        base.OnCreate();
    }
    protected override JobHandle OnUpdate( JobHandle inputDeps )
    {
        MoveToTargetJob moveToTargetJob = new MoveToTargetJob
        {
            translationData = GetComponentDataFromEntity<Translation>() ,
            entityCommandBuffer = endSimulationEntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent() ,
            dt = Time.DeltaTime
        };

        JobHandle jobHandle = moveToTargetJob.Schedule( this , inputDeps );
        endSimulationEntityCommandBufferSystem.AddJobHandleForProducer( jobHandle );

        return jobHandle;
    }
}

Here is the extra system I added.

public class UnitMoveSystem : JobComponentSystem
{
    [ExcludeComponent( typeof( AtTarget ) ) ] [RequireComponentTag( typeof( HasTarget ) ) ][BurstCompile]
    private struct UnitMoveJob : IJobForEachWithEntity<Translation , MoveAmount>
    {
        public void Execute( Entity entity , int index , ref Translation translation , ref MoveAmount moveAmount )
        {
            translation.Value += moveAmount.value;
        }
    }

    protected override JobHandle OnUpdate( JobHandle inputDeps )
    {
        UnitMoveJob job = new UnitMoveJob();

        JobHandle handle = job.Schedule( this , inputDeps );

        return handle;
    }
}

Here is the system that removes the HasTarget tag on all units that have reaches their target, because AFAIK the EntityCommandBuffer does not yet support BurstCompile for the RemoveComponent method yet, so I had to put this logic into another job not using burst.

public class RemoveHasTargetScript : JobComponentSystem
{
    private struct RemoveHasTargetJob : IJobForEachWithEntity<Remove_HasTarget>
    {
        public EntityCommandBuffer.Concurrent ecb;

        public void Execute( Entity entity , int index , [ReadOnly] ref Remove_HasTarget removeHasTarget)
        {
            ecb.RemoveComponent( index , entity , typeof( Remove_HasTarget ) );
            ecb.RemoveComponent( index , entity , typeof( HasTarget ) );
        }
    }

    private EndSimulationEntityCommandBufferSystem ecbSystem;
    protected override void OnCreate()
    {
        ecbSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
        base.OnCreate();
    }

    protected override JobHandle OnUpdate( JobHandle inputDeps )
    {
        RemoveHasTargetJob job = new RemoveHasTargetJob
        {
            ecb = ecbSystem.CreateCommandBuffer().ToConcurrent()
        };

        JobHandle jobHandle = job.Schedule( this , inputDeps );
        ecbSystem.AddJobHandleForProducer( jobHandle );

        return jobHandle;
    }
}
1 Like