Skip to main content

rapier2d/dynamics/island_manager/
manager.rs

1use super::Island;
2use crate::alloc_prelude::*;
3use crate::dynamics::{
4    ImpulseJointSet, MultibodyJointSet, RigidBody, RigidBodyChanges, RigidBodyHandle, RigidBodyIds,
5    RigidBodySet,
6};
7use crate::geometry::{ColliderSet, NarrowPhase};
8use crate::math::Real;
9use crate::utils::DotProduct;
10use parry::utils::VecMap;
11
12/// System that manages which bodies are active (awake) vs sleeping to optimize performance.
13///
14/// ## Sleeping Optimization
15///
16/// Bodies at rest automatically "sleep" - they're excluded from simulation until something
17/// disturbs them (collision, joint connection to moving body, manual wake-up). This can
18/// dramatically improve performance in scenes with many static/resting objects.
19///
20/// ## Islands
21///
22/// All awake bodies live in a single active set solved together. Sleep is decided per
23/// **island** — a connected component of the touching-contact/joint graph, maintained
24/// persistently (eager merges, deferred splits): an island falls asleep once
25/// *every* body has been sleep-eligible long enough, and wakes as a single unit.
26///
27/// You rarely interact with this directly - it's automatically managed by [`PhysicsPipeline`](crate::pipeline::PhysicsPipeline).
28#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
29#[derive(Clone, Default)]
30pub struct IslandManager {
31    /// Bumped whenever any body's `active_set_id`/island assignment changes, so
32    /// the solver's persistent constraint cache can cheaply detect that its cached
33    /// solver-body indices went stale.
34    pub(crate) active_set_epoch: u32,
35    pub(crate) islands: VecMap<Island>,
36    /// The single awake island's id, if any island is awake (all awake bodies live
37    /// in one island; every other `Island` container is a sleeping chunk).
38    pub(crate) awake_island: Option<usize>,
39    pub(crate) free_islands: Vec<usize>,
40    /// The awake set's substep solve-groups, recomputed each step by
41    /// [`Self::update_substep_groups`]. Empty = one implicit group spanning the whole
42    /// awake set (no body has `additional_solver_iterations > 0`).
43    #[cfg_attr(feature = "serde-serialize", serde(skip))]
44    pub(crate) solve_groups: Vec<super::SolveGroup>,
45    /// Scratch buffers for [`Self::update_substep_groups`].
46    #[cfg_attr(feature = "serde-serialize", serde(skip))]
47    pub(super) substep_groups_workspace: super::substep_groups::SubstepGroupsWorkspace,
48    /// The persistent islands: connected components of the touching-contact/joint
49    /// graph, maintained incrementally.
50    pub(crate) persistent: super::PersistentIslands,
51}
52
53impl IslandManager {
54    /// Creates a new empty island manager.
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    #[inline]
60    pub(crate) fn bump_active_set_epoch(&mut self) {
61        self.active_set_epoch = self.active_set_epoch.wrapping_add(1);
62    }
63
64    pub(crate) fn rigid_body_removed_or_disabled(
65        &mut self,
66        removed_handle: RigidBodyHandle,
67        removed_ids: &RigidBodyIds,
68        bodies: &mut RigidBodySet,
69    ) {
70        self.bump_active_set_epoch();
71
72        // Persistent islands: drop the body (clears the live body's ids too,
73        // for the disabled case; uses the captured ids for the removed case).
74        if let Some(rb) = bodies.get_mut_internal(removed_handle) {
75            rb.ids.island_id = super::INVALID_ISLAND;
76            rb.ids.island_index = u32::MAX;
77        }
78        self.persistent
79            .remove_body_raw(bodies, removed_ids.island_id, removed_ids.island_index);
80
81        let Some(island) = self.islands.get_mut(removed_ids.active_island_id as usize) else {
82            // The island already doesn’t exist.
83            return;
84        };
85
86        // If the rigid-body was disabled, it is still in the body set. Invalid its islands ids.
87        if let Some(body) = bodies.get_mut_internal(removed_handle) {
88            body.ids.active_island_id = u32::MAX;
89            body.ids.active_set_id = u32::MAX;
90        }
91
92        let swapped_handle = island.bodies.last().copied().unwrap_or(removed_handle);
93        island
94            .bodies
95            .swap_remove(removed_ids.active_set_id as usize);
96
97        // Remap the active_set_id of the body we moved with the `swap_remove`.
98        if swapped_handle != removed_handle {
99            let swapped_body = bodies
100                .get_mut(swapped_handle)
101                .expect("Internal error: bodies must be removed from islands on at a times");
102            swapped_body.ids.active_set_id = removed_ids.active_set_id;
103        }
104
105        // If we deleted the last body from this island, delete the island.
106        if island.bodies.is_empty() {
107            if self.awake_island == Some(removed_ids.active_island_id as usize) {
108                self.awake_island = None;
109            }
110            self.islands.remove(removed_ids.active_island_id as usize);
111            self.free_islands
112                .push(removed_ids.active_island_id as usize);
113        }
114    }
115
116    /// Handles an interaction starting or stopping between the two endpoints:
117    /// wakes both when requested.
118    pub(crate) fn interaction_changed(
119        &mut self,
120        bodies: &mut RigidBodySet,
121        handle1: Option<RigidBodyHandle>,
122        handle2: Option<RigidBodyHandle>,
123        wake_up: bool,
124    ) {
125        // NOTE: no epoch bump here: a contact start/stop within one island doesn't
126        // renumber anything; the actual id-changing paths (wakes inserting bodies,
127        // sleep extractions) bump the epoch themselves.
128        if wake_up {
129            for handle in [handle1, handle2].into_iter().flatten() {
130                self.wake_up(bodies, handle, false);
131            }
132        }
133
134        // Non-fixed enabled endpoints must be registered in the active set. (Two awake
135        // touching bodies sharing an island is structural: there is at most one awake
136        // island.)
137        #[cfg(debug_assertions)]
138        for handle in [handle1, handle2].into_iter().flatten() {
139            if let Some(rb) = bodies.get(handle) {
140                debug_assert!(
141                    rb.is_fixed() || !rb.is_enabled() || rb.ids.active_island_id != u32::MAX
142                );
143            }
144        }
145    }
146
147    pub(crate) fn island(&self, island_id: usize) -> &Island {
148        &self.islands[island_id]
149    }
150
151    /// Applies a deferred impulse-joint island event, first restoring the invariant for
152    /// `Link`: a sleeping island is woken before merging with an awake one. Two sleeping islands
153    /// merge *without* waking; a fixed or missing endpoint doesn't disturb a sleeping island.
154    pub(crate) fn apply_impulse_joint_island_event(
155        &mut self,
156        bodies: &mut RigidBodySet,
157        event: crate::dynamics::ImpulseJointIslandEvent,
158    ) {
159        if let crate::dynamics::ImpulseJointIslandEvent::Link { body1, body2, .. } = event {
160            self.wake_for_link(bodies, body1, body2);
161        }
162        self.persistent.apply_impulse_joint_event(bodies, event);
163    }
164
165    /// Refreshes a multibody's island-connectivity chain, first waking its
166    /// sleeping members if any member is awake (a multibody is atomic: its
167    /// bodies must share one sleep state).
168    pub(crate) fn refresh_multibody_chain(
169        &mut self,
170        bodies: &mut RigidBodySet,
171        multibody_joints: &MultibodyJointSet,
172        mb_id: crate::dynamics::MultibodyIndex,
173    ) {
174        if let Some(mb) = multibody_joints.get_multibody(mb_id) {
175            let mut any_awake = false;
176            let mut sleeping = Vec::new();
177            for link in mb.links() {
178                if let Some(rb) = bodies.get(link.rigid_body) {
179                    if !rb.is_fixed() && rb.is_enabled() {
180                        if rb.activation.sleeping {
181                            sleeping.push(link.rigid_body);
182                        } else {
183                            any_awake = true;
184                        }
185                    }
186                }
187            }
188            if any_awake {
189                for handle in sleeping {
190                    self.wake_up(bodies, handle, true);
191                }
192            }
193        }
194        self.persistent
195            .refresh_multibody_chain(bodies, multibody_joints, mb_id);
196    }
197
198    /// Wakes the sleeping side of a new link when the other side is awake.
199    fn wake_for_link(
200        &mut self,
201        bodies: &mut RigidBodySet,
202        h1: RigidBodyHandle,
203        h2: RigidBodyHandle,
204    ) {
205        let state = |bodies: &RigidBodySet, h: RigidBodyHandle| {
206            bodies
207                .get(h)
208                .filter(|rb| !rb.is_fixed() && rb.is_enabled())
209                .map(|rb| rb.activation.sleeping)
210        };
211        match (state(bodies, h1), state(bodies, h2)) {
212            (Some(false), Some(true)) => self.wake_up(bodies, h2, true),
213            (Some(true), Some(false)) => self.wake_up(bodies, h1, true),
214            _ => {}
215        }
216    }
217
218    /// The persistent island a body belongs to (`None` for fixed, disabled or removed bodies).
219    /// Test/debug introspection only — island ids are unstable across steps (merges and splits
220    /// recycle them); only *equality* between two bodies' islands is meaningful.
221    #[doc(hidden)]
222    pub fn persistent_island_of(
223        &self,
224        bodies: &RigidBodySet,
225        handle: RigidBodyHandle,
226    ) -> Option<u32> {
227        self.persistent.body_island(bodies, handle)
228    }
229
230    /// Handles of dynamic and kinematic rigid-bodies that are currently active (i.e. not sleeping).
231    #[inline]
232    pub fn active_bodies(&self) -> impl Iterator<Item = RigidBodyHandle> + '_ {
233        self.awake_island
234            .into_iter()
235            .flat_map(|i| self.islands[i].bodies.iter().copied())
236    }
237
238    /// The awake island's body slice (same content and order as
239    /// [`Self::active_bodies`]), for callers that want to chunk the active set in
240    /// parallel.
241    #[cfg(feature = "parallel")]
242    #[inline]
243    pub(crate) fn active_body_slices(&self) -> impl Iterator<Item = &[RigidBodyHandle]> {
244        self.awake_island
245            .into_iter()
246            .map(|i| self.islands[i].bodies.as_slice())
247    }
248
249    /// Number of currently active (non-sleeping) dynamic and kinematic bodies.
250    #[inline]
251    pub fn num_active_bodies(&self) -> usize {
252        self.awake_island
253            .map(|i| self.islands[i].bodies.len())
254            .unwrap_or(0)
255    }
256
257    pub(crate) fn rigid_body_updated(
258        &mut self,
259        handle: RigidBodyHandle,
260        bodies: &mut RigidBodySet,
261    ) {
262        self.bump_active_set_epoch();
263        let Some(rb) = bodies.get_mut(handle) else {
264            return;
265        };
266
267        if rb.is_fixed() {
268            // A body turned fixed leaves the persistent islands (fixed bodies
269            // are never island members).
270            self.persistent.remove_body(bodies, handle);
271            return;
272        }
273
274        // Check if this is the first time we see this rigid-body.
275        if rb.ids.active_island_id == u32::MAX {
276            if !rb.is_sleeping() {
277                // Awake bodies all live in the single awake island.
278                if let Some(id) = self.awake_island {
279                    let target_island = &mut self.islands[id];
280                    rb.ids.active_island_id = id as u32;
281                    rb.ids.active_set_id = (target_island.bodies.len()) as u32;
282                    target_island.bodies.push(handle);
283                } else {
284                    let new_island = Island::singleton(handle);
285                    let id = self.free_islands.pop().unwrap_or(self.islands.len());
286                    self.awake_island = Some(id);
287                    self.islands.insert(id, new_island);
288                    rb.ids.active_island_id = id as u32;
289                    rb.ids.active_set_id = 0;
290                }
291            } else {
292                // A body inserted asleep gets its own sleeping chunk.
293                let new_island = Island::singleton(handle);
294                let id = self.free_islands.pop().unwrap_or(self.islands.len());
295                self.islands.insert(id, new_island);
296                rb.ids.active_island_id = id as u32;
297                rb.ids.active_set_id = 0;
298            }
299        }
300
301        // Persistent islands: first-seen, re-enabled, or fixed-turned-dynamic
302        // bodies get a singleton island (no-op for existing members).
303        self.persistent.ensure_body(bodies, handle);
304        let rb = bodies.index_mut_internal(handle);
305
306        // Push the body to the active set if it is not inside the active set yet, and
307        // is not longer sleeping or became dynamic.
308        if (rb.changes.contains(RigidBodyChanges::SLEEP)
309            || rb.changes.contains(RigidBodyChanges::TYPE))
310            && rb.is_enabled()
311            // Don’t wake up if the user put it to sleep manually.
312            && !rb.activation.sleeping
313        {
314            self.wake_up(bodies, handle, false);
315        }
316    }
317
318    /// Updates a body's sleep-eligibility timer from its current velocities
319    /// and last-step displacement.
320    pub(crate) fn update_body_energy(rb: &mut RigidBody, dt: Real, length_unit: Real) {
321        let sq_linvel = rb.vels.linvel.length_squared();
322        let sq_angvel = rb.vels.angvel.gdot(rb.vels.angvel);
323        let pose = rb.pos.position;
324        rb.activation.update_energy(
325            rb.body_type,
326            length_unit,
327            sq_linvel,
328            sq_angvel,
329            rb.mprops.max_extent(),
330            &pose,
331            dt,
332        );
333    }
334
335    pub(crate) fn update_islands(
336        &mut self,
337        bodies: &mut RigidBodySet,
338        colliders: &ColliderSet,
339        narrow_phase: &mut NarrowPhase,
340        impulse_joints: &ImpulseJointSet,
341        multibody_joints: &MultibodyJointSet,
342        sleep_observations: &[(u32, bool)],
343    ) {
344        // First update after construction or deserialization: rebuild the persistent islands
345        // from the current graphs, and wake any sleeping body stranded in a mixed island
346        // (deserialized partial-island-era state) to restore the whole-island invariant.
347        if !self.persistent.bootstrapped {
348            let to_wake = self.persistent.bootstrap(
349                bodies,
350                narrow_phase.touching_pairs_with_ids(colliders),
351                impulse_joints,
352                multibody_joints,
353            );
354            for handle in to_wake {
355                self.wake_up(bodies, handle, false);
356            }
357
358            // `max_extent` (sleep metric) is only refreshed when colliders
359            // change: seed it for deserialized snapshots that predate it.
360            let handles: Vec<RigidBodyHandle> = bodies.iter().map(|(h, _)| h).collect();
361            for handle in handles {
362                let rb = bodies.index_mut_internal(handle);
363                if rb.mprops.max_extent() == 0.0 {
364                    rb.mprops.recompute_max_extent(colliders, &rb.colliders);
365                }
366            }
367        }
368
369        // Whole-island sleep decision: an island sleeps once *every* body has been
370        // sleep-eligible long enough; one that lost constraints must split first (unless
371        // single-body). Observations come from the pipeline's fused active-bodies traversal, so this never touches the body arena.
372        let mut chunks: Vec<Vec<RigidBodyHandle>> = Vec::new();
373        if !sleep_observations.is_empty() {
374            self.persistent.begin_sleep_scan();
375            for (island_id, eligible) in sleep_observations {
376                self.persistent
377                    .observe_body_for_sleep(*island_id, *eligible);
378            }
379
380            let sleepable = self.persistent.finish_sleep_scan();
381            for id in sleepable {
382                self.persistent.mark_island_sleeping(id);
383                chunks.push(self.persistent.islands[id as usize].bodies.clone());
384            }
385        }
386
387        if !chunks.is_empty() {
388            let awake_id = self
389                .awake_island
390                .expect("sleep observations imply an awake island");
391            let awake_len = self.islands[awake_id].len();
392            self.commit_sleeping_chunks(bodies, narrow_phase, awake_id, awake_len, chunks);
393        }
394
395        // Persistent-island structural validation (debug builds only): index
396        // consistency, plus "every touching pair with an island-member side is
397        // linked, into that member's island".
398        #[cfg(debug_assertions)]
399        {
400            self.persistent.assert_consistent(bodies);
401            for (edge_id, h1, h2) in narrow_phase.touching_pairs_with_ids(colliders) {
402                let member = |h: Option<RigidBodyHandle>| {
403                    h.and_then(|h| bodies.get(h))
404                        .filter(|rb| !rb.is_fixed() && rb.is_enabled())
405                        .map(|rb| rb.ids.island_id)
406                };
407                let m1 = member(h1);
408                let m2 = member(h2);
409                if m1.is_some() || m2.is_some() {
410                    let loc = self.persistent.contact_link_loc(edge_id);
411                    assert!(
412                        loc.is_some(),
413                        "touching pair (edge {edge_id}) not linked in the persistent islands"
414                    );
415                    let island = loc.unwrap().0;
416                    for m in [m1, m2].into_iter().flatten() {
417                        assert_eq!(
418                            m, island,
419                            "touching pair (edge {edge_id}) linked into the wrong island"
420                        );
421                    }
422                }
423            }
424        }
425    }
426}