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}