Raycast does not hit same frame Colliders even after BuildPhysicsWorld.FinalJobHandle.Complete()

Edit: This thread describes a different problem than it’s sibling, (Case 1287877) Raycasts not hitting anything - UnityEngine.Physics.Raycast does work! but if you run into one of the two, you should also read the other, as they both describe easy-to-run into scenarios with Dots, Physics and procedural generated colliders and their race conditions.

I create a test collider and then try to raycast it. The first time the raycast always misses. Anyone has any ideas why?

EntityManager      em                = World.DefaultGameObjectInjectionWorld.EntityManager;
var                buildPhysicsWorld = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<BuildPhysicsWorld>();
ref PhysicsWorld   physicsWorld      = ref buildPhysicsWorld.PhysicsWorld;
ref CollisionWorld collisionWorld    = ref physicsWorld.CollisionWorld;
// Sanity check to ensure it hits
var quad     = em.CreateEntity();
var vertices = new NativeList<float3>(Allocator.Temp);
vertices.Add(new float3(-100, 0, -100));
vertices.Add(new float3(100,  0, -100));
vertices.Add(new float3(-100, 0, 100));
vertices.Add(new float3(100,  0, 100));
var triangles = new NativeList<int3>(Allocator.Temp);
triangles.Add(new int3(0, 2, 1));
triangles.Add(new int3(2, 3, 1));
em.AddComponent<PhysicsCollider>(quad);
var physicsCollider = new PhysicsCollider {
Value = MeshCollider.Create(vertices, triangles)
};
vertices.Dispose();
triangles.Dispose();
em.AddComponent<Unity.Transforms.LocalToWorld>(quad);
em.SetComponentData(quad, physicsCollider);
em.SetComponentData(quad, new LocalToWorld {Value = float4x4.identity});
em.SetName(quad, "quad");

var quadTestRaycastInput = new RaycastInput {
   Start  = new float3(0, 10,  0),
   End    = new float3(0, -10, 0),
   Filter = CollisionFilter.Default
};
if (collisionWorld.CastRay(quadTestRaycastInput,
                          out Unity.Physics.RaycastHit quadTestHit))
{
   this.LogDebug("Hit quad (forward):" + quadTestHit.Fraction);
   Debug.DrawLine((Vector3) quadTestRaycastInput.Start, (Vector3) quadTestHit.Position, Color.green, 5);
}
else
{
   this.LogDebug("missed quad");
   Debug.DrawLine((Vector3) quadTestRaycastInput.Start, (Vector3) quadTestRaycastInput.End, Color.red, 5);
}

It seems like the first time the collider does not exist yet.

You are right, in order for CollisionWorld to “see” the changes in component data (a new PhysicsCollider for example), you would need to wait for BuildPhysicsWorld system to execute once after the change. That syste, updates the CollisionWorld based on the component data that changed since the last run…

1 Like

@petarmHavok Thanks! However even after calling buildPhysicsWorld.FinalJobHandle.Complete() after adding the PhysicsCollider it still does only work on a later frame.

// 1. Get systems
EntityManager      em                = World.DefaultGameObjectInjectionWorld.EntityManager;
var                buildPhysicsWorld = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<BuildPhysicsWorld>();
ref PhysicsWorld   physicsWorld      = ref buildPhysicsWorld.PhysicsWorld;
ref CollisionWorld collisionWorld    = ref physicsWorld.CollisionWorld;

// 2. Create Entity
...

// 3. Complete BuildPhysicsWorld
buildPhysicsWorld.FinalJobHandle.Complete(); // wait until the physics world is build

// 4. Raycast via CSharp/MonoBehaviour
if(collisionWorld.CastRay(input, out hit))
... does not hit on first frame ...

// 5. Raycast via Job & dependency
JobHandle raycastJob = job.Schedule(2, 64, buildPhysicsWorld.FinalJobHandle);
raycastJob.Complete();
... does not hit on first frame ...

What bothers me, is that the job variant has the buildPhysicsWorld explicitly as dependency and therefore should run after the physics world is up to date.

Furthermore the MonoBehaviour variant does hit the back fo the collider, while the job variant does not

