#help understanding "The previously scheduled job...reads from Unity.Transforms.LocalToWorld" errors

1 messages · Page 1 of 1 (latest)

nocturne crow
#

hi there, i'm new to entities/jobs and trying to understand the cause of the following error:

InvalidOperationException: The previously scheduled job FireArrows:ReadTargetPosJob reads from the ComponentTypeHandle<Unity.Transforms.LocalToWorld> ReadTargetPosJob.JobData.__TypeHandle.__Unity_Transforms_LocalToWorld_RO_ComponentTypeHandle. You are trying to schedule a new job LocalToWorldSystem:ComputeWorldSpaceLocalToWorldJob, which writes to the same ComponentTypeHandle<Unity.Transforms.LocalToWorld> (via ComputeWorldSpaceLocalToWorldJob.JobData.LocalToWorldTypeHandleRW). To guarantee safety, you must include FireArrows:ReadTargetPosJob as a dependency of the newly scheduled job.

i understand that there are potential synchronization issues between me reading LocalToWorld in a job and the built-in systems writing to it during ComputeWorldSpaceLocalToWorldJob, but i don't understand why the error is thrown when it is.

  • i've read the explanation for this error in the common error messages.
  • i believe i'm wiring up state.Dependency correctly to capture all scheduled jobs.
  • i've tried other flavors specify the dependencies in state.Dependency, manually combining them again via JobHandle.combineDependencies to no avail.

so i feel like i must have a gap in my understand of how these system operate, or i'm missing something more fundamental.

some abbreviated examples, to follow. happy to post more context if desired or needed.

#

this snippet does not throw the error:

[BurstCompile]
[GenerateTestsForBurstCompatibility]
[RequireMatchingQueriesForUpdate]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(TransformSystemGroup))]
partial struct FireArrows: ISystem {
    ...
    public void OnUpdate(ref SystemState state) {
        ...
        // initialize per-target data
        var readTargetPosJob = new ReadTargetPosJob {
            TargetPos = targetPos,
        };
    
        var readTargetPosHandle = readTargetPosJob.ScheduleParallel(
            squadTargetQuery,
            state.Dependency
        );
    
        state.Dependency = readTargetPosHandle;
    }
}

[BurstCompile]
[GenerateTestsForBurstCompatibility]
partial struct ReadTargetPosJob: IJobEntity {
    /// the position of each target
    public NativeArray<float3> TargetPos;

    // -- IJobEntity --
    void Execute([EntityIndexInQuery] int i, in LocalToWorld ltw) {
        TargetPos[i] = ltw.Position;
    }
}
#

but this one does throw the error:

[BurstCompile]
[GenerateTestsForBurstCompatibility]
[RequireMatchingQueriesForUpdate]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(TransformSystemGroup))]
partial struct FireArrows: ISystem {
    ...
    public void OnUpdate(ref SystemState state) {
          ...
          // initialize per-target data
          var readTargetPosJob = new ReadTargetPosJob {
              TargetPos = targetPos,
          };
    
          var readTargetPosHandle = readTargetPosJob.ScheduleParallel(
              squadTargetQuery,
              state.Dependency
          );
    
          var readTargetDirJob = new ReadTargetDirJob {
              TargetDir = targetPos,
              TargetPos = targetPos,
          };
    
          var readTargetDirHandle = readTargetDirJob.ScheduleParallel(
              squadQuery,
              readTargetPosHandle
          );
    
          state.Dependency = readTargetDirHandle;
    }  
}

[BurstCompile]
[GenerateTestsForBurstCompatibility]
partial struct ReadTargetPosJob: IJobEntity {
    /// the position of each target
    public NativeArray<float3> TargetPos;

    // -- IJobEntity --
    void Execute([EntityIndexInQuery] int i, in LocalToWorld ltw) {
        TargetPos[i] = ltw.Position;
    }
}


[BurstCompile]
[GenerateTestsForBurstCompatibility]
partial struct ReadTargetDirJob: IJobEntity {
    /// the position of each target
    public NativeArray<float3> TargetDir;

    /// the position of each target
    [ReadOnly]
    public NativeArray<float3> TargetPos;

    // -- IJobEntity --
    void Execute([EntityIndexInQuery] int i, in Squad squad) {
        TargetDir[i] = math.normalizesafe(TargetPos[i] - squad.Center);
    }
}
whole verge
#

