use super::{BodyObservation, PhysicsWorld};
use crate::math::Fix128;
#[cfg(feature = "std")]
use crate::math::Vec3Fix;
#[cfg(feature = "std")]
use crate::world_participant::{
run_substep_with_sdf_contacts, wakes_parked_body, ForceAccumulator, ObservationSink, Observed,
Participant, ParticipantKind, ParticipantPlan, StepRule, SubstepTime,
};
use crate::world_participant::{
FieldBoard, FieldError, FieldLayout, FieldMode, PortId, RegisterError, StepError, WorldFault,
};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DeclareFieldError {
Field(FieldError),
Ports(RegisterError),
}
impl core::fmt::Display for DeclareFieldError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Field(e) => write!(f, "field refused by the board: {e:?}"),
Self::Ports(e) => write!(f, "field does not fit the registered ports: {e:?}"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for DeclareFieldError {}
#[cfg(feature = "std")]
fn checked_add_vec(a: Vec3Fix, b: Vec3Fix) -> Option<Vec3Fix> {
let add = |x: Fix128, y: Fix128| -> Option<Fix128> {
let wide = |f: Fix128| (i128::from(f.hi) << 64) | i128::from(f.lo);
let s = wide(x).checked_add(wide(y))?;
Some(Fix128 {
hi: (s >> 64) as i64,
lo: s as u64,
})
};
Some(Vec3Fix::new(add(a.x, b.x)?, add(a.y, b.y)?, add(a.z, b.z)?))
}
#[cfg(feature = "std")]
fn list_mut(m: &mut std::sync::Mutex<Vec<Box<dyn Participant>>>) -> &mut Vec<Box<dyn Participant>> {
m.get_mut()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
impl PhysicsWorld {
#[cfg(feature = "std")]
pub fn add_participant(
&mut self,
participant: Box<dyn Participant>,
) -> Result<usize, RegisterError> {
if let StepRule::Fixed(step) = participant.step_rule() {
if step <= Fix128::ZERO {
return Err(RegisterError::NonPositiveStep);
}
}
let list = list_mut(&mut self.participants);
list.push(participant);
match ParticipantPlan::new(list, &self.fields) {
Ok(plan) => {
self.participant_plan = Some(plan);
Ok(list.len() - 1)
}
Err(e) => {
list.pop();
Err(e)
}
}
}
#[cfg(feature = "std")]
#[must_use]
pub fn participant_count(&self) -> usize {
self.lock_participants().len()
}
#[cfg(feature = "std")]
pub(crate) fn participant_law_parts(
&self,
) -> Vec<(
crate::world_participant::ParticipantKind,
crate::world_participant::StepRule,
Vec<crate::world_participant::Port>,
)> {
self.lock_participants()
.iter()
.map(|p| (p.kind(), p.step_rule(), p.ports().to_vec()))
.collect()
}
#[cfg(feature = "std")]
#[must_use]
pub fn participant_kinds(&self) -> Vec<ParticipantKind> {
self.lock_participants().iter().map(|p| p.kind()).collect()
}
#[cfg(feature = "std")]
#[must_use]
pub fn participant_state(&self, index: usize) -> Option<Vec<u8>> {
let list = self.lock_participants();
let p = list.get(index)?;
let mut out = Vec::new();
p.write_state(&mut out);
Some(out)
}
#[cfg(feature = "std")]
#[must_use]
pub fn observe_participant(&self, index: usize) -> Option<Observed<ObservationSink>> {
let list = self.lock_participants();
let p = list.get(index)?;
if self.fault.is_some() {
return Some(Observed::Undecided);
}
let mut sink = ObservationSink::new();
p.observe(&mut sink);
Some(Observed::Exact(sink))
}
#[cfg(feature = "std")]
fn lock_participants(&self) -> std::sync::MutexGuard<'_, Vec<Box<dyn Participant>>> {
self.participants
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(feature = "std")]
pub(super) fn write_participants(&self, out: &mut Vec<(u32, Vec<u8>)>) {
for p in self.lock_participants().iter() {
let mut payload = Vec::new();
p.write_state(&mut payload);
out.push((p.kind().get(), payload));
}
}
pub(super) fn check_participants(
&mut self,
stored: &[(u32, Vec<u8>)],
) -> Result<(), super::WorldSnapshotError> {
#[cfg(feature = "std")]
{
let list = list_mut(&mut self.participants);
let snapshot: Vec<ParticipantKind> =
stored.iter().map(|s| ParticipantKind::new(s.0)).collect();
let world: Vec<ParticipantKind> = list.iter().map(|p| p.kind()).collect();
if let Some(m) =
crate::world_participant::ParticipantMismatch::classify(&snapshot, &world)
{
return Err(super::WorldSnapshotError::ParticipantMismatch(m));
}
for (index, (p, (_, payload))) in list.iter().zip(stored).enumerate() {
p.check_state(payload).map_err(|error| {
super::WorldSnapshotError::ParticipantState { index, error }
})?;
}
Ok(())
}
#[cfg(not(feature = "std"))]
{
if stored.is_empty() {
Ok(())
} else {
Err(super::WorldSnapshotError::ParticipantMismatch(
crate::world_participant::ParticipantMismatch::Count {
snapshot: stored.len(),
world: 0,
},
))
}
}
}
pub(super) fn read_participants(&mut self, stored: &[(u32, Vec<u8>)]) {
#[cfg(feature = "std")]
for (p, (_, payload)) in list_mut(&mut self.participants).iter_mut().zip(stored) {
p.read_state(payload);
}
#[cfg(not(feature = "std"))]
debug_assert!(stored.is_empty(), "checked by check_participants");
}
#[must_use]
pub const fn fault(&self) -> Option<WorldFault> {
self.fault
}
pub fn clear_fault(&mut self) {
self.fault = None;
}
pub(super) fn set_fault(&mut self, fault: Option<WorldFault>) {
self.fault = fault;
}
fn record_fault(&mut self, fault: WorldFault) {
if self.fault.is_none() {
self.fault = Some(fault);
}
}
#[must_use]
pub fn observe_body_checked(
&self,
body_idx: usize,
) -> Option<crate::world_participant::Observed<BodyObservation>> {
let o = self.observe_body(body_idx)?;
Some(if self.fault.is_some() || self.overflow_detected {
crate::world_participant::Observed::Undecided
} else {
crate::world_participant::Observed::Exact(o)
})
}
#[must_use]
pub const fn fields(&self) -> &FieldBoard {
&self.fields
}
pub fn declare_field(
&mut self,
id: PortId,
layout: FieldLayout,
mode: FieldMode,
) -> Result<(), DeclareFieldError> {
let mut board = self.fields.clone();
board
.declare(id, layout, mode)
.map_err(DeclareFieldError::Field)?;
#[cfg(feature = "std")]
{
let list = list_mut(&mut self.participants);
if !list.is_empty() {
let plan = ParticipantPlan::new(list, &board).map_err(DeclareFieldError::Ports)?;
self.participant_plan = Some(plan);
}
}
self.fields = board;
Ok(())
}
pub fn set_field(&mut self, id: PortId, values: &[Fix128]) -> Result<(), FieldError> {
self.fields.set(id, values)
}
pub(super) fn fields_mut(&mut self) -> &mut FieldBoard {
&mut self.fields
}
pub fn try_step(&mut self, dt: Fix128) -> Result<(), StepError> {
if !self.check_step(dt, self.participant_substep_width(dt))? {
return Ok(());
}
self.run_step(dt);
self.fault
.map_or(Ok(()), |f| Err(StepError::FaultRaised(f)))
}
#[cfg(feature = "parallel")]
pub fn try_step_parallel(&mut self, dt: Fix128) -> Result<(), StepError> {
if !self.check_step(dt, Self::divided_substep_width(dt, self.config.substeps))? {
return Ok(());
}
self.run_step_parallel(dt);
self.fault
.map_or(Ok(()), |f| Err(StepError::FaultRaised(f)))
}
pub(super) fn check_step(&self, dt: Fix128, h: Fix128) -> Result<bool, StepError> {
if let Some(f) = self.fault {
return Err(StepError::Faulted(f));
}
if dt <= Fix128::ZERO {
return Ok(false);
}
#[cfg(feature = "std")]
{
let list = self.lock_participants();
if !list.is_empty() {
for (index, p) in list.iter().enumerate() {
p.step_rule()
.steps_per_substep(h)
.map_err(|error| StepError::Rule { index, error })?;
}
for field in self.fields.ids() {
if let Some(FieldLayout::PerBody { .. }) = self.fields.layout(field) {
let samples = self.fields.value(field).map_or(0, <[Fix128]>::len);
if samples != self.bodies.len() {
return Err(StepError::BodyCount {
field,
samples,
bodies: self.bodies.len(),
});
}
}
}
}
}
Ok(true)
}
pub(super) fn participant_substep_width(&self, dt: Fix128) -> Fix128 {
let substeps = self.config.substeps;
#[cfg(feature = "std")]
if substeps > 0 && matches!(self.config.solver_backend, super::SolverBackend::Tgs) {
return dt * Fix128::from_f32(1.0 / substeps as f32);
}
Self::divided_substep_width(dt, substeps)
}
pub(super) fn divided_substep_width(dt: Fix128, substeps: usize) -> Fix128 {
dt / Fix128::from_int(substeps as i64)
}
pub(super) fn participant_flags(&mut self) -> Vec<bool> {
#[cfg(feature = "std")]
{
alloc_flags(list_mut(&mut self.participants).len())
}
#[cfg(not(feature = "std"))]
{
Vec::new()
}
}
pub(super) fn participants_begin_substep(
&mut self,
index: usize,
count: usize,
h: Fix128,
frozen: &mut [bool],
) -> bool {
let at_start = self.overflow_detected;
if frozen.is_empty() {
return at_start;
}
#[cfg(feature = "std")]
{
let Some(plan) = self.participant_plan.as_ref() else {
return at_start;
};
let mut forces = ForceAccumulator::new(self.bodies.len());
let faults = run_substep_with_sdf_contacts(
list_mut(&mut self.participants),
plan,
frozen,
&self.bodies,
&mut self.fields,
&mut forces,
SubstepTime { index, count, h },
&self.sdf_contact_log,
);
match faults {
Ok(faults) => {
for f in faults {
self.record_fault(f);
}
}
Err(e) => unreachable!("participant inputs checked before the step: {e:?}"),
}
self.apply_participant_forces(&forces, h);
}
#[cfg(not(feature = "std"))]
let _ = (index, count, h);
at_start
}
pub(super) fn participants_end_substep(&mut self, overflow_at_start: bool, frozen: &[bool]) {
if frozen.is_empty() {
return;
}
if !overflow_at_start && self.overflow_detected {
self.record_fault(WorldFault::RigidOverflow);
}
}
#[cfg(feature = "std")]
fn apply_participant_forces(&mut self, forces: &ForceAccumulator, h: Fix128) {
for i in 0..self.bodies.len() {
let (Some(force), Some(torque)) = (forces.force(i), forces.torque(i)) else {
continue;
};
let body = &self.bodies[i];
if !body.is_dynamic() {
continue;
}
let parked = self.park.is_parked(i);
if parked || self.islands.is_sleeping(i) {
if !wakes_parked_body(body, force, torque, h, &self.islands.config) {
continue;
}
if parked {
self.islands.wake_body(i);
self.park.unpark(i, &mut self.stage_work);
} else {
self.islands.wake_island(i);
}
}
let body = &self.bodies[i];
let v = force
.checked_scale(body.inv_mass)
.and_then(|a| a.checked_scale(h))
.and_then(|dv| checked_add_vec(body.velocity, dv));
let w = body
.checked_world_inv_inertia_apply(torque)
.and_then(|a| a.checked_scale(h))
.and_then(|dw| checked_add_vec(body.angular_velocity, dw));
match (v, w) {
(Some(v), Some(w)) => {
let body = &mut self.bodies[i];
body.velocity = v;
body.angular_velocity = w;
}
_ => self.record_fault(WorldFault::ForceOutOfRange { body: i }),
}
}
}
}
#[cfg(feature = "std")]
fn alloc_flags(n: usize) -> Vec<bool> {
vec![false; n]
}