pie_core/
pool.rs

1//! A generic pool allocator for a multi-headed doubly-linked index-list.
2
3use crate::elem::ListElem;
4use crate::Index;
5use std::fmt;
6
7/// An error type representing failures in list operations.
8///
9/// These errors typically arise from providing an invalid `Index` to a pool
10/// method, such as one that is out of bounds or points to an already-freed element.
11#[derive(Debug, PartialEq, Eq)]
12pub enum IndexError {
13    /// The provided index was `Index::NONE`.
14    IndexIsNone,
15    /// The provided index exceeds the bounds of the pool's element vector.
16    IndexOutOfBounds,
17    /// The element at the index is on the pool's free list and cannot be used.
18    ElementIsFree,
19    /// An attempt was made to operate on the free list's own sentinel node.
20    ElementIsFreeSentinel,
21    /// A consistency check failed: an element's `prev` link does not point back correctly.
22    BrokenPrevLink,
23    /// A consistency check failed: an element's `next` link does not point back correctly.
24    BrokenNextLink,
25}
26
27impl std::error::Error for IndexError {}
28
29impl fmt::Display for IndexError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::IndexIsNone => write!(f, "Index is NONE"),
33            Self::IndexOutOfBounds => write!(f, "Index is out of bounds"),
34            Self::ElementIsFree => write!(f, "Element is on the free list"),
35            Self::ElementIsFreeSentinel => write!(f, "Element is the free list sentinel"),
36            Self::BrokenPrevLink => write!(f, "Element's previous link is inconsistent"),
37            Self::BrokenNextLink => write!(f, "Element's next link is inconsistent"),
38        }
39    }
40}
41
42/// A pool of `ListElem<T>` nodes that provides memory for multiple data structures.
43///
44/// # Rationale
45///
46/// The `ElemPool` is the cornerstone of this library's design. It acts as a
47/// specialized memory allocator. By pre-allocating memory in a `Vec` and managing
48/// its own free list, it avoids the performance cost of frequent calls to the
49/// global allocator. This makes creating and destroying elements extremely fast.
50///
51/// All list elements, regardless of which `PieList` or `FibHeap` they belong to,
52/// are stored contiguously within this single structure, leading to better cache
53/// locality during traversals compared to traditional node-based linked lists.
54///
55/// Its public API is minimal, as most interactions are performed through `PieList`,
56/// `CursorMut`, or `FibHeap` methods, which take the pool as an argument.
57#[derive(Clone, Debug)]
58pub struct ElemPool<T> {
59    /// The contiguous storage for all list elements (nodes).
60    elems: Vec<ListElem<T>>,
61    /// A count of elements currently in the free list.
62    freed: usize,
63    /// The number of elements that contain user data (`Some(T)`).
64    /// This count excludes all sentinel nodes and free elements.
65    used: usize,
66}
67
68impl<T> Default for ElemPool<T> {
69    /// Creates a new `ElemPool`, initialized with a single sentinel element
70    /// for its internal free list.
71    fn default() -> Self {
72        let sentinel_index = Self::free_sentinel_index();
73        let mut sentinel_elem = ListElem::default();
74        // The free list sentinel points to itself, indicating an empty free list.
75        let _ = sentinel_elem.new_links(sentinel_index, sentinel_index);
76        Self {
77            elems: vec![sentinel_elem],
78            freed: 0,
79            used: 0,
80        }
81    }
82}
83
84// --- Public API ---
85impl<T> ElemPool<T> {
86    /// Creates a new, empty element pool.
87    ///
88    /// The pool is initialized with a capacity for zero elements but contains
89    /// one internal node to act as the sentinel for the free list.
90    pub fn new() -> Self {
91        Default::default()
92    }
93
94    /// Returns the fixed index for the free list's sentinel node, which is always 0.
95    #[inline(always)]
96    fn free_sentinel_index() -> Index<T> {
97        Index::from(0u32)
98    }
99
100    /// Returns the number of elements holding user data in the pool.
101    ///
102    /// This is a semantic count that excludes sentinel nodes for active lists
103    /// and any elements on the free list. It provides a clear measure of how
104    /// many items are actually being stored across all lists.
105    #[inline]
106    pub fn len(&self) -> usize {
107        self.used
108    }
109
110    /// Returns `true` if the pool contains no user data.
111    #[inline]
112    pub fn is_empty(&self) -> bool {
113        self.used == 0
114    }
115
116    /// Returns the total number of elements (used, sentinels, or free) that the pool
117    /// can hold without reallocating its internal vector. This count excludes the
118    /// pool's own free-list sentinel.
119    #[inline]
120    pub fn capacity(&self) -> usize {
121        self.elems.len() - 1
122    }
123
124    /// Returns the number of free elements in the pool.
125    #[inline]
126    pub fn free_len(&self) -> usize {
127        self.freed
128    }
129
130    /// Returns the number of active lists associated with this pool.
131    ///
132    /// This is calculated by subtracting the number of data-holding elements and
133    /// free elements from the total capacity, with the remainder being the
134    /// sentinel nodes for active lists.
135    #[inline]
136    pub fn list_count(&self) -> usize {
137        self.capacity() - self.used - self.freed
138    }
139
140    /// Reserves capacity for at least `additional` more elements to be
141    /// allocated in the pool.
142    ///
143    /// The pool's underlying storage may reallocate if its capacity is
144    /// less than the current length plus `additional`. If the capacity is
145    /// already sufficient, this does nothing.
146    ///
147    /// This is useful to avoid multiple reallocations when a large number
148    /// of elements are expected to be added.
149    ///
150    /// # Panics
151    ///
152    /// Panics if the new capacity overflows `isize::MAX`.
153    #[inline]
154    pub fn reserve(&mut self, additional: usize) {
155        self.elems.reserve(additional);
156    }
157
158    /// Checks if a given index points to an element that contains user data.
159    ///
160    /// Returns `false` if the index is `NONE`, out of bounds, or points to a
161    /// free/sentinel element.
162    #[inline]
163    pub fn contains(&self, index: Index<T>) -> bool {
164        index
165            .get()
166            .and_then(|n| self.elems.get(n))
167            .is_some_and(|e| e.is_used())
168    }
169
170    /// Performs a detailed validation of an index and its surrounding links.
171    ///
172    /// This is a powerful debugging tool to verify the structural integrity of a list.
173    /// It checks that:
174    /// 1. The index is valid and in bounds.
175    /// 2. The element at the index contains data.
176    /// 3. The `prev` element's `next` link points back to this index.
177    /// 4. The `next` element's `prev` link points back to this index.
178    ///
179    /// # Errors
180    /// Returns `Ok(())` on success, or an `IndexError` variant describing the
181    /// first validation failure encountered.
182    #[inline]
183    pub fn validate_index(&self, index: Index<T>) -> Result<(), IndexError> {
184        let ndx = index.get().ok_or(IndexError::IndexIsNone)?;
185        let elem = self.elems.get(ndx).ok_or(IndexError::IndexOutOfBounds)?;
186        if !elem.is_used() {
187            return Err(IndexError::ElementIsFree);
188        }
189        let prev_ndx = elem.prev.get().ok_or(IndexError::BrokenPrevLink)?;
190        let prev_elem = self
191            .elems
192            .get(prev_ndx)
193            .ok_or(IndexError::BrokenPrevLink)?;
194        if prev_elem.next != index {
195            return Err(IndexError::BrokenPrevLink);
196        }
197        let next_ndx = elem.next.get().ok_or(IndexError::BrokenNextLink)?;
198        let next_elem = self
199            .elems
200            .get(next_ndx)
201            .ok_or(IndexError::BrokenNextLink)?;
202        if next_elem.prev != index {
203            return Err(IndexError::BrokenNextLink);
204        }
205        Ok(())
206    }
207
208    /// Allocates a new index, reusing a free element if available or creating a new one.
209    ///
210    /// This is the primary method for acquiring a new node from the pool. It
211    /// first checks the free list. If the free list is not empty, it unlinks
212    /// and returns the first available node. If the free list is empty, it
213    /// pushes a new `ListElem` to the end of the internal `Vec`.
214    pub(crate) fn index_new(&mut self) -> Result<Index<T>, IndexError> {
215        let free_sentinel_ndx = Self::free_sentinel_index();
216        let ndx_to_reuse = self.next(free_sentinel_ndx);
217
218        if ndx_to_reuse != free_sentinel_ndx {
219            // Free list is not empty, reuse an element.
220            self.index_linkout(ndx_to_reuse)?;
221            self.freed -= 1;
222            Ok(ndx_to_reuse)
223        } else {
224            // Free list is empty, allocate a new element.
225            // This can only fail on OOM, which will panic.
226            let ndx = Index::from(self.elems.len());
227            let mut new_elem = ListElem::default();
228            // A new element is initialized to point to itself.
229            let _ = new_elem.new_links(ndx, ndx);
230            self.elems.push(new_elem);
231            Ok(ndx)
232        }
233    }
234
235    /// Returns an index to the free list.
236    ///
237    /// The caller must ensure the element has already been unlinked from any
238    /// active list and that its data has been taken. This method links the
239    /// element at the given `index` to the front of the free list.
240    pub(crate) fn index_del(&mut self, index: Index<T>) -> Result<(), IndexError> {
241        let free_sentinel_ndx = Self::free_sentinel_index();
242        if index == free_sentinel_ndx {
243            return Err(IndexError::ElementIsFreeSentinel);
244        }
245        // This check ensures the index is valid before we use it.
246        self.get(index)?;
247        // Link the element into the front of the free list.
248        self.index_link_after(index, free_sentinel_ndx)?;
249        self.freed += 1;
250        Ok(())
251    }
252
253    /// Gets an immutable reference to the `ListElem` at the given index.
254    #[inline]
255    pub(crate) fn get(&self, index: Index<T>) -> Result<&ListElem<T>, IndexError> {
256        let n = index.get().ok_or(IndexError::IndexIsNone)?;
257        self.elems.get(n).ok_or(IndexError::IndexOutOfBounds)
258    }
259
260    /// Gets a mutable reference to the `ListElem` at the given index.
261    #[inline]
262    pub(crate) fn get_mut(&mut self, index: Index<T>) -> Result<&mut ListElem<T>, IndexError> {
263        let n = index.get().ok_or(IndexError::IndexIsNone)?;
264        self.elems.get_mut(n).ok_or(IndexError::IndexOutOfBounds)
265    }
266
267    /// Gets the `next` index for the element at the given index.
268    #[inline]
269    pub(crate) fn next(&self, index: Index<T>) -> Index<T> {
270        // `unwrap_or_default` returns `Index::NONE` on error.
271        self.get(index).map(|i| i.next).unwrap_or_default()
272    }
273
274    /// Gets the `prev` index for the element at the given index.
275    #[inline]
276    pub(crate) fn prev(&self, index: Index<T>) -> Index<T> {
277        self.get(index).map(|i| i.prev).unwrap_or_default()
278    }
279
280    /// Gets an immutable reference to the data inside the element at the given index.
281    #[inline]
282    pub(crate) fn data(&self, index: Index<T>) -> Option<&T> {
283        self.get(index).ok().and_then(|i| i.data.as_ref())
284    }
285
286    /// Gets a mutable reference to the data inside the element at the given index.
287    #[inline]
288    pub(crate) fn data_mut(&mut self, index: Index<T>) -> Option<&mut T> {
289        self.get_mut(index).ok().and_then(|i| i.data.as_mut())
290    }
291
292    /// Swaps the data in an element and updates the pool's `used` count accordingly.
293    ///
294    /// This is the sole method responsible for modifying an element's data, as it
295    /// correctly maintains the pool's `used` counter.
296    #[inline]
297    pub(crate) fn data_swap(&mut self, index: Index<T>, data: Option<T>) -> Option<T> {
298        let elem = self.get_mut(index).ok()?;
299        let old_data = elem.new_data(data);
300        match (old_data.is_some(), elem.data.is_some()) {
301            (false, true) => self.used += 1, // Went from None -> Some
302            (true, false) => self.used -= 1, // Went from Some -> None
303            _ => {}                          // No change in used status
304        }
305        old_data
306    }
307
308    /// Unlinks an element from its current position in a list.
309    /// After this operation, the element points to itself.
310    #[inline]
311    pub(crate) fn index_linkout(&mut self, index: Index<T>) -> Result<(), IndexError> {
312        let (prev_ndx, next_ndx) = self.get_mut(index)?.new_links(index, index);
313        self.get_mut(prev_ndx)?.new_next(next_ndx);
314        self.get_mut(next_ndx)?.new_prev(prev_ndx);
315        Ok(())
316    }
317
318    /// Links `this` element immediately after the `after` element.
319    #[inline]
320    pub(crate) fn index_link_after(
321        &mut self,
322        this: Index<T>,
323        after: Index<T>,
324    ) -> Result<(), IndexError> {
325        let next_ndx = self.get_mut(after)?.new_next(this);
326        let _ = self.get_mut(this)?.new_links(after, next_ndx);
327        self.get_mut(next_ndx)?.new_prev(this);
328        Ok(())
329    }
330
331    /// Links `this` element immediately before the `before` element.
332    #[inline]
333    pub(crate) fn index_link_before(
334        &mut self,
335        this: Index<T>,
336        before: Index<T>,
337    ) -> Result<(), IndexError> {
338        let prev_ndx = self.get_mut(before)?.new_prev(this);
339        let _ = self.get_mut(this)?.new_links(prev_ndx, before);
340        self.get_mut(prev_ndx)?.new_next(this);
341        Ok(())
342    }
343}
344
345impl<T> fmt::Display for ElemPool<T>
346where
347    T: fmt::Display,
348{
349    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
350        writeln!(
351            f,
352            "ElemPool used {}/{}, {} free:",
353            self.len(),
354            self.capacity(),
355            self.freed
356        )?;
357        for (i, elem) in self.elems.iter().enumerate() {
358            writeln!(f, "  [{}]: {}", i, elem)?;
359        }
360        Ok(())
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367    use crate::list::PieList;
368
369    // Helper function to create a pool and add some elements for testing.
370    fn create_pool_with_elems<T>(count: usize, default_data: T) -> (ElemPool<T>, Vec<Index<T>>)
371    where
372        T: Clone,
373    {
374        let mut pool = ElemPool::new();
375        let mut indices = Vec::new();
376        for _i in 0..count {
377            let index = pool.index_new().unwrap();
378            // Use data_swap to ensure the 'used' counter is updated correctly.
379            pool.data_swap(index, Some(default_data.clone()));
380            indices.push(index);
381        }
382        (pool, indices)
383    }
384
385    #[test]
386    fn test_pool_creation_and_len() {
387        let pool: ElemPool<i32> = ElemPool::new();
388        assert_eq!(pool.len(), 0); // Should have 0 used elements
389        assert_eq!(pool.capacity(), 0);
390        assert!(pool.is_empty());
391        assert_eq!(pool.elems.len(), 1); // Internal vec has free sentinel
392    }
393
394    #[test]
395    fn test_index_new_and_len() {
396        let (pool, indices) = create_pool_with_elems(3, 100);
397        assert_eq!(pool.len(), 3); // 3 used elements
398        assert_eq!(pool.capacity(), 3);
399        assert!(!pool.is_empty());
400        assert_eq!(indices.len(), 3);
401        assert_eq!(indices[0].get(), Some(1));
402        assert_eq!(indices[1].get(), Some(2));
403        assert_eq!(indices[2].get(), Some(3));
404    }
405
406    #[test]
407    fn test_del_and_reuse() {
408        let (mut pool, indices) = create_pool_with_elems(5, 0);
409        assert_eq!(pool.len(), 5);
410        assert_eq!(pool.freed, 0);
411
412        let deleted_index = indices[2];
413        // To delete an element from the pool, we must first remove its data.
414        let _data = pool.data_swap(deleted_index, None);
415        // data_swap automatically decrements pool.len()
416        assert_eq!(pool.len(), 4);
417
418        // Now we can return the data-less element to the free list
419        pool.index_del(deleted_index).unwrap();
420        assert_eq!(pool.freed, 1);
421        assert!(!pool.contains(deleted_index));
422        assert_eq!(pool.next(ElemPool::free_sentinel_index()), deleted_index);
423
424        // Allocate a new element, it should reuse the deleted index.
425        let reused_index = pool.index_new().unwrap();
426        assert_eq!(reused_index, deleted_index);
427        // The pool's used count is still 4 because the new element has no data yet
428        assert_eq!(pool.len(), 4);
429        assert_eq!(pool.freed, 0);
430
431        // Add data to the reused element, length should go up
432        pool.data_swap(reused_index, Some(999));
433        assert_eq!(pool.len(), 5);
434    }
435
436    #[test]
437    fn test_del_errors() {
438        let mut pool: ElemPool<i32> = ElemPool::new();
439        let index = pool.index_new().unwrap();
440        pool.data_swap(index, Some(100));
441
442        // Can't delete the sentinel
443        assert_eq!(
444            pool.index_del(ElemPool::free_sentinel_index()),
445            Err(IndexError::ElementIsFreeSentinel)
446        );
447
448        // Deleting the same index twice should ideally fail, but our simplified
449        // `index_del` doesn't have a robust double-free check. The `PieList` pop
450        // logic prevents this from happening in practice. We test the boundary
451        // conditions that `index_del` *does* check.
452    }
453
454    #[test]
455    fn test_contains() {
456        let (pool, indices) = create_pool_with_elems(2, 0);
457        assert!(pool.contains(indices[0]));
458        assert!(pool.contains(indices[1]));
459        let nonexistent_index = Index::from(99 as u32);
460        assert!(!pool.contains(nonexistent_index));
461        assert!(!pool.contains(Index::NONE));
462    }
463
464    #[test]
465    fn test_linking_logic() {
466        let (mut pool, indices) = create_pool_with_elems(3, 0);
467        let i1 = indices[0];
468        let i2 = indices[1];
469        let i3 = indices[2];
470
471        // Initially, new elements point to themselves
472        assert_eq!(pool.next(i1), i1);
473        assert_eq!(pool.prev(i1), i1);
474
475        // Link them together: i1 <-> i2 <-> i3 <-> i1 (circular)
476        pool.index_link_after(i2, i1).unwrap();
477        pool.index_link_after(i3, i2).unwrap();
478        // Complete the circle
479        pool.get_mut(i1).unwrap().new_prev(i3);
480        pool.get_mut(i3).unwrap().new_next(i1);
481
482        assert_eq!(pool.next(i1), i2);
483        assert_eq!(pool.next(i2), i3);
484        assert_eq!(pool.next(i3), i1);
485
486        assert_eq!(pool.prev(i1), i3);
487        assert_eq!(pool.prev(i3), i2);
488        assert_eq!(pool.prev(i2), i1);
489
490        // Unlink i2
491        pool.index_linkout(i2).unwrap();
492
493        // i2 should now point to itself
494        assert_eq!(pool.next(i2), i2);
495        assert_eq!(pool.prev(i2), i2);
496        // i1 and i3 should now be linked
497        assert_eq!(pool.next(i1), i3);
498        assert_eq!(pool.prev(i3), i1);
499
500        // Link i2 back in before i3
501        pool.index_link_before(i2, i3).unwrap();
502
503        assert_eq!(pool.next(i1), i2);
504        assert_eq!(pool.next(i2), i3);
505        assert_eq!(pool.prev(i3), i2);
506        assert_eq!(pool.prev(i2), i1);
507    }
508
509    #[test]
510    fn test_validate_index() {
511        // Let's create a known structure with a real list
512        let (mut pool, _) = create_pool_with_elems(0, 0);
513        let mut list = PieList::new(&mut pool);
514        list.push_back(10, &mut pool).unwrap();
515        list.push_back(20, &mut pool).unwrap();
516        list.push_back(30, &mut pool).unwrap();
517
518        let i1 = pool.next(list.sentinel);
519        let i2 = pool.next(i1);
520        let i3 = pool.next(i2);
521
522        // All indices in a valid list should validate correctly.
523        assert_eq!(pool.validate_index(i1), Ok(()));
524        assert_eq!(pool.validate_index(i2), Ok(()));
525        assert_eq!(pool.validate_index(i3), Ok(()));
526
527        // Test specific error cases
528        assert_eq!(
529            pool.validate_index(Index::NONE),
530            Err(IndexError::IndexIsNone)
531        );
532        assert_eq!(
533            pool.validate_index(Index::from(99_u32)),
534            Err(IndexError::IndexOutOfBounds)
535        );
536
537        // Manually free an element to test ElementIsFree error
538        pool.data_swap(i2, None);
539        assert_eq!(pool.validate_index(i2), Err(IndexError::ElementIsFree));
540        pool.data_swap(i2, Some(20)); // Restore for next test
541
542        // Manually break a link to test for inconsistency
543        // i1's next now points to i3, but i3's prev still points to i2
544        pool.get_mut(i1).unwrap().next = i3;
545
546        // i2 thinks its prev is i1, but i1's next is i3. So i2's prev link is broken.
547        assert_eq!(pool.validate_index(i2), Err(IndexError::BrokenPrevLink));
548        // i1 thinks its next is i3, but i3's prev is i2. So i1's next link is broken.
549        assert_eq!(pool.validate_index(i1), Err(IndexError::BrokenNextLink));
550    }
551}