do you have a second exception

#

that is thrown after this

nocturne crow
#

the other exception thrown (or rather error logged) is:

The system Archers.FireArrows reads Unity.Transforms.LocalToWorld via FireArrows:ReadTargetPosJob but that type was not assigned to the Dependency property. To ensure correct behavior of other systems, the job or a dependency must be assigned to the Dependency property before returning from the OnUpdate method.
#

but that's it, it logs that pair every update.

whole verge
#

ok so yeah that is the exception caused by the first exception - i'm not seeing anything wrong from your code though

#

do you have any ohter systems running between these 2 that could have somehow dropped a handle

#

what happens here

#

not using state.dependency or something right

nocturne crow
#

i'll paste that in a sec

#

there are a few other systems, but they all use state.Dependency in this same way.

one of the systems is a heavily adapted version of the boids sample, and i had a similar error in that system that i kind of hacked around by changing a job that wrote to LocalToWorld to instead write to a custom component's Center property, which is then synced to LocalToWorld in a job scheduled later:


[BurstCompile]
[GenerateTestsForBurstCompatibility]
[RequireMatchingQueriesForUpdate]
[UpdateAfter(typeof(MoveSquadMembers))]
public partial struct MoveSquad: ISystem {
    // -- ISystem --
    public void OnUpdate(ref SystemState state) {
        // build entity queries
        var squadQuery = SystemAPI
            .QueryBuilder()
            .WithAll<Squad>()
            .WithAllRW<LocalToWorld>()
            .Build();

        // update squad data
        var moveSquadJob = new MoveSquadJob {
        };

        var moveSquadHandle = moveSquadJob.ScheduleParallel(
            squadQuery,
            state.Dependency
        );

        // wire the chain of work to the current dependency to dispose on completion
        state.Dependency = moveSquadHandle;
    }

    // -- jobs --
    [BurstCompile]
    partial struct MoveSquadJob: IJobEntity {
        // -- IJobEntity --
        static void Execute(in Squad squad, ref LocalToWorld ltw) {
            ltw.Value = float4x4.TRS(
                squad.Center,
                quaternion.identity,
                1f
            );
        }
    }
}
whole verge
#

nothing looks suss in what you've posted

nocturne crow
# whole verge what happens here

that looks like this (i flattened out the loop / filter over the shared components for testing):

// build entity queries
var squadQuery = SystemAPI
    .QueryBuilder()
    .WithAllRW<Squad>()
    .Build();

var squadMemberQuery = SystemAPI
    .QueryBuilder()
    .WithAll<SquadMember, SquadAttacking>()
    .WithAllRW<LocalTransform>()
    .Build();

var squadTargetQuery = SystemAPI
    .QueryBuilder()
    .WithAll<SquadTarget, LocalToWorld>()
    .Build();

// get globals
var world = state.WorldUnmanaged;
var delta = math.min(SystemAPI.Time.DeltaTime, 0.05f);

// group entities by squad
state.EntityManager.GetAllUniqueSharedComponents(
    out NativeList<SquadMember> squads,
    world.UpdateAllocator.ToAllocator
);

var squad = squads[0];

// if this squad is empty, skip it
var squadSize = squadMemberQuery.CalculateEntityCount();
if (squadSize == 0) {
    squadMemberQuery.ResetFilter();
    // continue;
    return;
}

// allocate collections for subsequent jobs
var targetPos = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(1, ref world.UpdateAllocator);
var targetDir = CollectionHelper.CreateNativeArray<float3, RewindableAllocator>(1, ref world.UpdateAllocator);
nocturne crow
#

there was in fact a silly bug in the snippet that produced the error, and another error in the log that was just difficult to see:

InvalidOperationException: The writeable Unity.Collections.NativeArray`1[Unity.Mathematics.float3] ReadTargetDirJob.JobData.TargetDir is the same Unity.Collections.NativeArray`1[Unity.Mathematics.float3] as ReadTargetDirJob.JobData.TargetPos, two containers may not be the same (aliasing).

i'll be a bit more assiduous in looking for similarish appearing errors in the future:

var readTargetDirJob = new ReadTargetDirJob {
    TargetDir = targetPos,
    TargetPos = targetPos,
};

🤦