DOTS: Get Entity data within job?

Ok, so… I am beyond frustrated.

I have two sets of Entities; Autos and Roads. I have a Job that is designed to move the cars along the points of the road.

The job executes based on an AutoTag component (and has the Auto’s Transform, the car’s current RoadIdentity and LaneIdentity) During the job I need to loop through the Road Entities (which is passed to the job via a NativeArray) Thing is, when I attempt to get the component’s data I am getting errors. (I have walked away from my development system out of frustration, so atm I don’t have the exact error message)

So basically I am attempting to understand how to access other entities and their data within a job. Thoughts?

using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

public class ER3D_TrafficControlSystem : JobComponentSystem
{
    [BurstCompile]
    [RequireComponentTag(typeof(ERAutoTag))]
    private struct AutoNaviationJob : IJobForEachWithEntity<
        HoverCarLevelComponent,
        HoverCarSpeedComponent,
        RoadIdentityComponent,
        LaneIdentityComponent,
        CurrentPositionIndexComponent,
        Translation>
    {
        [DeallocateOnJobCompletionAttribute]
        [ReadOnly]
        public NativeArray<Entity> roadEntities;
        public float deltaTime;


        public void Execute(
            Entity entity,
            int index,
            ref HoverCarLevelComponent carLevel,
            ref HoverCarSpeedComponent carSpeed,
            ref RoadIdentityComponent carRoadIdentity,
            ref LaneIdentityComponent carLaneIdentity,
            ref CurrentPositionIndexComponent currentPositionIndex,
            ref Translation translation)
        {
  
           
            //EntityManager entityManager = World.Active.EntityManager;

            translation.Value.z += 1;

            for (int i = 0; i < roadEntities.Length; i++)
            {
                Entity roadEntity = roadEntities[i];


                //var rI = ComponentDataFromEntity<IdentityComponent>(roadEntity);

                //var roadIdentity = GetComponentData<IdentityComponent>(roadEntity);
                //var roadIdentity = entityManager.GetComponentData<IdentityComponent>(roadEntity);
                //var laneIdentity = entityManager.GetComponentData<LaneIdentityComponent>(roadEntity);

                //    if (roadIdentity.value == carRoadIdentity.value &&
                //        laneIdentity.value == carLaneIdentity.value)
                //    {
                //        //translation.Value.z += 1;
                //        break;
                //    }
            }
        }
    }

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var entityManager = World.Active.EntityManager;

        EntityQuery allRoadsQuery = GetEntityQuery(
            typeof(ERRoadTag),
            ComponentType.ReadOnly<IdentityComponent>(),
            ComponentType.ReadOnly<LaneIdentityComponent>(),
            ComponentType.ReadOnly<LanePointsComponent>());

       
        NativeArray<Entity> entityArray = allRoadsQuery.ToEntityArray(Allocator.TempJob);
       
        AutoNaviationJob autoNaviationJob = new AutoNaviationJob
        {
            roadEntities = entityArray,
            deltaTime = Time.deltaTime
        };

        JobHandle jobHandle = autoNaviationJob.Schedule(this, inputDeps);


        return jobHandle;
    }
}

we need code to see what’s going wrong

1 Like

You’re right, I should have included the code. Thanks for the heads up. I added the code.

It looks like you want to use ComponentDataFromEntity.

I got a similar answer a few minutes ago on Reddit, but no example of how to use it. Perhaps you have an example?

You can get ComponentDataFromEntity inside a JobComponentSystem

        /// <summary>
        /// Gets an array-like container containing all components of type T, indexed by Entity.
        /// </summary>
        /// <param name="isReadOnly">Whether the data is only read, not written. Access data as
        /// read-only whenever possible.</param>
        /// <typeparam name="T">A struct that implements <see cref="IComponentData"/>.</typeparam>
        /// <returns>All component data of type T.</returns>
        public ComponentDataFromEntity<T> GetComponentDataFromEntity<T>(bool isReadOnly = false)
            where T : struct, IComponentData

Example,

ComponentDataFromEntity<Team> teams = this.GetComponentDataFromEntity<Team>(true);

This can be passed to a job. ComponentDataFromEntity has a couple of methods.

