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 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}