var forward = new RaycastInput {
Start  = new float3(0, 10,  0),
End    = new float3(0, -10, 0),
Filter = CollisionFilter.Default
};
// does hit from a MonoBevaviour and from a burstified Job
var backward = new RaycastInput {
Start = new float3(0, -10, 0),
End    = new float3(0, 10,  0),
Filter = CollisionFilter.Default
};
// does hit from a MonoBevaviour but not from a burstified Job

Seems I am running into the very same again… How to ensure Physics World is updated within the same frame? . This is really grinding my gears, it can’t be so difficult to add a collider and tell the job to run after the BuildPhysicsWorld system has updated …

It worked back then and seems not to work in 2020.1.0b9 with the new packages

"com.unity.entities": "0.10.0-preview.6",
"com.unity.jobs": "0.2.9-preview.15",
"com.unity.physics": "0.3.2-preview",
"com.unity.burst": "1.3.0-preview.13"

I might have not been clear enough on this, but simply calling Complete() on BuildPhysicsWorld probably won’t work, because the system might have already started with stale data (without your new colliders). What you want to do is actually add the collider before BuildPhysicsWorld, so that it picks up your new collider. Then you don’t need to add the Complete() in your code, just add the colliders and let BuildPhysicsWorld do the trick. Then, after BuildPhysicsWorld you can safely ray cast and it will hit. Would that work for you?

2 Likes

@petarmHavok … it makes incredibly much sense reading it this way :smile:

1 Like

I was just looking at a similar issue, where one system raycasts and another system acts on the results of the raycast; it looks like the second system always has to wait until the NEXT frame after the raycast, or else it won’t play nicely with BuildPhysicsWorld?

@MNNoxMortem have you looked at MousePickBehaviour.cs (link) in the UnityPhysicsSamples project? I think you just need the [UpdateAfter] and [UpdateBefore] attributes on your system to make sure your entities are created at the proper time. And in some cases you also need to wait until the next frame depending on what you are trying to do.

However there still seems to be something strange about the BuildPhysicsWorld dependencies. I don’t totally understand this sample code, maybe someone else can explain it:

Pick : IJob

  • Execute()

  • raycasts against BuildPhysicsWorld.PhysicsWorld.CollisionWorld and stores hit info in a NativeArray

MousePickSystem:

  • [UpdateAfter(typeof(BuildPhysicsWorld))]

  • [UpdateBefore(typeof(EndFramePhysicsSystem))]

  • OnUpdate()

  • schedules a Pick job with BuildPhysicsWorld.FinalJobHandle as a dependency

  • keeps a reference to that job called PickJobHandle

  • calls PickJobHandle.Complete()

MouseSpringSystem:

  • [UpdateBefore(typeof(BuildPhysicsWorld))]

  • OnUpdate()

  • calls MousePickSystem.PickJobHandle.Complete() (<-- what?)

  • takes the NativeArray from the Pick job and calculates some physics with it

So… MouseSpringSystem updates BEFORE BuildPhysicsWorld, and MousePickSystem updates AFTER BuildPhysicsWorld … but MouseSpringSystem depends on the results from MousePickSystem. How is this possible? I’m not good enough at debugging ECS to see the full picture yet, but MouseSpringSystem must actually be using the data that MousePickSystem created on the previous frame?

5921519--632561--job_order.png

Here’s the code reduced to just the important bits:

[UpdateAfter(typeof(BuildPhysicsWorld))]
[UpdateBefore(typeof(EndFramePhysicsSystem))]
public class MousePickSystem : SystemBase
{
    public NativeArray<SpringData> SpringDataArray;
    public JobHandle? PickJobHandle;
    private BuildPhysicsWorld _physicsWorld;

    public struct SpringData { /* some data */ }

    protected override void OnCreate()
    {
        _physics = World.GetOrCreateSystem<BuildPhysicsWorld>();
        SpringDataArray = new NativeArray<SpringData>(1, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
    }

    protected override void OnUpdate()
    {
        if (Input.GetMouseButtonDown(0))
        {
            // Schedule picking job, after the collision world has been built
            JobHandle jobHandle = new PickJob
            {
                SpringDataArray = SpringDataArray
            }.Schedule(JobHandle.CombineDependencies(Dependency, _physicsWorld.FinalJobHandle));

            PickJobHandle = jobHandle;

            jobHandle.Complete();
        }
    }

    struct PickJob : IJob
    {
        public NativeArray<SpringData> SpringDataArray;

        public void Execute()
        {
            /* if (DoRaycast()) { SpringDataArray[0] = new SpringData() { data = hit info }; } */
        }
    }
}

[UpdateBefore(typeof(BuildPhysicsWorld))]
public class MouseSpringSystem : SystemBase
{
    MousePickSystem _pickSystem;

