Perf Question on calling job.Complete() (Fixing Entities 0.17 breaking change)

Hi, just upgraded my learning project to 0.17 and have a single breaking change

I am getting this error: InvalidOperationException: The previously scheduled job EnemySystem:OnUpdate_LambdaJob0 reads from the Unity.Collections.NativeHashMap

InvalidOperationException: The previously scheduled job EnemySystem:OnUpdate_LambdaJob0 reads from the Unity.Collections.NativeHashMap2[Unity.Entities.Entity,System.Int32] OnUpdate_LambdaJob0.JobData.raycaster.pw.CollisionWorld.EntityBodyIndexMap. You must call JobHandle.Complete() on the job EnemySystem:OnUpdate_LambdaJob0, before you can write to the Unity.Collections.NativeHashMap2[Unity.Entities.Entity,System.Int32] safely.
Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at :0)
Unity.Collections.NativeHashMap2[TKey,TValue].CheckWrite () (at Library/PackageCache/com.unity.collections@0.15.0-preview.21/Unity.Collections/NativeHashMap.cs:554) Unity.Collections.NativeHashMap2[TKey,TValue].Clear () (at Library/PackageCache/com.unity.collections@0.15.0-preview.21/Unity.Collections/NativeHashMap.cs:194)
Unity.Physics.CollisionWorld.Reset (System.Int32 numStaticBodies, System.Int32 numDynamicBodies) (at Library/PackageCache/com.unity.physics@0.6.0-preview.3/Unity.Physics/Collision/World/CollisionWorld.cs:53)
Unity.Physics.PhysicsWorld.Reset (System.Int32 numStaticBodies, System.Int32 numDynamicBodies, System.Int32 numJoints) (at Library/PackageCache/com.unity.physics@0.6.0-preview.3/Unity.Physics/Dynamics/World/PhysicsWorld.cs:41)
Unity.Physics.Systems.BuildPhysicsWorld.OnUpdate () (at Library/PackageCache/com.unity.physics@0.6.0-preview.3/Unity.Physics/ECS/Base/Systems/BuildPhysicsWorld.cs:122)
Unity.Entities.SystemBase.Update () (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/SystemBase.cs:412)
Unity.Entities.ComponentSystemGroup.UpdateAllSystems () (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:472)
UnityEngine.Debug:LogException(Exception)
Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/Stubs/Unity/Debug.cs:19)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:477)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:423)
Unity.Entities.ComponentSystem:Update() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystem.cs:114)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:472)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:417)
Unity.Entities.ComponentSystem:Update() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystem.cs:114)
Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ScriptBehaviourUpdateOrder.cs:333)

I think it makes sense. I think it is complaining that my Job is still running at the time the next frame kicks off another copy of the same job. I am doing a physics raycast in my job like so:

using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Physics;
using Unity.Physics.Systems;

[UpdateAfter(typeof(EndFramePhysicsSystem))]
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
public class EnemySystem : SystemBase
{

    protected override void OnUpdate()
    {
        //this code doesn't "work" anymore, it just is the mimum to reproduce the runtime error.
        var pw = World.GetOrCreateSystem<BuildPhysicsWorld>().PhysicsWorld;
      
        Entities
            .WithReadOnly(pw)
            .ForEach((int nativeThreadIndex, ref Movable mov, ref Enemy enemy, in Translation trans) =>
        {
            var targetDir = new float3(0, 0, -1);
            var ray = new RaycastInput()
            {
                Start = trans.Value,
                End = trans.Value + (targetDir * 0.9f),
                Filter = new CollisionFilter()
                {
                    GroupIndex = 0,
                    BelongsTo = 1u << 1, //bitmasks, so using bitshifts
                    CollidesWith = 1u << 2
                }
            };

            var hit = pw.CastRay(ray, out Unity.Physics.RaycastHit closestHit);

            if (hit)
            {
                mov.direction = targetDir;
            }
            enemy.lastPos = trans.Value;
        }).ScheduleParallel();
    }
}

In Entities 0.16 I didn’t get an error on that. In 0.17 I do. I can understand that if that Job takes multiple frames to complete, the raycast is going to be innacurate, plus that there will be multiple runs of this job (1 each frame) stacking on top of eachother.

Then “solution” is simple, add
this.Dependency.Complete(); to the bottom.

My question is: Adding the .Complete() call will stall the frame completion until this Job completes, right? Or does it stall starting of other Jobs that need to be enqueued this frame? If this was “normal” c# then it would be the latter (bad) case. But as this is burst compiled I hope this is smart enough to init other jobs while this is completing.

EDIT: updated code to mimum repro

That’s incorrect. If you have built-in Dependency and not track dependencies manually by yourself then before OnUpdate will be called, BeforeOnUpdate of every system completes its previous frame dependency, exactly for exclude jobs stacking and infinite growing chains.
Probably your problem is if you’re using DOTS Physics you should read and write system dependency in physics dependency chain, don’t remember exactly name of physics world method as we don’t use it and has our own physics, but you can search in DOTS Physics forum.

Main thread will wait competition of all jobs in current dependency chain on which job handle you called complete, for example if you have chain with jobs A B and C and another chain with D and E, and will call Complete on D-E chain main thread will stall and wait until D and E jobs completed, BUT ABC chain will still work in parallel and will continue even after DE competition, as they independent, while its chain not being completed by user or one of BeforeOnUpdate or one of Synch Points

