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, RigidBodyType, SoftBodyHandle, SoftBodySet,
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    /// Workspace 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.) An island manager without any island tracks no body at all, like the
137        // one driven by the `CollisionPipeline` which never registers bodies.
138        #[cfg(debug_assertions)]
139        for handle in [handle1, handle2].into_iter().flatten() {
140            if let Some(rb) = bodies.get(handle).filter(|_| !self.islands.is_empty()) {
141                debug_assert!(
142                    rb.is_fixed() || !rb.is_enabled() || rb.ids.active_island_id != u32::MAX
143                );
144            }
145        }
146    }
147
148    pub(crate) fn island(&self, island_id: usize) -> &Island {
149        &self.islands[island_id]
150    }
151
152    /// Applies a deferred impulse-joint island event, first restoring the invariant for
153    /// `Link`: a sleeping island is woken before merging with an awake one. Two sleeping islands
154    /// merge *without* waking; a fixed or missing endpoint doesn't disturb a sleeping island.
155    pub(crate) fn apply_impulse_joint_island_event(
156        &mut self,
157        bodies: &mut RigidBodySet,
158        event: crate::dynamics::ImpulseJointIslandEvent,
159    ) {
160        if let crate::dynamics::ImpulseJointIslandEvent::Link { body1, body2, .. } = event {
161            self.wake_for_link(bodies, body1, body2);
162        }
163        self.persistent.apply_impulse_joint_event(bodies, event);
164    }
165
166    /// Updates a multibody's island-connectivity chain, first waking its
167    /// sleeping members if any member is awake (a multibody is atomic: its
168    /// bodies must share one sleep state).
169    pub(crate) fn update_multibody_chain(
170        &mut self,
171        bodies: &mut RigidBodySet,
172        multibody_joints: &MultibodyJointSet,
173        mb_id: crate::dynamics::MultibodyIndex,
174    ) {
175        if let Some(mb) = multibody_joints.get_multibody(mb_id) {
176            let mut any_awake = false;
177            let mut sleeping = Vec::new();
178            for link in mb.links() {
179                if let Some(rb) = bodies.get(link.rigid_body) {
180                    if !rb.is_fixed() && rb.is_enabled() {
181                        if rb.activation.sleeping {
182                            sleeping.push(link.rigid_body);
183                        } else {
184                            any_awake = true;
185                        }
186                    }
187                }
188            }
189            if any_awake {
190                for handle in sleeping {
191                    self.wake_up(bodies, handle, true);
192                }
193            }
194        }
195        self.persistent
196            .update_multibody_chain(bodies, multibody_joints, mb_id);
197    }
198
199    /// Updates a soft body's attachment links and its proxy chain, first waking the sleeping
200    /// side of every attachment whose other side is awake, and the sleeping proxies if any
201    /// sibling is awake (a soft body is atomic for sleep, like a multibody).
202    pub(crate) fn update_soft_body_attachments(
203        &mut self,
204        bodies: &mut RigidBodySet,
205        soft_bodies: &SoftBodySet,
206        handle: SoftBodyHandle,
207    ) {
208        if let Some(sb) = soft_bodies.get(handle) {
209            let root = sb.root_body();
210            for attachment in sb.particle_attachments() {
211                self.wake_for_link(bodies, root, attachment.body);
212            }
213            let mut any_awake = false;
214            let mut sleeping = Vec::new();
215            for (_, cluster) in sb.live_clusters() {
216                if let Some(rb) = bodies.get(cluster.proxy()) {
217                    if rb.is_enabled() {
218                        if rb.activation().sleeping {
219                            sleeping.push(cluster.proxy());
220                        } else {
221                            any_awake = true;
222                        }
223                    }
224                }
225            }
226            if any_awake {
227                for proxy in sleeping {
228                    self.wake_up(bodies, proxy, true);
229                }
230            }
231        }
232        self.persistent
233            .update_soft_body_attachments(bodies, soft_bodies, handle);
234        self.persistent
235            .update_soft_body_proxy_chain(bodies, soft_bodies, handle);
236    }
237
238    /// Wakes the sleeping side of a new link when the other side is awake.
239    fn wake_for_link(
240        &mut self,
241        bodies: &mut RigidBodySet,
242        h1: RigidBodyHandle,
243        h2: RigidBodyHandle,
244    ) {
245        let state = |bodies: &RigidBodySet, h: RigidBodyHandle| {
246            bodies
247                .get(h)
248                .filter(|rb| !rb.is_fixed() && rb.is_enabled())
249                .map(|rb| rb.activation.sleeping)
250        };
251        match (state(bodies, h1), state(bodies, h2)) {
252            (Some(false), Some(true)) => self.wake_up(bodies, h2, true),
253            (Some(true), Some(false)) => self.wake_up(bodies, h1, true),
254            _ => {}
255        }
256    }
257
258    /// The persistent island a body belongs to (`None` for fixed, disabled or removed bodies).
259    /// Test/debug introspection only — island ids are unstable across steps (merges and splits
260    /// recycle them); only *equality* between two bodies' islands is meaningful.
261    #[doc(hidden)]
262    pub fn persistent_island_of(
263        &self,
264        bodies: &RigidBodySet,
265        handle: RigidBodyHandle,
266    ) -> Option<u32> {
267        self.persistent.body_island(bodies, handle)
268    }
269
270    /// Handles of dynamic and kinematic rigid-bodies that are currently active (i.e. not sleeping).
271    #[inline]
272    pub fn active_bodies(&self) -> impl Iterator<Item = RigidBodyHandle> + '_ {
273        self.awake_island
274            .into_iter()
275            .flat_map(|i| self.islands[i].bodies.iter().copied())
276    }
277
278    /// The awake island's body slice (same content and order as
279    /// [`Self::active_bodies`]), for callers that want to chunk the active set in
280    /// parallel.
281    #[cfg(feature = "parallel")]
282    #[inline]
283    pub(crate) fn active_body_slices(&self) -> impl Iterator<Item = &[RigidBodyHandle]> {
284        self.awake_island
285            .into_iter()
286            .map(|i| self.islands[i].bodies.as_slice())
287    }
288
289    /// Number of currently active (non-sleeping) dynamic and kinematic bodies.
290    #[inline]
291    pub fn num_active_bodies(&self) -> usize {
292        self.awake_island
293            .map(|i| self.islands[i].bodies.len())
294            .unwrap_or(0)
295    }
296
297    pub(crate) fn rigid_body_updated(
298        &mut self,
299        handle: RigidBodyHandle,
300        bodies: &mut RigidBodySet,
301    ) {
302        self.bump_active_set_epoch();
303        let Some(rb) = bodies.get_mut(handle) else {
304            return;
305        };
306
307        if rb.is_fixed() {
308            // A body turned fixed leaves the persistent islands (fixed bodies
309            // are never island members).
310            self.persistent.remove_body(bodies, handle);
311            return;
312        }
313
314        // Check if this is the first time we see this rigid-body.
315        if rb.ids.active_island_id == u32::MAX {
316            if !rb.is_sleeping() {
317                // Awake bodies all live in the single awake island.
318                if let Some(id) = self.awake_island {
319                    let target_island = &mut self.islands[id];
320                    rb.ids.active_island_id = id as u32;
321                    rb.ids.active_set_id = (target_island.bodies.len()) as u32;
322                    target_island.bodies.push(handle);
323                } else {
324                    let new_island = Island::singleton(handle);
325                    let id = self.free_islands.pop().unwrap_or(self.islands.len());
326                    self.awake_island = Some(id);
327                    self.islands.insert(id, new_island);
328                    rb.ids.active_island_id = id as u32;
329                    rb.ids.active_set_id = 0;
330                }
331            } else {
332                // A body inserted asleep gets its own sleeping chunk.
333                let new_island = Island::singleton(handle);
334                let id = self.free_islands.pop().unwrap_or(self.islands.len());
335                self.islands.insert(id, new_island);
336                rb.ids.active_island_id = id as u32;
337                rb.ids.active_set_id = 0;
338            }
339        }
340
341        // Persistent islands: first-seen, re-enabled, or fixed-turned-dynamic
342        // bodies get a singleton island (no-op for existing members).
343        self.persistent.ensure_body(bodies, handle);
344        let rb = bodies.index_mut_internal(handle);
345
346        // Push the body to the active set if it is not inside the active set yet, and
347        // is not longer sleeping or became dynamic.
348        if (rb.changes.contains(RigidBodyChanges::SLEEP)
349            || rb.changes.contains(RigidBodyChanges::TYPE))
350            && rb.is_enabled()
351            // Don’t wake up if the user put it to sleep manually.
352            && !rb.activation.sleeping
353        {
354            self.wake_up(bodies, handle, false);
355        }
356    }
357
358    /// Updates a body's sleep-eligibility timer from its current velocities
359    /// and last-step displacement.
360    pub(crate) fn update_body_energy(
361        rb: &mut RigidBody,
362        soft_bodies: &SoftBodySet,
363        dt: Real,
364        length_unit: Real,
365    ) {
366        // A soft-frame proxy's velocities are its cluster's average, so the sleep rule reads the
367        // soft body's fastest particle instead.
368        // TODO: have the soft-body code write a frame-velocity estimate into the rigid body?
369        let sq_linvel = if rb.body_type == RigidBodyType::SoftFrame {
370            let speed = soft_bodies
371                .get(rb.soft_body)
372                .map_or(0.0, |sb| sb.sleep_speed);
373            speed * speed
374        } else {
375            rb.vels.linvel.length_squared()
376        };
377        let sq_angvel = rb.vels.angvel.gdot(rb.vels.angvel);
378        let pose = rb.pos.position;
379        rb.activation.update_energy(
380            rb.body_type,
381            length_unit,
382            sq_linvel,
383            sq_angvel,
384            rb.mprops.max_extent(),
385            &pose,
386            dt,
387        );
388    }
389
390    pub(crate) fn update_islands(
391        &mut self,
392        bodies: &mut RigidBodySet,
393        colliders: &ColliderSet,
394        narrow_phase: &mut NarrowPhase,
395        impulse_joints: &ImpulseJointSet,
396        multibody_joints: &MultibodyJointSet,
397        soft_bodies: &SoftBodySet,
398        sleep_observations: &[(u32, bool)],
399    ) {
400        // First update after construction or deserialization: rebuild the persistent islands
401        // from the current graphs, and wake any sleeping body stranded in a mixed island
402        // (deserialized partial-island-era state) to restore the whole-island invariant.
403        if !self.persistent.bootstrapped {
404            let to_wake = self.persistent.bootstrap(
405                bodies,
406                narrow_phase.touching_pairs_with_ids(colliders),
407                impulse_joints,
408                multibody_joints,
409                soft_bodies,
410            );
411            for handle in to_wake {
412                self.wake_up(bodies, handle, false);
413            }
414
415            // `max_extent` (sleep metric) is only updated when colliders
416            // change: seed it for deserialized snapshots that predate it.
417            let handles: Vec<RigidBodyHandle> = bodies.iter().map(|(h, _)| h).collect();
418            for handle in handles {
419                let rb = bodies.index_mut_internal(handle);
420                if rb.mprops.max_extent() == 0.0 {
421                    rb.mprops.recompute_max_extent(colliders, &rb.colliders);
422                }
423            }
424        }
425
426        // Whole-island sleep decision: an island sleeps once *every* body has been
427        // sleep-eligible long enough; one that lost constraints must split first (unless
428        // single-body). Observations come from the pipeline's fused active-bodies traversal, so this never touches the body arena.
429        let mut chunks: Vec<Vec<RigidBodyHandle>> = Vec::new();
430        if !sleep_observations.is_empty() {
431            self.persistent.begin_sleep_scan();
432            for (island_id, eligible) in sleep_observations {
433                self.persistent
434                    .observe_body_for_sleep(*island_id, *eligible);
435            }
436
437            let sleepable = self.persistent.finish_sleep_scan();
438            for id in sleepable {
439                self.persistent.mark_island_sleeping(id);
440                chunks.push(self.persistent.islands[id as usize].bodies.clone());
441            }
442        }
443
444        if !chunks.is_empty() {
445            let awake_id = self
446                .awake_island
447                .expect("sleep observations imply an awake island");
448            let awake_len = self.islands[awake_id].len();
449            self.commit_sleeping_chunks(bodies, narrow_phase, awake_id, awake_len, chunks);
450        }
451
452        // Persistent-island structural validation (debug builds only): index
453        // consistency, plus "every touching pair with an island-member side is
454        // linked, into that member's island".
455        #[cfg(debug_assertions)]
456        {
457            self.persistent.assert_consistent(bodies);
458            for (edge_id, h1, h2) in narrow_phase.touching_pairs_with_ids(colliders) {
459                let member = |h: Option<RigidBodyHandle>| {
460                    h.and_then(|h| bodies.get(h))
461                        .filter(|rb| !rb.is_fixed() && rb.is_enabled())
462                        .map(|rb| rb.ids.island_id)
463                };
464                let m1 = member(h1);
465                let m2 = member(h2);
466                if m1.is_some() || m2.is_some() {
467                    let loc = self.persistent.contact_link_loc(edge_id);
468                    assert!(
469                        loc.is_some(),
470                        "touching pair (edge {edge_id}) not linked in the persistent islands"
471                    );
472                    let island = loc.unwrap().0;
473                    for m in [m1, m2].into_iter().flatten() {
474                        assert_eq!(
475                            m, island,
476                            "touching pair (edge {edge_id}) linked into the wrong island"
477                        );
478                    }
479                }
480            }
481        }
482    }
483}