rapier2d/pipeline/physics_pipeline/mod.rs
1//! Physics pipeline structures.
2
3use crate::alloc_prelude::*;
4
5use crate::counters::Counters;
6use crate::dynamics::{
7 CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
8 RigidBodySet,
9};
10use crate::geometry::{
11 BroadPhaseBvh, BroadPhasePairEvent, ColliderHandle, ColliderSet, ContactManifoldIndex,
12 NarrowPhase,
13};
14use crate::math::Vector;
15use crate::pipeline::{EventHandler, PhysicsHooks};
16
17mod quarantine;
18pub use quarantine::Quarantine;
19mod solve;
20mod substep;
21#[cfg(test)]
22mod test;
23#[cfg(test)]
24mod test_staged;
25
26/// The main physics simulation engine that runs your physics world forward in time.
27///
28/// Think of this as the "game loop" for your physics simulation. Each frame, you call
29/// [`PhysicsPipeline::step`] to advance the simulation by one timestep. This structure
30/// handles all the complex physics calculations: detecting collisions between objects,
31/// resolving contacts so objects don't overlap, and updating positions and velocities.
32///
33/// ## Performance note
34/// This structure only contains temporary working memory (scratch buffers). You can create
35/// a new one anytime, but it's more efficient to reuse the same instance across frames
36/// since Rapier can reuse allocated memory.
37///
38/// ## How it works (simplified)
39/// Rapier uses a time-stepping approach where each step involves:
40/// 1. **Collision detection**: Find which objects are touching or overlapping
41/// 2. **Constraint solving**: Calculate forces to prevent overlaps and enforce joint constraints
42/// 3. **Integration**: Update object positions and velocities based on forces and gravity
43/// 4. **Position correction**: Fix any remaining overlaps that might have occurred
44// NOTE: this contains only workspace data, so there is no point in making this serializable.
45pub struct PhysicsPipeline {
46 /// Counters used for benchmarking only.
47 pub counters: Counters,
48 joint_constraint_indices: Vec<ContactManifoldIndex>,
49 /// Whether [`Self::joint_constraint_indices`] has been filled by this pipeline yet.
50 /// The joint set memoizes its selection against the buffer the caller keeps, so a
51 /// pipeline that just came into existence must invalidate that memo before its first
52 /// selection — otherwise it reuses a buffer it never filled.
53 joint_selection_primed: bool,
54 broad_phase_events: Vec<BroadPhasePairEvent>,
55 /// Colliders moved by the last `advance_to_final_positions` with their fresh broad-phase
56 /// AABBs, fed to the broad-phase refresh without the user-modification tracking. AABBs are
57 /// computed inside the advance loop while body/collider are in cache.
58 end_step_collider_aabbs: Vec<(ColliderHandle, crate::geometry::Aabb)>,
59 /// Non-finite state detected and neutralized during the last step.
60 quarantine: Quarantine,
61 /// Scratch buffer holding the active body handles (parallel body update).
62 #[cfg(feature = "parallel")]
63 active_body_handles: Vec<crate::dynamics::RigidBodyHandle>,
64 /// Scratch: per-active-body sleep observations `(persistent island id, eligible)`,
65 /// run-length compressed by the fused traversal, consumed by `IslandManager::
66 /// update_islands`'s whole-island sleep decision — which never re-touches the body arena.
67 sleep_observations: Vec<(u32, bool)>,
68 /// The single, unified solver: the awake island is solved by its colored,
69 /// staged workers. On a non-parallel (or wasm) build it runs with one worker
70 /// inline on the calling thread.
71 staged_solver: crate::dynamics::StagedIslandSolver,
72 /// Handle on the BVH optimization pass running concurrently with the narrow
73 /// phase and solver (the `Mutex` only exists to keep the pipeline `Sync`; it is
74 /// never contended).
75 #[cfg(feature = "parallel")]
76 deferred_bvh:
77 std::sync::Mutex<Option<std::sync::mpsc::Receiver<crate::geometry::DeferredBvhOptimize>>>,
78 /// Deferred BVH optimization that had no spare worker to run on (single-threaded
79 /// pool, or `parallel` off): run inline by `join_deferred_bvh_optimize`, i.e. at
80 /// the same point of the step where the concurrent one is joined.
81 deferred_bvh_inline: Option<crate::geometry::DeferredBvhOptimize>,
82 /// Pool running the parallel parts of the step (see [`Self::configure_thread_pool`]).
83 /// `None` uses whichever pool the calling thread is in.
84 #[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
85 thread_pool: Option<std::sync::Arc<rayon::ThreadPool>>,
86}
87
88impl Default for PhysicsPipeline {
89 fn default() -> Self {
90 PhysicsPipeline::new()
91 }
92}
93
94#[allow(dead_code)]
95fn check_pipeline_send_sync() {
96 fn do_test<T: Sync>() {}
97 do_test::<PhysicsPipeline>();
98}
99
100impl PhysicsPipeline {
101 /// Creates a new physics pipeline.
102 ///
103 /// Call this once when setting up your physics world. The pipeline can be reused
104 /// across multiple frames for better performance.
105 pub fn new() -> PhysicsPipeline {
106 PhysicsPipeline {
107 counters: Counters::new(true),
108 #[cfg(feature = "parallel")]
109 active_body_handles: vec![],
110 sleep_observations: Vec::new(),
111 staged_solver: crate::dynamics::StagedIslandSolver::new(),
112 #[cfg(feature = "parallel")]
113 deferred_bvh: std::sync::Mutex::new(None),
114 deferred_bvh_inline: None,
115 #[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
116 thread_pool: None,
117 joint_constraint_indices: vec![],
118 joint_selection_primed: false,
119 broad_phase_events: vec![],
120 end_step_collider_aabbs: vec![],
121 quarantine: Quarantine::default(),
122 }
123 }
124
125 /// Completes the BVH optimization pass deferred by the last broad-phase update (if
126 /// any) and puts the optimized tree back into the broad-phase. Must be called before
127 /// anything uses the broad-phase tree again.
128 ///
129 /// Waits for the concurrent pass when one was spawned; otherwise runs it here. Both
130 /// paths leave the same tree behind, so the build and the pool size don't change what
131 /// the rest of the step sees.
132 fn join_deferred_bvh_optimize(&mut self, broad_phase: &mut BroadPhaseBvh) {
133 #[cfg(feature = "parallel")]
134 if let Some(rx) = self.deferred_bvh.get_mut().unwrap().take() {
135 let task = rx.recv().expect("the deferred BVH optimization task died");
136 broad_phase.finish_deferred_optimize(task);
137 return;
138 }
139
140 if let Some(mut task) = self.deferred_bvh_inline.take() {
141 task.run();
142 broad_phase.finish_deferred_optimize(task);
143 }
144 }
145
146 /// Advances the physics simulation by one timestep.
147 ///
148 /// This is the main function you'll call every frame in your game loop. It performs all
149 /// physics calculations: collision detection, constraint solving, and updating object positions.
150 ///
151 /// # Parameters
152 ///
153 /// * `gravity` - The gravity vector applied to all dynamic bodies (e.g., `vector![0.0, -9.81, 0.0]` for Earth gravity pointing down)
154 /// * `integration_parameters` - Controls the simulation quality and timestep size (typically 60 Hz = 1/60 second per step)
155 /// * `islands` - Internal system that groups connected objects together for efficient solving (automatically managed)
156 /// * `broad_phase` - Fast collision detection phase that filters out distant object pairs (automatically managed)
157 /// * `narrow_phase` - Precise collision detection that computes exact contact points (automatically managed)
158 /// * `bodies` - Your collection of rigid bodies (the physical objects that move and collide)
159 /// * `colliders` - The collision shapes attached to your bodies (boxes, spheres, meshes, etc.)
160 /// * `impulse_joints` - Regular joints connecting bodies (hinges, sliders, etc.)
161 /// * `multibody_joints` - Articulated joints for robot-like structures (optional, can be empty)
162 /// * `ccd_solver` - Continuous collision detection to prevent fast objects from tunneling through thin walls
163 /// * `hooks` - Optional callbacks to customize collision filtering and contact modification
164 /// * `events` - Optional handler to receive collision events (when objects start/stop touching)
165 ///
166 /// # Example
167 ///
168 /// ```
169 /// # use rapier3d::prelude::*;
170 /// # let mut bodies = RigidBodySet::new();
171 /// # let mut colliders = ColliderSet::new();
172 /// # let mut impulse_joints = ImpulseJointSet::new();
173 /// # let mut multibody_joints = MultibodyJointSet::new();
174 /// # let mut islands = IslandManager::new();
175 /// # let mut broad_phase = BroadPhaseBvh::new();
176 /// # let mut narrow_phase = NarrowPhase::new();
177 /// # let mut ccd_solver = CCDSolver::new();
178 /// # let mut physics_pipeline = PhysicsPipeline::new();
179 /// # let integration_parameters = IntegrationParameters::default();
180 /// // In your game loop:
181 /// physics_pipeline.step(
182 /// Vector::new(0.0, -9.81, 0.0), // Gravity pointing down
183 /// &integration_parameters,
184 /// &mut islands,
185 /// &mut broad_phase,
186 /// &mut narrow_phase,
187 /// &mut bodies,
188 /// &mut colliders,
189 /// &mut impulse_joints,
190 /// &mut multibody_joints,
191 /// &mut ccd_solver,
192 /// &(), // No custom hooks
193 /// &(), // No event handler
194 /// );
195 /// ```
196 pub fn step(
197 &mut self,
198 gravity: Vector,
199 integration_parameters: &IntegrationParameters,
200 islands: &mut IslandManager,
201 broad_phase: &mut BroadPhaseBvh,
202 narrow_phase: &mut NarrowPhase,
203 bodies: &mut RigidBodySet,
204 colliders: &mut ColliderSet,
205 impulse_joints: &mut ImpulseJointSet,
206 multibody_joints: &mut MultibodyJointSet,
207 ccd_solver: &mut CCDSolver,
208 hooks: &dyn PhysicsHooks,
209 events: &dyn EventHandler,
210 ) {
211 // With a dedicated pool configured, run the whole step inside it.
212 #[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
213 if let Some(pool) = self.thread_pool.clone() {
214 return pool.install(|| {
215 self.step_inner(
216 gravity,
217 integration_parameters,
218 islands,
219 broad_phase,
220 narrow_phase,
221 bodies,
222 colliders,
223 impulse_joints,
224 multibody_joints,
225 ccd_solver,
226 hooks,
227 events,
228 )
229 });
230 }
231
232 self.step_inner(
233 gravity,
234 integration_parameters,
235 islands,
236 broad_phase,
237 narrow_phase,
238 bodies,
239 colliders,
240 impulse_joints,
241 multibody_joints,
242 ccd_solver,
243 hooks,
244 events,
245 )
246 }
247}
248
249#[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
250impl PhysicsPipeline {
251 /// Configures a dedicated thread pool for this pipeline's parallel work (default:
252 /// whichever pool the calling thread is in — usually the global one or the one
253 /// setup with `ThreadPool::install`.
254 pub fn configure_thread_pool(
255 &mut self,
256 num_threads: usize,
257 ) -> Result<(), rayon::ThreadPoolBuildError> {
258 let builder = rayon::ThreadPoolBuilder::new()
259 .num_threads(num_threads)
260 .thread_name(|i| alloc::format!("rapier-worker-{i}"));
261
262 self.thread_pool = Some(std::sync::Arc::new(builder.build()?));
263 Ok(())
264 }
265
266 /// The thread-pool used by this physics pipeline, if it was configured.
267 pub fn thread_pool(&self) -> Option<std::sync::Arc<rayon::ThreadPool>> {
268 self.thread_pool.clone()
269 }
270
271 /// Sets (or clears) the thread pool running this pipeline's parallel work.
272 ///
273 /// Unlike [`Self::configure_thread_pool`], this takes an existing pool.
274 pub fn set_thread_pool(&mut self, pool: Option<std::sync::Arc<rayon::ThreadPool>>) {
275 self.thread_pool = pool;
276 }
277
278 /// Removes the dedicated thread pool: the parallel parts of the step run on whichever
279 /// pool the calling thread is in again.
280 pub fn clear_thread_pool(&mut self) {
281 self.thread_pool = None;
282 }
283
284 /// The number of workers this pipeline's parallel work runs on: the size of its
285 /// dedicated thread pool, or of the pool the calling thread is in if it has none.
286 pub fn num_threads(&self) -> Option<usize> {
287 self.thread_pool
288 .as_ref()
289 .map(|pool| pool.current_num_threads())
290 }
291}