Scheduling Multiple Parallel Jobs with different SharedComponentFilter leads to Dependency errors

In the OnUpdate Method i start multiple Jobs of the same Type "PlexusVertexMovementJob". (when only 1 job is started everything works fine, but when its multiple then the errors occur)

every Jobs Query only differes by a SharedComponentFilter . so that every job has its own set of entities and no Job should end up with writing to the same Compontent Data in parrallel.

I am not sure what triggers the problem, but the error mesages point to dependency issues. but i even try to set the dependencies so that the system will wait for all jobs to finish, but not all jobs have to wait for each other.
short version, see full code more below

NativeArray<JobHandle> jobHandles = new NativeArray<JobHandle>(plexusObjectEntities.Length, Allocator.Temp);
for (int i =0; i < plexusObjectEntities.Length; i++) {
    //...
    jobHandles[i] = job.ScheduleParallel(plexusPointsByPlexusObjectIdEntityQuery, state.Dependency);
}
JobHandle combinedJobsDependecies = JobHandle.CombineDependencies(jobHandles);
state.Dependency = JobHandle.CombineDependencies(state.Dependency, combinedJobsDependecies);

First Error

Second Error

using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;


namespace WireframePlexus {

[UpdateAfter(typeof(SyncEntityToGameobjectPositionSystem))]
    public partial struct VertexMoveSystem : ISystem {

        EntityQuery plexusObjectEntityQuery;
        EntityQuery plexusPointsByPlexusObjectIdEntityQuery;

        public void OnCreate(ref SystemState state) {
            state.RequireForUpdate<VertexMovementData>();
            plexusObjectEntityQuery = new EntityQueryBuilder(Allocator.Temp).WithAll<PlexusObjectData, LocalTransform, LocalToWorld>().Build(ref state);
            plexusPointsByPlexusObjectIdEntityQuery = new EntityQueryBuilder(Allocator.Temp).WithAll<LocalTransform, VertexMovementData, LocalToWorld, PlexusObjectIdData>().Build(ref state);
        }

        public void OnUpdate(ref SystemState state) {
            var plexusObjectEntities = plexusObjectEntityQuery.ToEntityArray(Allocator.Temp);
           
            NativeArray<JobHandle> jobHandles = new NativeArray<JobHandle>(plexusObjectEntities.Length, Allocator.Temp);

            for (int i =0; i < plexusObjectEntities.Length; i++) {
                var plexusObjectData = state.EntityManager.GetComponentData<PlexusObjectData>(plexusObjectEntities[i]);
                var plexusObjectTransform = state.EntityManager.GetComponentData<LocalTransform>(plexusObjectEntities[i]);
                plexusPointsByPlexusObjectIdEntityQuery.ResetFilter();
                plexusPointsByPlexusObjectIdEntityQuery.SetSharedComponentFilter(new PlexusObjectIdData { ObjectId = plexusObjectData.WireframePlexusObjectId });

                PlexusVertexMovementJob job = new PlexusVertexMovementJob {
                    DeltaTime = SystemAPI.Time.DeltaTime,
                    PointPositions = plexusObjectData.VertexPositions,
                    MaxVertexMoveDistance = plexusObjectData.MaxVertexMoveDistance,
                    MaxVertexMovementSpeed = plexusObjectData.MaxVertexMoveSpeed,
                    MinVertexMovementSpeed = plexusObjectData.MinVertexMoveSpeed,
                    CameraWolrdPos = (float3)Camera.main.transform.position,
                    ParentRotation = plexusObjectTransform.Rotation
                };

                jobHandles[i] = job.ScheduleParallel(plexusPointsByPlexusObjectIdEntityQuery, state.Dependency);
            }
           
            JobHandle combinedJobsDependecies = JobHandle.CombineDependencies(jobHandles);
            state.Dependency = JobHandle.CombineDependencies(state.Dependency, combinedJobsDependecies);
        }

        [BurstCompile]
        public partial struct PlexusVertexMovementJob : IJobEntity {


            [ReadOnly] public quaternion ParentRotation;
            [ReadOnly] public float3 CameraWolrdPos;
            [ReadOnly] public float DeltaTime;
            [ReadOnly] public float MinVertexMovementSpeed;
            [ReadOnly] public float MaxVertexMovementSpeed;
            [ReadOnly] public float MaxVertexMoveDistance;
            [NativeDisableContainerSafetyRestriction] public NativeArray<float3> PointPositions;

