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, SoftBodySet,
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 (workspace 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 update 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    /// Colliders the end-of-step soft-body sync deformed or moved to their cluster proxy's fresh
60    /// pose, whose broad-phase AABBs follow them.
61    soft_synced_colliders: Vec<ColliderHandle>,
62    /// Non-finite state detected and neutralized during the last step.
63    quarantine: Quarantine,
64    /// Workspace buffer holding the active body handles (parallel body update).
65    #[cfg(feature = "parallel")]
66    active_body_handles: Vec<crate::dynamics::RigidBodyHandle>,
67    /// Workspace: per-active-body sleep observations `(persistent island id, eligible)`,
68    /// run-length compressed by the fused traversal, consumed by `IslandManager::
69    /// update_islands`'s whole-island sleep decision — which never re-touches the body arena.
70    sleep_observations: Vec<(u32, bool)>,
71    /// The single, unified solver: the awake island is solved by its colored,
72    /// staged workers. On a non-parallel (or wasm) build it runs with one worker
73    /// inline on the calling thread.
74    staged_solver: crate::dynamics::StagedIslandSolver,
75    /// Handle on the BVH optimization pass running concurrently with the narrow
76    /// phase and solver (the `Mutex` only exists to keep the pipeline `Sync`; it is
77    /// never contended).
78    #[cfg(feature = "parallel")]
79    deferred_bvh:
80        std::sync::Mutex<Option<std::sync::mpsc::Receiver<crate::geometry::DeferredBvhOptimize>>>,
81    /// Deferred BVH optimization that had no spare worker to run on (single-threaded
82    /// pool, or `parallel` off): run inline by `join_deferred_bvh_optimize`, i.e. at
83    /// the same point of the step where the concurrent one is joined.
84    deferred_bvh_inline: Option<crate::geometry::DeferredBvhOptimize>,
85    /// Pool running the parallel parts of the step (see [`Self::configure_thread_pool`]).
86    /// `None` uses whichever pool the calling thread is in.
87    #[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
88    thread_pool: Option<std::sync::Arc<rayon::ThreadPool>>,
89}
90
91impl Default for PhysicsPipeline {
92    fn default() -> Self {
93        PhysicsPipeline::new()
94    }
95}
96
97#[allow(dead_code)]
98fn check_pipeline_send_sync() {
99    fn do_test<T: Sync>() {}
100    do_test::<PhysicsPipeline>();
101}
102
103impl PhysicsPipeline {
104    /// Creates a new physics pipeline.
105    ///
106    /// Call this once when setting up your physics world. The pipeline can be reused
107    /// across multiple frames for better performance.
108    pub fn new() -> PhysicsPipeline {
109        PhysicsPipeline {
110            counters: Counters::new(true),
111            #[cfg(feature = "parallel")]
112            active_body_handles: vec![],
113            sleep_observations: Vec::new(),
114            staged_solver: crate::dynamics::StagedIslandSolver::new(),
115            #[cfg(feature = "parallel")]
116            deferred_bvh: std::sync::Mutex::new(None),
117            deferred_bvh_inline: None,
118            #[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
119            thread_pool: None,
120            joint_constraint_indices: vec![],
121            joint_selection_primed: false,
122            broad_phase_events: vec![],
123            end_step_collider_aabbs: vec![],
124            soft_synced_colliders: vec![],
125            quarantine: Quarantine::default(),
126        }
127    }
128
129    /// Completes the BVH optimization pass deferred by the last broad-phase update (if
130    /// any) and puts the optimized tree back into the broad-phase. Must be called before
131    /// anything uses the broad-phase tree again.
132    ///
133    /// Waits for the concurrent pass when one was spawned; otherwise runs it here. Both
134    /// paths leave the same tree behind, so the build and the pool size don't change what
135    /// the rest of the step sees.
136    fn join_deferred_bvh_optimize(&mut self, broad_phase: &mut BroadPhaseBvh) {
137        #[cfg(feature = "parallel")]
138        if let Some(rx) = self.deferred_bvh.get_mut().unwrap().take() {
139            let task = rx.recv().expect("the deferred BVH optimization task died");
140            broad_phase.finish_deferred_optimize(task);
141            return;
142        }
143
144        if let Some(mut task) = self.deferred_bvh_inline.take() {
145            task.run();
146            broad_phase.finish_deferred_optimize(task);
147        }
148    }
149
150    /// Advances the physics simulation by one timestep.
151    ///
152    /// This is the main function you'll call every frame in your game loop. It performs all
153    /// physics calculations: collision detection, constraint solving, and updating object positions.
154    ///
155    /// # Parameters
156    ///
157    /// * `gravity` - The gravity vector applied to all dynamic bodies (e.g., `vector![0.0, -9.81, 0.0]` for Earth gravity pointing down)
158    /// * `integration_parameters` - Controls the simulation quality and timestep size (typically 60 Hz = 1/60 second per step)
159    /// * `islands` - Internal system that groups connected objects together for efficient solving (automatically managed)
160    /// * `broad_phase` - Fast collision detection phase that filters out distant object pairs (automatically managed)
161    /// * `narrow_phase` - Precise collision detection that computes exact contact points (automatically managed)
162    /// * `bodies` - Your collection of rigid bodies (the physical objects that move and collide)
163    /// * `colliders` - The collision shapes attached to your bodies (boxes, spheres, meshes, etc.)
164    /// * `impulse_joints` - Regular joints connecting bodies (hinges, sliders, etc.)
165    /// * `multibody_joints` - Articulated joints for robot-like structures (optional, can be empty)
166    /// * `soft_bodies` - Deformable particle bodies (optional, can be empty)
167    /// * `ccd_solver` - Continuous collision detection to prevent fast objects from tunneling through thin walls
168    /// * `hooks` - Optional callbacks to customize collision filtering and contact modification
169    /// * `events` - Optional handler to receive collision events (when objects start/stop touching)
170    ///
171    /// A step with a zero [`IntegrationParameters::dt`] (e.g. the first frame of a variable
172    /// timestep) doesn't simulate anything: it only applies the user changes and updates the
173    /// collision detection.
174    ///
175    /// # Example
176    ///
177    /// ```
178    /// # use rapier3d::prelude::*;
179    /// # let mut bodies = RigidBodySet::new();
180    /// # let mut colliders = ColliderSet::new();
181    /// # let mut impulse_joints = ImpulseJointSet::new();
182    /// # let mut multibody_joints = MultibodyJointSet::new();
183    /// # let mut soft_bodies = SoftBodySet::new();
184    /// # let mut islands = IslandManager::new();
185    /// # let mut broad_phase = BroadPhaseBvh::new();
186    /// # let mut narrow_phase = NarrowPhase::new();
187    /// # let mut ccd_solver = CCDSolver::new();
188    /// # let mut physics_pipeline = PhysicsPipeline::new();
189    /// # let integration_parameters = IntegrationParameters::default();
190    /// // In your game loop:
191    /// physics_pipeline.step(
192    ///     Vector::new(0.0, -9.81, 0.0),  // Gravity pointing down
193    ///     &integration_parameters,
194    ///     &mut islands,
195    ///     &mut broad_phase,
196    ///     &mut narrow_phase,
197    ///     &mut bodies,
198    ///     &mut colliders,
199    ///     &mut impulse_joints,
200    ///     &mut multibody_joints,
201    ///     &mut soft_bodies,
202    ///     &mut ccd_solver,
203    ///     &(),  // No custom hooks
204    ///     &(),  // No event handler
205    /// );
206    /// ```
207    pub fn step(
208        &mut self,
209        gravity: Vector,
210        integration_parameters: &IntegrationParameters,
211        islands: &mut IslandManager,
212        broad_phase: &mut BroadPhaseBvh,
213        narrow_phase: &mut NarrowPhase,
214        bodies: &mut RigidBodySet,
215        colliders: &mut ColliderSet,
216        impulse_joints: &mut ImpulseJointSet,
217        multibody_joints: &mut MultibodyJointSet,
218        soft_bodies: &mut SoftBodySet,
219        ccd_solver: &mut CCDSolver,
220        hooks: &dyn PhysicsHooks,
221        events: &dyn EventHandler,
222    ) {
223        // With a dedicated pool configured, run the whole step inside it.
224        #[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
225        if let Some(pool) = self.thread_pool.clone() {
226            return pool.install(|| {
227                self.step_inner(
228                    gravity,
229                    integration_parameters,
230                    islands,
231                    broad_phase,
232                    narrow_phase,
233                    bodies,
234                    colliders,
235                    impulse_joints,
236                    multibody_joints,
237                    soft_bodies,
238                    ccd_solver,
239                    hooks,
240                    events,
241                )
242            });
243        }
244
245        self.step_inner(
246            gravity,
247            integration_parameters,
248            islands,
249            broad_phase,
250            narrow_phase,
251            bodies,
252            colliders,
253            impulse_joints,
254            multibody_joints,
255            soft_bodies,
256            ccd_solver,
257            hooks,
258            events,
259        )
260    }
261}
262
263#[cfg(all(feature = "parallel", not(feature = "unsync-callbacks")))]
264impl PhysicsPipeline {
265    /// Configures a dedicated thread pool for this pipeline's parallel work (default:
266    /// whichever pool the calling thread is in — usually the global one or the one
267    /// setup with `ThreadPool::install`.
268    pub fn configure_thread_pool(
269        &mut self,
270        num_threads: usize,
271    ) -> Result<(), rayon::ThreadPoolBuildError> {
272        let builder = rayon::ThreadPoolBuilder::new()
273            .num_threads(num_threads)
274            .thread_name(|i| alloc::format!("rapier-worker-{i}"));
275
276        self.thread_pool = Some(std::sync::Arc::new(builder.build()?));
277        Ok(())
278    }
279
280    /// The thread-pool used by this physics pipeline, if it was configured.
281    pub fn thread_pool(&self) -> Option<std::sync::Arc<rayon::ThreadPool>> {
282        self.thread_pool.clone()
283    }
284
285    /// Sets (or clears) the thread pool running this pipeline's parallel work.
286    ///
287    /// Unlike [`Self::configure_thread_pool`], this takes an existing pool.
288    pub fn set_thread_pool(&mut self, pool: Option<std::sync::Arc<rayon::ThreadPool>>) {
289        self.thread_pool = pool;
290    }
291
292    /// Removes the dedicated thread pool: the parallel parts of the step run on whichever
293    /// pool the calling thread is in again.
294    pub fn clear_thread_pool(&mut self) {
295        self.thread_pool = None;
296    }
297
298    /// The number of workers this pipeline's parallel work runs on: the size of its
299    /// dedicated thread pool, or of the pool the calling thread is in if it has none.
300    pub fn num_threads(&self) -> Option<usize> {
301        self.thread_pool
302            .as_ref()
303            .map(|pool| pool.current_num_threads())
304    }
305}