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