use alloc::collections::BTreeSet;
use alloc::vec::Vec;
const COMPACT_GEN_BASE: u16 = 0x8000;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Handle {
index: u32,
generation: u16,
}
impl Handle {
#[must_use]
pub const fn to_raw(self) -> u64 {
((self.generation as u64) << 32) | self.index as u64
}
#[must_use]
pub const fn from_raw(raw: u64) -> Self {
Self {
index: (raw & 0xffff_ffff) as u32,
generation: ((raw >> 32) & 0xffff) as u16,
}
}
}
enum Slot<T> {
Occupied {
generation: u16,
age: u8,
value: T,
},
Free {
generation: u16,
},
}
pub struct Heap<T> {
slots: Vec<Slot<T>>,
free: Vec<u32>,
live: usize,
compact_gen: u16,
remembered: BTreeSet<u32>,
}
impl<T> Default for Heap<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Heap<T> {
#[must_use]
pub fn new() -> Self {
Self {
slots: Vec::new(),
free: Vec::new(),
live: 0,
compact_gen: COMPACT_GEN_BASE,
remembered: BTreeSet::new(),
}
}
#[must_use]
pub const fn len(&self) -> usize {
self.live
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.live == 0
}
pub fn alloc(&mut self, value: T) -> Handle {
self.live += 1;
if let Some(index) = self.free.pop() {
let slot = &mut self.slots[index as usize];
let generation = match slot {
Slot::Free { generation } => *generation,
Slot::Occupied { .. } => unreachable!("free list pointed at a live slot"),
};
*slot = Slot::Occupied {
generation,
age: 0,
value,
};
Handle { index, generation }
} else {
let index = self.slots.len() as u32;
self.slots.push(Slot::Occupied {
generation: 0,
age: 0,
value,
});
Handle {
index,
generation: 0,
}
}
}
#[must_use]
pub fn get(&self, handle: Handle) -> Option<&T> {
match self.slots.get(handle.index as usize)? {
Slot::Occupied {
generation, value, ..
} if *generation == handle.generation => Some(value),
_ => None,
}
}
pub fn get_mut(&mut self, handle: Handle) -> Option<&mut T> {
match self.slots.get_mut(handle.index as usize)? {
Slot::Occupied {
generation, value, ..
} if *generation == handle.generation => Some(value),
_ => None,
}
}
#[must_use]
pub fn is_live(&self, handle: Handle) -> bool {
self.get(handle).is_some()
}
#[must_use]
pub fn live_handles(&self) -> Vec<Handle> {
self.slots
.iter()
.enumerate()
.filter_map(|(i, slot)| match slot {
Slot::Occupied { generation, .. } => Some(Handle {
index: i as u32,
generation: *generation,
}),
Slot::Free { .. } => None,
})
.collect()
}
#[must_use]
pub fn age(&self, handle: Handle) -> Option<u8> {
match self.slots.get(handle.index as usize)? {
Slot::Occupied {
generation, age, ..
} if *generation == handle.generation => Some(*age),
_ => None,
}
}
pub fn tenure(&mut self, handle: Handle) {
if let Some(Slot::Occupied {
generation, age, ..
}) = self.slots.get_mut(handle.index as usize)
&& *generation == handle.generation
{
*age = age.saturating_add(1);
}
}
#[must_use]
pub fn handles_where(&self, keep: impl Fn(u8) -> bool) -> Vec<Handle> {
self.slots
.iter()
.enumerate()
.filter_map(|(i, slot)| match slot {
Slot::Occupied {
generation, age, ..
} if keep(*age) => Some(Handle {
index: i as u32,
generation: *generation,
}),
_ => None,
})
.collect()
}
pub fn record_edge(&mut self, container: Handle, target: Handle, old_age: u8) {
if self.age(container).is_some_and(|a| a >= old_age)
&& self.age(target).is_some_and(|a| a < old_age)
{
self.remembered.insert(container.index);
}
}
#[must_use]
pub fn remembered_roots(&self) -> Vec<Handle> {
self.remembered
.iter()
.filter_map(|&index| match self.slots.get(index as usize) {
Some(Slot::Occupied { generation, .. }) => Some(Handle {
index,
generation: *generation,
}),
_ => None,
})
.collect()
}
pub fn clear_remembered(&mut self) {
self.remembered.clear();
}
pub fn compact_to(&mut self, keep: &BTreeSet<Handle>) -> Vec<(Handle, Handle)> {
let new_gen = self.compact_gen;
self.compact_gen = self.compact_gen.wrapping_add(1).max(COMPACT_GEN_BASE);
let old_slots = core::mem::take(&mut self.slots);
self.free.clear();
self.remembered.clear();
let mut new_slots: Vec<Slot<T>> = Vec::new();
let mut map: Vec<(Handle, Handle)> = Vec::new();
for (i, slot) in old_slots.into_iter().enumerate() {
if let Slot::Occupied {
generation,
age,
value,
} = slot
{
let old = Handle {
index: i as u32,
generation,
};
if keep.contains(&old) {
let new = Handle {
index: new_slots.len() as u32,
generation: new_gen,
};
new_slots.push(Slot::Occupied {
generation: new_gen,
age,
value,
});
map.push((old, new));
}
}
}
self.slots = new_slots;
self.live = self.slots.len();
map
}
pub fn free(&mut self, handle: Handle) -> Option<T> {
let slot = self.slots.get_mut(handle.index as usize)?;
match slot {
Slot::Occupied { generation, .. } if *generation == handle.generation => {
let next_gen = generation.wrapping_add(1);
let Slot::Occupied { value, .. } = core::mem::replace(
slot,
Slot::Free {
generation: next_gen,
},
) else {
unreachable!()
};
self.free.push(handle.index);
self.live -= 1;
Some(value)
}
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_get_and_len() {
let mut h: Heap<i32> = Heap::new();
assert!(h.is_empty());
let a = h.alloc(10);
let b = h.alloc(20);
assert_eq!(h.len(), 2);
assert_eq!(h.get(a), Some(&10));
assert_eq!(h.get(b), Some(&20));
*h.get_mut(a).unwrap() = 11;
assert_eq!(h.get(a), Some(&11));
}
#[test]
fn free_returns_value_and_invalidates_handle() {
let mut h: Heap<&str> = Heap::new();
let a = h.alloc("hello");
assert!(h.is_live(a));
assert_eq!(h.free(a), Some("hello"));
assert!(!h.is_live(a));
assert_eq!(h.get(a), None);
assert_eq!(h.len(), 0);
assert_eq!(h.free(a), None);
}
#[test]
fn reused_slot_makes_old_handle_stale() {
let mut h: Heap<i32> = Heap::new();
let a = h.alloc(1);
h.free(a);
let b = h.alloc(2);
assert_eq!(b.to_raw() & 0xffff_ffff, a.to_raw() & 0xffff_ffff); assert_ne!(a, b); assert_eq!(h.get(b), Some(&2));
assert_eq!(h.get(a), None); }
#[test]
fn free_list_reuses_before_growing() {
let mut h: Heap<i32> = Heap::new();
let a = h.alloc(1);
let b = h.alloc(2);
let c = h.alloc(3);
h.free(b);
let d = h.alloc(4);
assert_eq!(h.slots.len(), 3);
assert_eq!(h.len(), 3);
assert_eq!(h.get(a), Some(&1));
assert_eq!(h.get(c), Some(&3));
assert_eq!(h.get(d), Some(&4));
}
#[test]
fn handle_raw_round_trips() {
for &(index, generation) in &[(0u32, 0u16), (1, 0), (42, 7), (0xffff_ffff, 0xffff)] {
let raw = Handle { index, generation }.to_raw();
assert!(raw <= 0x0000_ffff_ffff_ffff, "fits in 48 bits");
let back = Handle::from_raw(raw);
assert_eq!(back, Handle { index, generation });
}
}
#[test]
fn handles_index_into_the_heap_via_raw() {
let mut h: Heap<i32> = Heap::new();
let handle = h.alloc(99);
let raw = handle.to_raw();
let reconstructed = Handle::from_raw(raw);
assert_eq!(h.get(reconstructed), Some(&99));
}
}