/// <summary>
/// Reports whether the specified <see cref="Entity"/> instance still refers to a valid entity and that it has a
/// component of type T.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>True if the entity has a component of type T, and false if it does not. Also returns false if
/// the Entity instance refers to an entity that has been destroyed.</returns>
/// <remarks>To report if the provided entity has a component of type T, this function confirms
/// whether the <see cref="EntityArchetype"/> of the provided entity includes components of type T.
/// </remarks>
public bool Exists(Entity entity)

/// <summary>
/// Reports whether the specified <see cref="Entity"/> instance still refers to a valid entity and that it has a
/// component of type T.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>True if the entity has a component of type T, and false if it does not. Also returns false if
/// the Entity instance refers to an entity that has been destroyed.</returns>
/// <remarks>To report if the provided entity has a component of type T, this function confirms
/// whether the <see cref="EntityArchetype"/> of the provided entity includes components of type T.
/// </remarks>
public bool HasComponent(Entity entity)

/// <summary>
/// Gets the <see cref="IComponentData"/> instance of type T for the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns>An <see cref="IComponentData"/> type.</returns>
/// <remarks>You cannot use ComponentDataFromEntity to get zero-sized <see cref="IComponentData"/>.
/// Use <see cref="Exists"/> to check whether an entity has the zero-sized component instead.
///
/// Normally, you cannot write to components accessed using a ComponentDataFromEntity instance
/// in a parallel Job. This restriction is in place because multiple threads could write to the same component,
/// leading to a race condition and nondeterministic results. However, when you are certain that your algorithm
/// cannot write to the same component from different threads, you can manually disable this safety check
/// by putting the
/// [NativeDisableParallelForRestrictions](https://docs.unity3d.com/ScriptReference/Unity.Collections.NativeDisableParallelForRestrictionAttribute.html)
/// attribute on the ComponentDataFromEntity field in the Job.
/// </remarks>
/// <exception cref="System.ArgumentException">Thrown if T is zero-size.</exception>
public T this[Entity entity]

Note Exists and HasComponent do the exact same thing. Not actually sure why they are both there.

The index overload [Entity] lets you get the component data from an entity inside a job.

Team team = this.Teams[entity];

There is also a BufferFromEntity version.

Manual is here: Struct ComponentDataFromEntity<T> | Package Manager UI website

Ok people, I am getting closer… I believe.

I figured out how to use ComponentDataFromEntity, and I can get the road identity’s value (see code below) but when I added another ComponentDataFromEntity for the LaneIdentity I get an error (see detail below the code) and I am just LOST.

using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

public class ER3D_TrafficControlSystem : JobComponentSystem
{
    [BurstCompile]
    [RequireComponentTag(typeof(ERAutoTag))]
    private struct AutoNavigationJob : IJobForEach<
        HoverCarLevelComponent,
        HoverCarSpeedComponent,
        RoadIdentityComponent,
        LaneIdentityComponent,
        CurrentPositionIndexComponent,
        Translation>
    {
         [ReadOnly]
        public ComponentDataFromEntity<IdentityComponent> RoadIdentityFromEntity;

        [ReadOnly]
        public ComponentDataFromEntity<LaneIdentityComponent> RoadLaneIdentityFromEntity;

        [DeallocateOnJobCompletionAttribute]
        [ReadOnly]
        public NativeArray<Entity> roadEntities;
        public float deltaTime;

        public void Execute(
            ref HoverCarLevelComponent carLevel,
            ref HoverCarSpeedComponent carSpeed,
            ref RoadIdentityComponent carRoadIdentity,
            ref LaneIdentityComponent carLaneIdentity,
            ref CurrentPositionIndexComponent currentPositionIndex,
            ref Translation translation)
        {
  
            for (int i = 0; i < roadEntities.Length; i++)
            {
                Entity roadEntity = roadEntities[i];

                var roadIdentity = RoadIdentityFromEntity[roadEntity];
                var laneIdentity = RoadLaneIdentityFromEntity[roadEntity];

                 if (roadIdentity.value == carRoadIdentity.value &&
                    laneIdentity.value == carLaneIdentity.value)
                {
                    translation.Value.z += 1;  //TEMP.  Need actual value here
                    break;
                }
            }
        }
    }

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var entityManager = World.Active.EntityManager;

        EntityQuery allRoadsQuery = GetEntityQuery(
            typeof(ERRoadTag),
            ComponentType.ReadOnly<IdentityComponent>(),
            ComponentType.ReadOnly<LaneIdentityComponent>(),
            ComponentType.ReadOnly<IsLaneDirectionLeftComponent>(),
            ComponentType.ReadOnly<LanePointsComponent>());


