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