Skip to main content

strop_core/
id.rs

1//! Stable identities and typed coordinates (0014 wave 2).
2//!
3//! Two families:
4//! - **IDs**: generational arena keys (`DocumentId`, `ViewId`, …). A
5//!   closed document's id fails lookup instead of silently resolving to
6//!   whatever moved into its old vector slot.
7//! - **Coordinates**: newtypes so byte offsets, line indexes, and the
8//!   three column kinds (bytes / UTF-16 / display) can't mix silently.
9
10/// A generational-arena key: the index names the slot, the generation
11/// names the occupant. Stale keys fail lookup.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub struct Id<K> {
14    index: u32,
15    generation: u32,
16    _kind: std::marker::PhantomData<K>,
17}
18
19impl<K> Id<K> {
20    pub fn index(self) -> usize {
21        self.index as usize
22    }
23}
24
25/// Marker kinds for the arena's identities.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct DocumentKind;
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct ViewKind;
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct PaneKind;
32
33pub type DocumentId = Id<DocumentKind>;
34pub type ViewId = Id<ViewKind>;
35pub type PaneId = Id<PaneKind>;
36
37/// A minimal generational arena (house rule: 40 boring lines beat a
38/// dependency). Slots are reused; each reuse bumps the generation.
39pub struct Arena<K, T> {
40    slots: Vec<Slot<T>>,
41    free: Vec<u32>,
42    _kind: std::marker::PhantomData<K>,
43}
44
45#[derive(Debug)]
46struct Slot<T> {
47    generation: u32,
48    value: Option<T>,
49}
50
51impl<K, T> Default for Arena<K, T> {
52    fn default() -> Self {
53        Self {
54            slots: Vec::new(),
55            free: Vec::new(),
56            _kind: std::marker::PhantomData,
57        }
58    }
59}
60
61impl<K, T> Arena<K, T> {
62    pub fn insert(&mut self, value: T) -> Id<K> {
63        if let Some(index) = self.free.pop() {
64            let slot = &mut self.slots[index as usize];
65            slot.generation += 1;
66            slot.value = Some(value);
67            return Id {
68                index,
69                generation: slot.generation,
70                _kind: std::marker::PhantomData,
71            };
72        }
73        let index = self.slots.len() as u32;
74        self.slots.push(Slot {
75            generation: 0,
76            value: Some(value),
77        });
78        Id {
79            index,
80            generation: 0,
81            _kind: std::marker::PhantomData,
82        }
83    }
84
85    /// None for a stale id — never the wrong document.
86    pub fn get(&self, id: Id<K>) -> Option<&T> {
87        self.slots
88            .get(id.index as usize)
89            .filter(|s| s.generation == id.generation)
90            .and_then(|s| s.value.as_ref())
91    }
92
93    pub fn get_mut(&mut self, id: Id<K>) -> Option<&mut T> {
94        self.slots
95            .get_mut(id.index as usize)
96            .filter(|s| s.generation == id.generation)
97            .and_then(|s| s.value.as_mut())
98    }
99
100    /// Remove and return the value; stale ids get None.
101    pub fn remove(&mut self, id: Id<K>) -> Option<T> {
102        let slot = self.slots.get_mut(id.index as usize)?;
103        if slot.generation != id.generation {
104            return None;
105        }
106        let value = slot.value.take()?;
107        self.free.push(id.index);
108        Some(value)
109    }
110
111    pub fn iter(&self) -> impl Iterator<Item = (Id<K>, &T)> {
112        self.slots.iter().enumerate().filter_map(|(i, s)| {
113            s.value.as_ref().map(|v| {
114                (
115                    Id {
116                        index: i as u32,
117                        generation: s.generation,
118                        _kind: std::marker::PhantomData,
119                    },
120                    v,
121                )
122            })
123        })
124    }
125
126    pub fn len(&self) -> usize {
127        self.slots.iter().filter(|s| s.value.is_some()).count()
128    }
129
130    /// Drop every live value (slot reuse still bumps generations).
131    pub fn clear(&mut self) {
132        let live: Vec<u32> = self
133            .slots
134            .iter()
135            .enumerate()
136            .filter(|(_, s)| s.value.is_some())
137            .map(|(i, _)| i as u32)
138            .collect();
139        for s in &mut self.slots {
140            s.value = None;
141        }
142        self.free.extend(live);
143    }
144    pub fn is_empty(&self) -> bool {
145        self.len() == 0
146    }
147}
148
149/// Byte offset into a document's UTF-8 text. The storage coordinate.
150pub type ByteOffset = usize;
151/// Zero-based line index.
152pub type LineIndex = usize;
153
154// NOTE: full newtype wrappers (ByteOffset(usize), Utf16Column(u32), …)
155// are the target; the pragmatic cutover is to name the domains first
156// (this module + the conversion functions) and tighten the Buffer API
157// per call-site cluster as waves 2–4 touch them. A big-bang usize→newtype
158// rewrite of every arithmetic site would be unreviewable.
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn stale_ids_fail_lookup() {
166        let mut a: Arena<DocumentKind, String> = Arena::default();
167        let one = a.insert("one".into());
168        let two = a.insert("two".into());
169        assert_eq!(a.get(one).map(String::as_str), Some("one"));
170        a.remove(one);
171        assert_eq!(a.get(one), None, "removed");
172        let three = a.insert("three".into()); // reuses the slot
173        assert_eq!(a.get(one), None, "stale generation must not resolve");
174        assert_eq!(a.get(three).map(String::as_str), Some("three"));
175        assert_eq!(a.get(two).map(String::as_str), Some("two"));
176        assert_eq!(a.len(), 2);
177    }
178}