intid_allocator/
reusing.rs

1use alloc::collections::BinaryHeap;
2
3use crate::{IdExhaustedError, UniqueIdAllocator};
4use intid::IntegerIdCounter;
5
6/// A type that allocates integer ids,
7/// with the ability to free unused ids back to storage.
8///
9/// This will minimize the integer value of the keys,
10/// reducing memory needed for lookup tables and bitsets.
11/// It is useful in conjunction with the "direct" maps/sets of the [idmap crate][idmap].
12///
13/// If the ability to free unused ids is not necessary,
14/// consider [`crate::UniqueIdAllocator`] or [`crate::UniqueIdAllocatorAtomic`].
15/// These are more efficient and do not require an allocator.
16///
17/// There is not any way to iterate over all currently allocated ids.
18/// With the current implementation (a [`BinaryHeap`]),
19/// it would be difficult to implement without any allocation.
20///
21/// [idmap]: https://docs.rs/idmap/
22pub struct IdAllocator<T: IntegerIdCounter> {
23    next_id: crate::UniqueIdAllocator<T>,
24    /// Tracks freed ids for reuse, preferring smaller ids where possible
25    ///
26    /// The use of a [`BinaryHeap`] here is inspired by the `thread-local` crate.
27    /// No part of the implementation was copied.
28    heap: BinaryHeap<core::cmp::Reverse<intid::utils::OrderByInt<T>>>,
29}
30impl<T: IntegerIdCounter> Default for IdAllocator<T> {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl<T: IntegerIdCounter> IdAllocator<T> {
37    /// Create a new allocator, with ids starting at [`T::START`] (usually zero).
38    ///
39    /// [`T::START`]: IntegerIdCounter::START
40    #[inline]
41    #[rustversion::attr(since(1.80), const)]
42    pub fn new() -> Self {
43        Self::with_start(T::START)
44    }
45
46    /// Create a new allocator, with ids starting at the specified value.
47    ///
48    /// This function is a `const fn` on all rust versions since 1.80.
49    /// Before that release, it can only be called at runtime.
50    #[inline]
51    #[rustversion::attr(since(1.80), const)]
52    #[rustversion::attr(since(1.80), clippy::msrv = "1.80")]
53    pub fn with_start(start: T) -> Self {
54        IdAllocator {
55            next_id: UniqueIdAllocator::with_start(start),
56            heap: alloc::collections::BinaryHeap::new(),
57        }
58    }
59
60    /// Allocate a new id, reusing freed ids wherever possible.
61    ///
62    /// # Errors
63    /// If no more ids are available, this will return an error.
64    /// This can only happen if the entire range of the [`IntegerIdCounter`]
65    /// has been , and none have been fred
66    #[inline]
67    pub fn try_alloc(&mut self) -> Result<T, IdExhaustedError<T>> {
68        match self.heap.pop() {
69            Some(existing) => Ok(existing.0 .0),
70            None => self.next_id.try_alloc(),
71        }
72    }
73
74    /// Allocate a new id, reusing freed ids wherever possible.
75    ///
76    /// # Panics
77    /// If there are no ids available, this will panic.
78    /// See [`Self::try_alloc`] for a version that returns an error instead.
79    #[track_caller]
80    #[inline]
81    #[must_use]
82    pub fn alloc(&mut self) -> T {
83        match self.try_alloc() {
84            Ok(id) => id,
85            Err(e) => e.panic(),
86        }
87    }
88
89    /// Free all existing ids, resetting the allocator.
90    #[inline]
91    pub fn free_all(&mut self) {
92        self.heap.clear();
93        self.next_id.reset();
94    }
95
96    /// Free the specified id, making it available
97    ///
98    /// Used ids will be used in preference to creating new ones.
99    #[inline]
100    pub fn free(&mut self, id: T) {
101        self.heap
102            .push(core::cmp::Reverse(intid::utils::OrderByInt(id)));
103    }
104}