Search Unity

[Unity Physics] PhysicsWorld in a job (more specifically, in a ICollisionEventsJob)

Discussion in 'Data Oriented Technology Stack' started by zardini123, Feb 13, 2020 at 5:09 PM.

  1. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    23
    Is passing an instance of PhysicsWorld into a job non-optimal, especially for memory performance?

    Background:
    The only way I know to get collision events from Unity Physics is using the job interface ICollisionEventsJob. My goal is to apply a force/impulse to the collided body at a collision event, therefore I need to have the PhysicsWorld passed to the job to then run world.ApplyImpulse using the PhysicsWorldExtensions. PhysicsWorld has a bunch of native arrays associated with it, including MotionData, MotionVelocity, and Body arrays, so passing the world passes along all those other arrays.

    Is PhysicsWorld designed to be passed into Jobs like this (like in the code below)? Or should I store collision events somewhere, and then move any usage of PhysicsWorld to the main thread?

    Also due to my usage of PhysicsWorld in multithreaded logic, my job has to be dependent on BuildPhysicsWorld, and is required to complete before any other job happens. Isn't this also extremely non-optimal?

    The code I am using:
    Code (CSharp):
    1. [UpdateAfter(typeof(BuildPhysicsWorld)), UpdateBefore(typeof(StepPhysicsWorld))]
    2. unsafe public class ForceOnCollisionSystem : JobComponentSystem
    3. {
    4.   protected override void OnCreate()
    5.   {
    6.     // Get related physics systems here...
    7.   }
    8.  
    9.   [BurstCompile]
    10.   struct ForceOnCollisionJob : ICollisionEventsJob
    11.   {
    12.     // TODO: REVIEW:  Is passing the entirety of world into a job a extremely poor memory choice?
    13.     public PhysicsWorld world;
    14.     [ReadOnly] public float timeDelta;
    15.  
    16.     public void Execute(CollisionEvent collisionEvent)
    17.     {
    18.       // Find out what entities are what using collisionEvent
    19.  
    20.       // Find rigid body associated with a collision entity "wheel"
    21.       int rigidBodyIndex = world.GetRigidBodyIndex(wheel);
    22.  
    23.       // Math and stuff blah blah blah...
    24.  
    25.       world.ApplyImpulse(rigidBodyIndex, finalImpulse, details.AverageContactPointPosition);
    26.     }
    27.   }
    28.   protected override JobHandle OnUpdate(JobHandle inputDeps)
    29.   {
    30.     PhysicsWorld world = m_BuildPhysicsWorldSystem.PhysicsWorld;
    31.  
    32.     // These dependencies are needed due to usage of PhysicsWorld
    33.     inputDeps = JobHandle.CombineDependencies(inputDeps, m_BuildPhysicsWorldSystem.FinalJobHandle);
    34.  
    35.     JobHandle jobHandle = new ForceOnCollisionJob
    36.     {
    37.       world = world,
    38.       timeDelta = Time.fixedDeltaTime
    39.     }.Schedule(m_StepPhysicsWorldSystem.Simulation, ref world, inputDeps);
    40.  
    41.     // Needed due to dependency of PhysicsWorld
    42.     jobHandle.Complete();
    43.  
    44.     return jobHandle;
    45.   }
    46. }
     
  2. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,257
    from what I gather, passing PhysicsWorld to a job is totally fine to do. However, your call to Complete() should not be necessary since the job system should figure this out by itself




    If you look inside PhysicsWorld and the things it contains like CollisionWorld, they all just contain NativeArrays. Which means that when you copy the PhysicsWorld to a job, it only copies the "pointers" to the arrays; not the contents of the arrays. This seems like it would be pretty inexpensive
     
    Last edited: Feb 13, 2020 at 5:40 PM
    zardini123 and Rory_Havok like this.
  3. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    23
    Very interesting! That makes a lot of sense, thank you!

    (EDIT: turns out the jobHandle.Complete() is certainly unnessicary, and Unity doesn't spew errors when removing it (unlike what it did to me awhile ago), but the following still applies)
    Yes you are correct, but the issue I had is with
    world.ApplyImpulse
    in a ICollisionEventsJob. I assume collisions are determined and queried sometime between StepPhysicsWorld and ExportPhysicsWorld. By the time collisions are starting to be queried in the ICollisionEventsJob, its somewhere at or after StepPhysicsWorld and/or ExportPhysicsWorld. Calling
    world.ApplyImpulse
    at this time after the world has been simulated results in that call being essentially "eaten";
    world.ApplyImpulse
    has no effect.

    To combat this, I state the system to run after BuildPhysicsWorld. But now due to Bodies having multiple dependents at the same time, I have to add the job dependency to BuildPhysicsWorld
    jobHandle.CombineDependencies(inputDeps, m_BuildPhysicsWorldSystem.FinalJobHandle);
    . This is incredibly jank, especially being I believe now things are being ran sequential instead of parallel. Also, the collisions I'm querying might be a frame old. Thats just a speculation.

    That leads me to the question: is there a correct way to apply any impulse at the event of a collision?
     
  4. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    2,739
    You may have to use a EntityCommandBuffer to execute the Impulse. This is all new to me but I was using ICollisionEventsJob and it only resolved with an ECB. May be you add an Impulse tag and apply that data in the next job,.
     
unityunity