Skip to main content

ogeom_core/
arena.rs

1//! Typed generational arenas.
2//!
3//! Topology lives in arenas rather than behind reference counting; see
4//! `docs/DATA_MODEL.md` ยง11. Keys are small, `Copy`, comparable and hashable,
5//! which is what makes stable entity identity possible at all.
6//!
7//! Slots are generational: freeing a slot bumps its generation, so a stale key
8//! fails to resolve instead of silently aliasing whatever was allocated there
9//! next. That failure mode is worth eight bytes per key in a kernel where the
10//! alternative is a wrong answer rather than a crash.
11//!
12//! # Keys are scoped to the arena that issued them
13//!
14//! A generation catches a key that has outlived its slot. It cannot catch a key
15//! from a *different* arena, because index 3 generation 0 means something in
16//! every arena, so a handle from one document resolved against another comes
17//! back with whatever sits at that index, and answers confidently about the
18//! wrong entity. Nothing about the result says so.
19//!
20//! Every arena therefore takes an identifier the first time something is put
21//! in it, every key it issues carries that identifier, and every lookup
22//! compares it. A foreign key resolves to `None`, exactly as a stale one does.
23//! The cost is four bytes per key and one comparison per lookup, against a
24//! whole class of silent wrong answers.
25//!
26//! Cloning an arena keeps its identifier, because a clone is the same document
27//! and handles into it should keep working. Identifiers are per-process and are
28//! never serialized: a document read back from a file is a new arena with a new
29//! identifier, and the reader re-stamps the handles it read.
30
31use core::fmt;
32use core::hash::{Hash, Hasher};
33use core::marker::PhantomData;
34use core::sync::atomic::{AtomicU32, Ordering};
35
36/// Hands out arena identifiers.
37///
38/// Starts at one so that zero can mean *unscoped*: the state of a key built
39/// by a deserializer that does not yet know which arena it will belong to.
40static NEXT_SCOPE: AtomicU32 = AtomicU32::new(1);
41
42/// An identifier that no arena in this process shares.
43fn next_scope() -> u32 {
44    NEXT_SCOPE.fetch_add(1, Ordering::Relaxed)
45}
46
47/// The identifier a key carries before it has been bound to an arena.
48///
49/// A key with this scope resolves in no arena at all. That is deliberate: a
50/// handle read from a file is meaningless until the reader says which document
51/// it belongs to.
52pub const UNSCOPED: u32 = 0;
53
54/// A handle into an [`Arena<T>`].
55///
56/// Phantom-typed, so a `Key<Face>` cannot be used to index an `Arena<Edge>`.
57/// The marker is `fn() -> T` so the key stays `Copy`, `Send` and `Sync`
58/// regardless of `T`.
59pub struct Key<T> {
60    index: u32,
61    generation: u32,
62    scope: u32,
63    marker: PhantomData<fn() -> T>,
64}
65
66impl<T> Key<T> {
67    const fn new(index: u32, generation: u32, scope: u32) -> Self {
68        Self {
69            index,
70            generation,
71            scope,
72            marker: PhantomData,
73        }
74    }
75
76    /// Which arena issued this key.
77    ///
78    /// [`UNSCOPED`] for a key that has not been bound to one.
79    #[must_use]
80    pub const fn scope(self) -> u32 {
81        self.scope
82    }
83
84    /// This key, bound to the arena with the given identifier.
85    ///
86    /// For a deserializer, which rebuilds handles before it has an arena to
87    /// bind them to. Nothing else should need it: a key that came from an arena
88    /// already names the right one, and moving a key between arenas is the
89    /// mistake the scope exists to catch.
90    #[must_use]
91    pub const fn with_scope(self, scope: u32) -> Self {
92        Self { scope, ..self }
93    }
94
95    /// Position of the slot this key refers to.
96    #[must_use]
97    pub const fn index(self) -> u32 {
98        self.index
99    }
100
101    /// Generation stamp, used to detect a key outliving its slot.
102    #[must_use]
103    pub const fn generation(self) -> u32 {
104        self.generation
105    }
106
107    /// A key naming a given slot, for reading a document back from a file.
108    ///
109    /// Deliberately narrow. Forging a handle is precisely what generations
110    /// exist to prevent, and [`Arena::insert`] is what issues one within a
111    /// process. But a file records the handles a document was written with, and
112    /// a reader that could not rebuild them would have to renumber everything,
113    /// which is to say, hand back a different document.
114    ///
115    /// A key made this way is not trusted: it resolves through [`Arena::get`]
116    /// like any other, so a stale or out-of-range one comes back `None` rather
117    /// than aliasing whatever sits at that index.
118    #[must_use]
119    pub const fn from_parts(index: u32, generation: u32) -> Self {
120        Self::new(index, generation, UNSCOPED)
121    }
122}
123
124// Derived impls would demand `T: Clone` and friends; the key holds no `T`.
125impl<T> Clone for Key<T> {
126    fn clone(&self) -> Self {
127        *self
128    }
129}
130impl<T> Copy for Key<T> {}
131impl<T> PartialEq for Key<T> {
132    fn eq(&self, other: &Self) -> bool {
133        self.index == other.index
134            && self.generation == other.generation
135            && self.scope == other.scope
136    }
137}
138impl<T> Eq for Key<T> {}
139impl<T> Hash for Key<T> {
140    fn hash<H: Hasher>(&self, state: &mut H) {
141        self.index.hash(state);
142        self.generation.hash(state);
143        self.scope.hash(state);
144    }
145}
146impl<T> PartialOrd for Key<T> {
147    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
148        Some(self.cmp(other))
149    }
150}
151impl<T> Ord for Key<T> {
152    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
153        (self.scope, self.index, self.generation).cmp(&(other.scope, other.index, other.generation))
154    }
155}
156impl<T> fmt::Debug for Key<T> {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        write!(f, "Key({}v{}@{})", self.index, self.generation, self.scope)
159    }
160}
161
162#[derive(Debug, Clone)]
163enum Slot<T> {
164    Occupied {
165        generation: u32,
166        value: T,
167    },
168    Vacant {
169        generation: u32,
170        next_free: Option<u32>,
171    },
172}
173
174/// A generational arena of `T`.
175#[derive(Debug, Clone)]
176pub struct Arena<T> {
177    slots: Vec<Slot<T>>,
178    free_head: Option<u32>,
179    len: usize,
180    /// Which arena this is. [`UNSCOPED`] until the first insert, because
181    /// `new` is `const` and a counter cannot be read from one, and an arena
182    /// with nothing in it has issued no keys to disagree with.
183    scope: u32,
184}
185
186impl<T> Default for Arena<T> {
187    fn default() -> Self {
188        Self::new()
189    }
190}
191
192impl<T> Arena<T> {
193    /// An empty arena.
194    #[must_use]
195    pub const fn new() -> Self {
196        Self {
197            slots: Vec::new(),
198            free_head: None,
199            len: 0,
200            scope: UNSCOPED,
201        }
202    }
203
204    /// An empty arena with room for `capacity` entries.
205    #[must_use]
206    pub fn with_capacity(capacity: usize) -> Self {
207        Self {
208            slots: Vec::with_capacity(capacity),
209            free_head: None,
210            len: 0,
211            scope: UNSCOPED,
212        }
213    }
214
215    /// Number of live entries.
216    #[must_use]
217    pub const fn len(&self) -> usize {
218        self.len
219    }
220
221    /// Whether there are no live entries.
222    #[must_use]
223    pub const fn is_empty(&self) -> bool {
224        self.len == 0
225    }
226
227    /// Which arena this is, for stamping keys that were rebuilt elsewhere.
228    ///
229    /// [`UNSCOPED`] until the first insert.
230    #[must_use]
231    pub const fn scope(&self) -> u32 {
232        self.scope
233    }
234
235    /// Whether a key was issued by this arena.
236    ///
237    /// Distinct from [`Arena::contains`], which also asks whether the slot is
238    /// still live. This asks only whether the key belongs here at all, which is
239    /// the question a caller wants when reporting *why* a lookup failed.
240    #[must_use]
241    pub const fn issued(&self, key: Key<T>) -> bool {
242        key.scope == self.scope
243    }
244
245    /// Insert a value, returning its key.
246    ///
247    /// The first insert is what fixes the arena's identity, since [`Arena::new`]
248    /// is `const` and cannot read a counter. That is safe because an arena with
249    /// nothing in it has issued no keys to disagree with.
250    ///
251    /// # Panics
252    ///
253    /// If the arena exceeds `u32::MAX` slots. A single model reaching four
254    /// billion topological entities is a bug elsewhere, not a case to handle.
255    #[allow(clippy::expect_used, reason = "documented panic; see # Panics")]
256    pub fn insert(&mut self, value: T) -> Key<T> {
257        if self.scope == UNSCOPED {
258            self.scope = next_scope();
259        }
260        self.len += 1;
261        match self.free_head {
262            Some(index) => {
263                let idx = index as usize;
264                let (generation, next_free) = match &self.slots[idx] {
265                    Slot::Vacant {
266                        generation,
267                        next_free,
268                    } => (*generation, *next_free),
269                    Slot::Occupied { .. } => unreachable!("free list pointed at an occupied slot"),
270                };
271                self.free_head = next_free;
272                self.slots[idx] = Slot::Occupied { generation, value };
273                Key::new(index, generation, self.scope)
274            }
275            None => {
276                let index = u32::try_from(self.slots.len()).expect("arena exceeded u32::MAX slots");
277                self.slots.push(Slot::Occupied {
278                    generation: 0,
279                    value,
280                });
281                Key::new(index, 0, self.scope)
282            }
283        }
284    }
285
286    /// Borrow the value behind `key`, or `None` if the key is stale.
287    #[must_use]
288    pub fn get(&self, key: Key<T>) -> Option<&T> {
289        if key.scope != self.scope {
290            return None;
291        }
292        match self.slots.get(key.index as usize)? {
293            Slot::Occupied { generation, value } if *generation == key.generation => Some(value),
294            _ => None,
295        }
296    }
297
298    /// Mutably borrow the value behind `key`, or `None` if the key is stale.
299    pub fn get_mut(&mut self, key: Key<T>) -> Option<&mut T> {
300        if key.scope != self.scope {
301            return None;
302        }
303        match self.slots.get_mut(key.index as usize)? {
304            Slot::Occupied { generation, value } if *generation == key.generation => Some(value),
305            _ => None,
306        }
307    }
308
309    /// Whether `key` resolves to a live entry.
310    #[must_use]
311    pub fn contains(&self, key: Key<T>) -> bool {
312        self.get(key).is_some()
313    }
314
315    /// Remove and return the value behind `key`, if it is live.
316    ///
317    /// The slot's generation is bumped, invalidating every outstanding copy of
318    /// `key`.
319    pub fn remove(&mut self, key: Key<T>) -> Option<T> {
320        if key.scope != self.scope {
321            return None;
322        }
323        let slot = self.slots.get_mut(key.index as usize)?;
324        let generation = match slot {
325            Slot::Occupied { generation, .. } if *generation == key.generation => *generation,
326            _ => return None,
327        };
328        // Saturating rather than wrapping: a slot recycled 4 billion times stops
329        // being reusable, which is strictly better than handing out a generation
330        // that collides with a key someone still holds.
331        let next = generation.saturating_add(1);
332        let replaced = core::mem::replace(
333            slot,
334            Slot::Vacant {
335                generation: next,
336                next_free: self.free_head,
337            },
338        );
339        if next != u32::MAX {
340            self.free_head = Some(key.index);
341        }
342        self.len -= 1;
343        match replaced {
344            Slot::Occupied { value, .. } => Some(value),
345            Slot::Vacant { .. } => None,
346        }
347    }
348
349    /// Iterate over live `(key, &value)` pairs, in slot order.
350    pub fn iter(&self) -> impl Iterator<Item = (Key<T>, &T)> {
351        let scope = self.scope;
352        self.slots
353            .iter()
354            .enumerate()
355            .filter_map(move |(i, slot)| match slot {
356                Slot::Occupied { generation, value } => {
357                    // `insert` refuses to grow past u32::MAX, so this cannot truncate.
358                    #[allow(clippy::cast_possible_truncation)]
359                    Some((Key::new(i as u32, *generation, scope), value))
360                }
361                Slot::Vacant { .. } => None,
362            })
363    }
364
365    /// Iterate over live `(key, &mut value)` pairs, in slot order.
366    pub fn iter_mut(&mut self) -> impl Iterator<Item = (Key<T>, &mut T)> {
367        let scope = self.scope;
368        self.slots
369            .iter_mut()
370            .enumerate()
371            .filter_map(move |(i, slot)| match slot {
372                Slot::Occupied { generation, value } =>
373                {
374                    #[allow(clippy::cast_possible_truncation)]
375                    Some((Key::new(i as u32, *generation, scope), value))
376                }
377                Slot::Vacant { .. } => None,
378            })
379    }
380
381    /// Iterate over live values.
382    pub fn values(&self) -> impl Iterator<Item = &T> {
383        self.iter().map(|(_, v)| v)
384    }
385
386    /// Consume the arena, yielding its live values in index order.
387    ///
388    /// For appending one arena's contents onto another: the receiving arena
389    /// hands out its own keys, so the values travel bare.
390    pub fn into_values(self) -> impl Iterator<Item = T> {
391        self.slots.into_iter().filter_map(|slot| match slot {
392            Slot::Occupied { value, .. } => Some(value),
393            Slot::Vacant { .. } => None,
394        })
395    }
396
397    /// Whether the arena has only ever been appended to: every slot occupied,
398    /// every generation zero.
399    ///
400    /// When this holds, [`Arena::len`] is also the next index [`Arena::insert`]
401    /// will hand out: the precondition for extending the arena by offset,
402    /// where a caller predicts the keys of entries it is about to append.
403    #[must_use]
404    pub fn is_dense(&self) -> bool {
405        self.len == self.slots.len()
406            && self
407                .slots
408                .iter()
409                .all(|slot| matches!(slot, Slot::Occupied { generation: 0, .. }))
410    }
411
412    /// Remove every entry, bumping all generations so existing keys go stale.
413    pub fn clear(&mut self) {
414        let keys: Vec<_> = self.iter().map(|(k, _)| k).collect();
415        for key in keys {
416            self.remove(key);
417        }
418    }
419}
420
421impl<T> core::ops::Index<Key<T>> for Arena<T> {
422    type Output = T;
423
424    /// # Panics
425    ///
426    /// If the key is stale. Use [`Arena::get`] where that is a possibility.
427    #[allow(
428        clippy::expect_used,
429        reason = "Index cannot return Result; see # Panics"
430    )]
431    fn index(&self, key: Key<T>) -> &T {
432        self.get(key).expect("stale arena key")
433    }
434}
435
436impl<T> core::ops::IndexMut<Key<T>> for Arena<T> {
437    /// # Panics
438    ///
439    /// If the key is stale. Use [`Arena::get_mut`] where that is a possibility.
440    #[allow(
441        clippy::expect_used,
442        reason = "IndexMut cannot return Result; see # Panics"
443    )]
444    fn index_mut(&mut self, key: Key<T>) -> &mut T {
445        self.get_mut(key).expect("stale arena key")
446    }
447}
448
449#[cfg(test)]
450#[allow(clippy::unwrap_used)]
451mod tests {
452    use super::*;
453
454    #[test]
455    fn insert_and_get() {
456        let mut a = Arena::new();
457        let k1 = a.insert("one");
458        let k2 = a.insert("two");
459        assert_eq!(a.get(k1), Some(&"one"));
460        assert_eq!(a.get(k2), Some(&"two"));
461        assert_eq!(a.len(), 2);
462    }
463
464    #[test]
465    fn removed_key_goes_stale_and_does_not_alias() {
466        let mut a = Arena::new();
467        let old = a.insert(1_u32);
468        assert_eq!(a.remove(old), Some(1));
469
470        // The slot is reused, but the old key must not resolve to the new value.
471        let new = a.insert(2_u32);
472        assert_eq!(new.index(), old.index(), "slot should have been reused");
473        assert_eq!(a.get(new), Some(&2));
474        assert_eq!(a.get(old), None, "stale key aliased a live entry");
475        assert!(!a.contains(old));
476    }
477
478    #[test]
479    fn double_remove_is_none() {
480        let mut a = Arena::new();
481        let k = a.insert(7_u8);
482        assert_eq!(a.remove(k), Some(7));
483        assert_eq!(a.remove(k), None);
484        assert_eq!(a.len(), 0);
485    }
486
487    #[test]
488    fn iteration_skips_holes() {
489        let mut a = Arena::new();
490        let keys: Vec<_> = (0..5_u32).map(|i| a.insert(i)).collect();
491        a.remove(keys[1]);
492        a.remove(keys[3]);
493        let live: Vec<_> = a.values().copied().collect();
494        assert_eq!(live, vec![0, 2, 4]);
495        assert_eq!(a.len(), 3);
496    }
497
498    #[test]
499    fn into_values_yields_values_in_index_order() {
500        let mut a = Arena::new();
501        for i in 0..5_u32 {
502            a.insert(i * 10);
503        }
504        let values: Vec<_> = a.into_values().collect();
505        assert_eq!(values, vec![0, 10, 20, 30, 40]);
506    }
507
508    #[test]
509    fn an_arena_that_never_removed_is_dense() {
510        let mut a = Arena::new();
511        assert!(a.is_dense(), "an empty arena has no holes");
512        for i in 0..4_u32 {
513            a.insert(i);
514        }
515        assert!(a.is_dense());
516    }
517
518    #[test]
519    fn a_removal_makes_an_arena_not_dense() {
520        let mut a = Arena::new();
521        let keys: Vec<_> = (0..3_u32).map(|i| a.insert(i)).collect();
522        a.remove(keys[1]);
523        assert!(!a.is_dense(), "a vacant slot is a hole");
524
525        // Refilling the slot does not restore density either: the recycled
526        // entry sits at a bumped generation, so `len` no longer predicts the
527        // keys of future appends alone.
528        a.insert(9);
529        assert!(!a.is_dense(), "a recycled slot is off generation zero");
530    }
531
532    #[test]
533    fn clear_invalidates_every_key() {
534        let mut a = Arena::new();
535        let keys: Vec<_> = (0..4_u32).map(|i| a.insert(i)).collect();
536        a.clear();
537        assert!(a.is_empty());
538        assert!(keys.iter().all(|&k| a.get(k).is_none()));
539    }
540
541    #[test]
542    fn keys_are_hashable_and_distinct() {
543        use std::collections::HashSet;
544        let mut a = Arena::new();
545        let set: HashSet<_> = (0..64_u32).map(|i| a.insert(i)).collect();
546        assert_eq!(set.len(), 64);
547    }
548}
549
550#[cfg(test)]
551#[allow(clippy::unwrap_used)]
552mod scope_tests {
553    use super::*;
554
555    #[test]
556    fn a_key_from_one_arena_does_not_resolve_in_another() {
557        // The whole reason the scope exists. Index 0 generation 0 means
558        // something in every arena, so without it this lookup succeeds and
559        // answers about the wrong value, confidently, with nothing about the
560        // result to say so.
561        let mut a: Arena<&str> = Arena::new();
562        let mut b: Arena<&str> = Arena::new();
563        let here = a.insert("in a");
564        let there = b.insert("in b");
565
566        assert_eq!(a.get(here), Some(&"in a"));
567        assert_eq!(b.get(there), Some(&"in b"));
568        assert_eq!(here.index(), there.index(), "the same slot in both");
569        assert_eq!(a.get(there), None, "a foreign key must not resolve");
570        assert_eq!(b.get(here), None);
571        assert!(!a.issued(there));
572    }
573
574    #[test]
575    fn foreign_keys_are_refused_by_every_route_in() {
576        let mut a: Arena<u32> = Arena::new();
577        let mut b: Arena<u32> = Arena::new();
578        let key = a.insert(1);
579        b.insert(2);
580
581        assert!(!b.contains(key));
582        assert_eq!(b.get_mut(key), None);
583        assert_eq!(b.remove(key), None, "and it must not remove something else");
584        assert_eq!(b.len(), 1, "nothing was taken out");
585    }
586
587    #[test]
588    fn keys_from_different_arenas_are_not_equal_and_do_not_collide() {
589        // Equality and hashing have to agree with resolution, or a map keyed on
590        // handles merges entries from two documents.
591        use std::collections::HashSet;
592        let mut a: Arena<u32> = Arena::new();
593        let mut b: Arena<u32> = Arena::new();
594        let here = a.insert(1);
595        let there = b.insert(2);
596
597        assert_ne!(here, there);
598        let mut set = HashSet::new();
599        set.insert(here);
600        set.insert(there);
601        assert_eq!(set.len(), 2, "two documents' handles collided in a map");
602    }
603
604    #[test]
605    fn an_unscoped_key_resolves_nowhere_until_it_is_bound() {
606        // What a deserializer builds. It names a slot but no arena, and a
607        // handle that names no arena is meaningless until someone says which.
608        let mut a: Arena<u32> = Arena::new();
609        let real = a.insert(7);
610        let loose: Key<u32> = Key::from_parts(real.index(), real.generation());
611
612        assert_eq!(loose.scope(), UNSCOPED);
613        assert_eq!(a.get(loose), None);
614        assert_eq!(a.get(loose.with_scope(a.scope())), Some(&7));
615    }
616
617    #[test]
618    fn a_clone_answers_to_the_originals_handles() {
619        // A clone is the same document (a snapshot), so handles into it keep
620        // working. If it took a fresh identifier, every handle a caller held
621        // would silently stop resolving after a clone.
622        let mut a: Arena<u32> = Arena::new();
623        let key = a.insert(5);
624        let copy = a.clone();
625        assert_eq!(copy.get(key), Some(&5));
626    }
627
628    #[test]
629    fn an_empty_arena_has_issued_nothing_to_disagree_with() {
630        // `new` is const, so the identifier cannot be taken until the first
631        // insert. That is safe precisely because an arena with nothing in it
632        // has handed out no keys.
633        let empty: Arena<u32> = Arena::new();
634        assert_eq!(empty.scope(), UNSCOPED);
635        let mut used: Arena<u32> = Arena::new();
636        used.insert(1);
637        assert_ne!(used.scope(), UNSCOPED);
638    }
639}