use alloc::vec::Vec;
const NIL: u32 = u32::MAX;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Id(u64);
impl Id {
#[inline(always)]
const fn new(index: u32, generation: u32) -> Self {
Self(((generation as u64) << 32) | index as u64)
}
#[inline(always)]
#[allow(clippy::cast_possible_truncation)]
fn index(self) -> u32 {
self.0 as u32
}
#[inline(always)]
fn generation(self) -> u32 {
(self.0 >> 32) as u32
}
#[inline(always)]
pub fn bits(self) -> u64 {
self.0
}
#[inline(always)]
pub fn from_bits(bits: u64) -> Self {
Self(bits)
}
}
enum State<T> {
Occupied(T),
Vacant {
next_free: u32,
},
}
struct Slot<T> {
generation: u32,
state: State<T>,
}
pub struct IdTable<T> {
slots: Vec<Slot<T>>,
free_head: u32,
len: usize,
}
impl<T> IdTable<T> {
pub const fn new() -> Self {
Self { slots: Vec::new(), free_head: NIL, len: 0 }
}
#[inline(always)]
pub fn len(&self) -> usize {
self.len
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn insert(&mut self, value: T) -> Id {
self.len += 1;
if self.free_head == NIL {
let index = u32::try_from(self.slots.len())
.ok()
.filter(|&i| i != NIL)
.expect("IdTable index space exhausted");
self.slots.push(Slot { generation: 0, state: State::Occupied(value) });
Id::new(index, 0)
} else {
let index = self.free_head;
let slot = &self.slots[index as usize];
let State::Vacant { next_free } = slot.state else {
unreachable!("free list points to an occupied slot");
};
let generation = slot.generation;
self.free_head = next_free;
self.slots[index as usize].state = State::Occupied(value);
Id::new(index, generation)
}
}
#[inline]
pub fn get(&self, id: Id) -> Option<&T> {
match self.slots.get(id.index() as usize)? {
Slot { generation, state: State::Occupied(v) } if *generation == id.generation() => {
Some(v)
},
_ => None,
}
}
#[inline]
pub fn get_mut(&mut self, id: Id) -> Option<&mut T> {
match self.slots.get_mut(id.index() as usize)? {
Slot { generation, state: State::Occupied(v) } if *generation == id.generation() => {
Some(v)
},
_ => None,
}
}
#[inline]
pub fn remove(&mut self, id: Id) -> Option<T> {
let index = id.index() as usize;
match self.slots.get(index) {
Some(Slot { generation, state: State::Occupied(_) })
if *generation == id.generation() => {},
_ => return None,
}
let free_head = self.free_head;
let slot = &mut self.slots[index];
slot.generation = slot.generation.wrapping_add(1);
let old = core::mem::replace(&mut slot.state, State::Vacant { next_free: free_head });
self.free_head = id.index();
self.len -= 1;
match old {
State::Occupied(v) => Some(v),
State::Vacant { .. } => unreachable!(),
}
}
}
impl<T> Default for IdTable<T> {
fn default() -> Self {
Self::new()
}
}