Skip to main content

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}