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}