Search Unity

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

Discussion in 'Physics for ECS' started by MNNoxMortem, May 24, 2020.

  1. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    Edit: This thread describes a different problem than it's sibling, https://forum.unity.com/threads/cas...unityengine-physics-raycast-does-work.994216/ 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?


    Code (CSharp):
    1. EntityManager      em                = World.DefaultGameObjectInjectionWorld.EntityManager;
    2. var                buildPhysicsWorld = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<BuildPhysicsWorld>();
    3. ref PhysicsWorld   physicsWorld      = ref buildPhysicsWorld.PhysicsWorld;
    4. ref CollisionWorld collisionWorld    = ref physicsWorld.CollisionWorld;
    5. // Sanity check to ensure it hits
    6. var quad     = em.CreateEntity();
    7. var vertices = new NativeList<float3>(Allocator.Temp);
    8. vertices.Add(new float3(-100, 0, -100));
    9. vertices.Add(new float3(100,  0, -100));
    10. vertices.Add(new float3(-100, 0, 100));
    11. vertices.Add(new float3(100,  0, 100));
    12. var triangles = new NativeList<int3>(Allocator.Temp);
    13. triangles.Add(new int3(0, 2, 1));
    14. triangles.Add(new int3(2, 3, 1));
    15. em.AddComponent<PhysicsCollider>(quad);
    16. var physicsCollider = new PhysicsCollider {
    17. Value = MeshCollider.Create(vertices, triangles)
    18. };
    19. vertices.Dispose();
    20. triangles.Dispose();
    21. em.AddComponent<Unity.Transforms.LocalToWorld>(quad);
    22. em.SetComponentData(quad, physicsCollider);
    23. em.SetComponentData(quad, new LocalToWorld {Value = float4x4.identity});
    24. em.SetName(quad, "quad");
    25.  
    26. var quadTestRaycastInput = new RaycastInput {
    27.    Start  = new float3(0, 10,  0),
    28.    End    = new float3(0, -10, 0),
    29.    Filter = CollisionFilter.Default
    30. };
    31. if (collisionWorld.CastRay(quadTestRaycastInput,
    32.                           out Unity.Physics.RaycastHit quadTestHit))
    33. {
    34.    this.LogDebug("Hit quad (forward):" + quadTestHit.Fraction);
    35.    Debug.DrawLine((Vector3) quadTestRaycastInput.Start, (Vector3) quadTestHit.Position, Color.green, 5);
    36. }
    37. else
    38. {
    39.    this.LogDebug("missed quad");
    40.    Debug.DrawLine((Vector3) quadTestRaycastInput.Start, (Vector3) quadTestRaycastInput.End, Color.red, 5);
    41. }
    42.  
    It seems like the first time the collider does not exist yet.
     
    Last edited: Nov 8, 2020
  2. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    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...
     
    MNNoxMortem likes this.
  3. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    @petarmHavok Thanks! However even after calling buildPhysicsWorld.FinalJobHandle.Complete() after adding the PhysicsCollider it still does only work on a later frame.


    Code (CSharp):
    1. // 1. Get systems
    2. EntityManager      em                = World.DefaultGameObjectInjectionWorld.EntityManager;
    3. var                buildPhysicsWorld = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<BuildPhysicsWorld>();
    4. ref PhysicsWorld   physicsWorld      = ref buildPhysicsWorld.PhysicsWorld;
    5. ref CollisionWorld collisionWorld    = ref physicsWorld.CollisionWorld;
    6.  
    7. // 2. Create Entity
    8. ...
    9.  
    10. // 3. Complete BuildPhysicsWorld
    11. buildPhysicsWorld.FinalJobHandle.Complete(); // wait until the physics world is build
    12.  
    13. // 4. Raycast via CSharp/MonoBehaviour
    14. if(collisionWorld.CastRay(input, out hit))
    15. ... does not hit on first frame ...
    16.  
    17. // 5. Raycast via Job & dependency
    18. JobHandle raycastJob = job.Schedule(2, 64, buildPhysicsWorld.FinalJobHandle);
    19. raycastJob.Complete();
    20. ... does not hit on first frame ...
    21.  
    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

    Code (CSharp):
    1. var forward = new RaycastInput {
    2. Start  = new float3(0, 10,  0),
    3. End    = new float3(0, -10, 0),
    4. Filter = CollisionFilter.Default
    5. };
    6. // does hit from a MonoBevaviour and from a burstified Job
    7. var backward = new RaycastInput {
    8. Start = new float3(0, -10, 0),
    9. End    = new float3(0, 10,  0),
    10. Filter = CollisionFilter.Default
    11. };
    12. // does hit from a MonoBevaviour but not from a burstified Job
    13.  
    Seems I am running into the very same again... https://forum.unity.com/threads/how-to-ensure-physics-world-is-updated-within-the-same-frame.767744/. 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
    Code (csharp):
    1.  
    2. "com.unity.entities": "0.10.0-preview.6",
    3. "com.unity.jobs": "0.2.9-preview.15",
    4. "com.unity.physics": "0.3.2-preview",
    5. "com.unity.burst": "1.3.0-preview.13"
    6.  
     
    Last edited: May 26, 2020
  4. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    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?
     
    MNNoxMortem and KwahuNashoba like this.
  5. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    @petarmHavok ... it makes incredibly much sense reading it this way :D
     
    Last edited: May 29, 2020
    petarmHavok likes this.
  6. RefugeZer0

    RefugeZer0

    Joined:
    Jan 28, 2018
    Posts:
    4
    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?

    job_order.png

    Here's the code reduced to just the important bits:
    Code (CSharp):
    1. [UpdateAfter(typeof(BuildPhysicsWorld))]
    2. [UpdateBefore(typeof(EndFramePhysicsSystem))]
    3. public class MousePickSystem : SystemBase
    4. {
    5.     public NativeArray<SpringData> SpringDataArray;
    6.     public JobHandle? PickJobHandle;
    7.     private BuildPhysicsWorld _physicsWorld;
    8.  
    9.     public struct SpringData { /* some data */ }
    10.  
    11.     protected override void OnCreate()
    12.     {
    13.         _physics = World.GetOrCreateSystem<BuildPhysicsWorld>();
    14.         SpringDataArray = new NativeArray<SpringData>(1, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
    15.     }
    16.  
    17.     protected override void OnUpdate()
    18.     {
    19.         if (Input.GetMouseButtonDown(0))
    20.         {
    21.             // Schedule picking job, after the collision world has been built
    22.             JobHandle jobHandle = new PickJob
    23.             {
    24.                 SpringDataArray = SpringDataArray
    25.             }.Schedule(JobHandle.CombineDependencies(Dependency, _physicsWorld.FinalJobHandle));
    26.  
    27.             PickJobHandle = jobHandle;
    28.  
    29.             jobHandle.Complete();
    30.         }
    31.     }
    32.  
    33.     struct PickJob : IJob
    34.     {
    35.         public NativeArray<SpringData> SpringDataArray;
    36.  
    37.         public void Execute()
    38.         {
    39.             /* if (DoRaycast()) { SpringDataArray[0] = new SpringData() { data = hit info }; } */
    40.         }
    41.     }
    42. }
    43.  
    44. [UpdateBefore(typeof(BuildPhysicsWorld))]
    45. public class MouseSpringSystem : SystemBase
    46. {
    47.     MousePickSystem _pickSystem;
    48.  
    49.     protected override void OnCreate()
    50.     {
    51.         _pickSystem = World.GetOrCreateSystem<MousePickSystem>();
    52.     }
    53.  
    54.     protected override void OnUpdate()
    55.     {
    56.         // If there's a pick job, wait for it to finish
    57.         if (_pickSystem.PickJobHandle != null)
    58.         {
    59.             var pickJob = JobHandle.CombineDependencies(Dependency, _pickSystem.PickJobHandle.Value);
    60.             pickJob.Complete();
    61.         }
    62.  
    63.         var springData = _pickSystem.SpringDataArray[0];
    64.         /* do stuff with the data */
    65.     }
    66. }

    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?
     
    MNNoxMortem likes this.
  7. MNNoxMortem

    MNNoxMortem

    Joined:
    Sep 11, 2016
    Posts:
    723
    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?
    1. (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
    2. ColliderChangedSystem.OnUpdate => SetComponentData(PhysicsCollider)
    3. Wait for BuildPhysicsWorld (implicit via UpdateAfter)
    4. Raycast in [UpdateAfter(typeof(BuildPhysicsWorld)] RaycastSystem
    5. 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.

    Code (CSharp):
    1. /* ECS Pseudo Code - just to visualize the concept */
    2. [UpdateBefore(typeof(BuildPhysicsWorld))]
    3. public class ColliderConsideredBeforeBuildPhysicsWorld : SystemBase
    4. {
    5.    protected override void OnUpdate()
    6.    {
    7.        // Once this is true, we know that any following BuildPhysicsWorld.Complete() will have considered it
    8.       Entities.ForEach((ref ColliderConsideredComponent ccc)=>
    9.       {
    10.        ccc.BeforeBuildPhysicsWorld = true;
    11.       }).ScheduleParallel();
    12.    }
    13. }
    14. public class ColliderConsideredComponent : IComponentData
    15. {
    16.    public bool BeforeBuildPhysicsWorld;
    17.  
    18. }
    19.  
    20. /*some MonoBehaviour*/
    21. var ColliderConsideredComponents = /* components we are waiting to be useable*/
    22. if(ColliderConsideredComponents.Any(c => !c.BeforeBuildPhysicsWorld))
    23.   return;
    24. // else: Start the raycast as all colliders are now useable.
    25.  
    ... 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.
     
    Last edited: May 31, 2020
  8. petarmHavok

    petarmHavok

    Joined:
    Nov 20, 2018
    Posts:
    461
    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).
     
    MNNoxMortem likes this.