1 Like

@eizenhorn thank you for the explanation. I thought I had the proper FixedStepSimulationSystemGroup group setup. I simplified the code to the mimimum and pasted it entirely to show the error. Any idea what I’m doing wrong here? It seems that physics is not running lockstep with this job.

using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Physics;
using Unity.Physics.Systems;

[UpdateAfter(typeof(EndFramePhysicsSystem))]
[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
public class EnemySystem : SystemBase
{

    protected override void OnUpdate()
    {
        //this code doesn't "work" anymore, it just is the mimum to reproduce the runtime error.
        var pw = World.GetOrCreateSystem<BuildPhysicsWorld>().PhysicsWorld;
     
        Entities
            .WithReadOnly(pw) 
            .ForEach((int nativeThreadIndex, ref Movable mov, ref Enemy enemy, in Translation trans) =>
        {
            var targetDir = new float3(0, 0, -1);
            var ray = new RaycastInput()
            {
                Start = trans.Value,
                End = trans.Value + (targetDir * 0.9f),
                Filter = new CollisionFilter()
                {
                    GroupIndex = 0,
                    BelongsTo = 1u << 1, //bitmasks, so using bitshifts
                    CollidesWith = 1u << 2
                }
            };

            var hit = pw.CastRay(ray, out Unity.Physics.RaycastHit closestHit);

            if (hit)
            {
                mov.direction = targetDir;
            }
            enemy.lastPos = trans.Value;
        }).ScheduleParallel();
    }
}

InvalidOperationException: The previously scheduled job EnemySystem:OnUpdate_LambdaJob0 reads from the Unity.Collections.NativeHashMap OnUpdate_LambdaJob0.JobData.pw.CollisionWorld.EntityBodyIndexMap. You must call JobHandle.Complete() on the job EnemySystem:OnUpdate_LambdaJob0, before you can write to the Unity.Collections.NativeHashMap safely.

InvalidOperationException: The previously scheduled job EnemySystem:OnUpdate_LambdaJob0 reads from the Unity.Collections.NativeHashMap2[Unity.Entities.Entity,System.Int32] OnUpdate_LambdaJob0.JobData.pw.CollisionWorld.EntityBodyIndexMap. You must call JobHandle.Complete() on the job EnemySystem:OnUpdate_LambdaJob0, before you can write to the Unity.Collections.NativeHashMap2[Unity.Entities.Entity,System.Int32] safely.
Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion (Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle handle) (at :0)
Unity.Collections.NativeHashMap2[TKey,TValue].CheckWrite () (at Library/PackageCache/com.unity.collections@0.15.0-preview.21/Unity.Collections/NativeHashMap.cs:554) Unity.Collections.NativeHashMap2[TKey,TValue].Clear () (at Library/PackageCache/com.unity.collections@0.15.0-preview.21/Unity.Collections/NativeHashMap.cs:194)
Unity.Physics.CollisionWorld.Reset (System.Int32 numStaticBodies, System.Int32 numDynamicBodies) (at Library/PackageCache/com.unity.physics@0.6.0-preview.3/Unity.Physics/Collision/World/CollisionWorld.cs:53)
Unity.Physics.PhysicsWorld.Reset (System.Int32 numStaticBodies, System.Int32 numDynamicBodies, System.Int32 numJoints) (at Library/PackageCache/com.unity.physics@0.6.0-preview.3/Unity.Physics/Dynamics/World/PhysicsWorld.cs:41)
Unity.Physics.Systems.BuildPhysicsWorld.OnUpdate () (at Library/PackageCache/com.unity.physics@0.6.0-preview.3/Unity.Physics/ECS/Base/Systems/BuildPhysicsWorld.cs:122)
Unity.Entities.SystemBase.Update () (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/SystemBase.cs:412)
Unity.Entities.ComponentSystemGroup.UpdateAllSystems () (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:472)
UnityEngine.Debug:LogException(Exception)
Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/Stubs/Unity/Debug.cs:19)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:477)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:423)
Unity.Entities.ComponentSystem:Update() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystem.cs:114)
Unity.Entities.ComponentSystemGroup:UpdateAllSystems() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:472)
Unity.Entities.ComponentSystemGroup:OnUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystemGroup.cs:417)
Unity.Entities.ComponentSystem:Update() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ComponentSystem.cs:114)
Unity.Entities.DummyDelegateWrapper:TriggerUpdate() (at Library/PackageCache/com.unity.entities@0.17.0-preview.41/Unity.Entities/ScriptBehaviourUpdateOrder.cs:333)

EDIT: Hmm, reading that stack trace in the spoiler, it quite clearly states that the prior instance of this job is still running. Am I doing something that would cause this to happen?

It’s not dependency management it’s only systems place/order/time when it will be called in main thread.
Read this part of documentation https://docs.unity3d.com/Packages/com.unity.physics@0.6/api/Unity.Physics.Systems.BuildPhysicsWorld.html
Especially methods like AddInputDependency and
GetOutputDependency and search by them in DOTS Physics forum

1 Like

Thank you, did some searches… there’s a lot of stuff to read I’ll update here if any of it works!

continued in the Physics sub-forum: Problem updating to 0.6: Raycast in System Entities.ForEach() - Unity Engine - Unity Discussions