use std::fmt;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use super::Color;
const COLOR_FREE: u8 = 0;
const COLOR_WHITE0: u8 = 1;
const COLOR_WHITE1: u8 = 2;
const COLOR_GRAY: u8 = 3;
const COLOR_BLACK: u8 = 4;
#[inline]
fn color_to_byte(c: Color) -> u8 {
match c {
Color::White0 => COLOR_WHITE0,
Color::White1 => COLOR_WHITE1,
Color::Gray => COLOR_GRAY,
Color::Black => COLOR_BLACK,
}
}
#[inline]
fn byte_to_color(b: u8) -> Option<Color> {
match b {
COLOR_WHITE0 => Some(Color::White0),
COLOR_WHITE1 => Some(Color::White1),
COLOR_GRAY => Some(Color::Gray),
COLOR_BLACK => Some(Color::Black),
_ => None,
}
}
pub struct GcRef<T> {
index: u32,
generation: u32,
_marker: PhantomData<T>,
}
#[cfg(feature = "send")]
#[allow(unsafe_code)]
unsafe impl<T> Send for GcRef<T> {}
#[cfg(feature = "send")]
#[allow(unsafe_code)]
unsafe impl<T> Sync for GcRef<T> {}
impl<T> Clone for GcRef<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for GcRef<T> {}
impl<T> PartialEq for GcRef<T> {
fn eq(&self, other: &Self) -> bool {
self.index == other.index && self.generation == other.generation
}
}
impl<T> Eq for GcRef<T> {}
impl<T> Hash for GcRef<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.index.hash(state);
self.generation.hash(state);
}
}
impl<T> fmt::Debug for GcRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GcRef({}, gen={})", self.index, self.generation)
}
}
impl<T> GcRef<T> {
#[inline]
pub fn index(self) -> u32 {
self.index
}
#[inline]
pub fn generation(self) -> u32 {
self.generation
}
}
enum EntryState<T> {
Occupied { value: T },
Free { next_free: Option<u32> },
}
struct Entry<T> {
generation: u32,
state: EntryState<T>,
}
pub struct Arena<T> {
entries: Vec<Entry<T>>,
colors: Vec<u8>,
free_head: Option<u32>,
len: u32,
}
impl<T> Arena<T> {
pub fn new() -> Self {
Self {
entries: Vec::new(),
colors: Vec::new(),
free_head: None,
len: 0,
}
}
#[inline]
pub fn len(&self) -> u32 {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn capacity(&self) -> u32 {
self.entries.len() as u32
}
pub fn alloc(&mut self, value: T, color: Color) -> GcRef<T> {
self.len += 1;
let color_byte = color_to_byte(color);
if let Some(free_idx) = self.free_head {
let entry = &mut self.entries[free_idx as usize];
if let EntryState::Free { next_free } = entry.state {
self.free_head = next_free;
}
let generation = entry.generation;
entry.state = EntryState::Occupied { value };
self.colors[free_idx as usize] = color_byte;
GcRef {
index: free_idx,
generation,
_marker: PhantomData,
}
} else {
let index = self.entries.len() as u32;
self.entries.push(Entry {
generation: 0,
state: EntryState::Occupied { value },
});
self.colors.push(color_byte);
GcRef {
index,
generation: 0,
_marker: PhantomData,
}
}
}
#[inline]
pub fn get(&self, r: GcRef<T>) -> Option<&T> {
let entry = self.entries.get(r.index as usize)?;
if entry.generation != r.generation {
return None;
}
match &entry.state {
EntryState::Occupied { value } => Some(value),
EntryState::Free { .. } => None,
}
}
#[inline]
pub fn get_mut(&mut self, r: GcRef<T>) -> Option<&mut T> {
let entry = self.entries.get_mut(r.index as usize)?;
if entry.generation != r.generation {
return None;
}
match &mut entry.state {
EntryState::Occupied { value } => Some(value),
EntryState::Free { .. } => None,
}
}
pub fn free(&mut self, r: GcRef<T>) -> Option<T> {
let entry = self.entries.get_mut(r.index as usize)?;
if entry.generation != r.generation {
return None;
}
let old_state = std::mem::replace(
&mut entry.state,
EntryState::Free {
next_free: self.free_head,
},
);
entry.generation = entry.generation.wrapping_add(1);
self.free_head = Some(r.index);
self.len -= 1;
self.colors[r.index as usize] = COLOR_FREE;
match old_state {
EntryState::Occupied { value } => Some(value),
EntryState::Free { .. } => None,
}
}
#[inline]
pub fn is_valid(&self, r: GcRef<T>) -> bool {
self.get(r).is_some()
}
#[inline]
pub fn color(&self, r: GcRef<T>) -> Option<Color> {
let entry = self.entries.get(r.index as usize)?;
if entry.generation != r.generation {
return None;
}
byte_to_color(self.colors[r.index as usize])
}
#[inline]
pub fn set_color(&mut self, r: GcRef<T>, new_color: Color) -> bool {
let Some(entry) = self.entries.get(r.index as usize) else {
return false;
};
if entry.generation != r.generation {
return false;
}
if self.colors[r.index as usize] == COLOR_FREE {
return false;
}
self.colors[r.index as usize] = color_to_byte(new_color);
true
}
pub fn iter(&self) -> ArenaIter<'_, T> {
ArenaIter {
entries: &self.entries,
colors: &self.colors,
pos: 0,
}
}
pub fn reset_colors(&mut self, color: Color) {
let byte = color_to_byte(color);
for c in &mut self.colors {
if *c != COLOR_FREE {
*c = byte;
}
}
}
pub fn sweep(&mut self, dead_color: Color, new_color: Color) -> u32 {
let dead_byte = color_to_byte(dead_color);
let new_byte = color_to_byte(new_color);
let mut local_free_head = self.free_head;
let mut local_len = self.len;
let mut freed = 0u32;
for (i, (entry, color_byte)) in self
.entries
.iter_mut()
.zip(self.colors.iter_mut())
.enumerate()
{
if *color_byte == COLOR_FREE {
continue;
}
if *color_byte == dead_byte {
entry.state = EntryState::Free {
next_free: local_free_head,
};
entry.generation = entry.generation.wrapping_add(1);
local_free_head = Some(i as u32);
local_len -= 1;
*color_byte = COLOR_FREE;
freed += 1;
} else {
*color_byte = new_byte;
}
}
self.free_head = local_free_head;
self.len = local_len;
freed
}
#[inline]
pub fn sweep_partial(
&mut self,
dead_color: Color,
new_color: Color,
start: u32,
max_count: u32,
) -> (u32, u32, bool) {
let start_usize = start as usize;
let total = self.entries.len() as u32;
let dead_byte = color_to_byte(dead_color);
let new_byte = color_to_byte(new_color);
let mut local_free_head = self.free_head;
let mut local_len = self.len;
let mut freed = 0u32;
let mut occupied_seen = 0u32;
let mut scanned = 0u32;
let entries = &mut self.entries[start_usize..];
let colors = &mut self.colors[start_usize..];
for (entry, color_byte) in entries.iter_mut().zip(colors.iter_mut()) {
if occupied_seen >= max_count {
break;
}
let abs_index = start + scanned;
scanned += 1;
if *color_byte == COLOR_FREE {
continue;
}
if *color_byte == dead_byte {
entry.state = EntryState::Free {
next_free: local_free_head,
};
entry.generation = entry.generation.wrapping_add(1);
local_free_head = Some(abs_index);
local_len -= 1;
*color_byte = COLOR_FREE;
freed += 1;
} else {
*color_byte = new_byte;
}
occupied_seen += 1;
}
self.free_head = local_free_head;
self.len = local_len;
let next_pos = start + scanned;
let is_done = next_pos >= total;
(freed, next_pos, is_done)
}
}
impl<T> Default for Arena<T> {
fn default() -> Self {
Self::new()
}
}
impl<'a, T> IntoIterator for &'a Arena<T> {
type Item = (GcRef<T>, &'a T, Color);
type IntoIter = ArenaIter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct ArenaIter<'a, T> {
entries: &'a [Entry<T>],
colors: &'a [u8],
pos: u32,
}
impl<'a, T> Iterator for ArenaIter<'a, T> {
type Item = (GcRef<T>, &'a T, Color);
fn next(&mut self) -> Option<Self::Item> {
while (self.pos as usize) < self.entries.len() {
let idx = self.pos;
self.pos += 1;
let color_byte = self.colors[idx as usize];
if let Some(color) = byte_to_color(color_byte) {
let entry = &self.entries[idx as usize];
if let EntryState::Occupied { value } = &entry.state {
return Some((
GcRef {
index: idx,
generation: entry.generation,
_marker: PhantomData,
},
value,
color,
));
}
}
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.entries.len() - self.pos as usize;
(0, Some(remaining))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_and_get() {
let mut arena: Arena<i32> = Arena::new();
let r = arena.alloc(42, Color::White0);
assert_eq!(arena.get(r), Some(&42));
assert_eq!(arena.len(), 1);
assert!(!arena.is_empty());
}
#[test]
fn alloc_and_get_mut() {
let mut arena: Arena<i32> = Arena::new();
let r = arena.alloc(42, Color::White0);
if let Some(val) = arena.get_mut(r) {
*val = 100;
}
assert_eq!(arena.get(r), Some(&100));
}
#[test]
fn free_returns_value() {
let mut arena: Arena<i32> = Arena::new();
let r = arena.alloc(42, Color::White0);
let val = arena.free(r);
assert_eq!(val, Some(42));
assert_eq!(arena.len(), 0);
assert!(arena.is_empty());
}
#[test]
fn stale_ref_after_free() {
let mut arena: Arena<i32> = Arena::new();
let r = arena.alloc(42, Color::White0);
arena.free(r);
assert_eq!(arena.get(r), None);
assert_eq!(arena.get_mut(r), None);
assert!(!arena.is_valid(r));
}
#[test]
fn generation_prevents_aba() {
let mut arena: Arena<i32> = Arena::new();
let r1 = arena.alloc(42, Color::White0);
arena.free(r1);
let r2 = arena.alloc(99, Color::White0);
assert_eq!(r1.index(), r2.index());
assert_ne!(r1.generation(), r2.generation());
assert_eq!(arena.get(r1), None);
assert_eq!(arena.get(r2), Some(&99));
}
#[test]
fn free_list_reuse() {
let mut arena: Arena<i32> = Arena::new();
let r1 = arena.alloc(1, Color::White0);
let r2 = arena.alloc(2, Color::White0);
let idx1 = r1.index();
arena.free(r1);
let r3 = arena.alloc(3, Color::White0);
assert_eq!(r3.index(), idx1);
assert_eq!(arena.get(r3), Some(&3));
assert_eq!(arena.get(r2), Some(&2));
assert_eq!(arena.capacity(), 2); }
#[test]
fn color_get_and_set() {
let mut arena: Arena<i32> = Arena::new();
let r = arena.alloc(42, Color::White0);
assert_eq!(arena.color(r), Some(Color::White0));
assert!(arena.set_color(r, Color::Gray));
assert_eq!(arena.color(r), Some(Color::Gray));
assert!(arena.set_color(r, Color::Black));
assert_eq!(arena.color(r), Some(Color::Black));
assert!(arena.set_color(r, Color::White1));
assert_eq!(arena.color(r), Some(Color::White1));
}
#[test]
fn color_stale_ref() {
let mut arena: Arena<i32> = Arena::new();
let r = arena.alloc(42, Color::White0);
arena.free(r);
assert_eq!(arena.color(r), None);
assert!(!arena.set_color(r, Color::Gray));
}
#[test]
fn double_free_returns_none() {
let mut arena: Arena<i32> = Arena::new();
let r = arena.alloc(42, Color::White0);
assert_eq!(arena.free(r), Some(42));
assert_eq!(arena.free(r), None);
}
#[test]
fn multiple_alloc_free_cycles() {
let mut arena: Arena<i32> = Arena::new();
let mut refs = Vec::new();
for i in 0..100 {
refs.push(arena.alloc(i, Color::White0));
}
assert_eq!(arena.len(), 100);
assert_eq!(arena.capacity(), 100);
for i in (0..100).step_by(2) {
arena.free(refs[i]);
}
assert_eq!(arena.len(), 50);
for i in 100..150 {
arena.alloc(i, Color::White0);
}
assert_eq!(arena.len(), 100);
assert_eq!(arena.capacity(), 100);
}
#[test]
fn empty_arena() {
let arena: Arena<i32> = Arena::new();
assert!(arena.is_empty());
assert_eq!(arena.len(), 0);
assert_eq!(arena.capacity(), 0);
}
#[test]
fn default_creates_empty() {
let arena: Arena<i32> = Arena::default();
assert!(arena.is_empty());
}
#[test]
fn iter_yields_occupied_only() {
let mut arena: Arena<i32> = Arena::new();
let r1 = arena.alloc(10, Color::White0);
let _r2 = arena.alloc(20, Color::Gray);
let r3 = arena.alloc(30, Color::Black);
arena.free(r1);
let items: Vec<_> = arena.iter().collect();
assert_eq!(items.len(), 2);
assert_eq!(*items[0].1, 20);
assert_eq!(items[0].2, Color::Gray);
assert_eq!(*items[1].1, 30);
assert_eq!(items[1].2, Color::Black);
assert!(arena.is_valid(items[1].0));
assert_eq!(items[1].0.index(), r3.index());
}
#[test]
fn reset_colors() {
let mut arena: Arena<i32> = Arena::new();
let r1 = arena.alloc(1, Color::White0);
let r2 = arena.alloc(2, Color::Black);
let r3 = arena.alloc(3, Color::Gray);
arena.reset_colors(Color::White1);
assert_eq!(arena.color(r1), Some(Color::White1));
assert_eq!(arena.color(r2), Some(Color::White1));
assert_eq!(arena.color(r3), Some(Color::White1));
}
#[test]
fn gcref_debug_format() {
let r: GcRef<i32> = GcRef {
index: 5,
generation: 3,
_marker: PhantomData,
};
let debug = format!("{r:?}");
assert!(debug.contains('5'));
assert!(debug.contains('3'));
}
#[test]
fn gcref_equality_and_hash() {
use std::collections::HashSet;
let mut arena: Arena<i32> = Arena::new();
let r1 = arena.alloc(1, Color::White0);
let r2 = arena.alloc(2, Color::White0);
assert_ne!(r1, r2);
let r1_copy = r1;
assert_eq!(r1, r1_copy);
let mut set = HashSet::new();
set.insert(r1);
set.insert(r2);
set.insert(r1_copy); assert_eq!(set.len(), 2);
}
#[test]
fn generation_wrapping() {
let mut arena: Arena<i32> = Arena::new();
let r = arena.alloc(1, Color::White0);
let mut last_ref = r;
for i in 0..10 {
arena.free(last_ref);
last_ref = arena.alloc(i, Color::White0);
}
assert_eq!(arena.get(r), None);
assert_eq!(arena.get(last_ref), Some(&9));
}
}