extern crate alloc;
use core::sync::atomic::{AtomicU64, Ordering};
use zerodds_rtps::wire_types::Guid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct InstanceHandle(u64);
impl InstanceHandle {
pub const NIL: Self = Self(0);
#[must_use]
pub const fn from_raw(raw: u64) -> Self {
Self(raw)
}
#[must_use]
pub const fn as_raw(&self) -> u64 {
self.0
}
#[must_use]
pub const fn is_nil(&self) -> bool {
self.0 == 0
}
#[must_use]
pub fn from_guid(guid: Guid) -> Self {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
let mut h = FNV_OFFSET;
for b in guid.to_bytes() {
h ^= u64::from(b);
h = h.wrapping_mul(FNV_PRIME);
}
if h == 0 { Self(1) } else { Self(h) }
}
}
impl core::fmt::Display for InstanceHandle {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_nil() {
f.write_str("HANDLE_NIL")
} else {
write!(f, "InstanceHandle({:#x})", self.0)
}
}
}
pub const HANDLE_NIL: InstanceHandle = InstanceHandle::NIL;
#[derive(Debug)]
pub struct InstanceHandleAllocator {
next: AtomicU64,
}
impl Default for InstanceHandleAllocator {
fn default() -> Self {
Self::new()
}
}
impl InstanceHandleAllocator {
#[must_use]
pub const fn new() -> Self {
Self {
next: AtomicU64::new(1),
}
}
#[must_use]
pub fn allocate(&self) -> InstanceHandle {
let v = self.next.fetch_add(1, Ordering::Relaxed);
if v == 0 {
self.allocate()
} else {
InstanceHandle(v)
}
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn handle_nil_is_zero() {
assert_eq!(HANDLE_NIL.as_raw(), 0);
assert!(HANDLE_NIL.is_nil());
}
#[test]
fn allocator_starts_at_one() {
let alloc = InstanceHandleAllocator::new();
let h = alloc.allocate();
assert_eq!(h.as_raw(), 1);
assert!(!h.is_nil());
}
#[test]
fn allocator_is_monotonic() {
let alloc = InstanceHandleAllocator::new();
let h1 = alloc.allocate();
let h2 = alloc.allocate();
let h3 = alloc.allocate();
assert!(h1.as_raw() < h2.as_raw());
assert!(h2.as_raw() < h3.as_raw());
}
#[test]
fn handles_are_distinct() {
let alloc = InstanceHandleAllocator::new();
let mut seen = alloc::collections::BTreeSet::new();
for _ in 0..1000 {
let h = alloc.allocate();
assert!(seen.insert(h.as_raw()));
}
}
#[test]
fn display_shows_nil_or_hex() {
assert_eq!(alloc::format!("{HANDLE_NIL}"), "HANDLE_NIL");
let h = InstanceHandle::from_raw(0xABCD);
assert_eq!(alloc::format!("{h}"), "InstanceHandle(0xabcd)");
}
#[test]
fn from_raw_and_as_raw_roundtrip() {
let h = InstanceHandle::from_raw(42);
assert_eq!(h.as_raw(), 42);
}
#[test]
fn from_guid_is_deterministic() {
use zerodds_rtps::wire_types::Guid;
let g = Guid::from_bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 1, 0xC1]);
let h1 = InstanceHandle::from_guid(g);
let h2 = InstanceHandle::from_guid(g);
assert_eq!(h1, h2);
assert!(!h1.is_nil());
}
#[test]
fn from_guid_distinguishes_keys() {
use zerodds_rtps::wire_types::Guid;
let g1 = Guid::from_bytes([1; 16]);
let g2 = Guid::from_bytes([2; 16]);
assert_ne!(InstanceHandle::from_guid(g1), InstanceHandle::from_guid(g2));
}
}