Skip to main content

avian3d/dynamics/solver/
constraint_graph.rs

1//! A [`ConstraintGraph`] with [graph coloring] for solving constraints in parallel.
2//!
3//! The constraint graph implements a greedy edge coloring algorithm that assigns
4//! constraints to colors such that no adjacent edges in the graph share the same color.
5//! This ensures that each body only appears in a given color once, allowing constraints
6//! within the same color to be solved in parallel without race conditions.
7//!
8//! See [`ConstraintGraph`] for more details on how the graph is structured.
9//!
10//! [graph coloring]: https://en.wikipedia.org/wiki/Graph_coloring
11//!
12//! # References
13//!
14//! - [High-Performance Physical Simulations on Next-Generation Architecture with Many Cores][Intel paper]
15//! - [Box2D - SIMD Matters] by [Erin Catto]
16//!
17//! [Intel paper]: http://web.eecs.umich.edu/~msmelyan/papers/physsim_onmanycore_itj.pdf
18//! [Box2D - SIMD Matters]: https://box2d.org/posts/2024/08/simd-matters/
19//! [Erin Catto]: https://github.com/erincatto
20
21#[cfg(feature = "serialize")]
22use bevy::reflect::{ReflectDeserialize, ReflectSerialize};
23use bevy::{
24    ecs::{entity::Entity, resource::Resource},
25    reflect::Reflect,
26};
27
28use smallvec::SmallVec;
29
30use crate::{
31    collision::contact_types::ContactId,
32    data_structures::bit_vec::BitVec,
33    prelude::{ContactPair, ContactPairFlags},
34};
35
36use super::contact::ContactConstraint;
37
38/// The [`ContactConstraintHandle`]s associated with a single contact, with one handle
39/// per contact manifold that generates constraints.
40///
41/// Stored inline for up to 2 (in 2D) or 4 (in 3D) manifolds before spilling to the heap.
42#[cfg(feature = "2d")]
43type ManifoldConstraintHandles = SmallVec<[ContactConstraintHandle; 2]>;
44
45/// The [`ContactConstraintHandle`]s associated with a single contact, with one handle
46/// per contact manifold that generates constraints.
47///
48/// Stored inline for up to 2 (in 2D) or 4 (in 3D) manifolds before spilling to the heap.
49#[cfg(feature = "3d")]
50type ManifoldConstraintHandles = SmallVec<[ContactConstraintHandle; 4]>;
51
52/// The maximum number of [`GraphColor`]s in the [`ConstraintGraph`].
53/// Constraints that cannot find a color are added to the overflow set,
54/// which is solved on a single thread.
55pub const GRAPH_COLOR_COUNT: usize = 24;
56
57/// The index of the overflow color in the graph, used for constraints that don't fit
58/// the graph color limit. This can happen when a single body is interacting with many other bodies.
59pub const COLOR_OVERFLOW_INDEX: usize = GRAPH_COLOR_COUNT - 1;
60
61/// The number of colors with constraints involving two non-static bodies.
62/// Leaving constraints involving static bodies to later colors gives higher priority
63/// to those constraints, reducing tunneling through static geometry.
64pub const DYNAMIC_COLOR_COUNT: usize = GRAPH_COLOR_COUNT - 4;
65
66/// A color in the [`ConstraintGraph`]. Each color is a set of bodies and constraints
67/// that can be solved in parallel without race conditions.
68///
69/// Only awake dynamic and kinematic bodies are included in graph coloring. For static bodies,
70/// the solver uses a dummy [`SolverBody`].
71///
72/// Kinematic bodies cannot use a dummy [`SolverBody`], because we cannot access the same body
73/// from multiple threads efficiently, since the (to-be-implemented) SIMD solver body scatter
74/// would write to the same body from multiple threads. Even if these writes didn't actually modify
75/// the body, they would still cause horrible cache stalls.
76///
77/// [`SolverBody`]: crate::dynamics::solver::solver_body::SolverBody
78#[derive(Clone, Debug, Default, Reflect)]
79#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
80#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
81#[reflect(Debug)]
82pub struct GraphColor {
83    /// A bit vector representing the [`SolverBody`]s that are part of this color.
84    ///
85    /// The bit vector is indexed by the body index, so it also contains static and sleeping bodies.
86    /// However, these bits are never iterated over, and the bit count is not used,
87    /// so it should not be a problem.
88    ///
89    /// [`SolverBody`]: crate::dynamics::solver::solver_body::SolverBody
90    pub body_set: BitVec,
91    /// The handles of the contact manifolds in this color.
92    pub manifold_handles: Vec<ContactManifoldHandle>,
93    /// The contact constraints in this color.
94    pub contact_constraints: Vec<ContactConstraint>,
95    // TODO: Joints
96}
97
98/// A handle to a contact manifold in the [`ContactGraph`].
99///
100/// [`ContactGraph`]: crate::collision::contact_types::ContactGraph
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
102#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
103#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
104#[reflect(Debug, PartialEq)]
105pub struct ContactManifoldHandle {
106    /// The stable identifier of the contact pair in the [`ContactGraph`].
107    ///
108    /// [`ContactGraph`]: crate::collision::contact_types::ContactGraph
109    pub contact_id: ContactId,
110    /// The index of the manifold in the [`ContactPair`]. This is not stable
111    /// and may change when the contact pair is updated.
112    pub manifold_index: usize,
113}
114
115/// A handle to a contact constraint in the [`ConstraintGraph`].
116#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
117#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
118#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
119#[reflect(Debug, PartialEq)]
120pub struct ContactConstraintHandle {
121    /// The index of the [`GraphColor`] of the constraint in the [`ConstraintGraph`].
122    pub color_index: u8,
123    /// The index of the constraint in the [`GraphColor`]. This is not stable
124    /// and may change when the contact pair is updated.
125    pub local_index: usize,
126}
127
128/// The constraint graph used for graph coloring to solve constraints in parallel.
129///
130/// The graph holds up to [`GRAPH_COLOR_COUNT`] colors, where each [`GraphColor`] is a set of bodies and constraints
131/// that can be solved in parallel without race conditions. Each body can appear in a given color only once.
132///
133/// The last color, [`COLOR_OVERFLOW_INDEX`], is used for constraints that cannot find a color.
134/// They are solved serially on a single thread.
135///
136/// Each [`ContactManifold`] is considered to be a separate edge, because each manifold has its own [`ContactConstraint`].
137///
138/// See the [module-level documentation](self) for more general information about graph coloring.
139///
140/// [`ContactManifold`]: crate::collision::contact_types::ContactManifold
141#[derive(Resource, Clone, Debug, Reflect)]
142#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
143#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
144#[reflect(Debug)]
145pub struct ConstraintGraph {
146    /// The colors in the graph.
147    pub colors: Vec<GraphColor>,
148
149    /// The [`ContactConstraintHandle`]s associated with each contact, indexed by [`ContactId`].
150    /// Each contact has one handle per contact manifold that generates constraints.
151    ///
152    /// Entries persist as empty lists once allocated, since [`ContactId`]s are reused by the
153    /// [`ContactGraph`].
154    ///
155    /// [`ContactGraph`]: crate::collision::contact_types::ContactGraph
156    #[reflect(ignore)]
157    contact_handles: Vec<ManifoldConstraintHandles>,
158}
159
160impl Default for ConstraintGraph {
161    fn default() -> Self {
162        Self::new(16)
163    }
164}
165
166impl ConstraintGraph {
167    /// Creates a new constraint graph with the given number of colors.
168    pub fn new(body_capacity: usize) -> Self {
169        let mut colors = Vec::with_capacity(GRAPH_COLOR_COUNT);
170
171        let bit_capacity = body_capacity.max(8);
172
173        // Initialize graph color bit vectors.
174        for _ in 0..GRAPH_COLOR_COUNT {
175            let mut body_set = BitVec::new(bit_capacity);
176            body_set.set_bit_count_and_clear(bit_capacity);
177            colors.push(GraphColor {
178                body_set,
179                // TODO: What's a good initial capacity for contacts and constraints?
180                manifold_handles: Vec::with_capacity(bit_capacity),
181                contact_constraints: Vec::with_capacity(bit_capacity),
182            });
183        }
184
185        Self {
186            colors,
187            contact_handles: Vec::new(),
188        }
189    }
190
191    /// Returns the [`ContactConstraintHandle`]s associated with the given contact,
192    /// with one handle per contact manifold that generates constraints.
193    ///
194    /// Returns an empty slice if the contact has no associated constraints.
195    #[inline]
196    pub fn contact_handles(&self, contact_id: ContactId) -> &[ContactConstraintHandle] {
197        self.contact_handles
198            .get(contact_id.0 as usize)
199            .map_or(&[], |handles| handles.as_slice())
200    }
201
202    /// Returns the number of contact manifold constraints associated with the given contact.
203    #[inline]
204    pub fn manifold_count(&self, contact_id: ContactId) -> usize {
205        self.contact_handles(contact_id).len()
206    }
207
208    /// Adds a manifold to the graph, associating it with the given contact pair.
209    pub fn push_manifold(&mut self, contact_pair: &ContactPair) {
210        // TODO: These shouldn't be `Option`s.
211        let (Some(body1), Some(body2)) = (contact_pair.body1, contact_pair.body2) else {
212            return;
213        };
214
215        let is_static1 = contact_pair.flags.contains(ContactPairFlags::STATIC1);
216        let is_static2 = contact_pair.flags.contains(ContactPairFlags::STATIC2);
217
218        debug_assert!(!contact_pair.manifolds.is_empty());
219        debug_assert!(!is_static1 || !is_static2);
220
221        let mut color_index = COLOR_OVERFLOW_INDEX;
222
223        // TODO: We could allow forcing overflow by making this optional.
224        if !is_static1 && !is_static2 {
225            // Constraints involving only non-static bodies cannot be in colors reserved
226            // for constraints involving static bodies. This helps reduce tunneling through
227            // static geometry by solving static contacts last.
228            for (i, color) in self.colors.iter_mut().enumerate().take(DYNAMIC_COLOR_COUNT) {
229                if color.body_set.get(body1.index_u32() as usize)
230                    || color.body_set.get(body2.index_u32() as usize)
231                {
232                    continue;
233                }
234
235                color.body_set.set_and_grow(body1.index_u32() as usize);
236                color.body_set.set_and_grow(body2.index_u32() as usize);
237                color_index = i;
238                break;
239            }
240        } else if !is_static1 {
241            // Build static colors from the end to give them higher priority.
242            for i in (1..COLOR_OVERFLOW_INDEX).rev() {
243                let color = &mut self.colors[i];
244                if color.body_set.get(body1.index_u32() as usize) {
245                    continue;
246                }
247
248                color.body_set.set_and_grow(body1.index_u32() as usize);
249                color_index = i;
250                break;
251            }
252        } else if !is_static2 {
253            // Build static colors from the end to give them higher priority.
254            for i in (1..COLOR_OVERFLOW_INDEX).rev() {
255                let color = &mut self.colors[i];
256                if color.body_set.get(body2.index_u32() as usize) {
257                    continue;
258                }
259
260                color.body_set.set_and_grow(body2.index_u32() as usize);
261                color_index = i;
262                break;
263            }
264        }
265
266        // Ensure there is a slot for this contact's constraint handles.
267        let contact_index = contact_pair.contact_id.0 as usize;
268        if contact_index >= self.contact_handles.len() {
269            self.contact_handles
270                .resize_with(contact_index + 1, ManifoldConstraintHandles::new);
271        }
272
273        // Add a constraint handle for the contact.
274        let handles = &mut self.contact_handles[contact_index];
275        let manifold_index = handles.len();
276        let color = &mut self.colors[color_index];
277        handles.push(ContactConstraintHandle {
278            color_index: color_index as u8,
279            local_index: color.manifold_handles.len(),
280        });
281
282        // Add the handle of the contact manifold to the color.
283        color.manifold_handles.push(ContactManifoldHandle {
284            contact_id: contact_pair.contact_id,
285            manifold_index,
286        });
287    }
288
289    /// Removes the [`ContactConstraintHandle`] corresponding to the last [`ContactManifold`]
290    /// associated with the given contact, updating the color's body set and manifold handles
291    /// accordingly.
292    ///
293    /// Returns the removed [`ContactConstraintHandle`] if it exists.
294    ///
295    /// To remove *all* constraints associated with a contact, use [`remove_contact`](Self::remove_contact).
296    ///
297    /// [`ContactManifold`]: crate::collision::contact_types::ContactManifold
298    pub fn pop_manifold(
299        &mut self,
300        contact_id: ContactId,
301        body1: Entity,
302        body2: Entity,
303    ) -> Option<ContactConstraintHandle> {
304        // Remove a constraint handle associated with the contact.
305        let handles = self.contact_handles.get_mut(contact_id.0 as usize)?;
306        let contact_constraint_handle = handles.pop()?;
307
308        let color_index = contact_constraint_handle.color_index as usize;
309        let local_index = contact_constraint_handle.local_index;
310        debug_assert!(color_index < GRAPH_COLOR_COUNT);
311
312        let color = &mut self.colors[color_index];
313
314        if color_index != COLOR_OVERFLOW_INDEX {
315            // Remove the bodies from the color's body set.
316            color.body_set.unset(body1.index_u32() as usize);
317            color.body_set.unset(body2.index_u32() as usize);
318        }
319
320        // Remove the manifold handle from the color.
321        let moved_index = color.manifold_handles.len() - 1;
322        color.manifold_handles.swap_remove(local_index);
323
324        if moved_index != local_index {
325            // Fix moved manifold handle.
326            let moved_handle = color.manifold_handles[local_index];
327
328            // Update the local index of the constraint handle associated with the moved manifold handle.
329            let constraint_handle = &mut self.contact_handles[moved_handle.contact_id.0 as usize]
330                [moved_handle.manifold_index];
331            debug_assert!(constraint_handle.color_index == color_index as u8);
332            debug_assert!(constraint_handle.local_index == moved_index);
333            constraint_handle.local_index = local_index;
334        }
335
336        // Return the constraint handle that was removed.
337        Some(contact_constraint_handle)
338    }
339
340    /// Removes all [`ContactConstraintHandle`]s associated with the given contact from the graph,
341    /// updating the colors' body sets and manifold handles accordingly.
342    ///
343    /// This is equivalent to calling [`pop_manifold`](Self::pop_manifold) until no handles remain.
344    pub fn remove_contact(&mut self, contact_id: ContactId, body1: Entity, body2: Entity) {
345        while self.pop_manifold(contact_id, body1, body2).is_some() {}
346    }
347
348    /// Clears the constraint graph, removing all colors and their contents.
349    ///
350    /// # Warning
351    ///
352    /// This does *not* clear the [`ContactGraph`]! You should additionally
353    /// call [`ContactGraph::clear`].
354    ///
355    /// [`ContactGraph`]: crate::collision::contact_types::ContactGraph
356    /// [`ContactGraph::clear`]: crate::collision::contact_types::ContactGraph::clear
357    pub fn clear(&mut self) {
358        for color in &mut self.colors {
359            color.body_set.clear();
360            color.manifold_handles.clear();
361            color.contact_constraints.clear();
362        }
363        self.contact_handles.clear();
364    }
365}