generational_buffer/
generational_buffer.rs

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