use std::fmt;
use std::sync::OnceLock;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct MemoryId(ulid::Ulid);
pub(crate) fn next_monotonic_ulid() -> ulid::Ulid {
static GENERATOR: OnceLock<Mutex<ulid::Generator>> = OnceLock::new();
GENERATOR
.get_or_init(|| Mutex::new(ulid::Generator::new()))
.lock()
.generate()
.expect("monotonic ULID overflow")
}
impl MemoryId {
#[must_use]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self(next_monotonic_ulid())
}
#[must_use]
pub const fn from_ulid(ulid: ulid::Ulid) -> Self {
Self(ulid)
}
#[must_use]
pub const fn as_ulid(&self) -> ulid::Ulid {
self.0
}
#[must_use]
pub const fn timestamp_ms(&self) -> u64 {
self.0.timestamp_ms()
}
pub fn parse(s: &str) -> Result<Self, crate::HirnError> {
ulid::Ulid::from_string(s)
.map(Self)
.map_err(|e| crate::HirnError::InvalidInput(format!("invalid memory id '{s}': {e}")))
}
}
impl fmt::Display for MemoryId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<ulid::Ulid> for MemoryId {
fn from(ulid: ulid::Ulid) -> Self {
Self(ulid)
}
}
const _: () = {
const fn assert_copy<T: Copy>() {}
const fn assert_send<T: Send>() {}
const fn assert_sync<T: Sync>() {}
assert_copy::<MemoryId>();
assert_send::<MemoryId>();
assert_sync::<MemoryId>();
};
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
#[test]
fn new_ids_are_unique() {
let a = MemoryId::new();
let b = MemoryId::new();
assert_ne!(a, b);
}
#[test]
fn ids_created_later_sort_after() {
let a = MemoryId::new();
std::thread::sleep(std::time::Duration::from_millis(2));
let b = MemoryId::new();
assert!(b > a, "later ID should sort after earlier ID");
}
#[test]
fn display_round_trip() {
let id = MemoryId::new();
let s = id.to_string();
assert!(!s.is_empty());
}
#[test]
fn serde_round_trip() {
let id = MemoryId::new();
let bytes = bincode::serialize(&id).unwrap();
let back: MemoryId = bincode::deserialize(&bytes).unwrap();
assert_eq!(id, back);
}
#[test]
fn size_is_16_bytes() {
assert_eq!(std::mem::size_of::<MemoryId>(), 16);
}
#[test]
fn copy_semantics() {
let a = MemoryId::new();
let b = a; assert_eq!(a, b); }
#[test]
fn hashmap_key() {
let id = MemoryId::new();
let mut map = HashMap::new();
map.insert(id, "hello");
assert_eq!(map.get(&id), Some(&"hello"));
assert_eq!(map.len(), 1);
let id2 = MemoryId::new();
map.insert(id2, "world");
assert_eq!(map.len(), 2);
assert_eq!(map.get(&id), Some(&"hello"));
assert_eq!(map.get(&id2), Some(&"world"));
}
}