alice_physics/world_participant.rs
1//! The contract a law signs to take part in [`crate::solver::PhysicsWorld`]'s
2//! substep loop: [`Participant`] and the small types around it.
3//!
4//! A participant owns its own state (a particle set, a grid, a pedestrian
5//! crowd, a force field) and advances it once per world substep. It reads the
6//! rigid bodies as they were at the start of the substep and hands its effect
7//! on them back as forces and torques in a [`ForceAccumulator`]; it never
8//! writes a body's position or velocity. Snapshots carry its state as an
9//! opaque payload tagged with its [`ParticipantKind`].
10//!
11//! **Unstable.** The types and rules of this module may still change before
12//! the participant API is declared stable, and such changes may ship in a
13//! minor release.
14//!
15//! # Contract
16//!
17//! | rule | how it is enforced |
18//! |---|---|
19//! | participants run in registration order, once per substep (before the rigid integration of that substep) | world side |
20//! | [`Participant::substep`] returning `Err` leaves the participant unchanged | participant side, checked by the conformance tests |
21//! | forces a participant staged in a substep that returned `Err` never reach the bodies | world side (staged per participant, merged on `Ok`) |
22//! | a participant cannot write a body | type: [`SubstepCtx`] only hands out `&[RigidBody]` and the accumulator |
23//! | [`Participant::check_state`] accepts every payload [`Participant::write_state`] produced, and [`Participant::read_state`] then restores the participant bit for bit | participant side, checked by the conformance tests |
24//! | a restore checks the target world holds the same number of participants, of the same kinds, in the same order | world side, [`ParticipantMismatch::classify`] |
25//! | participants run in the order [`execution_order`] gives for their [`Participant::ports`]: a writer of a port before its readers, registration order otherwise; with no ports declared that is registration order | world side calls [`execution_order`] at registration |
26//! | a loop of ports (a cycle) is refused at registration, never ordered silently | type: [`execution_order`] returns [`OrderError::Cycle`], the world returns it as [`RegisterError::Order`] |
27//! | the value behind a port that is not a shared field is state of the participant that holds it, so it is in that participant's [`Participant::write_state`] payload | participant side |
28//! | the summed force and torque reach a body through `v += F·h·inv_mass`, `ω += I⁻¹·τ·h`; a product out of range is a fault ([`WorldFault::ForceOutOfRange`]), never clamped | world side |
29//! | the rigid integration going out of range ([`WorldFault::RigidOverflow`]) is a fault like a participant's | world side |
30//! | a parked (sleeping) dynamic body is woken (and unparked) by participant forces whenever the change the world applies for the forces and torques of all participants, `Δv = F·inv_mass·h` or `Δω = I⁻¹τ·h`, is non-zero in any component; there is no threshold, so a participant force never leaves a sleeping body unchanged while the participant books its reaction; when both changes round to exactly zero the body stays asleep ([`wakes_parked_body`]; this replaces the earlier rule, which left a body asleep below the sleep thresholds) | world side calls [`wakes_parked_body`] |
31//! | a shared field ([`FieldBoard`]) is owned by the world alone; a participant reads the value committed before the substep began and writes only into its own [`FieldStage`] | type: [`SubstepCtx::field`] hands out `&[Fix128]` of the board, [`SubstepCtx::stage_field`] a buffer of the stage |
32//! | staged field writes are committed at the end of the substep, in [`execution_order`], all fields at once; a commit that would leave the range of [`Fix128`] changes no field and is a fault ([`WorldFault::FieldOutOfRange`]) | [`run_substep`] |
33//! | a participant that returned `Err` has its staged field writes dropped with its forces | [`run_substep`] |
34//! | a [`FieldMode::Replace`] field has at most one writer, a plain [`PortAccess::Read`] of a field and a [`PortAccess::ReadCommitted`] of a port that is not a field are refused at registration | [`check_field_ports`], returned as [`RegisterError::Field`] |
35//! | moving an extensive amount between layouts keeps its total exactly | [`remap_conserving`], [`deposit_bodies`] |
36//! | a snapshot holds the committed field values once, in the world's section, never in a participant payload | world side, bytes from [`FieldBoard::write_values`] |
37//! | participants can be registered only in builds with the `std` feature; a `no_std` world has no participants (the world-side participant API requires `std`) | world side |
38//!
39//! # Participant kinds
40//!
41//! A participant type in this crate takes as its [`ParticipantKind`] four
42//! ASCII bytes read big endian (`u32::from_be_bytes(*b"THRM")`), declared as
43//! a `pub const` next to the type. An assigned value never changes: a restore
44//! matches the participants of a snapshot to those of the world by kind
45//! value, so changing one makes every earlier snapshot refuse to load. Values
46//! below `0x0100_0000` (small integers) are left for tests and user code and
47//! are never assigned here.
48//!
49//! | code | value | type |
50//! |---|---|---|
51//! | `THRM` | `0x5448_524d` | `thermal::ThermalModifier` |
52//! | `PHAS` | `0x5048_4153` | `phase_change::PhaseChangeModifier` |
53//! | `PRES` | `0x5052_4553` | `pressure::PressureModifier` |
54//! | `FRAC` | `0x4652_4143` | `fracture::FractureModifier` |
55//! | `EROS` | `0x4552_4f53` | `erosion::ErosionModifier` |
56//! | `CRWD` | `0x4352_5744` | `crowd_force::CrowdParticipant` |
57//! | `MDVV` | `0x4d44_5656` | `molecular_dynamics::MdParticipant` |
58//! | `MEDM` | `0x4d45_444d` | `coupling_medium::DragMedium` |
59//! | `NBDY` | `0x4e42_4459` | `nbody::NBodyParticipant` |
60//! | `VHCL` | `0x5648_434c` | `vehicle::VehicleParticipant` |
61//!
62//! # Coupling through ports
63//!
64//! A participant declares the values it reads and writes as [`Port`]s
65//! ([`Participant::ports`], empty by default). A port is named by a
66//! [`PortId`], which, like [`ParticipantKind`], keeps its value across
67//! snapshots and versions. Within a substep, a participant that reads a port
68//! runs after every participant that writes it, so it sees the value of this
69//! substep. Participants that do not depend on each other keep their
70//! registration order. A participant that reads and writes the same port has
71//! no ordering constraint with itself.
72//!
73//! When the ports form a loop (A writes what B reads and B writes what A
74//! reads), no order lets every read see the value of this substep. The v1
75//! rule for such a loop is that its reads see the previous substep's values
76//! (explicit coupling); [`execution_order`] does not pick an order for it
77//! and returns [`OrderError::Cycle`] instead, so a loop is never coupled
78//! without being declared as one. How a participant declares a lagged read is
79//! not part of this version ([`PortAccess`] is `#[non_exhaustive]`). Implicit
80//! iteration between coupled participants is not part of this module either.
81//!
82//! # Shared fields
83//!
84//! A port whose value several participants exchange (a temperature on a grid,
85//! a heat source per body) is a *field* on the world's [`FieldBoard`]. A field
86//! is named by the same [`PortId`]: the id is what participants declare in
87//! [`Participant::ports`], so the execution order, the registration checks and
88//! the snapshot all speak of one name, and a port is either held by its
89//! participant (no field with that id) or held by the world (a field). The
90//! board, not a participant, owns the value, so a snapshot carries each field
91//! once however many participants use it.
92//!
93//! * **Values.** A field is an array of [`Fix128`] on a [`FieldLayout`]: one
94//! sample per rigid body ([`FieldLayout::PerBody`], the way to hand a body a
95//! quantity other than a force: heat, charge, deposited mass) or a uniform
96//! grid of cubic cells ([`FieldLayout::Grid`]), or a plain list of values
97//! with no position ([`FieldLayout::Channels`], the way a caller hands
98//! actions to an actuator between steps). A vector quantity is three
99//! fields, one per component, so every rule below (sums, conservation,
100//! snapshot) is the scalar one.
101//! * **Reads.** [`SubstepCtx::field`] returns the value committed at the end of
102//! the previous substep, whoever runs first. A field read is declared as
103//! [`Port::reads_committed`], which adds no ordering constraint, so two
104//! participants may each read what the other writes (thermal ⇄ structure)
105//! without forming a refused loop. The coupling through a field is explicit:
106//! one substep of lag, the same as through the rigid bodies.
107//! * **Writes.** [`SubstepCtx::stage_field`] hands out the participant's own
108//! staged buffer. Staged buffers are committed at the end of the substep in
109//! [`execution_order`], all fields at once ([`run_substep`]); a participant
110//! that returned `Err` has its staged writes dropped, as its forces are.
111//! * **Several writers.** A [`FieldMode::Replace`] field (a state some
112//! participant integrates, e.g. a temperature) has one writer, checked at
113//! registration; its staged buffer starts as the committed value and
114//! replaces it. A [`FieldMode::Sum`] field (a load or source, e.g. Joule
115//! heat) takes any number of writers; each staged buffer starts at zero and
116//! the committed value is the sum of this substep's buffers, partial sums
117//! taken in execution order and checked, so the first writer that leaves the
118//! range is named the same way on every run. Nothing written in a substep
119//! gives zero, like the force accumulator.
120//! * **Layouts.** Moving an extensive amount (heat in J per cell, not a
121//! temperature) between nested grids or from bodies into a grid keeps the
122//! total exactly ([`remap_conserving`], [`deposit_bodies`]); converting a
123//! density to an amount is the participant's job.
124//! * **Snapshot.** Proposed section of snapshot format version 2, after
125//! `participants` and `fault`: `fields` = [`FieldBoard::write_values`]
126//! (`count: u64`, then per field in ascending id: `id: u32`, `mode: u8`,
127//! `layout: u8` and its parameters, the values as `hi: i64`, `lo: u64`
128//! little endian). Staged buffers are empty between steps and are not
129//! stored. A restore checks the layouts against the target world's board
130//! ([`FieldBoard::check_values`]) before reading any value, next to the
131//! participant checks. The `fault` section codes, with the field fault
132//! added: `0` none, `1` [`WorldFault::Participant`] (`index: u64`,
133//! `kind: u32`, fault tag `u8`), `2` [`WorldFault::RigidOverflow`], `3`
134//! [`WorldFault::ForceOutOfRange`] (`body: u64`), `4`
135//! [`WorldFault::FieldOutOfRange`] (`field: u32`, `index: u64`).
136//!
137//! A participant cannot write a body because [`SubstepCtx`] has no way to
138//! reach one mutably:
139//!
140//! ```compile_fail
141//! use alice_physics::world_participant::{ForceAccumulator, SubstepCtx};
142//! use alice_physics::{Fix128, RigidBody, Vec3Fix};
143//!
144//! let bodies = [RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE)];
145//! let mut forces = ForceAccumulator::new(1);
146//! let ctx = SubstepCtx::new(&bodies, &mut forces, 0, 1, Fix128::ONE).unwrap();
147//! ctx.bodies()[0].velocity = Vec3Fix::ZERO; // `bodies()` is `&[RigidBody]`
148//! ```
149
150use crate::math::{Fix128, Vec3Fix};
151use crate::sleeping::SleepConfig;
152use crate::solver::RigidBody;
153
154#[cfg(not(feature = "std"))]
155use alloc::{boxed::Box, vec, vec::Vec};
156
157/// Stable tag of a participant type, written in front of its snapshot
158/// payload. A type keeps its tag across versions.
159#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
160pub struct ParticipantKind(u32);
161
162impl ParticipantKind {
163 /// The tag `raw`.
164 #[must_use]
165 pub const fn new(raw: u32) -> Self {
166 Self(raw)
167 }
168
169 /// The raw tag.
170 #[must_use]
171 pub const fn get(self) -> u32 {
172 self.0
173 }
174}
175
176/// Stable name of a value participants exchange (a field, a set of loads).
177/// A port keeps its id across snapshots and versions.
178#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
179pub struct PortId(u32);
180
181impl PortId {
182 /// The port `raw`.
183 #[must_use]
184 pub const fn new(raw: u32) -> Self {
185 Self(raw)
186 }
187
188 /// The raw id.
189 #[must_use]
190 pub const fn get(self) -> u32 {
191 self.0
192 }
193}
194
195/// Whether a participant reads or writes a port.
196#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
197#[non_exhaustive]
198pub enum PortAccess {
199 /// Reads the value of the current substep (runs after every writer).
200 Read,
201 /// Writes the value (runs before every reader).
202 Write,
203 /// Reads a shared field's value as committed at the end of the previous
204 /// substep. Adds no ordering constraint; the port must be a field of the
205 /// world's [`FieldBoard`].
206 ReadCommitted,
207}
208
209/// One declared access of a participant to a port.
210#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
211pub struct Port {
212 id: PortId,
213 access: PortAccess,
214}
215
216impl Port {
217 /// A read of `id`.
218 #[must_use]
219 pub const fn reads(id: PortId) -> Self {
220 Self {
221 id,
222 access: PortAccess::Read,
223 }
224 }
225
226 /// A read of the committed value of the shared field `id`.
227 #[must_use]
228 pub const fn reads_committed(id: PortId) -> Self {
229 Self {
230 id,
231 access: PortAccess::ReadCommitted,
232 }
233 }
234
235 /// A write of `id`.
236 #[must_use]
237 pub const fn writes(id: PortId) -> Self {
238 Self {
239 id,
240 access: PortAccess::Write,
241 }
242 }
243
244 /// The port.
245 #[must_use]
246 pub const fn id(self) -> PortId {
247 self.id
248 }
249
250 /// Read or write.
251 #[must_use]
252 pub const fn access(self) -> PortAccess {
253 self.access
254 }
255}
256
257/// Why the participants' ports admit no execution order.
258#[derive(Clone, Debug, PartialEq, Eq)]
259#[non_exhaustive]
260pub enum OrderError {
261 /// The ports form a loop within one substep.
262 Cycle {
263 /// Registration indices of every participant on a loop, ascending.
264 /// Participants that only depend on a loop are not listed.
265 members: Vec<usize>,
266 },
267}
268
269/// The order participants run in within a substep, from their declared
270/// ports: `ports[i]` is what [`Participant::ports`] returns for the
271/// participant registered at index `i`, and the result lists registration
272/// indices in run order.
273///
274/// A participant that writes a port runs before every other participant that
275/// reads it. Among participants free to run, the one registered first runs
276/// first, so with no ports at all the order is `0, 1, …, n-1`, and the result
277/// depends only on `ports`. Two writers of one port are not ordered against
278/// each other by that port.
279///
280/// # Errors
281///
282/// [`OrderError::Cycle`] when the dependencies form a loop; no order is
283/// chosen.
284pub fn execution_order(ports: &[&[Port]]) -> Result<Vec<usize>, OrderError> {
285 let n = ports.len();
286 // succ[w] = readers that must run after writer w
287 let mut succ: Vec<Vec<usize>> = vec![Vec::new(); n];
288 for (w, wp) in ports.iter().enumerate() {
289 for p in wp.iter().filter(|p| p.access == PortAccess::Write) {
290 for (r, rp) in ports.iter().enumerate() {
291 if r != w
292 && rp
293 .iter()
294 .any(|q| q.access == PortAccess::Read && q.id == p.id)
295 {
296 succ[w].push(r);
297 }
298 }
299 }
300 }
301 let mut indegree = vec![0_usize; n];
302 for s in &mut succ {
303 s.sort_unstable();
304 s.dedup();
305 for &r in s.iter() {
306 indegree[r] += 1;
307 }
308 }
309 let mut done = vec![false; n];
310 let mut order = Vec::with_capacity(n);
311 while let Some(next) = (0..n).find(|&i| !done[i] && indegree[i] == 0) {
312 done[next] = true;
313 order.push(next);
314 for &r in &succ[next] {
315 indegree[r] -= 1;
316 }
317 }
318 if order.len() == n {
319 return Ok(order);
320 }
321 // A participant left over is on a loop iff it reaches itself through
322 // other left-over participants.
323 let members = (0..n)
324 .filter(|&v| !done[v] && reaches_itself(v, &succ, &done))
325 .collect();
326 Err(OrderError::Cycle { members })
327}
328
329fn reaches_itself(start: usize, succ: &[Vec<usize>], done: &[bool]) -> bool {
330 let mut seen = vec![false; succ.len()];
331 let mut stack: Vec<usize> = succ[start].clone();
332 while let Some(v) = stack.pop() {
333 if v == start {
334 return true;
335 }
336 if done[v] || seen[v] {
337 continue;
338 }
339 seen[v] = true;
340 stack.extend_from_slice(&succ[v]);
341 }
342 false
343}
344
345/// How a participant's own time step relates to the world substep `h`.
346#[derive(Clone, Copy, Debug, PartialEq, Eq)]
347#[non_exhaustive]
348pub enum StepRule {
349 /// One call per world substep, advancing by `h`.
350 FollowSubstep,
351 /// The participant advances by a fixed `Δt` of its own; registration is
352 /// refused unless `h = n·Δt` for an integer `n ≥ 1` (exactly, in
353 /// [`Fix128`]). The world still calls it once per substep with `h`; the
354 /// participant takes `n` internal steps.
355 Fixed(Fix128),
356 /// One call per world substep with `h`; the participant splits `h`
357 /// itself, deterministically from its own state (e.g. a CFL limit).
358 Subcycle,
359}
360
361impl StepRule {
362 /// Internal steps per world substep of width `h`, or why this rule cannot
363 /// follow `h`. `FollowSubstep` and `Subcycle` give 1 (the participant is
364 /// called once); `Fixed(Δt)` gives `n` with `h = n·Δt`.
365 ///
366 /// # Errors
367 ///
368 /// [`RegisterError::NonPositiveStep`] when `h ≤ 0` or `Δt ≤ 0`;
369 /// [`RegisterError::StepRuleMismatch`] when `h` is not an integer multiple
370 /// of `Δt`.
371 pub fn steps_per_substep(self, h: Fix128) -> Result<u64, RegisterError> {
372 if h <= Fix128::ZERO {
373 return Err(RegisterError::NonPositiveStep);
374 }
375 match self {
376 Self::FollowSubstep | Self::Subcycle => Ok(1),
377 Self::Fixed(step) => {
378 if step <= Fix128::ZERO {
379 return Err(RegisterError::NonPositiveStep);
380 }
381 let n = h / step;
382 let whole = n.lo == 0 && n.hi >= 1;
383 if whole && Fix128::from_int(n.hi) * step == h {
384 Ok(n.hi.unsigned_abs())
385 } else {
386 Err(RegisterError::StepRuleMismatch { substep: h, step })
387 }
388 }
389 }
390 }
391}
392
393/// Why a participant could not be registered.
394#[derive(Clone, Debug, PartialEq, Eq)]
395#[non_exhaustive]
396pub enum RegisterError {
397 /// The world substep or the participant's own step is not positive.
398 NonPositiveStep,
399 /// `StepRule::Fixed(step)` does not divide the world substep.
400 StepRuleMismatch {
401 /// World substep `h`.
402 substep: Fix128,
403 /// The participant's fixed step.
404 step: Fix128,
405 },
406 /// With the new participant the declared ports form a loop
407 /// ([`execution_order`]); the participant was not registered.
408 Order(OrderError),
409 /// The declared ports do not fit the world's shared fields
410 /// ([`check_field_ports`]); the participant was not registered.
411 Field(FieldPortError),
412}
413
414/// Why a participant's substep failed. The participant is unchanged and its
415/// forces of that substep are dropped; the world records the fault.
416#[derive(Clone, Copy, Debug, PartialEq, Eq)]
417#[non_exhaustive]
418pub enum ParticipantFault {
419 /// A value left the representable or valid range.
420 OutOfRange,
421 /// The participant found its own state inconsistent.
422 InvalidState,
423}
424
425impl ParticipantFault {
426 /// Snapshot tag of this fault.
427 #[must_use]
428 pub const fn tag(self) -> u8 {
429 match self {
430 Self::OutOfRange => 1,
431 Self::InvalidState => 2,
432 }
433 }
434
435 /// The fault with snapshot tag `tag`, if any.
436 #[must_use]
437 pub const fn from_tag(tag: u8) -> Option<Self> {
438 match tag {
439 1 => Some(Self::OutOfRange),
440 2 => Some(Self::InvalidState),
441 _ => None,
442 }
443 }
444}
445
446/// The first fault the world recorded (sticky until cleared).
447#[derive(Clone, Copy, Debug, PartialEq, Eq)]
448#[non_exhaustive]
449pub enum WorldFault {
450 /// A participant's substep returned `Err`.
451 Participant {
452 /// Registration index of the participant that failed.
453 index: usize,
454 /// Its kind.
455 kind: ParticipantKind,
456 /// What failed.
457 fault: ParticipantFault,
458 },
459 /// The rigid integration (position prediction, velocity from positions)
460 /// left the range of [`Fix128`]; the world's overflow flag in fault form.
461 /// It names no body because the flag does not.
462 ///
463 /// The flag covers only part of the products today: the XPBD position
464 /// prediction and the velocity derived from jointed positions. The TGS
465 /// path does not raise it, and a wrapping addition of a position (XPBD or
466 /// TGS) is not detected.
467 RigidOverflow,
468 /// Applying the summed participant force or torque to body `body`
469 /// (`F·h·inv_mass`, `I⁻¹·τ·h`) left the range of [`Fix128`]. Nothing is
470 /// clamped.
471 ForceOutOfRange {
472 /// Index of the body.
473 body: usize,
474 },
475 /// Committing the staged writes of a [`FieldMode::Sum`] field left the
476 /// range of [`Fix128`] when the writes of participant `index` were added.
477 /// No field changed in that substep.
478 FieldOutOfRange {
479 /// The field.
480 field: PortId,
481 /// Registration index of the participant whose write left the range.
482 index: usize,
483 },
484}
485
486/// Why a checked world step did not run normally.
487#[derive(Clone, Debug, PartialEq, Eq)]
488#[non_exhaustive]
489pub enum StepError {
490 /// A fault was already recorded; the world was not touched.
491 Faulted(WorldFault),
492 /// A participant failed during this step. The step ran to the end (the
493 /// failed participant was frozen at its state before the failing call and
494 /// its forces of that substep were dropped); the fault is now recorded.
495 FaultRaised(WorldFault),
496 /// The participant at `index` has a [`StepRule::Fixed`] step that does not
497 /// divide this frame's substep width, the one the running path hands its
498 /// participants (`dt / substeps`, or on `PhysicsWorld::try_step` under
499 /// `SolverBackend::Tgs` the width its solve uses); nothing ran. The step is
500 /// only known when the world steps, so the check runs at the start of
501 /// every step rather than at registration.
502 Rule {
503 /// Registration index of the participant.
504 index: usize,
505 /// Why its rule cannot follow the substep.
506 error: RegisterError,
507 },
508 /// The [`FieldLayout::PerBody`] field `field` has `samples` samples but
509 /// the world holds `bodies` bodies (a body was added or removed after the
510 /// field was declared): the step is refused and the world is left
511 /// unchanged, nothing ran. The world-side form of
512 /// [`ExchangeError::BodyCount`], checked at the start of every step like
513 /// [`Self::Rule`].
514 BodyCount {
515 /// The field.
516 field: PortId,
517 /// Samples of the field.
518 samples: usize,
519 /// Bodies in the world.
520 bodies: usize,
521 },
522}
523
524/// A kind as its four ASCII letters when they are printable (`PRBE`), else as
525/// hex (`0x00000007`).
526struct KindText(ParticipantKind);
527
528impl core::fmt::Display for KindText {
529 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
530 let bytes = self.0.get().to_be_bytes();
531 if bytes.iter().all(|b| b.is_ascii_graphic()) {
532 for &b in &bytes {
533 write!(f, "{}", char::from(b))?;
534 }
535 Ok(())
536 } else {
537 write!(f, "{:#010x}", self.0.get())
538 }
539 }
540}
541
542impl core::fmt::Display for ParticipantFault {
543 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
544 f.write_str(match self {
545 Self::OutOfRange => "a value left its range",
546 Self::InvalidState => "its state is inconsistent",
547 })
548 }
549}
550
551impl core::fmt::Display for WorldFault {
552 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
553 match self {
554 Self::Participant { index, kind, fault } => {
555 write!(
556 f,
557 "participant {index} ({}) failed: {fault}",
558 KindText(*kind)
559 )
560 }
561 Self::RigidOverflow => f.write_str("the rigid integration left the range of Fix128"),
562 Self::ForceOutOfRange { body } => {
563 write!(
564 f,
565 "the participant force on body {body} left the range of Fix128"
566 )
567 }
568 Self::FieldOutOfRange { field, index } => write!(
569 f,
570 "committing field {} left the range of Fix128 at participant {index}",
571 field.get()
572 ),
573 }
574 }
575}
576
577impl core::fmt::Display for RegisterError {
578 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
579 match self {
580 Self::NonPositiveStep => {
581 f.write_str("the world substep or the participant's fixed step is not positive")
582 }
583 Self::StepRuleMismatch { substep, step } => {
584 write!(f, "the fixed step {step} does not divide the substep {substep}")
585 }
586 Self::Order(OrderError::Cycle { members }) => {
587 write!(f, "the declared ports form a loop through participants {members:?}")
588 }
589 Self::Field(FieldPortError::SecondWriter { field, first, second }) => write!(
590 f,
591 "replace field {} is written by participants {first} and {second}",
592 field.get()
593 ),
594 Self::Field(FieldPortError::ReadOfField { participant, field }) => write!(
595 f,
596 "participant {participant} reads field {} with a plain read (a field is read committed)",
597 field.get()
598 ),
599 Self::Field(FieldPortError::UnknownField { participant, field }) => write!(
600 f,
601 "participant {participant} reads port {} as committed, but no field has that id",
602 field.get()
603 ),
604 }
605 }
606}
607
608impl core::fmt::Display for StepError {
609 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
610 match self {
611 Self::Faulted(fault) => {
612 write!(
613 f,
614 "a fault is already recorded, the world was not stepped: {fault}"
615 )
616 }
617 Self::FaultRaised(fault) => write!(f, "a participant failed during this step: {fault}"),
618 Self::Rule { index, error } => {
619 write!(
620 f,
621 "participant {index} cannot follow this frame's substep: {error}"
622 )
623 }
624 Self::BodyCount {
625 field,
626 samples,
627 bodies,
628 } => write!(
629 f,
630 "per-body field {} has {samples} samples but the world has {bodies} bodies",
631 field.get()
632 ),
633 }
634 }
635}
636
637#[cfg(feature = "std")]
638impl std::error::Error for RegisterError {}
639
640#[cfg(feature = "std")]
641impl std::error::Error for StepError {
642 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
643 match self {
644 Self::Rule { error, .. } => Some(error),
645 _ => None,
646 }
647 }
648}
649
650/// Why a participant rejected a snapshot payload.
651#[derive(Clone, Copy, Debug, PartialEq, Eq)]
652#[non_exhaustive]
653pub enum StateError {
654 /// The payload has a different length than the participant needs.
655 Length {
656 /// Bytes needed.
657 expected: usize,
658 /// Bytes given.
659 found: usize,
660 },
661 /// A value in the payload is not valid for the participant.
662 InvalidValue,
663}
664
665/// Why a restore refused the participants of the target world.
666#[derive(Clone, Copy, Debug, PartialEq, Eq)]
667#[non_exhaustive]
668pub enum ParticipantMismatch {
669 /// Different numbers of participants.
670 Count {
671 /// Participants in the snapshot.
672 snapshot: usize,
673 /// Participants in the target world.
674 world: usize,
675 },
676 /// The same number, but not the same kinds (as multisets).
677 Kind {
678 /// First index whose kind differs.
679 index: usize,
680 /// Kind in the snapshot.
681 snapshot: ParticipantKind,
682 /// Kind in the target world.
683 world: ParticipantKind,
684 },
685 /// The same kinds, in another order.
686 Order {
687 /// First index whose kind differs.
688 index: usize,
689 /// Kind in the snapshot.
690 snapshot: ParticipantKind,
691 /// Kind in the target world.
692 world: ParticipantKind,
693 },
694}
695
696impl ParticipantMismatch {
697 /// Compare the kinds stored in a snapshot with those registered in the
698 /// target world. `None` when they agree (same count, same kinds, same
699 /// order).
700 #[must_use]
701 pub fn classify(snapshot: &[ParticipantKind], world: &[ParticipantKind]) -> Option<Self> {
702 if snapshot.len() != world.len() {
703 return Some(Self::Count {
704 snapshot: snapshot.len(),
705 world: world.len(),
706 });
707 }
708 let index = snapshot.iter().zip(world).position(|(s, w)| s != w)?;
709 let (s, w) = (snapshot[index], world[index]);
710 let mut a: Vec<ParticipantKind> = snapshot.to_vec();
711 let mut b: Vec<ParticipantKind> = world.to_vec();
712 a.sort_unstable();
713 b.sort_unstable();
714 Some(if a == b {
715 Self::Order {
716 index,
717 snapshot: s,
718 world: w,
719 }
720 } else {
721 Self::Kind {
722 index,
723 snapshot: s,
724 world: w,
725 }
726 })
727 }
728}
729
730/// Why a force could not be staged.
731#[derive(Clone, Copy, Debug, PartialEq, Eq)]
732#[non_exhaustive]
733pub enum AccumulateError {
734 /// The body index is past the end of the body list.
735 BodyOutOfRange {
736 /// The index.
737 index: usize,
738 /// Number of bodies.
739 len: usize,
740 },
741 /// The accumulator does not have one slot per body.
742 LengthMismatch {
743 /// Slots in the accumulator.
744 slots: usize,
745 /// Bodies.
746 bodies: usize,
747 },
748}
749
750/// Per-body sums of force and torque (world frame, [`Fix128`]) for one
751/// substep.
752///
753/// The sums use the wrapping addition of [`Fix128`], which is associative and
754/// commutative, so the totals do not depend on the order the forces were
755/// added in. The world still fixes the order (registration order, then each
756/// participant's own order) so that any later checked accumulation stays
757/// deterministic.
758#[derive(Clone, Debug, PartialEq, Eq)]
759pub struct ForceAccumulator {
760 force: Vec<Vec3Fix>,
761 torque: Vec<Vec3Fix>,
762}
763
764impl ForceAccumulator {
765 /// Zero force and torque for `bodies` bodies.
766 #[must_use]
767 pub fn new(bodies: usize) -> Self {
768 Self {
769 force: vec![Vec3Fix::ZERO; bodies],
770 torque: vec![Vec3Fix::ZERO; bodies],
771 }
772 }
773
774 /// Number of body slots.
775 #[must_use]
776 pub fn len(&self) -> usize {
777 self.force.len()
778 }
779
780 /// Whether there are no body slots.
781 #[must_use]
782 pub fn is_empty(&self) -> bool {
783 self.force.is_empty()
784 }
785
786 fn slot(&self, body: usize) -> Result<usize, AccumulateError> {
787 if body < self.force.len() {
788 Ok(body)
789 } else {
790 Err(AccumulateError::BodyOutOfRange {
791 index: body,
792 len: self.force.len(),
793 })
794 }
795 }
796
797 /// Add `force` to body `body`.
798 ///
799 /// # Errors
800 ///
801 /// [`AccumulateError::BodyOutOfRange`] when `body` has no slot (nothing
802 /// is added).
803 pub fn add_force(&mut self, body: usize, force: Vec3Fix) -> Result<(), AccumulateError> {
804 let i = self.slot(body)?;
805 self.force[i] = self.force[i] + force;
806 Ok(())
807 }
808
809 /// Add `torque` to body `body`.
810 ///
811 /// # Errors
812 ///
813 /// [`AccumulateError::BodyOutOfRange`] when `body` has no slot (nothing
814 /// is added).
815 pub fn add_torque(&mut self, body: usize, torque: Vec3Fix) -> Result<(), AccumulateError> {
816 let i = self.slot(body)?;
817 self.torque[i] = self.torque[i] + torque;
818 Ok(())
819 }
820
821 /// Summed force on `body` (`None` past the end).
822 #[must_use]
823 pub fn force(&self, body: usize) -> Option<Vec3Fix> {
824 self.force.get(body).copied()
825 }
826
827 /// Summed torque on `body` (`None` past the end).
828 #[must_use]
829 pub fn torque(&self, body: usize) -> Option<Vec3Fix> {
830 self.torque.get(body).copied()
831 }
832
833 /// Add every slot of `other` into `self`, slot by slot.
834 ///
835 /// # Errors
836 ///
837 /// [`AccumulateError::LengthMismatch`] when the slot counts differ
838 /// (`self` is unchanged).
839 pub fn merge(&mut self, other: &Self) -> Result<(), AccumulateError> {
840 if other.len() != self.len() {
841 return Err(AccumulateError::LengthMismatch {
842 slots: other.len(),
843 bodies: self.len(),
844 });
845 }
846 for i in 0..self.len() {
847 self.force[i] = self.force[i] + other.force[i];
848 self.torque[i] = self.torque[i] + other.torque[i];
849 }
850 Ok(())
851 }
852
853 /// Reset every slot to zero, keeping the slot count.
854 pub fn clear(&mut self) {
855 self.force.fill(Vec3Fix::ZERO);
856 self.torque.fill(Vec3Fix::ZERO);
857 }
858}
859
860/// What a participant sees during one substep: the bodies as they were at the
861/// start of the substep (read only) and an accumulator for its forces.
862#[derive(Debug)]
863pub struct SubstepCtx<'a> {
864 bodies: &'a [RigidBody],
865 forces: &'a mut ForceAccumulator,
866 substep_index: usize,
867 substeps: usize,
868 h: Fix128,
869 board: Option<&'a FieldBoard>,
870 stage: Option<&'a mut FieldStage>,
871 ports: &'a [Port],
872 sdf_contacts: &'a [crate::sdf_collider::SdfContact],
873}
874
875impl<'a> SubstepCtx<'a> {
876 /// A context over `bodies`, staging into `forces` (one slot per body),
877 /// for substep `substep_index` of `substeps` with width `h`.
878 ///
879 /// # Errors
880 ///
881 /// [`AccumulateError::LengthMismatch`] when `forces` does not have one
882 /// slot per body.
883 pub fn new(
884 bodies: &'a [RigidBody],
885 forces: &'a mut ForceAccumulator,
886 substep_index: usize,
887 substeps: usize,
888 h: Fix128,
889 ) -> Result<Self, AccumulateError> {
890 if forces.len() != bodies.len() {
891 return Err(AccumulateError::LengthMismatch {
892 slots: forces.len(),
893 bodies: bodies.len(),
894 });
895 }
896 Ok(Self {
897 bodies,
898 forces,
899 substep_index,
900 substeps,
901 h,
902 board: None,
903 stage: None,
904 ports: &[],
905 sdf_contacts: &[],
906 })
907 }
908
909 /// The same context with the world's shared fields: reads come from
910 /// `board` (committed values), writes go to `stage`, and only the ports in
911 /// `ports` (what the participant declared) may be read or written.
912 #[must_use]
913 pub fn with_fields(
914 mut self,
915 board: &'a FieldBoard,
916 stage: &'a mut FieldStage,
917 ports: &'a [Port],
918 ) -> Self {
919 self.board = Some(board);
920 self.stage = Some(stage);
921 self.ports = ports;
922 self
923 }
924
925 /// The same context with the SDF contacts the world resolved so far in
926 /// the current step ([`Self::sdf_contacts`]).
927 #[must_use]
928 pub fn with_sdf_contacts(mut self, contacts: &'a [crate::sdf_collider::SdfContact]) -> Self {
929 self.sdf_contacts = contacts;
930 self
931 }
932
933 fn declared(&self, id: PortId, access: PortAccess) -> Result<(), FieldAccessError> {
934 if self.ports.iter().any(|p| p.id == id && p.access == access) {
935 Ok(())
936 } else {
937 Err(FieldAccessError::NotDeclared { field: id, access })
938 }
939 }
940
941 /// The value of field `id` committed before this substep began.
942 ///
943 /// # Errors
944 ///
945 /// [`FieldAccessError::Unknown`] when there is no such field (or no
946 /// board); [`FieldAccessError::NotDeclared`] when the participant did not
947 /// declare [`Port::reads_committed`] for it.
948 pub fn field(&self, id: PortId) -> Result<&'a [Fix128], FieldAccessError> {
949 let board = self.board.ok_or(FieldAccessError::Unknown(id))?;
950 let slot = board.slot(id).ok_or(FieldAccessError::Unknown(id))?;
951 self.declared(id, PortAccess::ReadCommitted)?;
952 Ok(&slot.value)
953 }
954
955 /// The participant's staged buffer for field `id`, created on first use:
956 /// a copy of the committed value for a [`FieldMode::Replace`] field,
957 /// zeros for a [`FieldMode::Sum`] field. What it holds when the substep
958 /// returns `Ok` is committed at the end of the substep.
959 ///
960 /// # Errors
961 ///
962 /// [`FieldAccessError::Unknown`] when there is no such field (or no
963 /// board); [`FieldAccessError::NotDeclared`] when the participant did not
964 /// declare [`Port::writes`] for it.
965 pub fn stage_field(&mut self, id: PortId) -> Result<&mut [Fix128], FieldAccessError> {
966 let board = self.board.ok_or(FieldAccessError::Unknown(id))?;
967 let slot = board.slot(id).ok_or(FieldAccessError::Unknown(id))?;
968 self.declared(id, PortAccess::Write)?;
969 let stage = self
970 .stage
971 .as_deref_mut()
972 .ok_or(FieldAccessError::Unknown(id))?;
973 Ok(stage.buffer(id, || match slot.mode {
974 FieldMode::Replace => slot.value.clone(),
975 FieldMode::Sum => vec![Fix128::ZERO; slot.value.len()],
976 }))
977 }
978
979 /// The bodies at the start of this substep. With TGS (one detection per
980 /// tick) as well as XPBD, positions and velocities are those at the head
981 /// of the substep.
982 #[must_use]
983 pub fn bodies(&self) -> &[RigidBody] {
984 self.bodies
985 }
986
987 /// The SDF contacts the world resolved in the current step before this
988 /// participant runs, in the order they were applied (see
989 /// [`crate::PhysicsWorld::last_step_sdf_contacts`]).
990 ///
991 /// Participants run at the head of each substep, before its SDF
992 /// resolution, so with the XPBD paths (`step`, `step_parallel`,
993 /// `step_with_bridge`) substep `i` sees the contacts of substeps
994 /// `0..i` of this step (none in substep 0). The TGS backend resolves SDF
995 /// overlap once, before its substeps, so every substep sees all of the
996 /// step's contacts. Empty for a context built by [`Self::new`] alone.
997 #[must_use]
998 pub fn sdf_contacts(&self) -> &'a [crate::sdf_collider::SdfContact] {
999 self.sdf_contacts
1000 }
1001
1002 /// Index of this substep within the frame (`0..substeps`).
1003 #[must_use]
1004 pub fn substep_index(&self) -> usize {
1005 self.substep_index
1006 }
1007
1008 /// Substeps in the frame.
1009 #[must_use]
1010 pub fn substeps(&self) -> usize {
1011 self.substeps
1012 }
1013
1014 /// Width of this substep.
1015 #[must_use]
1016 pub fn h(&self) -> Fix128 {
1017 self.h
1018 }
1019
1020 /// Stage `force` on body `body` for this substep.
1021 ///
1022 /// # Errors
1023 ///
1024 /// [`AccumulateError::BodyOutOfRange`] for a body that does not exist.
1025 pub fn add_force(&mut self, body: usize, force: Vec3Fix) -> Result<(), AccumulateError> {
1026 self.forces.add_force(body, force)
1027 }
1028
1029 /// Stage `torque` on body `body` for this substep.
1030 ///
1031 /// # Errors
1032 ///
1033 /// [`AccumulateError::BodyOutOfRange`] for a body that does not exist.
1034 pub fn add_torque(&mut self, body: usize, torque: Vec3Fix) -> Result<(), AccumulateError> {
1035 self.forces.add_torque(body, torque)
1036 }
1037}
1038
1039/// A value read from the world: exact, or undecided because a fault (a value
1040/// out of range) was recorded.
1041#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1042pub enum Observed<T> {
1043 /// The exact value.
1044 Exact(T),
1045 /// No value can be stated: the world recorded a fault.
1046 Undecided,
1047}
1048
1049/// Three-valued outcome of a predicate over an [`Observed`] value.
1050#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1051pub enum Verdict {
1052 /// The value is exact and the predicate holds.
1053 Holds,
1054 /// The value is exact and the predicate does not hold.
1055 Violated,
1056 /// The value is undecided.
1057 Undecided,
1058}
1059
1060impl<T> Observed<T> {
1061 /// Evaluate `predicate` on the exact value; [`Verdict::Undecided`] when
1062 /// there is none.
1063 pub fn verdict(&self, predicate: impl FnOnce(&T) -> bool) -> Verdict {
1064 match self {
1065 Self::Exact(v) if predicate(v) => Verdict::Holds,
1066 Self::Exact(_) => Verdict::Violated,
1067 Self::Undecided => Verdict::Undecided,
1068 }
1069 }
1070}
1071
1072/// Channels a participant reports from [`Participant::observe`], in the order
1073/// it pushed them.
1074#[derive(Clone, Debug, Default, PartialEq, Eq)]
1075pub struct ObservationSink {
1076 values: Vec<(u32, Fix128)>,
1077}
1078
1079impl ObservationSink {
1080 /// An empty sink.
1081 #[must_use]
1082 pub fn new() -> Self {
1083 Self::default()
1084 }
1085
1086 /// Report `value` on `channel`.
1087 pub fn push(&mut self, channel: u32, value: Fix128) {
1088 self.values.push((channel, value));
1089 }
1090
1091 /// Every reported `(channel, value)`, in push order.
1092 #[must_use]
1093 pub fn values(&self) -> &[(u32, Fix128)] {
1094 &self.values
1095 }
1096}
1097
1098/// A law that advances its own state inside the world's substep loop.
1099///
1100/// See the module documentation for the contract.
1101pub trait Participant: Send {
1102 /// Snapshot tag of this participant type.
1103 fn kind(&self) -> ParticipantKind;
1104
1105 /// How this participant's time step relates to the world substep.
1106 fn step_rule(&self) -> StepRule {
1107 StepRule::FollowSubstep
1108 }
1109
1110 /// The ports this participant reads and writes, empty by default. The
1111 /// world reads them once, at registration, to fix the execution order
1112 /// ([`execution_order`]); they must not change while the participant is
1113 /// registered.
1114 fn ports(&self) -> &[Port] {
1115 &[]
1116 }
1117
1118 /// Advance by one world substep of width `h` (`ctx.h()`). Read bodies
1119 /// from `ctx`, stage forces on them through `ctx`.
1120 ///
1121 /// # Errors
1122 ///
1123 /// A [`ParticipantFault`]; the participant must then be exactly as it
1124 /// was before the call (the forces it staged are dropped by the world).
1125 fn substep(&mut self, ctx: &mut SubstepCtx<'_>, h: Fix128) -> Result<(), ParticipantFault>;
1126
1127 /// Report observable values. The world reports them as
1128 /// [`Observed::Undecided`] while a fault is recorded.
1129 fn observe(&self, out: &mut ObservationSink);
1130
1131 /// Append the whole state to `out`.
1132 fn write_state(&self, out: &mut Vec<u8>);
1133
1134 /// Check that `bytes` is a payload [`Self::read_state`] can apply. Must
1135 /// not change `self`.
1136 ///
1137 /// # Errors
1138 ///
1139 /// A [`StateError`] saying why the payload is unusable.
1140 fn check_state(&self, bytes: &[u8]) -> Result<(), StateError>;
1141
1142 /// Replace the state with `bytes`. Only called with a payload
1143 /// [`Self::check_state`] accepted.
1144 fn read_state(&mut self, bytes: &[u8]);
1145}
1146
1147// ============================================================================
1148// Shared fields
1149// ============================================================================
1150
1151/// Where the samples of a shared field sit.
1152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1153#[non_exhaustive]
1154pub enum FieldLayout {
1155 /// One sample per rigid body, indexed like the world's body list.
1156 PerBody {
1157 /// Number of bodies.
1158 bodies: usize,
1159 },
1160 /// A uniform grid of cubic cells. Cell `(ix, iy, iz)` covers
1161 /// `origin + [ix, ix+1)·cell` (and so on per axis); samples are stored row
1162 /// major with `ix` fastest, `index = (iz·ny + iy)·nx + ix`.
1163 Grid {
1164 /// Lower corner of cell `(0, 0, 0)`.
1165 origin: Vec3Fix,
1166 /// Edge length of a cell (positive).
1167 cell: Fix128,
1168 /// Cells per axis `[nx, ny, nz]` (each at least 1).
1169 dims: [usize; 3],
1170 },
1171 // LIMITATION(COV-ENGINE-022): The world does not record the writes; a replaying caller keeps the list of (frame, values) itself.
1172 /// `count` values with no position, indexed `0..count` (at least 1): a
1173 /// list the caller and the participants agree on, such as the inputs of
1174 /// an actuator written by the caller between steps
1175 /// ([`crate::PhysicsWorld::set_field`]) and read committed by the
1176 /// participant, so they are world state that snapshots carry. The world
1177 /// does not record the writes: a caller that replays keeps the list of
1178 /// `(frame, values)` itself.
1179 Channels {
1180 /// Number of values.
1181 count: usize,
1182 },
1183}
1184
1185impl FieldLayout {
1186 /// Number of samples, or `None` for a grid with a non-positive cell, an
1187 /// axis of 0 cells, or more samples than `usize` holds.
1188 #[must_use]
1189 pub fn samples(&self) -> Option<usize> {
1190 match *self {
1191 Self::PerBody { bodies } => Some(bodies),
1192 Self::Grid { cell, dims, .. } => {
1193 if cell <= Fix128::ZERO || dims.contains(&0) {
1194 return None;
1195 }
1196 dims[0].checked_mul(dims[1])?.checked_mul(dims[2])
1197 }
1198 Self::Channels { count } => (count > 0).then_some(count),
1199 }
1200 }
1201}
1202
1203/// How the staged writes of one substep become the field's value.
1204#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1205#[non_exhaustive]
1206pub enum FieldMode {
1207 /// At most one writer; its staged buffer (starting as the committed value)
1208 /// replaces the value. Without a write in a substep the value stays.
1209 Replace,
1210 /// Any number of writers; each staged buffer starts at zero and the value
1211 /// becomes the sum of this substep's buffers (zero when nobody wrote).
1212 Sum,
1213}
1214
1215impl FieldMode {
1216 const fn tag(self) -> u8 {
1217 match self {
1218 Self::Replace => 0,
1219 Self::Sum => 1,
1220 }
1221 }
1222}
1223
1224/// Why a field could not be declared or set.
1225#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1226#[non_exhaustive]
1227pub enum FieldError {
1228 /// A field with this id is already declared.
1229 Duplicate(PortId),
1230 /// The layout has no valid sample count ([`FieldLayout::samples`]).
1231 InvalidLayout,
1232 /// No field with this id.
1233 Unknown(PortId),
1234 /// The values do not have one entry per sample.
1235 Length {
1236 /// Samples of the field.
1237 expected: usize,
1238 /// Values given.
1239 found: usize,
1240 },
1241}
1242
1243/// Why the declared ports do not fit the shared fields.
1244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1245#[non_exhaustive]
1246pub enum FieldPortError {
1247 /// A [`FieldMode::Replace`] field declared written by two participants.
1248 SecondWriter {
1249 /// The field.
1250 field: PortId,
1251 /// Registration index of the first writer.
1252 first: usize,
1253 /// Registration index of the second writer.
1254 second: usize,
1255 },
1256 /// A plain [`PortAccess::Read`] of a field: a field read sees the
1257 /// committed value and must be declared [`PortAccess::ReadCommitted`].
1258 ReadOfField {
1259 /// Registration index of the participant.
1260 participant: usize,
1261 /// The field.
1262 field: PortId,
1263 },
1264 /// [`PortAccess::ReadCommitted`] of a port that is not a field.
1265 UnknownField {
1266 /// Registration index of the participant.
1267 participant: usize,
1268 /// The port.
1269 field: PortId,
1270 },
1271}
1272
1273/// Why a participant could not read or stage a field.
1274#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1275#[non_exhaustive]
1276pub enum FieldAccessError {
1277 /// No field with this id in the context.
1278 Unknown(PortId),
1279 /// The participant did not declare this access in [`Participant::ports`].
1280 NotDeclared {
1281 /// The field.
1282 field: PortId,
1283 /// The access it would need.
1284 access: PortAccess,
1285 },
1286}
1287
1288#[derive(Clone, Debug, PartialEq, Eq)]
1289struct FieldSlot {
1290 id: PortId,
1291 layout: FieldLayout,
1292 mode: FieldMode,
1293 value: Vec<Fix128>,
1294}
1295
1296/// The world's shared fields: committed values, owned by the world alone.
1297///
1298/// See the module documentation (*Shared fields*) for the rules.
1299#[derive(Clone, Debug, Default, PartialEq, Eq)]
1300pub struct FieldBoard {
1301 // ascending id
1302 fields: Vec<FieldSlot>,
1303}
1304
1305impl FieldBoard {
1306 /// A board without fields.
1307 #[must_use]
1308 pub fn new() -> Self {
1309 Self::default()
1310 }
1311
1312 fn slot(&self, id: PortId) -> Option<&FieldSlot> {
1313 self.fields
1314 .binary_search_by_key(&id, |f| f.id)
1315 .ok()
1316 .map(|i| &self.fields[i])
1317 }
1318
1319 /// Declare field `id`, all samples zero.
1320 ///
1321 /// # Errors
1322 ///
1323 /// [`FieldError::Duplicate`] for an id already declared,
1324 /// [`FieldError::InvalidLayout`] for a layout without a sample count; the
1325 /// board is unchanged.
1326 pub fn declare(
1327 &mut self,
1328 id: PortId,
1329 layout: FieldLayout,
1330 mode: FieldMode,
1331 ) -> Result<(), FieldError> {
1332 let n = layout.samples().ok_or(FieldError::InvalidLayout)?;
1333 match self.fields.binary_search_by_key(&id, |f| f.id) {
1334 Ok(_) => Err(FieldError::Duplicate(id)),
1335 Err(at) => {
1336 self.fields.insert(
1337 at,
1338 FieldSlot {
1339 id,
1340 layout,
1341 mode,
1342 value: vec![Fix128::ZERO; n],
1343 },
1344 );
1345 Ok(())
1346 }
1347 }
1348 }
1349
1350 /// Set the committed value of field `id` (an initial condition, set
1351 /// between steps).
1352 ///
1353 /// # Errors
1354 ///
1355 /// [`FieldError::Unknown`] or [`FieldError::Length`]; the board is
1356 /// unchanged.
1357 pub fn set(&mut self, id: PortId, values: &[Fix128]) -> Result<(), FieldError> {
1358 let i = self
1359 .fields
1360 .binary_search_by_key(&id, |f| f.id)
1361 .map_err(|_| FieldError::Unknown(id))?;
1362 let slot = &mut self.fields[i];
1363 if values.len() != slot.value.len() {
1364 return Err(FieldError::Length {
1365 expected: slot.value.len(),
1366 found: values.len(),
1367 });
1368 }
1369 slot.value.copy_from_slice(values);
1370 Ok(())
1371 }
1372
1373 /// The committed value of field `id`.
1374 #[must_use]
1375 pub fn value(&self, id: PortId) -> Option<&[Fix128]> {
1376 self.slot(id).map(|f| f.value.as_slice())
1377 }
1378
1379 /// The layout of field `id`.
1380 #[must_use]
1381 pub fn layout(&self, id: PortId) -> Option<FieldLayout> {
1382 self.slot(id).map(|f| f.layout)
1383 }
1384
1385 /// The mode of field `id`.
1386 #[must_use]
1387 pub fn mode(&self, id: PortId) -> Option<FieldMode> {
1388 self.slot(id).map(|f| f.mode)
1389 }
1390
1391 /// Every declared field id, ascending.
1392 #[must_use]
1393 pub fn ids(&self) -> Vec<PortId> {
1394 self.fields.iter().map(|f| f.id).collect()
1395 }
1396
1397 /// Commit `stages` (registration index, stage) in run order. Either every
1398 /// field takes its new value or, on error, none changes.
1399 fn commit(&mut self, stages: &[(usize, FieldStage)]) -> Result<(), WorldFault> {
1400 let mut next: Vec<Vec<Fix128>> = Vec::with_capacity(self.fields.len());
1401 for slot in &self.fields {
1402 let mut v = match slot.mode {
1403 FieldMode::Replace => slot.value.clone(),
1404 FieldMode::Sum => vec![Fix128::ZERO; slot.value.len()],
1405 };
1406 for (index, stage) in stages {
1407 let Some(staged) = stage.staged(slot.id) else {
1408 continue;
1409 };
1410 match slot.mode {
1411 FieldMode::Replace => v.copy_from_slice(staged),
1412 FieldMode::Sum => {
1413 for (acc, add) in v.iter_mut().zip(staged) {
1414 *acc = checked_add(*acc, *add).ok_or(WorldFault::FieldOutOfRange {
1415 field: slot.id,
1416 index: *index,
1417 })?;
1418 }
1419 }
1420 }
1421 }
1422 next.push(v);
1423 }
1424 for (slot, v) in self.fields.iter_mut().zip(next) {
1425 slot.value = v;
1426 }
1427 Ok(())
1428 }
1429
1430 fn write_descriptor(slot: &FieldSlot, out: &mut Vec<u8>) {
1431 out.extend_from_slice(&slot.id.get().to_le_bytes());
1432 out.push(slot.mode.tag());
1433 match slot.layout {
1434 FieldLayout::PerBody { bodies } => {
1435 out.push(0);
1436 out.extend_from_slice(&(bodies as u64).to_le_bytes());
1437 }
1438 FieldLayout::Grid { origin, cell, dims } => {
1439 out.push(1);
1440 for f in [origin.x, origin.y, origin.z, cell] {
1441 put_fix(out, f);
1442 }
1443 for d in dims {
1444 out.extend_from_slice(&(d as u64).to_le_bytes());
1445 }
1446 }
1447 FieldLayout::Channels { count } => {
1448 out.push(2);
1449 out.extend_from_slice(&(count as u64).to_le_bytes());
1450 }
1451 }
1452 }
1453
1454 /// Append every field (descriptor and committed values) to `out`, the
1455 /// bytes of the snapshot section `fields`.
1456 pub fn write_values(&self, out: &mut Vec<u8>) {
1457 out.extend_from_slice(&(self.fields.len() as u64).to_le_bytes());
1458 for slot in &self.fields {
1459 Self::write_descriptor(slot, out);
1460 for &f in &slot.value {
1461 put_fix(out, f);
1462 }
1463 }
1464 }
1465
1466 /// Check that `bytes` (from [`Self::write_values`]) describes exactly the
1467 /// fields of this board (ids, modes, layouts); the values are not checked.
1468 /// Does not change the board.
1469 ///
1470 /// # Errors
1471 ///
1472 /// [`StateError::Length`] when the byte count differs from what this board
1473 /// needs, [`StateError::InvalidValue`] when a descriptor differs.
1474 pub fn check_values(&self, bytes: &[u8]) -> Result<(), StateError> {
1475 let mut own = Vec::new();
1476 self.write_values(&mut own);
1477 if own.len() != bytes.len() {
1478 return Err(StateError::Length {
1479 expected: own.len(),
1480 found: bytes.len(),
1481 });
1482 }
1483 let mut at = 8;
1484 if own[..at] != bytes[..at] {
1485 return Err(StateError::InvalidValue);
1486 }
1487 for slot in &self.fields {
1488 let mut d = Vec::new();
1489 Self::write_descriptor(slot, &mut d);
1490 if bytes[at..at + d.len()] != d[..] {
1491 return Err(StateError::InvalidValue);
1492 }
1493 at += d.len() + 16 * slot.value.len();
1494 }
1495 Ok(())
1496 }
1497
1498 /// Replace every committed value with those in `bytes`. Only called with
1499 /// bytes [`Self::check_values`] accepted.
1500 pub fn read_values(&mut self, bytes: &[u8]) {
1501 let mut at = 8;
1502 for slot in &mut self.fields {
1503 let mut d = Vec::new();
1504 Self::write_descriptor(slot, &mut d);
1505 at += d.len();
1506 for f in &mut slot.value {
1507 *f = get_fix(&bytes[at..at + 16]);
1508 at += 16;
1509 }
1510 }
1511 }
1512}
1513
1514fn put_fix(out: &mut Vec<u8>, f: Fix128) {
1515 out.extend_from_slice(&f.hi.to_le_bytes());
1516 out.extend_from_slice(&f.lo.to_le_bytes());
1517}
1518
1519fn get_fix(b: &[u8]) -> Fix128 {
1520 let mut hi = [0_u8; 8];
1521 let mut lo = [0_u8; 8];
1522 hi.copy_from_slice(&b[0..8]);
1523 lo.copy_from_slice(&b[8..16]);
1524 Fix128 {
1525 hi: i64::from_le_bytes(hi),
1526 lo: u64::from_le_bytes(lo),
1527 }
1528}
1529
1530/// `a + b`, or `None` when the sum leaves the range of [`Fix128`].
1531fn checked_add(a: Fix128, b: Fix128) -> Option<Fix128> {
1532 let wide = |f: Fix128| (i128::from(f.hi) << 64) | i128::from(f.lo);
1533 let s = wide(a).checked_add(wide(b))?;
1534 Some(Fix128 {
1535 hi: (s >> 64) as i64,
1536 lo: s as u64,
1537 })
1538}
1539
1540/// One participant's staged field writes for one substep.
1541#[derive(Clone, Debug, Default, PartialEq, Eq)]
1542pub struct FieldStage {
1543 // ascending id
1544 staged: Vec<(PortId, Vec<Fix128>)>,
1545}
1546
1547impl FieldStage {
1548 /// An empty stage.
1549 #[must_use]
1550 pub fn new() -> Self {
1551 Self::default()
1552 }
1553
1554 /// Whether nothing was staged.
1555 #[must_use]
1556 pub fn is_empty(&self) -> bool {
1557 self.staged.is_empty()
1558 }
1559
1560 /// The staged buffer of field `id`, if one was created.
1561 #[must_use]
1562 pub fn staged(&self, id: PortId) -> Option<&[Fix128]> {
1563 self.staged
1564 .binary_search_by_key(&id, |s| s.0)
1565 .ok()
1566 .map(|i| self.staged[i].1.as_slice())
1567 }
1568
1569 fn buffer(&mut self, id: PortId, init: impl FnOnce() -> Vec<Fix128>) -> &mut [Fix128] {
1570 let i = match self.staged.binary_search_by_key(&id, |s| s.0) {
1571 Ok(i) => i,
1572 Err(at) => {
1573 self.staged.insert(at, (id, init()));
1574 at
1575 }
1576 };
1577 &mut self.staged[i].1
1578 }
1579}
1580
1581/// Check the declared ports of every participant (`ports[i]` for the
1582/// participant registered at `i`) against the fields of `board`.
1583///
1584/// # Errors
1585///
1586/// The first [`FieldPortError`] in registration order, then port order.
1587pub fn check_field_ports(board: &FieldBoard, ports: &[&[Port]]) -> Result<(), FieldPortError> {
1588 let mut writers: Vec<(PortId, usize)> = Vec::new();
1589 for (i, ps) in ports.iter().enumerate() {
1590 for p in ps.iter() {
1591 let field = board.slot(p.id);
1592 match (p.access, field) {
1593 (PortAccess::Read, Some(_)) => {
1594 return Err(FieldPortError::ReadOfField {
1595 participant: i,
1596 field: p.id,
1597 })
1598 }
1599 (PortAccess::ReadCommitted, None) => {
1600 return Err(FieldPortError::UnknownField {
1601 participant: i,
1602 field: p.id,
1603 })
1604 }
1605 (PortAccess::Write, Some(slot)) if slot.mode == FieldMode::Replace => {
1606 match writers.iter().find(|w| w.0 == p.id) {
1607 Some(&(_, first)) if first != i => {
1608 return Err(FieldPortError::SecondWriter {
1609 field: p.id,
1610 first,
1611 second: i,
1612 })
1613 }
1614 Some(_) => {}
1615 None => writers.push((p.id, i)),
1616 }
1617 }
1618 _ => {}
1619 }
1620 }
1621 }
1622 Ok(())
1623}
1624
1625/// What the world fixes at registration for a list of participants: the run
1626/// order within a substep and each participant's declared ports.
1627#[derive(Clone, Debug, PartialEq, Eq)]
1628pub struct ParticipantPlan {
1629 order: Vec<usize>,
1630 ports: Vec<Vec<Port>>,
1631}
1632
1633impl ParticipantPlan {
1634 /// The plan for `participants` (registration order) over the fields of
1635 /// `board`. The world builds it again whenever a participant or a field is
1636 /// added.
1637 ///
1638 /// # Errors
1639 ///
1640 /// [`RegisterError::Order`] for a loop of ports, [`RegisterError::Field`]
1641 /// when the ports do not fit the fields.
1642 pub fn new(
1643 participants: &[Box<dyn Participant>],
1644 board: &FieldBoard,
1645 ) -> Result<Self, RegisterError> {
1646 let ports: Vec<Vec<Port>> = participants.iter().map(|p| p.ports().to_vec()).collect();
1647 let views: Vec<&[Port]> = ports.iter().map(Vec::as_slice).collect();
1648 check_field_ports(board, &views).map_err(RegisterError::Field)?;
1649 let order = execution_order(&views).map_err(RegisterError::Order)?;
1650 Ok(Self { order, ports })
1651 }
1652
1653 /// Registration indices in run order.
1654 #[must_use]
1655 pub fn order(&self) -> &[usize] {
1656 &self.order
1657 }
1658}
1659
1660/// Position and width of one substep.
1661#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1662pub struct SubstepTime {
1663 /// Index of the substep within the frame.
1664 pub index: usize,
1665 /// Substeps in the frame.
1666 pub count: usize,
1667 /// Width of the substep.
1668 pub h: Fix128,
1669}
1670
1671/// Why [`run_substep`] did not run (nothing was called or changed).
1672#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1673#[non_exhaustive]
1674pub enum ExchangeError {
1675 /// The plan was built for another number of participants.
1676 Plan {
1677 /// Participants given.
1678 participants: usize,
1679 /// Participants in the plan.
1680 planned: usize,
1681 },
1682 /// `frozen` does not have one flag per participant.
1683 Frozen {
1684 /// Flags given.
1685 flags: usize,
1686 /// Participants given.
1687 participants: usize,
1688 },
1689 /// The force accumulator does not have one slot per body.
1690 Accumulate(AccumulateError),
1691 /// A [`FieldLayout::PerBody`] field does not have one sample per body.
1692 BodyCount {
1693 /// The field.
1694 field: PortId,
1695 /// Samples of the field.
1696 samples: usize,
1697 /// Bodies.
1698 bodies: usize,
1699 },
1700}
1701
1702/// One substep of every participant, the part of the world step that runs the
1703/// participants and exchanges their results.
1704///
1705/// Participants that are not `frozen` run in the plan's order. Each gets the
1706/// bodies at the start of the substep, the fields as committed before the
1707/// substep, and its own empty force accumulator and field stage. On `Ok` its
1708/// forces are merged into `forces`; on `Err` its forces and staged writes are
1709/// dropped, it is marked frozen and a [`WorldFault::Participant`] is
1710/// reported. After every participant ran, the staged writes of those that
1711/// returned `Ok` are committed in run order ([`FieldBoard`] rules); a commit
1712/// out of range changes no field and is reported as
1713/// [`WorldFault::FieldOutOfRange`].
1714///
1715/// Returns the faults of this substep in the order they happened.
1716///
1717/// # Errors
1718///
1719/// An [`ExchangeError`] when the inputs do not fit together; nothing ran.
1720pub fn run_substep(
1721 participants: &mut [Box<dyn Participant>],
1722 plan: &ParticipantPlan,
1723 frozen: &mut [bool],
1724 bodies: &[RigidBody],
1725 board: &mut FieldBoard,
1726 forces: &mut ForceAccumulator,
1727 time: SubstepTime,
1728) -> Result<Vec<WorldFault>, ExchangeError> {
1729 run_substep_with_sdf_contacts(participants, plan, frozen, bodies, board, forces, time, &[])
1730}
1731
1732/// [`run_substep`] with the SDF contacts each participant reads through
1733/// [`SubstepCtx::sdf_contacts`]; `run_substep` passes none.
1734///
1735/// # Errors
1736///
1737/// As [`run_substep`].
1738#[allow(clippy::too_many_arguments)]
1739pub fn run_substep_with_sdf_contacts(
1740 participants: &mut [Box<dyn Participant>],
1741 plan: &ParticipantPlan,
1742 frozen: &mut [bool],
1743 bodies: &[RigidBody],
1744 board: &mut FieldBoard,
1745 forces: &mut ForceAccumulator,
1746 time: SubstepTime,
1747 sdf_contacts: &[crate::sdf_collider::SdfContact],
1748) -> Result<Vec<WorldFault>, ExchangeError> {
1749 let n = participants.len();
1750 if plan.order.len() != n {
1751 return Err(ExchangeError::Plan {
1752 participants: n,
1753 planned: plan.order.len(),
1754 });
1755 }
1756 if frozen.len() != n {
1757 return Err(ExchangeError::Frozen {
1758 flags: frozen.len(),
1759 participants: n,
1760 });
1761 }
1762 if forces.len() != bodies.len() {
1763 return Err(ExchangeError::Accumulate(AccumulateError::LengthMismatch {
1764 slots: forces.len(),
1765 bodies: bodies.len(),
1766 }));
1767 }
1768 for slot in &board.fields {
1769 if let FieldLayout::PerBody { .. } = slot.layout {
1770 if slot.value.len() != bodies.len() {
1771 return Err(ExchangeError::BodyCount {
1772 field: slot.id,
1773 samples: slot.value.len(),
1774 bodies: bodies.len(),
1775 });
1776 }
1777 }
1778 }
1779 let mut faults = Vec::new();
1780 let mut kept: Vec<(usize, FieldStage)> = Vec::with_capacity(n);
1781 for &i in &plan.order {
1782 if frozen[i] {
1783 continue;
1784 }
1785 let mut staged_forces = ForceAccumulator::new(bodies.len());
1786 let mut stage = FieldStage::new();
1787 let result = {
1788 let ctx = SubstepCtx::new(bodies, &mut staged_forces, time.index, time.count, time.h)
1789 .map_err(ExchangeError::Accumulate)?;
1790 let mut ctx = ctx
1791 .with_fields(board, &mut stage, &plan.ports[i])
1792 .with_sdf_contacts(sdf_contacts);
1793 participants[i].substep(&mut ctx, time.h)
1794 };
1795 match result {
1796 Ok(()) => {
1797 forces
1798 .merge(&staged_forces)
1799 .map_err(ExchangeError::Accumulate)?;
1800 kept.push((i, stage));
1801 }
1802 Err(fault) => {
1803 frozen[i] = true;
1804 faults.push(WorldFault::Participant {
1805 index: i,
1806 kind: participants[i].kind(),
1807 fault,
1808 });
1809 }
1810 }
1811 }
1812 if let Err(fault) = board.commit(&kept) {
1813 faults.push(fault);
1814 }
1815 Ok(faults)
1816}
1817
1818/// Why an amount could not be moved between layouts.
1819#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1820#[non_exhaustive]
1821pub enum RemapError {
1822 /// The values do not have one entry per source sample.
1823 Length {
1824 /// Samples of the source.
1825 expected: usize,
1826 /// Values given.
1827 found: usize,
1828 },
1829 /// A layout is not a valid grid ([`FieldLayout::samples`]).
1830 InvalidLayout,
1831 /// The grids are not nested: not the same origin, or cell sizes not an
1832 /// integer ratio, or not covering the same box.
1833 NotNested,
1834 /// A sum left the range of [`Fix128`].
1835 OutOfRange,
1836 /// Body `body` lies outside the grid.
1837 OutsideGrid {
1838 /// Index of the body.
1839 body: usize,
1840 },
1841}
1842
1843/// `big = n·small` exactly with an integer `n ≥ 1`.
1844fn integer_ratio(big: Fix128, small: Fix128) -> Option<usize> {
1845 let n = big / small;
1846 if n.lo == 0 && n.hi >= 1 && Fix128::from_int(n.hi) * small == big {
1847 usize::try_from(n.hi).ok()
1848 } else {
1849 None
1850 }
1851}
1852
1853fn grid_of(layout: &FieldLayout) -> Result<(Vec3Fix, Fix128, [usize; 3]), RemapError> {
1854 match *layout {
1855 FieldLayout::Grid { origin, cell, dims } if layout.samples().is_some() => {
1856 Ok((origin, cell, dims))
1857 }
1858 _ => Err(RemapError::InvalidLayout),
1859 }
1860}
1861
1862/// Move an extensive amount (per cell, not a density) from grid `src` to grid
1863/// `dst`, keeping the total exactly.
1864///
1865/// The grids must be nested: the same origin, cell sizes in an integer ratio
1866/// `r`, and the same box. Coarsening sums the `r³` fine cells of each coarse
1867/// cell. Refining gives each fine cell `q / r³` of its coarse cell's amount
1868/// `q`, and the first fine cell (lowest index) also the remainder
1869/// `q − r³·(q / r³)`, so the fine cells add up to `q` exactly. Equal grids
1870/// copy.
1871///
1872/// # Errors
1873///
1874/// [`RemapError::InvalidLayout`] when a layout is not a grid,
1875/// [`RemapError::Length`] for `values` of the wrong length,
1876/// [`RemapError::NotNested`] for grids that are not nested,
1877/// [`RemapError::OutOfRange`] when a coarse sum leaves the range.
1878pub fn remap_conserving(
1879 src: &FieldLayout,
1880 values: &[Fix128],
1881 dst: &FieldLayout,
1882) -> Result<Vec<Fix128>, RemapError> {
1883 let (so, sc, sd) = grid_of(src)?;
1884 let (d_o, dc, dd) = grid_of(dst)?;
1885 let n_src = src.samples().ok_or(RemapError::InvalidLayout)?;
1886 let n_dst = dst.samples().ok_or(RemapError::InvalidLayout)?;
1887 if values.len() != n_src {
1888 return Err(RemapError::Length {
1889 expected: n_src,
1890 found: values.len(),
1891 });
1892 }
1893 if so != d_o {
1894 return Err(RemapError::NotNested);
1895 }
1896 let coarsen = dc >= sc;
1897 let r = if coarsen {
1898 integer_ratio(dc, sc)
1899 } else {
1900 integer_ratio(sc, dc)
1901 }
1902 .ok_or(RemapError::NotNested)?;
1903 let (fine, coarse) = if coarsen { (sd, dd) } else { (dd, sd) };
1904 for a in 0..3 {
1905 if coarse[a].checked_mul(r) != Some(fine[a]) {
1906 return Err(RemapError::NotNested);
1907 }
1908 }
1909 let parent =
1910 |ix: usize, iy: usize, iz: usize| ((iz / r) * coarse[1] + iy / r) * coarse[0] + ix / r;
1911 if coarsen {
1912 let mut out = vec![Fix128::ZERO; n_dst];
1913 for iz in 0..fine[2] {
1914 for iy in 0..fine[1] {
1915 for ix in 0..fine[0] {
1916 let f = (iz * fine[1] + iy) * fine[0] + ix;
1917 let c = parent(ix, iy, iz);
1918 out[c] = checked_add(out[c], values[f]).ok_or(RemapError::OutOfRange)?;
1919 }
1920 }
1921 }
1922 Ok(out)
1923 } else {
1924 let k = r
1925 .checked_mul(r)
1926 .and_then(|v| v.checked_mul(r))
1927 .and_then(|v| i64::try_from(v).ok())
1928 .ok_or(RemapError::NotNested)?;
1929 let kf = Fix128::from_int(k);
1930 let mut out = vec![Fix128::ZERO; n_dst];
1931 for iz in 0..fine[2] {
1932 for iy in 0..fine[1] {
1933 for ix in 0..fine[0] {
1934 let q = values[parent(ix, iy, iz)];
1935 let base = q / kf;
1936 let first = ix % r == 0 && iy % r == 0 && iz % r == 0;
1937 out[(iz * fine[1] + iy) * fine[0] + ix] =
1938 if first { base + (q - base * kf) } else { base };
1939 }
1940 }
1941 }
1942 Ok(out)
1943 }
1944}
1945
1946/// Deposit an extensive amount per body (`amounts[i]` for `bodies[i]`) into
1947/// the grid cell that holds each body's position, keeping the total exactly.
1948///
1949/// # Errors
1950///
1951/// [`RemapError::InvalidLayout`] when `dst` is not a grid,
1952/// [`RemapError::Length`] when `amounts` is not one per body,
1953/// [`RemapError::OutsideGrid`] for a body outside the grid (nothing is
1954/// dropped silently), [`RemapError::OutOfRange`] when a cell sum leaves the
1955/// range.
1956pub fn deposit_bodies(
1957 bodies: &[RigidBody],
1958 amounts: &[Fix128],
1959 dst: &FieldLayout,
1960) -> Result<Vec<Fix128>, RemapError> {
1961 let (origin, cell, dims) = grid_of(dst)?;
1962 let n = dst.samples().ok_or(RemapError::InvalidLayout)?;
1963 if amounts.len() != bodies.len() {
1964 return Err(RemapError::Length {
1965 expected: bodies.len(),
1966 found: amounts.len(),
1967 });
1968 }
1969 let mut out = vec![Fix128::ZERO; n];
1970 for (b, (body, &q)) in bodies.iter().zip(amounts).enumerate() {
1971 let p = body.position;
1972 let mut idx = [0_usize; 3];
1973 for (a, (x, o)) in [(p.x, origin.x), (p.y, origin.y), (p.z, origin.z)]
1974 .into_iter()
1975 .enumerate()
1976 {
1977 let t = (x - o) / cell;
1978 idx[a] = usize::try_from(t.hi)
1979 .ok()
1980 .filter(|&i| i < dims[a])
1981 .ok_or(RemapError::OutsideGrid { body: b })?;
1982 }
1983 let c = (idx[2] * dims[1] + idx[1]) * dims[0] + idx[0];
1984 out[c] = checked_add(out[c], q).ok_or(RemapError::OutOfRange)?;
1985 }
1986 Ok(out)
1987}
1988
1989// ============================================================================
1990// Waking a parked body
1991// ============================================================================
1992
1993/// Whether a change is non-zero in a component; a change too large to
1994/// represent (`None`) counts as non-zero.
1995fn changes(d: Option<Vec3Fix>) -> bool {
1996 d.is_none_or(|d| !(d.x.is_zero() && d.y.is_zero() && d.z.is_zero()))
1997}
1998
1999/// Whether participant force `force` and torque `torque` (summed over every
2000/// participant for this substep) wake the parked or sleeping body `body`: the
2001/// change the world would apply in one substep of width `h`,
2002/// `Δv = (F·inv_mass)·h` or `Δω = (I⁻¹τ)·h` (`I⁻¹` in the world frame, the
2003/// same [`Fix128`] products the world uses), is non-zero in any component.
2004/// There is no threshold: a participant force that changes the body at all
2005/// wakes it, so the momentum a participant books for its force always
2006/// reaches the body. When both changes round to exactly zero the body stays
2007/// asleep (a force of a few raw units does not keep bodies awake). A change
2008/// too large to represent wakes the body (the world then reports it as
2009/// [`WorldFault::ForceOutOfRange`]). A static body never wakes.
2010///
2011/// This replaces the earlier rule for participant forces, which compared the
2012/// change with the sleep thresholds of `sleep` and left a body asleep below
2013/// them (the force then had no effect on it). `sleep` is no longer read; it
2014/// stays in the signature of this unstable function. Wakes from contacts,
2015/// impulses and other sources keep their own thresholds, and waking spreads
2016/// through an island by the existing rules.
2017#[must_use]
2018pub fn wakes_parked_body(
2019 body: &RigidBody,
2020 force: Vec3Fix,
2021 torque: Vec3Fix,
2022 h: Fix128,
2023 _sleep: &SleepConfig,
2024) -> bool {
2025 if body.is_static() {
2026 return false;
2027 }
2028 let dv = force
2029 .checked_scale(body.inv_mass)
2030 .and_then(|a| a.checked_scale(h));
2031 let dw = body
2032 .checked_world_inv_inertia_apply(torque)
2033 .and_then(|a| a.checked_scale(h));
2034 changes(dv) || changes(dw)
2035}
2036
2037#[cfg(test)]
2038mod error_text_tests {
2039 use super::*;
2040 #[cfg(not(feature = "std"))]
2041 use alloc::{format, string::ToString};
2042
2043 /// Every variant prints a message naming what it carries (the exact
2044 /// texts are pinned in `tests/world_participant_errors.rs`).
2045 #[test]
2046 fn every_variant_names_its_values() {
2047 let field = PortId::new(11);
2048 let kind = ParticipantKind::new(u32::from_be_bytes(*b"ABCD"));
2049 let faults = [
2050 WorldFault::Participant {
2051 index: 5,
2052 kind,
2053 fault: ParticipantFault::OutOfRange,
2054 },
2055 WorldFault::Participant {
2056 index: 6,
2057 kind: ParticipantKind::new(1),
2058 fault: ParticipantFault::InvalidState,
2059 },
2060 WorldFault::RigidOverflow,
2061 WorldFault::ForceOutOfRange { body: 12 },
2062 WorldFault::FieldOutOfRange { field, index: 13 },
2063 ];
2064 let texts: Vec<_> = faults.iter().map(ToString::to_string).collect();
2065 assert!(texts[0].contains("participant 5 (ABCD)"));
2066 assert!(texts[1].contains("(0x00000001)") && texts[1].contains("inconsistent"));
2067 assert!(texts[2].contains("rigid"));
2068 assert!(texts[3].contains("body 12"));
2069 assert!(texts[4].contains("field 11") && texts[4].contains("participant 13"));
2070 let registers = [
2071 RegisterError::NonPositiveStep,
2072 RegisterError::StepRuleMismatch {
2073 substep: Fix128::ONE,
2074 step: Fix128::from_int(3),
2075 },
2076 RegisterError::Order(OrderError::Cycle {
2077 members: vec![1, 4],
2078 }),
2079 RegisterError::Field(FieldPortError::SecondWriter {
2080 field,
2081 first: 2,
2082 second: 3,
2083 }),
2084 RegisterError::Field(FieldPortError::ReadOfField {
2085 participant: 7,
2086 field,
2087 }),
2088 RegisterError::Field(FieldPortError::UnknownField {
2089 participant: 8,
2090 field,
2091 }),
2092 ];
2093 for (e, part) in registers.iter().zip([
2094 "not positive",
2095 "3.0000",
2096 "[1, 4]",
2097 "2 and 3",
2098 "participant 7",
2099 "participant 8",
2100 ]) {
2101 assert!(e.to_string().contains(part), "{e:?}: {e}");
2102 }
2103 let steps = [
2104 StepError::Faulted(faults[2]),
2105 StepError::FaultRaised(faults[3]),
2106 StepError::Rule {
2107 index: 9,
2108 error: RegisterError::NonPositiveStep,
2109 },
2110 StepError::BodyCount {
2111 field,
2112 samples: 1,
2113 bodies: 2,
2114 },
2115 ];
2116 for (e, part) in
2117 steps
2118 .iter()
2119 .zip(["already recorded", "body 12", "participant 9", "1 samples"])
2120 {
2121 assert!(e.to_string().contains(part), "{e:?}: {e}");
2122 }
2123 assert_eq!(
2124 format!("{}", ParticipantFault::OutOfRange),
2125 "a value left its range"
2126 );
2127 }
2128
2129 #[cfg(feature = "std")]
2130 #[test]
2131 fn only_the_rule_error_has_a_source() {
2132 use std::error::Error;
2133 let rule = StepError::Rule {
2134 index: 0,
2135 error: RegisterError::NonPositiveStep,
2136 };
2137 assert!(rule.source().is_some());
2138 assert!(StepError::FaultRaised(WorldFault::RigidOverflow)
2139 .source()
2140 .is_none());
2141 assert!(RegisterError::NonPositiveStep.source().is_none());
2142 }
2143}