use serde::{Deserialize, Serialize};
use super::{Arena, Slot};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArenaSeed<T> {
pub slots: Vec<(u32, Option<T>)>,
pub free: Vec<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum ArenaSeedError {
#[error("too many arena slots")]
TooManySlots,
#[error("free slot outside arena")]
FreeOutOfBounds,
#[error("invalid or repeated free slot")]
InvalidFreeSlot,
#[error("missing free slot")]
MissingFreeSlot,
}
impl<K, T> Arena<K, T> {
pub fn seed_with<U>(&self, mut f: impl FnMut(&T) -> U) -> ArenaSeed<U> {
ArenaSeed {
slots: self
.slots
.iter()
.map(|slot| (slot.generation, slot.value.as_ref().map(&mut f)))
.collect(),
free: self.free.clone(),
}
}
pub fn from_seed(seed: ArenaSeed<T>) -> Result<Self, ArenaSeedError> {
if seed.slots.len() > u32::MAX as usize {
return Err(ArenaSeedError::TooManySlots);
}
let mut free = vec![false; seed.slots.len()];
for &index in &seed.free {
let Some(seen) = free.get_mut(index as usize) else {
return Err(ArenaSeedError::FreeOutOfBounds);
};
if *seen || seed.slots[index as usize].1.is_some() {
return Err(ArenaSeedError::InvalidFreeSlot);
}
*seen = true;
}
if seed
.slots
.iter()
.enumerate()
.any(|(index, (_, value))| value.is_none() != free[index])
{
return Err(ArenaSeedError::MissingFreeSlot);
}
Ok(Self {
slots: seed
.slots
.into_iter()
.map(|(generation, value)| Slot { generation, value })
.collect(),
free: seed.free,
_kind: std::marker::PhantomData,
})
}
}
#[cfg(test)]
mod tests {
use super::super::{Arena, DocumentKind};
use super::{ArenaSeed, ArenaSeedError};
#[test]
fn seed_round_trips_ids_free_slots_and_generations() {
let mut arena: Arena<DocumentKind, String> = Arena::default();
let a = arena.insert("a".into());
let b = arena.insert("b".into());
arena.remove(a);
let seed = arena.seed_with(|value| value.clone());
let mut rebuilt = Arena::from_seed(seed).unwrap();
assert_eq!(rebuilt.get(a), None, "freed id stays dead");
assert_eq!(rebuilt.get(b).map(String::as_str), Some("b"));
let c = rebuilt.insert("c".into());
assert_eq!(c.index(), a.index(), "free slot is reused");
assert_eq!(c.generation(), a.generation() + 1);
}
#[test]
fn corrupt_seeds_are_rejected_not_repaired() {
let mut arena: Arena<DocumentKind, ()> = Arena::default();
let a = arena.insert(());
let seed = arena.seed_with(|&()| ());
let ArenaSeed { slots, .. } = seed;
let corrupt = ArenaSeed {
slots: slots.clone(),
free: vec![a.index() as u32],
};
assert!(matches!(
Arena::<DocumentKind, _>::from_seed(corrupt),
Err(ArenaSeedError::InvalidFreeSlot)
));
let corrupt = ArenaSeed {
slots: vec![(0, None)],
free: Vec::new(),
};
assert!(matches!(
Arena::<DocumentKind, ()>::from_seed(corrupt),
Err(ArenaSeedError::MissingFreeSlot)
));
let corrupt = ArenaSeed {
slots,
free: vec![9],
};
assert!(matches!(
Arena::<DocumentKind, _>::from_seed(corrupt),
Err(ArenaSeedError::FreeOutOfBounds)
));
}
}