avian2d/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 crate::{
29    collision::contact_types::{ContactEdge, ContactGraphInternal, ContactId},
30    data_structures::bit_vec::BitVec,
31    prelude::{ContactPair, ContactPairFlags},
32};
33
34use super::contact::ContactConstraint;
35
36/// The maximum number of [`GraphColor`]s in the [`ConstraintGraph`].
37/// Constraints that cannot find a color are added to the overflow set,
38/// which is solved on a single thread.
39pub const GRAPH_COLOR_COUNT: usize = 24;
40
41/// The index of the overflow color in the graph, used for constraints that don't fit
42/// the graph color limit. This can happen when a single body is interacting with many other bodies.
43pub const COLOR_OVERFLOW_INDEX: usize = GRAPH_COLOR_COUNT - 1;
44
45/// The number of colors with constraints involving two non-static bodies.
46/// Leaving constraints involving static bodies to later colors gives higher priority
47/// to those constraints, reducing tunneling through static geometry.
48pub const DYNAMIC_COLOR_COUNT: usize = GRAPH_COLOR_COUNT - 4;
49
50/// A color in the [`ConstraintGraph`]. Each color is a set of bodies and constraints
51/// that can be solved in parallel without race conditions.
52///
53/// Only awake dynamic and kinematic bodies are included in graph coloring. For static bodies,
54/// the solver uses a dummy [`SolverBody`].
55///
56/// Kinematic bodies cannot use a dummy [`SolverBody`], because we cannot access the same body
57/// from multiple threads efficiently, since the (to-be-implemented) SIMD solver body scatter
58/// would write to the same body from multiple threads. Even if these writes didn't actually modify
59/// the body, they would still cause horrible cache stalls.
60///
61/// [`SolverBody`]: crate::dynamics::solver::solver_body::SolverBody
62#[derive(Clone, Debug, Default, Reflect)]
63#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
64#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
65#[reflect(Debug)]
66pub struct GraphColor {
67    /// A bit vector representing the [`SolverBody`]s that are part of this color.
68    ///
69    /// The bit vector is indexed by the body index, so it also contains static and sleeping bodies.
70    /// However, these bits are never iterated over, and the bit count is not used,
71    /// so it should not be a problem.
72    ///
73    /// [`SolverBody`]: crate::dynamics::solver::solver_body::SolverBody
74    pub body_set: BitVec,
75    /// The handles of the contact manifolds in this color.
76    pub manifold_handles: Vec<ContactManifoldHandle>,
77    /// The contact constraints in this color.
78    pub contact_constraints: Vec<ContactConstraint>,
79    // TODO: Joints
80}
81
82/// A handle to a contact manifold in the [`ContactGraph`].
83///
84/// [`ContactGraph`]: crate::collision::contact_types::ContactGraph
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
86#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
87#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
88#[reflect(Debug, PartialEq)]
89pub struct ContactManifoldHandle {
90    /// The stable identifier of the contact pair in the [`ContactGraph`].
91    ///
92    /// [`ContactGraph`]: crate::collision::contact_types::ContactGraph
93    pub contact_id: ContactId,
94    /// The index of the manifold in the [`ContactPair`]. This is not stable
95    /// and may change when the contact pair is updated.
96    pub manifold_index: usize,
97}
98
99/// A handle to a contact constraint in the [`ConstraintGraph`].
100#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
101#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
102#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
103#[reflect(Debug, PartialEq)]
104pub struct ContactConstraintHandle {
105    /// The index of the [`GraphColor`] of the constraint in the [`ConstraintGraph`].
106    pub color_index: u8,
107    /// The index of the constraint in the [`GraphColor`]. This is not stable
108    /// and may change when the contact pair is updated.
109    pub local_index: usize,
110}
111
112/// The constraint graph used for graph coloring to solve constraints in parallel.
113///
114/// The graph holds up to [`GRAPH_COLOR_COUNT`] colors, where each [`GraphColor`] is a set of bodies and constraints
115/// that can be solved in parallel without race conditions. Each body can appear in a given color only once.
116///
117/// The last color, [`COLOR_OVERFLOW_INDEX`], is used for constraints that cannot find a color.
118/// They are solved serially on a single thread.
119///
120/// Each [`ContactManifold`] is considered to be a separate edge, because each manifold has its own [`ContactConstraint`].
121///
122/// See the [module-level documentation](self) for more general information about graph coloring.
123///
124/// [`ContactManifold`]: crate::collision::contact_types::ContactManifold
125#[derive(Resource, Clone, Debug, Reflect)]
126#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
127#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
128#[reflect(Debug)]
129pub struct ConstraintGraph {
130    /// The colors in the graph.
131    pub colors: Vec<GraphColor>,
132}
133
134impl Default for ConstraintGraph {
135    fn default() -> Self {
136        Self::new(16)
137    }
138}
139
140impl ConstraintGraph {
141    /// Creates a new constraint graph with the given number of colors.
142    pub fn new(body_capacity: usize) -> Self {
143        let mut colors = Vec::with_capacity(GRAPH_COLOR_COUNT);
144
145        let bit_capacity = body_capacity.max(8);
146
147        // Initialize graph color bit vectors.
148        for _ in 0..GRAPH_COLOR_COUNT {
149            let mut body_set = BitVec::new(bit_capacity);
150            body_set.set_bit_count_and_clear(bit_capacity);
151            colors.push(GraphColor {
152                body_set,
153                // TODO: What's a good initial capacity for contacts and constraints?
154                manifold_handles: Vec::with_capacity(bit_capacity),
155                contact_constraints: Vec::with_capacity(bit_capacity),
156            });
157        }
158
159        Self { colors }
160    }
161
162    /// Adds a manifold to the graph, associating it with the given contact edge and contact pair.
163    pub fn push_manifold(&mut self, contact_edge: &mut ContactEdge, contact_pair: &ContactPair) {
164        // TODO: These shouldn't be `Option`s.
165        let (Some(body1), Some(body2)) = (contact_pair.body1, contact_pair.body2) else {
166            return;
167        };
168
169        let is_static1 = contact_pair.flags.contains(ContactPairFlags::STATIC1);
170        let is_static2 = contact_pair.flags.contains(ContactPairFlags::STATIC2);
171
172        debug_assert!(!contact_pair.manifolds.is_empty());
173        debug_assert!(!is_static1 || !is_static2);
174
175        let mut color_index = COLOR_OVERFLOW_INDEX;
176
177        // TODO: We could allow forcing overflow by making this optional.
178        if !is_static1 && !is_static2 {
179            // Constraints involving only non-static bodies cannot be in colors reserved
180            // for constraints involving static bodies. This helps reduce tunneling through
181            // static geometry by solving static contacts last.
182            for i in 0..DYNAMIC_COLOR_COUNT {
183                let color = &mut self.colors[i];
184                if color.body_set.get(body1.index() as usize)
185                    || color.body_set.get(body2.index() as usize)
186                {
187                    continue;
188                }
189
190                color.body_set.set_and_grow(body1.index() as usize);
191                color.body_set.set_and_grow(body2.index() as usize);
192                color_index = i;
193                break;
194            }
195        } else if !is_static1 {
196            // Build static colors from the end to give them higher priority.
197            for i in (1..COLOR_OVERFLOW_INDEX).rev() {
198                let color = &mut self.colors[i];
199                if color.body_set.get(body1.index() as usize) {
200                    continue;
201                }
202
203                color.body_set.set_and_grow(body1.index() as usize);
204                color_index = i;
205                break;
206            }
207        } else if !is_static2 {
208            // Build static colors from the end to give them higher priority.
209            for i in (1..COLOR_OVERFLOW_INDEX).rev() {
210                let color = &mut self.colors[i];
211                if color.body_set.get(body2.index() as usize) {
212                    continue;
213                }
214
215                color.body_set.set_and_grow(body2.index() as usize);
216                color_index = i;
217                break;
218            }
219        }
220
221        // Add a constraint handle to the contact edge.
222        let color = &mut self.colors[color_index];
223        let manifold_index = contact_edge.constraint_handles.len();
224        contact_edge
225            .constraint_handles
226            .push(ContactConstraintHandle {
227                color_index: color_index as u8,
228                local_index: color.manifold_handles.len(),
229            });
230
231        // Add the handle of the contact manifold to the color.
232        color.manifold_handles.push(ContactManifoldHandle {
233            contact_id: contact_pair.contact_id,
234            manifold_index,
235        });
236    }
237
238    /// Removes a [`ContactConstraintHandle`] corresponding to a [`ContactManifold`]
239    /// from the end of the vector stored in the [`ContactEdge`], updating the color's
240    /// body set and manifold handles accordingly.
241    ///
242    /// Returns the removed [`ContactConstraintHandle`] if it exists.
243    ///
244    /// [`ContactManifold`]: crate::collision::contact_types::ContactManifold
245    pub fn pop_manifold(
246        &mut self,
247        contact_graph: &mut ContactGraphInternal,
248        contact_id: ContactId,
249        body1: Entity,
250        body2: Entity,
251    ) -> Option<ContactConstraintHandle> {
252        // Remove a constraint handle from the contact edge.
253        let contact_edge = contact_graph
254            .edge_weight_mut(contact_id.into())
255            .unwrap_or_else(|| panic!("Contact edge not found in graph: {contact_id:?}"));
256        let contact_constraint_handle = contact_edge.constraint_handles.pop()?;
257
258        let color_index = contact_constraint_handle.color_index as usize;
259        let local_index = contact_constraint_handle.local_index;
260        debug_assert!(color_index < GRAPH_COLOR_COUNT);
261
262        let color = &mut self.colors[color_index];
263
264        if color_index != COLOR_OVERFLOW_INDEX {
265            // Remove the bodies from the color's body set.
266            color.body_set.unset(body1.index() as usize);
267            color.body_set.unset(body2.index() as usize);
268        }
269
270        // Remove the manifold handle from the color.
271        let moved_index = color.manifold_handles.len() - 1;
272        color.manifold_handles.swap_remove(local_index);
273
274        if moved_index != local_index {
275            // Fix moved manifold handle.
276            let moved_handle = &mut color.manifold_handles[local_index];
277            let contact_edge = contact_graph
278                .edge_weight_mut(moved_handle.contact_id.into())
279                .unwrap_or_else(|| {
280                    panic!(
281                        "Contact edge not found in graph: {:?}",
282                        moved_handle.contact_id
283                    )
284                });
285
286            // Update the local index of the constraint handle associated with the moved manifold handle.
287            let constraint_handle =
288                &mut contact_edge.constraint_handles[moved_handle.manifold_index];
289            debug_assert!(constraint_handle.color_index == color_index as u8);
290            debug_assert!(constraint_handle.local_index == moved_index);
291            constraint_handle.local_index = local_index;
292        }
293
294        // Return the constraint handle that was removed.
295        Some(contact_constraint_handle)
296    }
297
298    /// Clears the constraint graph, removing all colors and their contents.
299    ///
300    /// # Warning
301    ///
302    /// This does *not* clear the [`ContactGraph`]! You should additionally
303    /// call [`ContactGraph::clear`].
304    ///
305    /// [`ContactGraph`]: crate::collision::contact_types::ContactGraph
306    /// [`ContactGraph::clear`]: crate::collision::contact_types::ContactGraph::clear
307    pub fn clear(&mut self) {
308        for color in &mut self.colors {
309            color.body_set.clear();
310            color.manifold_handles.clear();
311            color.contact_constraints.clear();
312        }
313    }
314}