Skip to main content

gizmo_physics_core/
body.rs

1//! Opaque body handle for the physics world.
2
3/// An opaque identifier for a body inside a physics world.
4///
5/// `BodyHandle` is the single identity the physics public API speaks in —
6/// collision/trigger/fracture events, raycast hits, joints and broadphase
7/// queries all carry `BodyHandle`s rather than the engine's ECS
8/// [`Entity`](gizmo_core::entity::Entity). The ECS↔physics bridge converts at
9/// the boundary (`BodyHandle::from_id(entity.id())` on the way in,
10/// `Entity::new(handle.id(), 0)` on the way out), so a non-ECS embedding can
11/// drive the simulation with its own handles and the internal representation
12/// can evolve post-1.0 without breaking the physics API.
13///
14/// It is a transparent newtype over `u32`, so where it appears in serialized
15/// data (e.g. scene-file joint endpoints) it round-trips as that single id.
16///
17/// # Determinism
18/// A handle wraps the body's stable `u32` id — the sole identity physics keys
19/// on (the deterministic state hash sorts and mixes bodies by [`id`](Self::id)).
20/// Carrying the same id the old `Entity` did keeps the state hash bit-identical,
21/// preserving cross-process/rollback determinism.
22#[derive(
23    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
24)]
25pub struct BodyHandle(u32);
26
27impl BodyHandle {
28    /// Sentinel value for "no body"; usable in place of `Option<BodyHandle>`.
29    pub const INVALID: Self = Self(u32::MAX);
30
31    /// Builds a handle from a raw body id.
32    #[inline]
33    pub const fn from_id(id: u32) -> Self {
34        Self(id)
35    }
36
37    /// The body's stable id (the value the deterministic state hash keys on).
38    #[inline]
39    pub const fn id(self) -> u32 {
40        self.0
41    }
42
43    /// `true` unless this is [`INVALID`](Self::INVALID).
44    #[inline]
45    pub fn is_valid(self) -> bool {
46        self != Self::INVALID
47    }
48}
49
50impl std::fmt::Display for BodyHandle {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        if *self == Self::INVALID {
53            write!(f, "BodyHandle(INVALID)")
54        } else {
55            write!(f, "BodyHandle({})", self.0)
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn from_id_roundtrips() {
66        let h = BodyHandle::from_id(42);
67        assert_eq!(h.id(), 42);
68        assert!(h.is_valid());
69    }
70
71    #[test]
72    fn invalid_sentinel() {
73        assert!(!BodyHandle::INVALID.is_valid());
74        assert_eq!(BodyHandle::INVALID.id(), u32::MAX);
75    }
76
77    #[test]
78    fn ordering_and_hash_by_id() {
79        use std::collections::HashSet;
80        assert!(BodyHandle::from_id(5) < BodyHandle::from_id(10));
81        let mut set = HashSet::new();
82        set.insert(BodyHandle::from_id(7));
83        assert!(set.contains(&BodyHandle::from_id(7)));
84        assert!(!set.contains(&BodyHandle::from_id(8)));
85    }
86}