    protected override void OnCreate()
    {
        _pickSystem = World.GetOrCreateSystem<MousePickSystem>();
    }

    protected override void OnUpdate()
    {
        // If there's a pick job, wait for it to finish
        if (_pickSystem.PickJobHandle != null)
        {
            var pickJob = JobHandle.CombineDependencies(Dependency, _pickSystem.PickJobHandle.Value);
            pickJob.Complete();
        }

        var springData = _pickSystem.SpringDataArray[0];
        /* do stuff with the data */
    }
}

EDIT: After some more experimenting, it turns out the dependencies in Unity’s example are a bit sloppy. MouseSpringSystem doesn’t need the MousePickSystem.PickJobHandle at all. You can just do Dependency.Complete() at the top of MouseSpringSystem.OnUpdate() and the example works exactly the same, which is what I would have expected.

But… logically it should be updating AFTER the raycasts on the same frame… maybe it actually is, because of some other dependency? But if you add [UpdateAfter(typeof(MousePickSystem)] to MouseSpringSystem then it no longer does anything. Anyone have some tips for how to debug this stuff?

1 Like

Is there a simple way to ensure such an ordering with Dots and continue on the main thread once the collider has been created, considered by the BuildPhysicsWorld s.t. I am able to start the RaycastJob?

  • (MainThread - can happen before/after any step 2-4 as jobs are scheduled parallel to the main thread) MonoBehaviour, User UI Interaction, Input.GetKeyUp(2), …start interaction

  • (blocks main thread, and continues on main thread via UniRx) await Task.WhenAll(Classic C# Threads for I&O Operations to load the collider data)

  • Handover collider data to [UpdateBefore(typeof(BuildPhysicsWorld)] ColliderChangedSystem

  • ColliderChangedSystem.OnUpdate => SetComponentData(PhysicsCollider)

  • Wait for BuildPhysicsWorld (implicit via UpdateAfter)

  • Raycast in [UpdateAfter(typeof(BuildPhysicsWorld)] RaycastSystem

  • Continue on main thread, retrieve Results, update UI, etc

What looked simply solved via [UpdateBefore(typeof(BuildPhysicsWorld)] ColliderChangedSystem and [UpdateAfter(typeof(BuildPhysicsWorld)] RaycastSystem turns out to recreate the problem again. If the user Input 1. happens 3. ColliderChangedSystem.OnUpdate, it will Complete() with stale data (implicitly), which will cause BuildPhysicsWorld to complete with stale data (implicitly) and therefore cause the Raycast to run against stale data.

One thing I considered was using a ColliderConsideredComponent to check if the collider existed before the BuildPhysicsWorld and on the main thread wait until all those Components return true.

/* ECS Pseudo Code - just to visualize the concept */
[UpdateBefore(typeof(BuildPhysicsWorld))]
public class ColliderConsideredBeforeBuildPhysicsWorld : SystemBase
{
   protected override void OnUpdate()
   {
       // Once this is true, we know that any following BuildPhysicsWorld.Complete() will have considered it
      Entities.ForEach((ref ColliderConsideredComponent ccc)=>
      {
       ccc.BeforeBuildPhysicsWorld = true;
      }).ScheduleParallel();
   }
}
public class ColliderConsideredComponent : IComponentData
{
   public bool BeforeBuildPhysicsWorld;

}

/*some MonoBehaviour*/
var ColliderConsideredComponents = /* components we are waiting to be useable*/
if(ColliderConsideredComponents.Any(c => !c.BeforeBuildPhysicsWorld))
  return;
// else: Start the raycast as all colliders are now useable.

… and while that would likely solve it, it seems incredibly complicated for such a simple scenario and I cannot believe there isn’t something much more trivial than this.

Yeah I think what you described above is exactly what you need to do. Unfortunately there is no way to schedule systems and mono behaviorus together, so there has to be some data dependency (ColliderConsideredComponent in your case).

1 Like