            public void Execute(ref LocalTransform localTransform, ref VertexMovementData movementData, in LocalToWorld localToWorld) {

               
                if ((MinVertexMovementSpeed == 0 && MaxVertexMovementSpeed == 0) || MaxVertexMoveDistance == 0) {
                    localTransform.Position = movementData.Position;
                    PointPositions[movementData.PointId] = localTransform.Position;

                } else {
                    if (movementData.CurrentMovementDuration <= 0) {
                        localTransform = localTransform.WithPosition(movementData.PositionTarget);
                        movementData.MoveSpeed = movementData.Random.NextFloat(MinVertexMovementSpeed, MaxVertexMovementSpeed);
                        movementData.PositionOrigin = movementData.PositionTarget;
                        movementData.PositionTarget = movementData.Position + movementData.Random.NextFloat3(-MaxVertexMoveDistance, MaxVertexMoveDistance);
                        movementData.CurrentMovementDuration = math.distance(movementData.PositionTarget, movementData.PositionOrigin) / movementData.MoveSpeed;
                        movementData.TotalMovementDuration = movementData.CurrentMovementDuration;
                    } else {
                        movementData.CurrentMovementDuration -= DeltaTime;
                        float interpolationPercent = 1 - (movementData.CurrentMovementDuration / movementData.TotalMovementDuration);
                        float3 newPosition = math.lerp(movementData.PositionOrigin, movementData.PositionTarget, interpolationPercent);
                        localTransform = localTransform.WithPosition(newPosition);
                    }
                    PointPositions[movementData.PointId] = localTransform.Position;
                }

                // make vertex face camera
                float3 relativePos = CameraWolrdPos - localToWorld.Position;
                // quaternion.LookRotationSafe cannot handle vectors that are collinear so for the case of the edge faceing directly up or down hardcoded a 90 degree rotation
                if (relativePos.y == 1 || relativePos.y == -1) {
                    localTransform.Rotation = math.mul(quaternion.RotateX(math.PIHALF) , math.inverse(ParentRotation));
                } else {
                    quaternion end = quaternion.LookRotationSafe(-relativePos, math.up());
                    localTransform.Rotation = math.mul(end.value, math.inverse(ParentRotation));
                }

            }
        }
    }
}

after trying out some stuff this line (where the error logs also point to) is the one causing the trouble. Deleting it removes the errors.
var plexusObjectTransform = state.EntityManager.GetComponentData<LocalTransform>(plexusObjectEntities*);*
but i am still trying to figure out why

Your IJobEntity uses ref LocalTransform, and between each job you are also accessing LocalTransform from EntityManager on the main thread. You can’t do that.

1 Like

thanks for answering

the LocalTransform access on the MainThread with the EntityManager and the one on the IJobEntity, will happen on different Entities with different Queries, does this make a difference?

i changed the code to generate the jobs first with the data from the LocalTransform and then schedule the jobs later and now it works, thank you.

        public void OnUpdate(ref SystemState state) {
            var plexusObjectEntities = plexusObjectEntityQuery.ToEntityArray(Allocator.Temp);
            NativeArray<PlexusVertexMovementJob> jobs = new NativeArray<PlexusVertexMovementJob>(plexusObjectEntities.Length, Allocator.TempJob);

            for (int i =0; i < plexusObjectEntities.Length; i++) {
                var plexusObjectTransform = state.EntityManager.GetComponentData<LocalTransform>(plexusObjectEntities[i]).Rotation;
                var plexusObjectData = state.EntityManager.GetComponentData<PlexusObjectData>(plexusObjectEntities[i]);
                plexusPointsByPlexusObjectIdEntityQuery.ResetFilter();
                plexusPointsByPlexusObjectIdEntityQuery.SetSharedComponentFilter(new PlexusObjectIdData { ObjectId = plexusObjectData.WireframePlexusObjectId });

                jobs[i] = new PlexusVertexMovementJob {
                    DeltaTime = SystemAPI.Time.DeltaTime,
                    PointPositions = plexusObjectData.VertexPositions,
                    MaxVertexMoveDistance = plexusObjectData.MaxVertexMoveDistance,
                    MaxVertexMovementSpeed = plexusObjectData.MaxVertexMoveSpeed,
                    MinVertexMovementSpeed = plexusObjectData.MinVertexMoveSpeed,
                    CameraWolrdPos = (float3)Camera.main.transform.position,
                    ParentRotation = plexusObjectTransform.value,
                };
            }
            foreach (var job in jobs) {
                job.ScheduleParallel(plexusPointsByPlexusObjectIdEntityQuery);
            }
        }

Currently the safety system isn’t smart enough to know the difference. As far as it is concerned, there could be an entity that matches both queries. There are rumors floating around that this may change in Entities 2.0. But for now, assume all that the safety system cares about is the component type.

1 Like