        NativeArray<Entity> entityArray = allRoadsQuery.ToEntityArray(Allocator.TempJob);
       
        AutoNavigationJob autoNaviationJob = new AutoNavigationJob
        {
            RoadIdentityFromEntity = GetComponentDataFromEntity<IdentityComponent>(),
            RoadLaneIdentityFromEntity = GetComponentDataFromEntity<LaneIdentityComponent>(),
            roadEntities = entityArray,
            deltaTime = Time.deltaTime
        };

        JobHandle jobHandle = autoNaviationJob.Schedule(this, inputDeps);

        return jobHandle;
    }
}

The exception is:

InvalidOperationException: The writable NativeArray AutoNavigationJob.Iterator is the same NativeArray as AutoNavigationJob.Data.RoadLaneIdentityFromEntity, two NativeArrays may not be the same (aliasing).
Unity.Entities.JobForEachExtensions.Schedule (System.Void* fullData, Unity.Collections.NativeArray`1[T] prefilterData, System.Int32 unfilteredLength, System.Int32 innerloopBatchCount, System.Boolean isParallelFor, System.Boolean isFiltered, Unity.Entities.JobForEachExtensions+JobForEachCache& cache, System.Void* deferredCountData, Unity.Jobs.JobHandle dependsOn, Unity.Jobs.LowLevel.Unsafe.ScheduleMode mode) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/IJobForEach.cs:431)
Unity.Entities.JobForEachExtensions.ScheduleInternal_CCCCCC[T] (T& jobData, Unity.Entities.ComponentSystemBase system, Unity.Entities.EntityQuery query, System.Int32 innerloopBatchCount, Unity.Jobs.JobHandle dependsOn, Unity.Jobs.LowLevel.Unsafe.ScheduleMode mode) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/IJobForEach.gen.cs:6190)
Unity.Entities.JobForEachExtensions.Schedule[T] (T jobData, Unity.Entities.ComponentSystemBase system, Unity.Jobs.JobHandle dependsOn) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/IJobForEach.gen.cs:883)
ER3D_TrafficControlSystem.OnUpdate (Unity.Jobs.JobHandle inputDeps) (at Assets/HBC/Scripts/ER3D_TrafficControlSystem.cs:156)
Unity.Entities.JobComponentSystem.InternalUpdate () (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:933)
Unity.Entities.ComponentSystemBase.Update () (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:284)
Unity.Entities.ComponentSystemGroup.OnUpdate () (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystemGroup.cs:602)
UnityEngine.Debug:LogException(Exception)
Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/Stubs/Unity/Debug.cs:25)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystemGroup.cs:606)
Unity.Entities.ComponentSystem:InternalUpdate() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:800)
Unity.Entities.ComponentSystemBase:Update() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ComponentSystem.cs:284)
Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at Library/PackageCache/com.unity.entities@0.1.1-preview/Unity.Entities/ScriptBehaviourUpdateOrder.cs:144)

the error is showing you cant use ComponentDataFromEntity for a component if you also use that component in the same job query and (appears to be LaneIdentityComponent)

1 Like

OH! That is VERY helpful. Thank you VERY much.

Have a great weekend

@JamesWjRose / @thelebaron so how would one handle a case where this is a necessary thing to do?

Say I have a Unit component with a translation, and every Unit can have a Target component, which hold a Unit (basically every Unit can target any other Unit). So in a “MoveUntiToTargetSystem” I need to plug the Translation of the other (target’s) Entity and I am running into the [is](http://www.google.com/search?q=is+msdn.microsoft.com) the same NativeArray from above. Is this just fundamentally not possible, or do I maybe need to change my data layout?

Maybe I could first fetch all Units into a NativeArray and pass that to the job or something like that? :hushed:

Forgive me if I am over simplifying, but I want to make sure I get your point, my code for roads/autos should do the trick: https://github.com/Blissgig/Easy-Road-3D-ECS-Traffic/blob/master/ER3D_TrafficSystem.cs

In your situation you have an Auto “A” going to Point “B”, where point B could be a place or another “Auto” Is that right?

Then you would need a list, an Entity Query of a list of destination points, and inside your MoveUnitToTargetSystem you would loop through the Query for a destination identity that matches the current Unit’s identity. When they match, move to towards that point

If that sounds right, check my code, specification this one: https://github.com/Blissgig/Easy-Road-3D-ECS-Traffic/blob/master/ER3D_TrafficSystem.cs on this line: for (int i = 0; i < roadEntities.Length; i++) at line 90, and you can see I am matching multiple values to get the next set of points I need, in your case you only need ONE point, the matched Unit’s position.

1 Like

Personally I would start with just using ComponentDataFromEntity as it would give you the ability to set read/write the translation for the entity you are accessing and the other entity.
so -

    private struct SetPositionToTargetJob : IJobForEachWithEntity<Target>
    {
        [NativeDisableParallelForRestriction] public ComponentDataFromEntity<Translation> TranslationData;

        public void Execute(Entity entity, int index, ref Target target)
        {
            if (TranslationData.Exists(target.Entity))
            {
                // get position of target
                var targetPosition = TranslationData[target.Entity];
               
                // set position of current entity
                var newTranslation = TranslationData[entity];
                newTranslation.Value = targetPosition.Value;
                TranslationData[entity] = newTranslation;
            }
        }
    }

Thanks guys, I really appreciate the input!

For anyone running into the same bug and trying to understand, let me explain here what made it click for me:

The key problem was that my IJobForEachWithEntity job had a <Target, Translation, ...> as well as a ComponentDataFromEntity<Translation>. The compiler complains because Translation exists in both at the same time, causing an aliasing (“concurrency”) issue. The solution is simple, just remove the <... ,Translation, ...> component from the Job and access the Translation of the current Entity through TranslationData[entity], just as @thelebaron suggested :slight_smile:

3 Likes

The final code that does moving, rotating and deletion if two entities are nearby:

using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;

public class UnitMoveToTargetSystem : JobComponentSystem {
    private EndSimulationEntityCommandBufferSystem endSimulationEntityCommandBufferSystem;

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

    protected override JobHandle OnUpdate(JobHandle inputDependencies) {
        var job = new UnitMoveToTargetSystemJob {
            deltaTime = UnityEngine.Time.deltaTime,
            translationsFromEntity = GetComponentDataFromEntity<Translation>(false),
            entityCommandBuffer = endSimulationEntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent()
        };
        inputDependencies = job.Schedule(this, inputDependencies);
        endSimulationEntityCommandBufferSystem.AddJobHandleForProducer(inputDependencies);
        return inputDependencies;
    }

    [BurstCompile]
    [RequireComponentTag(typeof(UnitTag))]
    struct UnitMoveToTargetSystemJob : IJobForEachWithEntity<Target, MoveSpeed, Rotation> {
        [NativeDisableParallelForRestriction]
        public ComponentDataFromEntity<Translation> translationsFromEntity;

        public EntityCommandBuffer.Concurrent entityCommandBuffer;

        public float deltaTime;

        public void Execute(Entity entity, int index, [ReadOnly] ref Target target, [ReadOnly] ref MoveSpeed moveSpeed, ref Rotation rotation) {
            if (translationsFromEntity.Exists(target.targetEntity)) {
                var unitTranslation = translationsFromEntity[entity];
                var targetTranslation = translationsFromEntity[target.targetEntity];

                float3 targetDir = targetTranslation.Value - unitTranslation.Value;
                var lookDirection = targetDir;
                lookDirection.y = 0;
                var smoothedRotation = math.slerp(rotation.Value, quaternion.LookRotationSafe(lookDirection, math.up()), 1f * 4 * deltaTime);
                rotation.Value = smoothedRotation;
                unitTranslation.Value += targetDir * moveSpeed.moveSpeed * deltaTime;
                translationsFromEntity[entity] = unitTranslation;

                var distanceSquared = math.distancesq(unitTranslation.Value, targetTranslation.Value);
                if (distanceSquared < 1f) {
                    /* Close to target, destroy it: */
                    entityCommandBuffer.DestroyEntity(index, target.targetEntity);
                    entityCommandBuffer.RemoveComponent(index, entity, typeof(Target));
                }
            } else {
                /* Target Entity already destroyed: */
                entityCommandBuffer.RemoveComponent(index, entity, typeof(Target));
            }
        }
    }
}

It’s a little messy (still trying to figure out proper ECS variable naming), but I figured I’d post it here before I forget :slight_smile:

1 Like