use selene_core::{EdgeId, NodeId};
use crate::graph::GraphMeta;
#[derive(Clone, Debug)]
pub struct IdAllocator {
next_node_id: u64,
next_edge_id: u64,
}
impl IdAllocator {
#[must_use]
pub const fn new() -> Self {
Self {
next_node_id: 1,
next_edge_id: 1,
}
}
#[must_use]
pub fn from_meta_with_floors(meta: &GraphMeta, node_floor: u64, edge_floor: u64) -> Self {
Self {
next_node_id: meta.next_node_id.max(node_floor),
next_edge_id: meta.next_edge_id.max(edge_floor),
}
}
#[must_use]
pub fn allocate_node(&mut self) -> NodeId {
let id = self.next_node_id;
self.next_node_id = self
.next_node_id
.checked_add(1)
.expect("node id allocator exhausted (u64 counter wrap)");
NodeId::new(id)
}
#[must_use]
pub fn allocate_edge(&mut self) -> EdgeId {
let id = self.next_edge_id;
self.next_edge_id = self
.next_edge_id
.checked_add(1)
.expect("edge id allocator exhausted (u64 counter wrap)");
EdgeId::new(id)
}
#[must_use]
pub const fn peek_next_node(&self) -> u64 {
self.next_node_id
}
#[must_use]
pub const fn peek_next_edge(&self) -> u64 {
self.next_edge_id
}
}
impl Default for IdAllocator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use selene_core::GraphId;
#[test]
fn allocate_node_advances_counter() {
let mut allocator = IdAllocator::new();
assert_eq!(allocator.allocate_node(), NodeId::new(1));
assert_eq!(allocator.peek_next_node(), 2);
}
#[test]
fn allocate_edge_advances_counter() {
let mut allocator = IdAllocator::new();
assert_eq!(allocator.allocate_edge(), EdgeId::new(1));
assert_eq!(allocator.peek_next_edge(), 2);
}
#[test]
fn from_meta_with_floors_restores_counters_with_zero_floors() {
let meta = GraphMeta {
graph_id: GraphId::new(1),
generation: 7,
next_node_id: 42,
next_edge_id: 99,
bound_type: None,
};
let allocator = IdAllocator::from_meta_with_floors(&meta, 0, 0);
assert_eq!(allocator.peek_next_node(), 42);
assert_eq!(allocator.peek_next_edge(), 99);
}
#[test]
fn from_meta_with_floors_takes_max_of_meta_and_storage() {
let meta = GraphMeta {
graph_id: GraphId::new(1),
generation: 0,
next_node_id: 5,
next_edge_id: 50,
bound_type: None,
};
let allocator = IdAllocator::from_meta_with_floors(&meta, 10, 30);
assert_eq!(
allocator.peek_next_node(),
10,
"storage floor wins for nodes"
);
assert_eq!(allocator.peek_next_edge(), 50, "meta wins for edges");
}
#[test]
fn from_meta_with_floors_uses_meta_when_higher() {
let meta = GraphMeta {
graph_id: GraphId::new(1),
generation: 0,
next_node_id: 100,
next_edge_id: 200,
bound_type: None,
};
let allocator = IdAllocator::from_meta_with_floors(&meta, 1, 1);
assert_eq!(allocator.peek_next_node(), 100);
assert_eq!(allocator.peek_next_edge(), 200);
}
}