use crate::error::AstCoreError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct IdSpace(u32);
impl IdSpace {
#[must_use]
pub const fn as_u32(self) -> u32 {
self.0
}
pub const MODEL: Self = Self(0);
pub const BUILTIN: Self = Self(1);
pub const SYNTHETIC: Self = Self(2);
pub const ALL: [Self; 3] = [Self::MODEL, Self::BUILTIN, Self::SYNTHETIC];
#[must_use]
pub const fn get(self) -> u32 {
self.0
}
#[must_use]
pub const fn from_raw(raw: u32) -> Self {
Self(raw)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NodeId(u128);
impl NodeId {
#[must_use]
pub const fn from_parts(space: IdSpace, counter: u64) -> Self {
Self(((space.0 as u128) << 64) | (counter as u128))
}
#[must_use]
pub const fn space(self) -> IdSpace {
IdSpace((self.0 >> 64) as u32)
}
#[must_use]
pub const fn counter(self) -> u64 {
self.0 as u64
}
#[must_use]
pub const fn to_bytes(self) -> [u8; 16] {
self.0.to_be_bytes()
}
#[must_use]
pub const fn from_bytes(bytes: [u8; 16]) -> Self {
Self(u128::from_be_bytes(bytes))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IdAllocator {
space: IdSpace,
next: u64,
}
impl IdAllocator {
#[must_use]
pub const fn new(space: IdSpace) -> Self {
Self { space, next: 1 }
}
#[must_use]
pub const fn space(&self) -> IdSpace {
self.space
}
#[must_use]
pub const fn next_counter(&self) -> u64 {
self.next
}
pub fn allocate(&mut self) -> Result<NodeId, AstCoreError> {
let counter = self.next;
self.next = self.next.checked_add(1).ok_or(AstCoreError::Exhausted)?;
Ok(NodeId::from_parts(self.space, counter))
}
pub fn resume_after(&mut self, highest: NodeId) {
if highest.space() == self.space {
self.next = self.next.max(highest.counter().saturating_add(1));
}
}
pub fn set_next_counter(&mut self, counter: u64) {
self.next = self.next.max(counter);
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn allocation_is_deterministic_for_the_same_sequence() {
let mut a = IdAllocator::new(IdSpace::MODEL);
let mut b = IdAllocator::new(IdSpace::MODEL);
for _ in 0..1000 {
assert_eq!(a.allocate(), b.allocate());
}
}
#[test]
fn spaces_do_not_collide() {
let mut model = IdAllocator::new(IdSpace::MODEL);
let mut builtin = IdAllocator::new(IdSpace::BUILTIN);
let m = model.allocate().expect("model id");
let b = builtin.allocate().expect("builtin id");
assert_ne!(m, b);
assert_eq!(m.counter(), b.counter());
assert_ne!(m.space(), b.space());
}
#[test]
fn byte_round_trip_is_exact() {
let mut alloc = IdAllocator::new(IdSpace::MODEL);
let id = alloc.allocate().expect("id");
assert_eq!(NodeId::from_bytes(id.to_bytes()), id);
}
#[test]
fn resume_after_continues_past_a_loaded_snapshot() {
let mut first = IdAllocator::new(IdSpace::MODEL);
let a = first.allocate().expect("a");
let b = first.allocate().expect("b");
let mut resumed = IdAllocator::new(IdSpace::MODEL);
resumed.resume_after(b);
let c = resumed.allocate().expect("c");
assert!(c > b && b > a);
}
#[test]
fn resume_after_ignores_a_foreign_namespace() {
let mut alloc = IdAllocator::new(IdSpace::MODEL);
alloc.resume_after(NodeId::from_parts(IdSpace::BUILTIN, 9999));
assert_eq!(alloc.allocate().expect("id").counter(), 1);
}
#[test]
fn ordering_follows_the_counter_within_a_space() {
let mut alloc = IdAllocator::new(IdSpace::MODEL);
let a = alloc.allocate().expect("a");
let b = alloc.allocate().expect("b");
assert!(a < b);
}
#[test]
fn counter_zero_is_never_allocated() {
let mut alloc = IdAllocator::new(IdSpace::MODEL);
assert_eq!(alloc.allocate().expect("id").counter(), 1);
}
}