Search Unity

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

Discussion in 'Entity Component System' started by zardini123, Feb 13, 2020.

  1. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    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. }
     
    KwahuNashoba likes this.
  2. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    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
    zardini123 and Rory_Havok like this.
  3. zardini123

    zardini123

    Joined:
    Jan 5, 2013
    Posts:
    68
    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:
    3,856
    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,.
     
  5. jconsole

    jconsole

    Joined:
    Oct 26, 2016
    Posts:
    10
    Necromancing here- I'm trying to do PhysicsComponentExtensions.ApplyLinearImpulse() inside an IJobChunk and can't get it to change the actual physics on the entity. There could be several things wrong here, I don't have the greatest grasp on C# or ECS yet. When I run this code the debugger will print

    "PhysicsVelocity changed PV =float3(-17.28436f, 407.5064f, -5352.974f) and current ChunkphysicsVelocities[0] = float3(0f, -2.568675f, 0f)"

    [Edit: there is only one rigid body and one bone in my scene, so I just hard code the index [0] in the chunks]

    as my cube falls straight down and isn't moved/impulsed upon. I can get it to work by altering the physicsVelocity chunk but (despite what @PhilSA says about impulse vs PV being derivable from eachother, I've had issues in the past where changing velocity directly can have bad consequences if the rigidbody is stuck against another, impulse/force seems to work better, though this was in PhysX) I'd like to get ApplyLinearImpulse to work.

    I see that to change the PV you must
    Code (CSharp):
    1. ChunkphysicsVelocities[0] = new PhysicsVelocity...
    and that changes the actual component value. I guess I'd like to do that with applying an impulse but I can't figure it out. By my (ugly) comments in the code, you'll see that I've been figuring out how to do this a several different ways. FinalJobHandle isn't available anymore and I haven't been able to figure out the dependency order. Does one still need to use an EntityCommandBuffer? Does it not make sense to try to use an IJobChunk for this physics task (I'd like to get an active rag doll type skeletal follower with rigidbodies following the bones of an animated rig)? Seems to me that it would make sense to line up all the LTW positions of the bones and then alter the physics of each corresponding rigidbody within a parallel job.

    Thanks for any help here!

    Code (CSharp):
    1. using Unity.Burst;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7. using UnityEditor;
    8. using UnityEngine;
    9. using Unity.Physics;
    10. using Unity.Physics.Extensions;
    11. using Unity.Physics.Systems;
    12.  
    13.  
    14. //[UpdateBefore(typeof(BuildPhysicsWorld))]
    15. public class BoneFollowSystem : JobComponentSystem
    16. {
    17.     [BurstCompile]
    18.     struct BoneFollowJob : IJobChunk
    19.     {
    20.         public float DeltaTime;
    21.         //public PhysicsWorld world;
    22.         //[ReadOnly] public ComponentTypeHandle<LocalToWorld> LTWHandle; //ArchetypeChunkComponentType<LocalToWorld> LTW;
    23.         //[ReadOnly] public ComponentTypeHandle<Attachable> AttachableHandle;
    24.         [ReadOnly] public ComponentTypeHandle<PhysicsVelocity> PVHandle;
    25.         [ReadOnly] public ComponentTypeHandle<PhysicsMass> PmassHandle;
    26.         [ReadOnly] public ComponentTypeHandle<RBitem> RBItemHandle;
    27.         [ReadOnly] public ComponentTypeHandle<Translation> TransHandle;
    28.         [ReadOnly] public ComponentDataFromEntity<LocalToWorld> LTWHandle;
    29.         [ReadOnly] public ComponentDataFromEntity<Attachable> AttachableHandle;
    30.  
    31.         public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    32.         {
    33.             //var ChunktAttachable = chunk.GetNativeArray(AttachableHandle);
    34.             var ChunkphysicsVelocities = chunk.GetNativeArray(PVHandle);
    35.             var ChunkphysicsMass = chunk.GetNativeArray(PmassHandle);
    36.             var ChunkRB = chunk.GetNativeArray(RBItemHandle);
    37.             var ChunkTrans = chunk.GetNativeArray(TransHandle);
    38.  
    39.             //for (var i = 0; i < chunk.Count; i++)
    40.             //{
    41.             // Debug.Log("chunk.Count " + chunk.Count + " \nChunktargetTranslation " + ChunktargetTranslation.Length + " ChunkphysicsVelocities " + ChunkphysicsVelocities.Length+
    42.             //     " ChunkRB" + ChunkRB.Length+ " ChunkTrans  " + ChunkTrans.Length +"v "+ ChunkTrans[0].Value);
    43.             // var translation = ChunktargetTranslation[0];
    44.             if (ChunkphysicsVelocities.Length > 0)
    45.             {
    46.                 var targetTranslation = LTWHandle[ChunkRB[0].currentTarget];
    47.                 PhysicsVelocity PV = ChunkphysicsVelocities[0];
    48.                 //var targetEntity = AttachableHandle[ChunkRB[0].currentTarget].RBentity;
    49.                 //var targetComponentTranslation = GetComponentData<LocalToWorld>(ChunkRB[0].currentTarget);
    50.                 // world.ApplyLinearImpulse( world.GetRigidBodyIndex(targetEntity), (targetTranslation.Position - ChunkTrans[0].Value) * ChunkRB[0].speed);
    51.                 PhysicsComponentExtensions.ApplyLinearImpulse(ref PV, ChunkphysicsMass[0], (targetTranslation.Position - ChunkTrans[0].Value) * ChunkRB[0].speed);
    52.              
    53.                 //ChunkphysicsVelocities[0] = new PhysicsVelocity
    54.                 //{
    55.              
    56.                 //    Linear = (targetTranslation.Position - ChunkTrans[0].Value) * ChunkRB[0].speed
    57.                 //};
    58.                 Debug.Log($"PhysicsVelocity changed PV ={PV.Linear} and current ChunkphysicsVelocities[0] = {ChunkphysicsVelocities[0].Linear}");// + $" attachable = {chunk.GetNativeArray(AttachableHandle).Length}");
    59.             }
    60.  
    61.             //}
    62.         }
    63.     }
    64.  
    65.     EntityQuery m_Group;
    66.   // BuildPhysicsWorld m_BuildPhysicsWorldStytem;
    67.     protected override void OnCreate()
    68.     {
    69.         var query0 = new EntityQueryDesc
    70.         {
    71.             All = new ComponentType[] { ComponentType.ReadOnly<LocalToWorld>(), ComponentType.ReadOnly<Attachable>() }
    72.         };
    73.  
    74.         var query1 = new EntityQueryDesc
    75.         {
    76.             All = new ComponentType[] { typeof(PhysicsVelocity), ComponentType.ReadOnly< PhysicsMass>(), ComponentType.ReadOnly<RBitem>(), ComponentType.ReadOnly<Translation>() },
    77.            None = new ComponentType[] { ComponentType.ReadOnly<Attachable>() }
    78.         };
    79.  
    80.         m_Group = GetEntityQuery(new EntityQueryDesc[] { query0, query1 }); //query0,
    81.       // m_BuildPhysicsWorldStytem = this.World.GetOrCreateSystem<BuildPhysicsWorld>();
    82.      
    83.     }
    84.  
    85.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    86.     {
    87.         //PhysicsWorld preworld = m_BuildPhysicsWorldStytem.PhysicsWorld;
    88.         //inputDeps = JobHandle.CombineDependencies(inputDeps, m_BuildPhysicsWorldStytem.FinalJobHandle);
    89.         //var preLTWHandle = GetComponentTypeHandle<LocalToWorld>(true);
    90.         var preAttachableHandle = GetComponentDataFromEntity<Attachable>(true);
    91.         var prePVHandle = GetComponentTypeHandle<PhysicsVelocity>();
    92.         var prePmassHandle = GetComponentTypeHandle<PhysicsMass>();
    93.         var preRBItemHandle = GetComponentTypeHandle<RBitem>(true);
    94.         var preTransHandle = GetComponentTypeHandle<Translation>(true);
    95.         var preLTWHandle = GetComponentDataFromEntity<LocalToWorld>(true);
    96.         //  PhysicsWorld physicsWorld = this.World.GetExistingSystem<PhysicsWorld>();
    97.  
    98.         //m_BuildPhysicsWorldStytem.AddInputDependency(inputDeps);
    99.  
    100.         return new BoneFollowJob
    101.         {
    102.             AttachableHandle = preAttachableHandle,
    103.             //world = preworld,
    104.             LTWHandle = preLTWHandle,
    105.             PVHandle = prePVHandle,
    106.             PmassHandle = prePmassHandle,
    107.             RBItemHandle = preRBItemHandle,
    108.             TransHandle = preTransHandle
    109.         }.Schedule(m_Group, inputDeps);// JobHandle.CombineDependencies(inputDeps, m_BuildPhysicsWorldStytem.AddInputDependencyToComplete.RequireForUpdate);
    110.  
    111.     }
    112. }
    113.  
     
    Last edited: Feb 11, 2021
  6. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    You need to add the following to your system

    [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    [UpdateAfter(typeof(ExportPhysicsWorld))]
    [UpdateBefore(typeof(EndFramePhysicsSystem))]

    If you end up needing an ECBS, use BeginFixedStepSimulationEntityCommandBufferSystem and EndFixedStepSimulationEntityCommandBufferSystem
     
    jconsole likes this.
  7. jconsole

    jconsole

    Joined:
    Oct 26, 2016
    Posts:
    10
    Thanks Abbrew!
    Unfortunately, adding those attributes here
    Code (CSharp):
    1. [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    2. [UpdateAfter(typeof(ExportPhysicsWorld))]
    3. [UpdateBefore(typeof(EndFramePhysicsSystem))]
    4. public class BoneFollowSystem : JobComponentSystem
    Didn't change anything.
    Is there no way of directly passing the reference to ChunkphysicsVelocities as an argument to PhysicsComponentExtensions.ApplyLinearImpulse()? like this?

    Code (CSharp):
    1. PhysicsComponentExtensions.ApplyLinearImpulse(ref ChunkphysicsVelocities[i], ChunkphysicsMass[i], (targetTranslation.Position - ChunkTrans[i].Value) * ChunkRB[i].speed);
    Gives me the error

    "A property or indexer may not be passes as an out or ref parameter"

    Okay, so sorry for the stream of thought here, but I ~figured it out. Finding a few other ApplyImpulse() examples via google on github helped.

    However much I don't like admitting/learning it, it seems that ApplyLinearImpulse() just modifies the ... PhysicsVelocity. So I ended up just making the PV copy

    PhysicsVelocity PV = ChunkphysicsVelocities[0];

    and then doing the impulse on it, then saving it back to the chunk data
    Code (CSharp):
    1.  
    2. PV.ApplyLinearImpulse( ChunkphysicsMass[0], (targetTranslation.Position - ChunkTrans[0].Value) * ChunkRB[0].speed);
    3. ChunkphysicsVelocities[0] = PV;
    I also had to remove the [ReadOnly] since I'm now altering the chunk data on PhysicsVelocities.
     
  8. Abbrew

    Abbrew

    Joined:
    Jan 1, 2018
    Posts:
    417
    If you want to set the velocity of a body, do
    Code (CSharp):
    1. velocity.Linear = <your velocity>;
    2.  
    ApplyLinearImpulse applies an impulse instead of directly setting the velocity, so the results are harder to predict.
    Sorry about
    Code (CSharp):
    1. [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    2.  
    not working. I know that property itself is necessary, but
    Code (CSharp):
    1. [UpdateAfter(typeof(ExportPhysicsWorld))]
    2. [UpdateBefore(typeof(EndFramePhysicsSystem))]
    might need to be swapped out for something else.
     
    jconsole likes this.