// Schedules a set of jobs to iterate the provided dispatch pairs and create contacts based on them. internal static SimulationJobHandles ScheduleCreateContactsJobs(ref PhysicsWorld world, sfloat timeStep, ref NativeStream contacts, ref NativeStream jacobians, ref NativeList <DispatchPairSequencer.DispatchPair> dispatchPairs, JobHandle inputDeps, ref DispatchPairSequencer.SolverSchedulerInfo solverSchedulerInfo, bool multiThreaded = true) { SimulationJobHandles returnHandles = default; if (!multiThreaded) { contacts = new NativeStream(1, Allocator.TempJob); jacobians = new NativeStream(1, Allocator.TempJob); returnHandles.FinalExecutionHandle = new CreateContactsJob { World = world, TimeStep = timeStep, DispatchPairs = dispatchPairs.AsDeferredJobArray(), ContactsWriter = contacts.AsWriter() }.Schedule(inputDeps); } else { var numWorkItems = solverSchedulerInfo.NumWorkItems; var contactsHandle = NativeStream.ScheduleConstruct(out contacts, numWorkItems, inputDeps, Allocator.TempJob); var jacobiansHandle = NativeStream.ScheduleConstruct(out jacobians, numWorkItems, inputDeps, Allocator.TempJob); var processHandle = new ParallelCreateContactsJob { World = world, TimeStep = timeStep, DispatchPairs = dispatchPairs.AsDeferredJobArray(), SolverSchedulerInfo = solverSchedulerInfo, ContactsWriter = contacts.AsWriter() }.ScheduleUnsafeIndex0(numWorkItems, 1, JobHandle.CombineDependencies(contactsHandle, jacobiansHandle)); returnHandles.FinalExecutionHandle = processHandle; } return(returnHandles); }
// Schedule all the jobs for the simulation step. // Enqueued callbacks can choose to inject additional jobs at defined sync points. // multiThreaded defines which simulation type will be called: // - true will result in default multithreaded simulation // - false will result in a very small number of jobs (1 per physics step phase) that are scheduled sequentially // Behavior doesn't change regardless of the multiThreaded argument provided. public unsafe SimulationJobHandles ScheduleStepJobs(SimulationStepInput input, SimulationCallbacks callbacksIn, JobHandle inputDeps, bool multiThreaded = true) { SafetyChecks.CheckFiniteAndPositiveAndThrow(input.TimeStep, nameof(input.TimeStep)); SafetyChecks.CheckInRangeAndThrow(input.NumSolverIterations, new int2(1, int.MaxValue), nameof(input.NumSolverIterations)); // Dispose and reallocate input velocity buffer, if dynamic body count has increased. // Dispose previous collision and trigger event data streams. // New event streams are reallocated later when the work item count is known. JobHandle handle = SimulationContext.ScheduleReset(input, inputDeps, false); SimulationContext.TimeStep = input.TimeStep; StepContext = new StepContext(); if (input.World.NumDynamicBodies == 0) { // No need to do anything, since nothing can move m_StepHandles = new SimulationJobHandles(handle); return(m_StepHandles); } SimulationCallbacks callbacks = callbacksIn ?? new SimulationCallbacks(); // Find all body pairs that overlap in the broadphase var handles = input.World.CollisionWorld.ScheduleFindOverlapsJobs( out NativeStream dynamicVsDynamicBodyPairs, out NativeStream dynamicVsStaticBodyPairs, handle, multiThreaded); handle = handles.FinalExecutionHandle; var disposeHandle1 = handles.FinalDisposeHandle; var postOverlapsHandle = handle; // Sort all overlapping and jointed body pairs into phases handles = m_Scheduler.ScheduleCreatePhasedDispatchPairsJob( ref input.World, ref dynamicVsDynamicBodyPairs, ref dynamicVsStaticBodyPairs, handle, ref StepContext.PhasedDispatchPairs, out StepContext.SolverSchedulerInfo, multiThreaded); handle = handles.FinalExecutionHandle; var disposeHandle2 = handles.FinalDisposeHandle; // Apply gravity and copy input velocities at this point (in parallel with the scheduler, but before the callbacks) var applyGravityAndCopyInputVelocitiesHandle = Solver.ScheduleApplyGravityAndCopyInputVelocitiesJob( input.World.DynamicsWorld.MotionVelocities, SimulationContext.InputVelocities, input.TimeStep * input.Gravity, multiThreaded ? postOverlapsHandle : handle, multiThreaded); handle = JobHandle.CombineDependencies(handle, applyGravityAndCopyInputVelocitiesHandle); handle = callbacks.Execute(SimulationCallbacks.Phase.PostCreateDispatchPairs, this, ref input.World, handle); // Create contact points & joint Jacobians handles = NarrowPhase.ScheduleCreateContactsJobs(ref input.World, input.TimeStep, ref StepContext.Contacts, ref StepContext.Jacobians, ref StepContext.PhasedDispatchPairs, handle, ref StepContext.SolverSchedulerInfo, multiThreaded); handle = handles.FinalExecutionHandle; var disposeHandle3 = handles.FinalDisposeHandle; handle = callbacks.Execute(SimulationCallbacks.Phase.PostCreateContacts, this, ref input.World, handle); // Create contact Jacobians handles = Solver.ScheduleBuildJacobiansJobs(ref input.World, input.TimeStep, input.Gravity, input.NumSolverIterations, handle, ref StepContext.PhasedDispatchPairs, ref StepContext.SolverSchedulerInfo, ref StepContext.Contacts, ref StepContext.Jacobians, multiThreaded); handle = handles.FinalExecutionHandle; var disposeHandle4 = handles.FinalDisposeHandle; handle = callbacks.Execute(SimulationCallbacks.Phase.PostCreateContactJacobians, this, ref input.World, handle); // Solve all Jacobians Solver.StabilizationData solverStabilizationData = new Solver.StabilizationData(input, SimulationContext); handles = Solver.ScheduleSolveJacobiansJobs(ref input.World.DynamicsWorld, input.TimeStep, input.NumSolverIterations, ref StepContext.Jacobians, ref SimulationContext.CollisionEventDataStream, ref SimulationContext.TriggerEventDataStream, ref StepContext.SolverSchedulerInfo, solverStabilizationData, handle, multiThreaded); handle = handles.FinalExecutionHandle; var disposeHandle5 = handles.FinalDisposeHandle; handle = callbacks.Execute(SimulationCallbacks.Phase.PostSolveJacobians, this, ref input.World, handle); // Integrate motions handle = Integrator.ScheduleIntegrateJobs(ref input.World.DynamicsWorld, input.TimeStep, handle, multiThreaded); // Synchronize the collision world if (input.SynchronizeCollisionWorld) { handle = input.World.CollisionWorld.ScheduleUpdateDynamicTree(ref input.World, input.TimeStep, input.Gravity, handle, multiThreaded); // TODO: timeStep = 0? } // Return the final simulation handle m_StepHandles.FinalExecutionHandle = handle; // Different dispose logic for single threaded simulation compared to "standard" threading (multi threaded) if (!multiThreaded) { handle = dynamicVsDynamicBodyPairs.Dispose(handle); handle = dynamicVsStaticBodyPairs.Dispose(handle); handle = StepContext.PhasedDispatchPairs.Dispose(handle); handle = StepContext.Contacts.Dispose(handle); handle = StepContext.Jacobians.Dispose(handle); handle = StepContext.SolverSchedulerInfo.ScheduleDisposeJob(handle); m_StepHandles.FinalDisposeHandle = handle; } else { // Return the final handle, which includes disposing temporary arrays JobHandle *deps = stackalloc JobHandle[5] { disposeHandle1, disposeHandle2, disposeHandle3, disposeHandle4, disposeHandle5 }; m_StepHandles.FinalDisposeHandle = JobHandleUnsafeUtility.CombineDependencies(deps, 5); } return(m_StepHandles); }
// Schedule a set of jobs which will write all overlapping body pairs to the given steam, // where at least one of the bodies is dynamic. The results are unsorted. public SimulationJobHandles ScheduleFindOverlapsJobs(out NativeStream dynamicVsDynamicPairsStream, out NativeStream staticVsDynamicPairsStream, JobHandle inputDeps, bool multiThreaded = true) { SimulationJobHandles returnHandles = default; if (!multiThreaded) { dynamicVsDynamicPairsStream = new NativeStream(1, Allocator.TempJob); staticVsDynamicPairsStream = new NativeStream(1, Allocator.TempJob); returnHandles.FinalExecutionHandle = new FindOverlapsJob { Broadphase = this, DynamicVsDynamicPairsWriter = dynamicVsDynamicPairsStream.AsWriter(), StaticVsDynamicPairsWriter = staticVsDynamicPairsStream.AsWriter() }.Schedule(inputDeps); return(returnHandles); } var dynamicVsDynamicNodePairIndices = new NativeList <int2>(Allocator.TempJob); var staticVsDynamicNodePairIndices = new NativeList <int2>(Allocator.TempJob); JobHandle allocateDeps = new AllocateDynamicVsStaticNodePairs { dynamicVsDynamicNodePairIndices = dynamicVsDynamicNodePairIndices, staticVsDynamicNodePairIndices = staticVsDynamicNodePairIndices, dynamicBranchCount = m_DynamicTree.BranchCount, staticBranchCount = m_StaticTree.BranchCount }.Schedule(inputDeps); // Build pairs of branch node indices JobHandle dynamicVsDynamicPairs = new DynamicVsDynamicBuildBranchNodePairsJob { Ranges = m_DynamicTree.Ranges, NumBranches = m_DynamicTree.BranchCount, NodePairIndices = dynamicVsDynamicNodePairIndices.AsDeferredJobArray() }.Schedule(allocateDeps); JobHandle staticVsDynamicPairs = new StaticVsDynamicBuildBranchNodePairsJob { DynamicRanges = m_DynamicTree.Ranges, StaticRanges = m_StaticTree.Ranges, NumStaticBranches = m_StaticTree.BranchCount, NumDynamicBranches = m_DynamicTree.BranchCount, NodePairIndices = staticVsDynamicNodePairIndices.AsDeferredJobArray() }.Schedule(allocateDeps); //@TODO: We only need a dependency on allocateDeps, but the safety system doesn't understand that we can not change length list in DynamicVsDynamicBuildBranchNodePairsJob & StaticVsDynamicBuildBranchNodePairsJob // if this is a performance issue we can use [NativeDisableContainerSafetyRestriction] on DynamicVsDynamicBuildBranchNodePairsJob & StaticVsDynamicBuildBranchNodePairsJob JobHandle dynamicConstruct = NativeStream.ScheduleConstruct(out dynamicVsDynamicPairsStream, dynamicVsDynamicNodePairIndices, dynamicVsDynamicPairs, Allocator.TempJob); JobHandle staticConstruct = NativeStream.ScheduleConstruct(out staticVsDynamicPairsStream, staticVsDynamicNodePairIndices, staticVsDynamicPairs, Allocator.TempJob); // Write all overlaps to the stream (also deallocates nodePairIndices) JobHandle dynamicVsDynamicHandle = new DynamicVsDynamicFindOverlappingPairsJob { DynamicTree = m_DynamicTree, PairWriter = dynamicVsDynamicPairsStream.AsWriter(), NodePairIndices = dynamicVsDynamicNodePairIndices.AsDeferredJobArray() }.Schedule(dynamicVsDynamicNodePairIndices, 1, JobHandle.CombineDependencies(dynamicVsDynamicPairs, dynamicConstruct)); // Write all overlaps to the stream (also deallocates nodePairIndices) JobHandle staticVsDynamicHandle = new StaticVsDynamicFindOverlappingPairsJob { StaticTree = m_StaticTree, DynamicTree = m_DynamicTree, PairWriter = staticVsDynamicPairsStream.AsWriter(), NodePairIndices = staticVsDynamicNodePairIndices.AsDeferredJobArray() }.Schedule(staticVsDynamicNodePairIndices, 1, JobHandle.CombineDependencies(staticVsDynamicPairs, staticConstruct)); // Dispose node pair lists var disposeOverlapPairs0 = dynamicVsDynamicNodePairIndices.Dispose(dynamicVsDynamicHandle); var disposeOverlapPairs1 = staticVsDynamicNodePairIndices.Dispose(staticVsDynamicHandle); returnHandles.FinalDisposeHandle = JobHandle.CombineDependencies(disposeOverlapPairs0, disposeOverlapPairs1); returnHandles.FinalExecutionHandle = JobHandle.CombineDependencies(dynamicVsDynamicHandle, staticVsDynamicHandle); return(returnHandles); }