zerodds_dcps/
instance_handle.rs1extern crate alloc;
20
21use core::sync::atomic::{AtomicU64, Ordering};
22
23use zerodds_rtps::wire_types::Guid;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
27pub struct InstanceHandle(u64);
28
29impl InstanceHandle {
30 pub const NIL: Self = Self(0);
32
33 #[must_use]
36 pub const fn from_raw(raw: u64) -> Self {
37 Self(raw)
38 }
39
40 #[must_use]
42 pub const fn as_raw(&self) -> u64 {
43 self.0
44 }
45
46 #[must_use]
48 pub const fn is_nil(&self) -> bool {
49 self.0 == 0
50 }
51
52 #[must_use]
63 pub fn from_guid(guid: Guid) -> Self {
64 const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
65 const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
66 let mut h = FNV_OFFSET;
67 for b in guid.to_bytes() {
68 h ^= u64::from(b);
69 h = h.wrapping_mul(FNV_PRIME);
70 }
71 if h == 0 { Self(1) } else { Self(h) }
72 }
73}
74
75impl core::fmt::Display for InstanceHandle {
76 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77 if self.is_nil() {
78 f.write_str("HANDLE_NIL")
79 } else {
80 write!(f, "InstanceHandle({:#x})", self.0)
81 }
82 }
83}
84
85pub const HANDLE_NIL: InstanceHandle = InstanceHandle::NIL;
87
88#[derive(Debug)]
93pub struct InstanceHandleAllocator {
94 next: AtomicU64,
95}
96
97impl Default for InstanceHandleAllocator {
98 fn default() -> Self {
99 Self::new()
100 }
101}
102
103impl InstanceHandleAllocator {
104 #[must_use]
107 pub const fn new() -> Self {
108 Self {
109 next: AtomicU64::new(1),
110 }
111 }
112
113 #[must_use]
116 pub fn allocate(&self) -> InstanceHandle {
117 let v = self.next.fetch_add(1, Ordering::Relaxed);
118 if v == 0 {
121 self.allocate()
122 } else {
123 InstanceHandle(v)
124 }
125 }
126}
127
128#[cfg(test)]
129#[allow(clippy::expect_used)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn handle_nil_is_zero() {
135 assert_eq!(HANDLE_NIL.as_raw(), 0);
136 assert!(HANDLE_NIL.is_nil());
137 }
138
139 #[test]
140 fn allocator_starts_at_one() {
141 let alloc = InstanceHandleAllocator::new();
142 let h = alloc.allocate();
143 assert_eq!(h.as_raw(), 1);
144 assert!(!h.is_nil());
145 }
146
147 #[test]
148 fn allocator_is_monotonic() {
149 let alloc = InstanceHandleAllocator::new();
150 let h1 = alloc.allocate();
151 let h2 = alloc.allocate();
152 let h3 = alloc.allocate();
153 assert!(h1.as_raw() < h2.as_raw());
154 assert!(h2.as_raw() < h3.as_raw());
155 }
156
157 #[test]
158 fn handles_are_distinct() {
159 let alloc = InstanceHandleAllocator::new();
160 let mut seen = alloc::collections::BTreeSet::new();
161 for _ in 0..1000 {
162 let h = alloc.allocate();
163 assert!(seen.insert(h.as_raw()));
164 }
165 }
166
167 #[test]
168 fn display_shows_nil_or_hex() {
169 assert_eq!(alloc::format!("{HANDLE_NIL}"), "HANDLE_NIL");
170 let h = InstanceHandle::from_raw(0xABCD);
171 assert_eq!(alloc::format!("{h}"), "InstanceHandle(0xabcd)");
172 }
173
174 #[test]
175 fn from_raw_and_as_raw_roundtrip() {
176 let h = InstanceHandle::from_raw(42);
177 assert_eq!(h.as_raw(), 42);
178 }
179
180 #[test]
181 fn from_guid_is_deterministic() {
182 use zerodds_rtps::wire_types::Guid;
183 let g = Guid::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 1, 0xC1]);
184 let h1 = InstanceHandle::from_guid(g);
185 let h2 = InstanceHandle::from_guid(g);
186 assert_eq!(h1, h2);
187 assert!(!h1.is_nil());
188 }
189
190 #[test]
191 fn from_guid_distinguishes_keys() {
192 use zerodds_rtps::wire_types::Guid;
193 let g1 = Guid::from_bytes([1; 16]);
194 let g2 = Guid::from_bytes([2; 16]);
195 assert_ne!(InstanceHandle::from_guid(g1), InstanceHandle::from_guid(g2));
196 }
197}