mod seed;
pub use seed::{ArenaSeed, ArenaSeedError};
use vstd::prelude::*;
verus! {
pub open spec fn generation_advances(current: int, next: int) -> bool {
next == current + 1
}
pub fn next_generation(current: u32) -> (next: Option<u32>)
ensures
next.is_some() == (current < u32::MAX),
next.is_some() ==> generation_advances(current as int, next.unwrap() as int),
next.is_some() ==> next.unwrap() != current,
{
if current == u32::MAX {
None
} else {
Some(current + 1)
}
}
pub fn index_for_len(len: usize) -> (index: Option<u32>)
ensures
index.is_some() == (len <= u32::MAX as usize),
index.is_some() ==> index.unwrap() as usize == len,
{
if len > u32::MAX as usize {
None
} else {
Some(len as u32)
}
}
proof fn reused_slot_never_aliases(current: int, next: int)
requires
generation_advances(current, next),
ensures
next != current,
next > current,
{
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
#[serde(bound = "")]
pub struct Id<K> {
#[serde(rename = "slot", alias = "index")]
index: u32,
generation: u32,
#[serde(skip)]
_kind: std::marker::PhantomData<K>,
}
impl<K> Id<K> {
pub fn index(self) -> usize {
self.index as usize
}
pub fn generation(self) -> u32 {
self.generation
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DocumentKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ViewKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PaneKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WorkspaceKind;
pub type DocumentId = Id<DocumentKind>;
pub type ViewId = Id<ViewKind>;
pub type PaneId = Id<PaneKind>;
pub type WorkspaceId = Id<WorkspaceKind>;
pub struct Arena<K, T> {
slots: Vec<Slot<T>>,
free: Vec<u32>,
_kind: std::marker::PhantomData<K>,
}
#[derive(Debug)]
struct Slot<T> {
generation: u32,
value: Option<T>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("arena identity space exhausted")]
pub struct ArenaExhausted;
impl<K, T> Default for Arena<K, T> {
fn default() -> Self {
Self {
slots: Vec::new(),
free: Vec::new(),
_kind: std::marker::PhantomData,
}
}
}
impl<K, T> Arena<K, T> {
pub fn try_insert(&mut self, value: T) -> Result<Id<K>, ArenaExhausted> {
while let Some(index) = self.free.pop() {
let slot = &mut self.slots[index as usize];
let Some(generation) = next_generation(slot.generation) else {
continue; };
slot.generation = generation;
slot.value = Some(value);
return Ok(Id {
index,
generation,
_kind: std::marker::PhantomData,
});
}
let Some(index) = index_for_len(self.slots.len()) else {
return Err(ArenaExhausted);
};
self.slots.push(Slot {
generation: 0,
value: Some(value),
});
Ok(Id {
index,
generation: 0,
_kind: std::marker::PhantomData,
})
}
pub fn insert_capacity(&self) -> u64 {
let reusable = self
.free
.iter()
.filter(|&&index| self.slots[index as usize].generation < u32::MAX)
.count() as u64;
reusable + (u64::from(u32::MAX) - self.slots.len() as u64)
}
pub fn get(&self, id: Id<K>) -> Option<&T> {
self.slots
.get(id.index as usize)
.filter(|s| s.generation == id.generation)
.and_then(|s| s.value.as_ref())
}
pub fn get_mut(&mut self, id: Id<K>) -> Option<&mut T> {
self.slots
.get_mut(id.index as usize)
.filter(|s| s.generation == id.generation)
.and_then(|s| s.value.as_mut())
}
pub fn remove(&mut self, id: Id<K>) -> Option<T> {
let slot = self.slots.get_mut(id.index as usize)?;
if slot.generation != id.generation {
return None;
}
let value = slot.value.take()?;
self.free.push(id.index);
Some(value)
}
pub fn iter(&self) -> impl Iterator<Item = (Id<K>, &T)> {
self.slots.iter().enumerate().filter_map(|(i, s)| {
s.value.as_ref().map(|v| {
(
Id {
index: i as u32,
generation: s.generation,
_kind: std::marker::PhantomData,
},
v,
)
})
})
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.slots.iter_mut().filter_map(|slot| slot.value.as_mut())
}
pub fn len(&self) -> usize {
self.slots.iter().filter(|s| s.value.is_some()).count()
}
pub fn clear(&mut self) {
let live: Vec<u32> = self
.slots
.iter()
.enumerate()
.filter(|(_, s)| s.value.is_some())
.map(|(i, _)| i as u32)
.collect();
for s in &mut self.slots {
s.value = None;
}
self.free.extend(live);
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
macro_rules! coordinate {
($name:ident, $unit:literal) => {
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Default,
serde::Serialize,
serde::Deserialize,
)]
#[repr(transparent)]
#[serde(transparent)]
pub struct $name(usize);
impl $name {
#[inline]
pub fn new(v: usize) -> Self {
Self(v)
}
#[inline]
pub fn get(self) -> usize {
self.0
}
#[inline]
pub fn saturating_sub(self, n: usize) -> Self {
Self(self.0.saturating_sub(n))
}
}
impl std::ops::AddAssign<usize> for $name {
#[inline]
fn add_assign(&mut self, n: usize) {
self.0 += n;
}
}
impl std::ops::SubAssign<usize> for $name {
#[inline]
fn sub_assign(&mut self, n: usize) {
self.0 -= n;
}
}
impl PartialEq<usize> for $name {
#[inline]
fn eq(&self, other: &usize) -> bool {
self.0 == *other
}
}
impl PartialOrd<usize> for $name {
#[inline]
fn partial_cmp(&self, other: &usize) -> Option<std::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
impl std::ops::Add<usize> for $name {
type Output = $name;
#[inline]
fn add(self, n: usize) -> $name {
$name(self.0 + n)
}
}
impl std::ops::Sub<usize> for $name {
type Output = $name;
#[inline]
fn sub(self, n: usize) -> $name {
$name(self.0 - n)
}
}
impl std::ops::Sub<$name> for $name {
type Output = usize; #[inline]
fn sub(self, other: $name) -> usize {
self.0 - other.0
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.0, $unit)
}
}
impl From<$name> for usize {
#[inline]
fn from(v: $name) -> usize {
v.0
}
}
impl From<usize> for $name {
#[inline]
fn from(v: usize) -> $name {
$name(v)
}
}
};
}
coordinate!(ByteOffset, "B");
coordinate!(LineIndex, "L");
coordinate!(ByteColumn, "col:B");
coordinate!(Utf16Column, "col:u16");
coordinate!(DisplayColumn, "col:dsp");
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Default,
serde::Serialize,
serde::Deserialize,
)]
#[serde(transparent)]
pub struct BufferRevision(u64);
impl BufferRevision {
pub const fn new(value: u64) -> Self {
Self(value)
}
pub const fn get(self) -> u64 {
self.0
}
pub fn checked_next(self) -> Option<Self> {
self.0.checked_add(1).map(Self)
}
}
impl std::fmt::Display for BufferRevision {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(formatter)
}
}
impl From<u64> for BufferRevision {
fn from(value: u64) -> Self {
Self(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stale_ids_fail_lookup() {
let mut a: Arena<DocumentKind, String> = Arena::default();
let one = a.try_insert("one".into()).unwrap();
let two = a.try_insert("two".into()).unwrap();
assert_eq!(a.get(one).map(String::as_str), Some("one"));
a.remove(one);
assert_eq!(a.get(one), None, "removed");
let three = a.try_insert("three".into()).unwrap(); assert_eq!(a.get(one), None, "stale generation must not resolve");
assert_eq!(a.get(three).map(String::as_str), Some("three"));
assert_eq!(a.get(two).map(String::as_str), Some("two"));
assert_eq!(a.len(), 2);
}
#[test]
fn generation_wrap_retires_the_slot() {
let mut a: Arena<DocumentKind, String> = Arena::from_seed(ArenaSeed {
slots: vec![(u32::MAX - 1, None)],
free: vec![0],
})
.unwrap();
assert_eq!(a.insert_capacity(), u64::from(u32::MAX));
let last = a.try_insert("last".into()).unwrap();
assert_eq!((last.index(), last.generation()), (0, u32::MAX));
a.remove(last);
let fresh = a.try_insert("fresh".into()).unwrap();
assert_eq!((fresh.index(), fresh.generation()), (1, 0));
assert_eq!(a.get(last), None, "a wrapped generation would alias");
assert_eq!(a.get(fresh).map(String::as_str), Some("fresh"));
a.remove(fresh);
let again = a.try_insert("again".into()).unwrap();
assert_eq!((again.index(), again.generation()), (1, 1));
assert_eq!(a.insert_capacity(), u64::from(u32::MAX) - 2);
}
#[test]
fn retired_slots_stay_dead_across_seeds() {
let mut a: Arena<DocumentKind, String> = Arena::from_seed(ArenaSeed {
slots: vec![(u32::MAX, None)],
free: vec![0],
})
.unwrap();
let live = a.try_insert("live".into()).unwrap();
assert_eq!(live.index(), 1, "the maxed slot was retired on reuse");
let seed = a.seed_with(|value| value.clone());
let mut rebuilt = Arena::from_seed(seed).unwrap();
let after = rebuilt.try_insert("after".into()).unwrap();
assert_eq!(after.index(), 2, "retirement is part of the seed");
assert_eq!(rebuilt.get(live).map(String::as_str), Some("live"));
}
}