generational_buffer/
generational_buffer.rs

1use std::{
2    fmt,
3    marker::PhantomData,
4};
5
6/// A handle that combines an index with a generation counter.
7///
8/// The handle is typed according to the type of data it refers to,
9/// but doesn't hold it.
10#[derive(Debug)]
11pub struct Handle<T> {
12    index: usize,
13    generation: u32,
14    phantom: PhantomData<T>,
15}
16
17impl<T> Handle<T> {
18    fn new(index: usize, generation: u32) -> Self {
19        Self {
20            index,
21            generation,
22            phantom: PhantomData,
23        }
24    }
25}
26
27/// A generic append-only circular buffer with generational IDs
28///
29/// Inserting returns a `Handle` that can be used to access the value later,
30/// checking the item hasn't been replaced in the meantime.
31///
32/// Items can't be individually removed, but the entire buffer can be cleared,
33///  which invalidates all existing handles.
34pub struct GenerationalBuffer<T> {
35    entries: Vec<T>,
36    max_capacity: usize,
37    next_index: usize,
38    current_generation: u32,
39}
40
41impl<T> GenerationalBuffer<T> {
42    /// Creates a new generational buffer with the specified capacity
43    pub fn new(max_capacity: usize) -> Self {
44        Self {
45            entries: Vec::new(),
46            max_capacity,
47            next_index: 0,
48            current_generation: 0,
49        }
50    }
51
52    /// Returns the maximum capacity of the buffer
53    pub fn capacity(&self) -> usize {
54        self.max_capacity
55    }
56
57    /// Returns the current number of entries in the buffer
58    pub fn len(&self) -> usize {
59        self.entries.len()
60    }
61
62    /// Returns true if the buffer is empty
63    pub fn is_empty(&self) -> bool {
64        self.entries.is_empty()
65    }
66
67    /// Clear the buffer, removing all entries and rendering all
68    /// existing handles invalid.
69    pub fn clear(&mut self) {
70        self.entries.clear();
71        self.next_index = 0;
72        self.current_generation += 2;
73    }
74
75    /// Returns true if the buffer has reached its maximum capacity
76    pub fn is_full(&self) -> bool {
77        self.entries.len() == self.max_capacity
78    }
79
80    /// Inserts a value into the buffer and returns a handle to it.
81    ///
82    /// This removes the oldest entry if the buffer is full.
83    pub fn push(&mut self, value: T) -> Handle<T> {
84        let index = self.next_index;
85        let generation = self.current_generation;
86
87        if self.entries.len() < self.max_capacity {
88            // Buffer is not full yet, just append
89            self.entries.push(value);
90        } else {
91            // Buffer is full, overwrite the oldest entry
92            self.entries[index] = value;
93        }
94
95        // Create handle with current generation
96        let handle = Handle::new(index, generation);
97
98        // Advance to the next position
99        self.next_index = (self.next_index + 1) % self.max_capacity;
100
101        // If we've wrapped around, increment the generation
102        if self.next_index == 0 {
103            self.current_generation = self.current_generation.wrapping_add(1);
104        }
105
106        handle
107    }
108
109    /// Gets a reference to the value associated with the handle
110    pub fn get(&self, handle: Handle<T>) -> Option<&T> {
111        if self.is_valid(handle) {
112            Some(&self.entries[handle.index])
113        } else  {
114            None
115        }
116    }
117
118    /// Gets a mutable reference to the value associated with the handle
119    pub fn get_mut(&mut self, handle: Handle<T>) -> Option<&mut T> {
120        if self.is_valid(handle) {
121            Some(&mut self.entries[handle.index])
122        } else  {
123            None
124        }
125    }
126
127    /// Checks if a handle is still valid (points to existing data)
128    pub fn is_valid(&self, handle: Handle<T>) -> bool {
129        if handle.index >= self.entries.len() {
130            return false;
131        }
132        handle.generation == self.calculate_generation_at_index(handle.index)
133    }
134
135    /// Returns an iterator over all entries with their handles,
136    ///  in no particular order
137    pub fn iter(&self) -> impl Iterator<Item = (Handle<T>, &T)> {
138        self.entries.iter().enumerate().map(|(i, value)| {
139            let generation = self.calculate_generation_at_index(i);
140            (Handle::new(i, generation), value)
141        })
142    }
143
144    /// Returns an iterator over all entries, in no particular order
145    pub fn values(&self) -> impl Iterator<Item = &T> {
146        self.entries.iter()
147    }
148
149    /// Returns an iterator over all valid handles, in no particular order
150    pub fn handles(&self) -> impl Iterator<Item = Handle<T>> + '_ {
151        (0..self.entries.len()).map(move |i| {
152            let generation = self.calculate_generation_at_index(i);
153            Handle::new(i, generation)
154        })
155    }
156
157    /// Calculate what generation should be at a given index
158    fn calculate_generation_at_index(&self, index: usize) -> u32 {
159        if index < self.next_index {
160            self.current_generation
161        } else {
162            self.current_generation.saturating_sub(1)
163        }
164    }
165}
166
167impl<T: fmt::Debug> fmt::Debug for GenerationalBuffer<T> {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        f.debug_struct("GenerationalBuffer")
170            .field("capacity", &self.capacity())
171            .field("len", &self.len())
172            .field("next_index", &self.next_index)
173            .field("current_generation", &self.current_generation)
174            .field("entries", &self.entries)
175            .finish()
176    }
177}
178impl<O> PartialEq for Handle<O> {
179    fn eq(&self, other: &Self) -> bool {
180        self.index == other.index && self.generation == other.generation
181    }
182}
183impl<O> Eq for Handle<O> {}
184impl<O> Clone for Handle<O> {
185    fn clone(&self) -> Self {
186        *self
187    }
188}
189impl<O> Copy for Handle<O> {}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn test_basic_operations() {
197        let mut buffer = GenerationalBuffer::new(3);
198
199        // Insert some values
200        let h1 = buffer.push(10);
201        let h2 = buffer.push(20);
202        let h3 = buffer.push(30);
203
204        // Check values
205        assert_eq!(buffer.get(h1), Some(&10));
206        assert_eq!(buffer.get(h2), Some(&20));
207        assert_eq!(buffer.get(h3), Some(&30));
208        assert_eq!(buffer.len(), 3);
209        assert!(buffer.is_full());
210    }
211
212    #[test]
213    fn test_circular_wrapping() {
214        let mut buffer = GenerationalBuffer::new(2);
215
216        // Fill the buffer
217        let h1 = buffer.push(10);
218        let h2 = buffer.push(20);
219
220        // Wrap around - this should invalidate h1
221        let h3 = buffer.push(30);
222
223        // h1 should now be invalid due to generation mismatch
224        assert_eq!(buffer.get(h1), None);
225        assert_eq!(buffer.get(h2), Some(&20));
226        assert_eq!(buffer.get(h3), Some(&30));
227        assert!(!buffer.is_valid(h1));
228        assert!(buffer.is_valid(h2));
229        assert!(buffer.is_valid(h3));
230        assert_eq!(buffer.len(), 2);
231
232        // Do one more turn
233        let h4 = buffer.push(40); // This should overwrite h2
234        let h5 = buffer.push(50); // This should overwrite h3
235        assert_eq!(buffer.get(h4), Some(&40));
236        assert_eq!(buffer.get(h5), Some(&50));
237        assert!(!buffer.is_valid(h2)); // h2 should be invalid now
238        assert!(!buffer.is_valid(h3)); // h3 should be invalid now
239
240        // Clear the buffer
241        buffer.clear();
242        assert!(buffer.is_empty());
243        assert!(!buffer.is_valid(h4)); // h4 should be invalid now
244        let h6 = buffer.push(60); // New handle after clear
245        assert_eq!(buffer.get(h6), Some(&60));
246    }
247
248    #[test]
249    fn test_generation_calculation() {
250        let mut buffer = GenerationalBuffer::new(3);
251
252        // Fill buffer multiple times
253        let handles: Vec<_> = (0..10).map(|i| buffer.push(i)).collect();
254
255        // Only the last 3 handles should be valid
256        for (i, &handle) in handles.iter().enumerate() {
257            if i < 7 {
258                assert!(!buffer.is_valid(handle), "Handle {i} should be invalid");
259            } else {
260                assert!(buffer.is_valid(handle), "Handle {i} should be valid");
261            }
262        }
263    }
264
265    #[test]
266    fn test_iterator() {
267        let mut buffer = GenerationalBuffer::new(3);
268
269        buffer.push(10);
270        buffer.push(20);
271        buffer.push(30);
272        buffer.push(40);
273        buffer.push(50);
274        buffer.push(60);
275        buffer.push(70);
276        buffer.push(80);
277
278        let mut values: Vec<i32> = buffer.values().cloned().collect();
279        values.sort(); // no order is currently guaranteed
280        assert_eq!(values, vec![60, 70, 80]);
281
282        let handles: Vec<_> = buffer.handles().collect();
283        assert_eq!(handles.len(), 3);
284
285        // Verify all handles are valid
286        for handle in handles {
287            assert!(buffer.is_valid(handle));
288        }
289    }
290
291    #[test]
292    fn test_growing_buffer() {
293        let mut buffer = GenerationalBuffer::new(5);
294
295        // Add elements one by one
296        let h1 = buffer.push(1);
297        let h2 = buffer.push(2);
298        let h3 = buffer.push(3);
299
300        assert_eq!(buffer.len(), 3);
301        assert!(!buffer.is_full());
302
303        // All handles should be valid
304        assert!(buffer.is_valid(h1));
305        assert!(buffer.is_valid(h2));
306        assert!(buffer.is_valid(h3));
307
308        // Values should be accessible
309        assert_eq!(buffer.get(h1), Some(&1));
310        assert_eq!(buffer.get(h2), Some(&2));
311        assert_eq!(buffer.get(h3), Some(&3));
312    }
313}