Skip to main content

alice_physics/solver/
participants.rs

1//! The world side of the participant contract ([`crate::world_participant`]):
2//! registration, the checked step, faults, observation and shared fields.
3//! Like the contract itself, this API is unstable and may change in a minor
4//! release (see the module documentation of [`crate::world_participant`]).
5//!
6//! Where the participants run: in every step loop ([`PhysicsWorld::try_step`]
7//! for XPBD and TGS, [`PhysicsWorld::try_step_parallel`],
8//! [`PhysicsWorld::step_with_bridge`]) at the start of each substep, before
9//! the substep body. The width `h` is the one of the substep loop that runs,
10//! so it depends on the path, not only on the backend: `h = dt / substeps`
11//! (a [`Fix128`] division) on [`PhysicsWorld::try_step`] with XPBD, and on
12//! [`PhysicsWorld::try_step_parallel`] and [`PhysicsWorld::step_with_bridge`]
13//! whatever the backend (both always run the XPBD substep loop); only
14//! [`PhysicsWorld::try_step`] under [`super::SolverBackend::Tgs`] hands the
15//! width the TGS solve itself uses, `dt · from_f32(1 / substeps)` (equal to
16//! `dt / substeps` when `substeps` is a power of two, otherwise it can differ
17//! in the last bits). Step rules are checked against that same width. Per
18//! substep:
19//!
20//! 1. [`run_substep_with_sdf_contacts`] calls the participants in their run
21//!    order with the bodies as they are at the start of the substep and the
22//!    SDF contacts resolved so far in the step, and commits the staged field
23//!    writes;
24//! 2. the summed forces reach the dynamic bodies, `v += F·inv_mass·h` and
25//!    `ω += I⁻¹·τ·h` (checked: a result out of range leaves the body as it was
26//!    and records [`WorldFault::ForceOutOfRange`]; the check covers the
27//!    intermediate `F·inv_mass` and `I⁻¹·τ` too, not only the final `·h`);
28//!    the sums themselves are not checked: [`ForceAccumulator`] adds with the
29//!    wrapping addition of [`Fix128`] (in [`run_substep_with_sdf_contacts`] and in
30//!    [`ForceAccumulator::merge`]), so staged forces whose sum leaves the
31//!    range wrap before this step sees them; a sleeping or parked body is
32//!    woken (and unparked) when the change applied to it, `Δv` or `Δω`, is
33//!    non-zero in any component ([`wakes_parked_body`], no sleep threshold),
34//!    and left asleep only when both round to exactly zero;
35//! 3. the substep body runs as before;
36//! 4. when the world's overflow flag ([`PhysicsWorld::overflow_detected`])
37//!    went up during the substep, [`WorldFault::RigidOverflow`] is recorded.
38//!
39//! With no participant registered none of this runs: the step is the one it
40//! was before participants existed, bit for bit, and no fault is recorded
41//! (the overflow flag then stays a flag, as it always was).
42//!
43//! Participants that stage nothing (no force, no torque, no field) leave the
44//! bodies bit for bit as in the same world without them, for every substep
45//! count and on every path: that is why TGS hands its own width to the
46//! participants instead of `dt / substeps`.
47//!
48//! Participants exist only with the `std` feature (they are held behind a
49//! [`std::sync::Mutex`] so that the world stays `Sync` while a participant
50//! is only `Send`).
51
52use super::{BodyObservation, PhysicsWorld};
53use crate::math::Fix128;
54#[cfg(feature = "std")]
55use crate::math::Vec3Fix;
56#[cfg(feature = "std")]
57use crate::world_participant::{
58    run_substep_with_sdf_contacts, wakes_parked_body, ForceAccumulator, ObservationSink, Observed,
59    Participant, ParticipantKind, ParticipantPlan, StepRule, SubstepTime,
60};
61use crate::world_participant::{
62    FieldBoard, FieldError, FieldLayout, FieldMode, PortId, RegisterError, StepError, WorldFault,
63};
64
65#[cfg(not(feature = "std"))]
66use alloc::vec::Vec;
67
68/// Why [`PhysicsWorld::declare_field`] refused a field. The world is
69/// unchanged.
70#[derive(Clone, Debug, PartialEq, Eq)]
71#[non_exhaustive]
72pub enum DeclareFieldError {
73    /// The board refused the field ([`FieldBoard::declare`]).
74    Field(FieldError),
75    /// With the new field the ports of the registered participants no longer
76    /// fit (a plain read of the new field, or a second writer of a
77    /// [`FieldMode::Replace`] field); see [`crate::world_participant::check_field_ports`].
78    Ports(RegisterError),
79}
80
81impl core::fmt::Display for DeclareFieldError {
82    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
83        match self {
84            Self::Field(e) => write!(f, "field refused by the board: {e:?}"),
85            Self::Ports(e) => write!(f, "field does not fit the registered ports: {e:?}"),
86        }
87    }
88}
89
90#[cfg(feature = "std")]
91impl std::error::Error for DeclareFieldError {}
92
93/// `a + b` per component, or `None` when a sum leaves the range of [`Fix128`].
94#[cfg(feature = "std")]
95fn checked_add_vec(a: Vec3Fix, b: Vec3Fix) -> Option<Vec3Fix> {
96    let add = |x: Fix128, y: Fix128| -> Option<Fix128> {
97        let wide = |f: Fix128| (i128::from(f.hi) << 64) | i128::from(f.lo);
98        let s = wide(x).checked_add(wide(y))?;
99        Some(Fix128 {
100            hi: (s >> 64) as i64,
101            lo: s as u64,
102        })
103    };
104    Some(Vec3Fix::new(add(a.x, b.x)?, add(a.y, b.y)?, add(a.z, b.z)?))
105}
106
107/// The participant list behind the mutex, recovering it if a participant
108/// panicked while the lock was held.
109#[cfg(feature = "std")]
110fn list_mut(m: &mut std::sync::Mutex<Vec<Box<dyn Participant>>>) -> &mut Vec<Box<dyn Participant>> {
111    m.get_mut()
112        .unwrap_or_else(std::sync::PoisonError::into_inner)
113}
114
115impl PhysicsWorld {
116    // ── Registration ─────────────────────────────────────────────────────
117
118    /// Register `participant`; it runs in every substep from the next step on.
119    /// Returns its registration index.
120    ///
121    /// The participant's ports ([`Participant::ports`]) are read once, here,
122    /// and checked with the ports of the participants already registered and
123    /// the world's fields ([`ParticipantPlan::new`]). A
124    /// [`StepRule::Fixed`] step is checked against the substep at every step
125    /// ([`StepError::Rule`]), because the substep is only known then.
126    ///
127    /// # Errors
128    ///
129    /// [`RegisterError::NonPositiveStep`] for `StepRule::Fixed(Δt ≤ 0)`,
130    /// [`RegisterError::Order`] when the ports would form a loop,
131    /// [`RegisterError::Field`] when they do not fit the fields. The world is
132    /// unchanged.
133    #[cfg(feature = "std")]
134    pub fn add_participant(
135        &mut self,
136        participant: Box<dyn Participant>,
137    ) -> Result<usize, RegisterError> {
138        if let StepRule::Fixed(step) = participant.step_rule() {
139            if step <= Fix128::ZERO {
140                return Err(RegisterError::NonPositiveStep);
141            }
142        }
143        let list = list_mut(&mut self.participants);
144        list.push(participant);
145        match ParticipantPlan::new(list, &self.fields) {
146            Ok(plan) => {
147                self.participant_plan = Some(plan);
148                Ok(list.len() - 1)
149            }
150            Err(e) => {
151                list.pop();
152                Err(e)
153            }
154        }
155    }
156
157    /// Number of registered participants.
158    #[cfg(feature = "std")]
159    #[must_use]
160    pub fn participant_count(&self) -> usize {
161        self.lock_participants().len()
162    }
163
164    /// Kind, step rule and ports of each registered participant, in
165    /// registration order: what `law_id` can observe of them.
166    #[cfg(feature = "std")]
167    pub(crate) fn participant_law_parts(
168        &self,
169    ) -> Vec<(
170        crate::world_participant::ParticipantKind,
171        crate::world_participant::StepRule,
172        Vec<crate::world_participant::Port>,
173    )> {
174        self.lock_participants()
175            .iter()
176            .map(|p| (p.kind(), p.step_rule(), p.ports().to_vec()))
177            .collect()
178    }
179
180    /// The kinds of the registered participants, in registration order.
181    #[cfg(feature = "std")]
182    #[must_use]
183    pub fn participant_kinds(&self) -> Vec<ParticipantKind> {
184        self.lock_participants().iter().map(|p| p.kind()).collect()
185    }
186
187    /// The state of participant `index` as its [`Participant::write_state`]
188    /// writes it (the payload a snapshot holds), or `None` for an index past
189    /// the last participant.
190    #[cfg(feature = "std")]
191    #[must_use]
192    pub fn participant_state(&self, index: usize) -> Option<Vec<u8>> {
193        let list = self.lock_participants();
194        let p = list.get(index)?;
195        let mut out = Vec::new();
196        p.write_state(&mut out);
197        Some(out)
198    }
199
200    /// What participant `index` reports ([`Participant::observe`]):
201    /// [`Observed::Undecided`] while a fault is recorded, `None` for an index
202    /// past the last participant.
203    #[cfg(feature = "std")]
204    #[must_use]
205    pub fn observe_participant(&self, index: usize) -> Option<Observed<ObservationSink>> {
206        let list = self.lock_participants();
207        let p = list.get(index)?;
208        if self.fault.is_some() {
209            return Some(Observed::Undecided);
210        }
211        let mut sink = ObservationSink::new();
212        p.observe(&mut sink);
213        Some(Observed::Exact(sink))
214    }
215
216    #[cfg(feature = "std")]
217    fn lock_participants(&self) -> std::sync::MutexGuard<'_, Vec<Box<dyn Participant>>> {
218        self.participants
219            .lock()
220            .unwrap_or_else(std::sync::PoisonError::into_inner)
221    }
222
223    /// The participants in registration order, for the snapshot writer.
224    #[cfg(feature = "std")]
225    pub(super) fn write_participants(&self, out: &mut Vec<(u32, Vec<u8>)>) {
226        for p in self.lock_participants().iter() {
227            let mut payload = Vec::new();
228            p.write_state(&mut payload);
229            out.push((p.kind().get(), payload));
230        }
231    }
232
233    /// Check snapshot payloads against the registered participants (kinds,
234    /// count, order, then every payload) without changing anything.
235    pub(super) fn check_participants(
236        &mut self,
237        stored: &[(u32, Vec<u8>)],
238    ) -> Result<(), super::WorldSnapshotError> {
239        #[cfg(feature = "std")]
240        {
241            let list = list_mut(&mut self.participants);
242            let snapshot: Vec<ParticipantKind> =
243                stored.iter().map(|s| ParticipantKind::new(s.0)).collect();
244            let world: Vec<ParticipantKind> = list.iter().map(|p| p.kind()).collect();
245            if let Some(m) =
246                crate::world_participant::ParticipantMismatch::classify(&snapshot, &world)
247            {
248                return Err(super::WorldSnapshotError::ParticipantMismatch(m));
249            }
250            for (index, (p, (_, payload))) in list.iter().zip(stored).enumerate() {
251                p.check_state(payload).map_err(|error| {
252                    super::WorldSnapshotError::ParticipantState { index, error }
253                })?;
254            }
255            Ok(())
256        }
257        #[cfg(not(feature = "std"))]
258        {
259            if stored.is_empty() {
260                Ok(())
261            } else {
262                Err(super::WorldSnapshotError::ParticipantMismatch(
263                    crate::world_participant::ParticipantMismatch::Count {
264                        snapshot: stored.len(),
265                        world: 0,
266                    },
267                ))
268            }
269        }
270    }
271
272    /// Read payloads [`Self::check_participants`] accepted.
273    pub(super) fn read_participants(&mut self, stored: &[(u32, Vec<u8>)]) {
274        #[cfg(feature = "std")]
275        for (p, (_, payload)) in list_mut(&mut self.participants).iter_mut().zip(stored) {
276            p.read_state(payload);
277        }
278        #[cfg(not(feature = "std"))]
279        debug_assert!(stored.is_empty(), "checked by check_participants");
280    }
281
282    // ── Faults ───────────────────────────────────────────────────────────
283
284    /// The first fault recorded, if any. While one is recorded,
285    /// [`Self::try_step`] refuses to run ([`StepError::Faulted`]) and the
286    /// checked observations are undecided. It is part of the snapshot.
287    #[must_use]
288    pub const fn fault(&self) -> Option<WorldFault> {
289        self.fault
290    }
291
292    /// Forget the recorded fault so that steps run again. The world's
293    /// overflow flag ([`Self::overflow_detected`]) is not touched.
294    pub fn clear_fault(&mut self) {
295        self.fault = None;
296    }
297
298    /// The fault as stored (snapshot writer and reader).
299    pub(super) fn set_fault(&mut self, fault: Option<WorldFault>) {
300        self.fault = fault;
301    }
302
303    fn record_fault(&mut self, fault: WorldFault) {
304        if self.fault.is_none() {
305            self.fault = Some(fault);
306        }
307    }
308
309    /// [`Self::observe_body`], or [`Observed::Undecided`] while a fault is
310    /// recorded or the overflow flag is up (the values may have left the
311    /// range of [`Fix128`]); `None` for an index past the last body.
312    #[must_use]
313    pub fn observe_body_checked(
314        &self,
315        body_idx: usize,
316    ) -> Option<crate::world_participant::Observed<BodyObservation>> {
317        let o = self.observe_body(body_idx)?;
318        Some(if self.fault.is_some() || self.overflow_detected {
319            crate::world_participant::Observed::Undecided
320        } else {
321            crate::world_participant::Observed::Exact(o)
322        })
323    }
324
325    // ── Shared fields ────────────────────────────────────────────────────
326
327    /// The world's shared fields.
328    #[must_use]
329    pub const fn fields(&self) -> &FieldBoard {
330        &self.fields
331    }
332
333    /// Declare shared field `id`, all samples zero. Declare a field before
334    /// the participants that use it.
335    ///
336    /// # Errors
337    ///
338    /// [`DeclareFieldError::Field`] when the board refuses it,
339    /// [`DeclareFieldError::Ports`] when the registered participants' ports
340    /// no longer fit with it. The world is unchanged.
341    pub fn declare_field(
342        &mut self,
343        id: PortId,
344        layout: FieldLayout,
345        mode: FieldMode,
346    ) -> Result<(), DeclareFieldError> {
347        let mut board = self.fields.clone();
348        board
349            .declare(id, layout, mode)
350            .map_err(DeclareFieldError::Field)?;
351        #[cfg(feature = "std")]
352        {
353            let list = list_mut(&mut self.participants);
354            if !list.is_empty() {
355                let plan = ParticipantPlan::new(list, &board).map_err(DeclareFieldError::Ports)?;
356                self.participant_plan = Some(plan);
357            }
358        }
359        self.fields = board;
360        Ok(())
361    }
362
363    /// Set the committed value of field `id` (an initial condition, between
364    /// steps).
365    ///
366    /// # Errors
367    ///
368    /// [`FieldError::Unknown`] or [`FieldError::Length`]; the field is
369    /// unchanged.
370    pub fn set_field(&mut self, id: PortId, values: &[Fix128]) -> Result<(), FieldError> {
371        self.fields.set(id, values)
372    }
373
374    /// The field section as stored (snapshot writer and reader).
375    pub(super) fn fields_mut(&mut self) -> &mut FieldBoard {
376        &mut self.fields
377    }
378
379    // ── Checked step ─────────────────────────────────────────────────────
380
381    /// One step of `dt`, refusing to run when it cannot run normally.
382    ///
383    /// In order: while a fault is recorded the step is refused
384    /// ([`StepError::Faulted`]) and the world is unchanged; a non-positive
385    /// `dt` changes nothing and is `Ok`; every participant's step rule is
386    /// checked against the substep width `h` the participants are handed
387    /// (`dt / substeps`, or the TGS width under
388    /// [`super::SolverBackend::Tgs`], see the module documentation and
389    /// `try_step_parallel` (feature `parallel`); [`StepError::Rule`]) and every
390    /// [`FieldLayout::PerBody`] field against the body count
391    /// ([`StepError::BodyCount`]), refused unchanged. Then the step runs (see
392    /// the module documentation of [`crate::world_participant`] and
393    /// [`Self::step`]); when a fault was recorded during it the step still
394    /// ran to the end and [`StepError::FaultRaised`] reports it.
395    ///
396    /// With no participant registered this is [`Self::step`] as it always
397    /// was, bit for bit.
398    ///
399    /// # Errors
400    ///
401    /// See above.
402    pub fn try_step(&mut self, dt: Fix128) -> Result<(), StepError> {
403        if !self.check_step(dt, self.participant_substep_width(dt))? {
404            return Ok(());
405        }
406        self.run_step(dt);
407        self.fault
408            .map_or(Ok(()), |f| Err(StepError::FaultRaised(f)))
409    }
410
411    /// [`Self::try_step`] on the batched (parallel) path of
412    /// [`Self::step_parallel`]. Participants are called one after the other,
413    /// as in [`Self::try_step`]. This path runs the XPBD substep loop whatever
414    /// [`super::SolverBackend`] is configured (with [`super::SolverBackend::Tgs`]
415    /// it gives the same bits as with `Xpbd`; a batched TGS path does not exist
416    /// yet), so the participants are handed
417    /// `h = dt / substeps` and step rules are checked against that width, also
418    /// under [`super::SolverBackend::Tgs`].
419    ///
420    /// # Errors
421    ///
422    /// As [`Self::try_step`].
423    #[cfg(feature = "parallel")]
424    pub fn try_step_parallel(&mut self, dt: Fix128) -> Result<(), StepError> {
425        if !self.check_step(dt, Self::divided_substep_width(dt, self.config.substeps))? {
426            return Ok(());
427        }
428        self.run_step_parallel(dt);
429        self.fault
430            .map_or(Ok(()), |f| Err(StepError::FaultRaised(f)))
431    }
432
433    /// The checks at the start of a step: `Ok(true)` to run, `Ok(false)` for
434    /// a non-positive `dt` (nothing to do), `Err` to refuse. Changes nothing.
435    /// `h` is the substep width the calling path hands its participants
436    /// ([`Self::participant_substep_width`] on [`Self::try_step`],
437    /// [`Self::divided_substep_width`] on the paths that always run the XPBD
438    /// substep loop); the step rules are checked against it.
439    pub(super) fn check_step(&self, dt: Fix128, h: Fix128) -> Result<bool, StepError> {
440        if let Some(f) = self.fault {
441            return Err(StepError::Faulted(f));
442        }
443        if dt <= Fix128::ZERO {
444            return Ok(false);
445        }
446        // the participants' step rules, the only readers of `h`, need `std`
447        #[cfg(not(feature = "std"))]
448        let _ = h;
449        #[cfg(feature = "std")]
450        {
451            let list = self.lock_participants();
452            if !list.is_empty() {
453                for (index, p) in list.iter().enumerate() {
454                    p.step_rule()
455                        .steps_per_substep(h)
456                        .map_err(|error| StepError::Rule { index, error })?;
457                }
458                for field in self.fields.ids() {
459                    if let Some(FieldLayout::PerBody { .. }) = self.fields.layout(field) {
460                        let samples = self.fields.value(field).map_or(0, <[Fix128]>::len);
461                        if samples != self.bodies.len() {
462                            return Err(StepError::BodyCount {
463                                field,
464                                samples,
465                                bodies: self.bodies.len(),
466                            });
467                        }
468                    }
469                }
470            }
471        }
472        Ok(true)
473    }
474
475    /// The substep width `h` [`Self::try_step`] hands its participants for a
476    /// step of `dt`: `dt / substeps` (a [`Fix128`] division) with XPBD, and
477    /// under [`super::SolverBackend::Tgs`] the width the TGS solve itself
478    /// uses, `dt · from_f32(1 / substeps)`, which can differ from
479    /// `dt / substeps` in the last bits when `substeps` is not a power of
480    /// two. Only [`Self::try_step`] dispatches on the backend; the parallel
481    /// path and the bridge use [`Self::divided_substep_width`]. Zero substeps
482    /// give `dt / 0`, which every step rule refuses.
483    pub(super) fn participant_substep_width(&self, dt: Fix128) -> Fix128 {
484        let substeps = self.config.substeps;
485        #[cfg(feature = "std")]
486        if substeps > 0 && matches!(self.config.solver_backend, super::SolverBackend::Tgs) {
487            return dt * Fix128::from_f32(1.0 / substeps as f32);
488        }
489        Self::divided_substep_width(dt, substeps)
490    }
491
492    /// `dt / substeps` (a [`Fix128`] division): the width the XPBD substep
493    /// loop hands its participants, on [`Self::try_step`] with XPBD and on
494    /// the parallel path and the bridge whatever the backend.
495    pub(super) fn divided_substep_width(dt: Fix128, substeps: usize) -> Fix128 {
496        dt / Fix128::from_int(substeps as i64)
497    }
498
499    /// One `false` per registered participant: the participants that failed
500    /// during the step being run (they are not called again in it). Empty
501    /// without participants, which turns the substep hooks off.
502    pub(super) fn participant_flags(&mut self) -> Vec<bool> {
503        #[cfg(feature = "std")]
504        {
505            alloc_flags(list_mut(&mut self.participants).len())
506        }
507        #[cfg(not(feature = "std"))]
508        {
509            Vec::new()
510        }
511    }
512
513    /// Steps 1 and 2 of a substep (module documentation): run the
514    /// participants and apply their forces. Returns the overflow flag as it
515    /// was at the start of the substep. Does nothing without participants.
516    pub(super) fn participants_begin_substep(
517        &mut self,
518        index: usize,
519        count: usize,
520        h: Fix128,
521        frozen: &mut [bool],
522    ) -> bool {
523        let at_start = self.overflow_detected;
524        if frozen.is_empty() {
525            return at_start;
526        }
527        #[cfg(feature = "std")]
528        {
529            let Some(plan) = self.participant_plan.as_ref() else {
530                return at_start;
531            };
532            let mut forces = ForceAccumulator::new(self.bodies.len());
533            let faults = run_substep_with_sdf_contacts(
534                list_mut(&mut self.participants),
535                plan,
536                frozen,
537                &self.bodies,
538                &mut self.fields,
539                &mut forces,
540                SubstepTime { index, count, h },
541                &self.sdf_contact_log,
542            );
543            match faults {
544                Ok(faults) => {
545                    for f in faults {
546                        self.record_fault(f);
547                    }
548                }
549                // Every input is built by the world for this substep and the
550                // per-body fields were checked against the body count before
551                // the step began (`check_step`), so the inputs always fit.
552                Err(e) => unreachable!("participant inputs checked before the step: {e:?}"),
553            }
554            self.apply_participant_forces(&forces, h);
555        }
556        #[cfg(not(feature = "std"))]
557        let _ = (index, count, h);
558        at_start
559    }
560
561    /// Step 4 of a substep: record [`WorldFault::RigidOverflow`] when the
562    /// overflow flag went up during the substep. Does nothing without
563    /// participants.
564    pub(super) fn participants_end_substep(&mut self, overflow_at_start: bool, frozen: &[bool]) {
565        if frozen.is_empty() {
566            return;
567        }
568        if !overflow_at_start && self.overflow_detected {
569            self.record_fault(WorldFault::RigidOverflow);
570        }
571    }
572
573    /// `v += F·inv_mass·h`, `ω += I⁻¹·τ·h` on every dynamic body. Every
574    /// operation is checked, the intermediate `F·inv_mass` and `I⁻¹·τ`
575    /// included (the products of [`Fix128`] wrap, so checking only the last
576    /// `·h` would let an out-of-range intermediate through): a result out of
577    /// range leaves the body as it was and records
578    /// [`WorldFault::ForceOutOfRange`].
579    #[cfg(feature = "std")]
580    fn apply_participant_forces(&mut self, forces: &ForceAccumulator, h: Fix128) {
581        for i in 0..self.bodies.len() {
582            let (Some(force), Some(torque)) = (forces.force(i), forces.torque(i)) else {
583                continue;
584            };
585            let body = &self.bodies[i];
586            // Static bodies ignore forces; a kinematic body follows its
587            // target, not forces.
588            if !body.is_dynamic() {
589                continue;
590            }
591            let parked = self.park.is_parked(i);
592            if parked || self.islands.is_sleeping(i) {
593                if !wakes_parked_body(body, force, torque, h, &self.islands.config) {
594                    continue;
595                }
596                if parked {
597                    self.islands.wake_body(i);
598                    self.park.unpark(i, &mut self.stage_work);
599                } else {
600                    self.islands.wake_island(i);
601                }
602            }
603            let body = &self.bodies[i];
604            let v = force
605                .checked_scale(body.inv_mass)
606                .and_then(|a| a.checked_scale(h))
607                .and_then(|dv| checked_add_vec(body.velocity, dv));
608            let w = body
609                .checked_world_inv_inertia_apply(torque)
610                .and_then(|a| a.checked_scale(h))
611                .and_then(|dw| checked_add_vec(body.angular_velocity, dw));
612            match (v, w) {
613                (Some(v), Some(w)) => {
614                    let body = &mut self.bodies[i];
615                    body.velocity = v;
616                    body.angular_velocity = w;
617                }
618                _ => self.record_fault(WorldFault::ForceOutOfRange { body: i }),
619            }
620        }
621    }
622}
623
624#[cfg(feature = "std")]
625fn alloc_flags(n: usize) -> Vec<bool> {
626    vec![false; n]
627}