managed/
slotmap.rs

1//! A slotmap, a vector-like container with unique keys instead of indices.
2//!
3//! See the documentation of [`SlotMap`] for details.
4//!
5//! [`SlotMap`]: struct.SlotMap.html
6use super::{ManagedSlice as Slice};
7
8/// Provides links between slots and elements.
9///
10/// The benefit of separating this struct from the elements is that it is unconditionally `Copy`
11/// and `Default`. It also provides better locality for both the indices and the elements which
12/// could help with iteration or very large structs.
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
14pub struct Slot {
15    /// The id of this slot.
16    ///
17    /// If the given out index mismatches the `generation_id` then the element was removed already
18    /// and we can return `None` on lookup.
19    ///
20    /// If the slot is currently unused we will instead provide the index to the previous slot in
21    /// the slot-free-list.
22    generation_id: GenerationOrFreelink,
23}
24
25/// Provides a slotmap based on external memory.
26///
27/// A slotmap provides a `Vec`-like interface where each entry is associated with a stable
28/// index-like key. Lookup with the key will detect if an entry has been removed but does not
29/// require a lifetime relation. Compared to other slotmap implementations this does not internally
30/// allocate any memory on its own but only relies on the [`Slice`] arguments in the constructor.
31///
32/// [`Slice`]: ../enum.Slice.html
33///
34/// ## Usage
35///
36/// The important aspect is that the slotmap does not create the storage of its own elements, it
37/// merely manages one given to it at construction time.
38///
39/// ```
40/// # use managed::{ManagedSlice, SlotMap, SlotIndex};
41///
42/// let mut elements = [0usize; 1024];
43/// let mut slots = [SlotIndex::default(); 1024];
44///
45/// let mut map = SlotMap::new(
46///     ManagedSlice::Borrowed(&mut elements[..]),
47///     ManagedSlice::Borrowed(&mut slots[..]));
48/// let index = map.insert(42).unwrap();
49/// assert_eq!(map.get(index).cloned(), Some(42));
50/// ```
51pub struct SlotMap<'a, T> {
52    /// The slice where elements are placed.
53    /// All of them are initialized at all times but not all are logically part of the map.
54    elements: Slice<'a, T>,
55    /// The logical list of used slots.
56    /// Note that a slot is never remove from this list but instead used to track the generation_id
57    /// and the link in the free list.
58    slots: Partial<'a, Slot>,
59    /// The source of generation ids.
60    /// Generation ids are a positive, non-zero value.
61    generation: Generation,
62    /// An index to the top element of the free list.
63    /// Refers to the one-past-the-end index of `slots` if there are no elements.
64    free_top: usize,
65    /// An abstraction around computing wrapped indices in the free list.
66    indices: IndexComputer,
67}
68
69/// A backing slice tracking an index how far it is logically initialized.
70struct Partial<'a, T> {
71    slice: Slice<'a, T>,
72    next_idx: usize,
73}
74
75/// An index into a slotmap.
76///
77/// The index remains valid until the entry is removed. If accessing the slotmap with the index
78/// again after the entry was removed will fail, even if the index where the element was previously
79/// stored has been reused for another element.
80#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
81pub struct Key {
82    idx: usize,
83    generation: Generation,
84}
85
86#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
87struct GenerationOrFreelink(isize);
88
89/// Newtype wrapper around the index of a free slot.
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
91struct FreeIndex(usize);
92
93/// The generation counter.
94///
95/// Has a strictly positive value.
96#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
97struct Generation(isize);
98
99/// Offset of a freelist entry to the next entry.
100///
101/// Has a negative or zero value. Represents the negative of the offset to the next element in the
102/// free list, wrapping around at the capacity.
103/// The base for the offset is the *next* element for two reasons:
104/// * Offset of `0` points to the natural successor.
105/// * Offset of `len` would point to the element itself and should not occur.
106#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
107struct Offset(isize);
108
109/// Links FreeIndex and Offset.
110struct IndexComputer(usize);
111
112impl<T> SlotMap<'_, T> {
113    /// Retrieve a value by index.
114    pub fn get(&self, index: Key) -> Option<&T> {
115        let slot_generation = self.slots
116            .get(index.idx)?
117            .generation_id
118            .generation().ok()?;
119
120        if slot_generation != index.generation {
121            return None;
122        }
123
124        self.elements.get(index.idx)
125    }
126
127    /// Retrieve a mutable value by index.
128    pub fn get_mut(&mut self, index: Key) -> Option<&mut T> {
129        let slot_generation = self.slots
130            .get(index.idx)?
131            .generation_id
132            .generation().ok()?;
133
134        if slot_generation != index.generation {
135            return None;
136        }
137
138        self.elements.get_mut(index.idx)
139    }
140
141    /// Reserve a new entry.
142    ///
143    /// In case of success, the returned key refers to the entry until it is removed. The entry
144    /// itself is not initialized with any particular value but instead retains the value it had in
145    /// the backing slice. It is only logically placed into the slot map.
146    pub fn reserve(&mut self) -> Option<(Key, &mut T)> {
147        let index = self.next_free_slot()?;
148        let slot = self.slots.get_mut(index.0).unwrap();
149        let element = &mut self.elements[index.0];
150
151        let offset = slot.generation_id
152            .free_link()
153            .expect("Free link should be free");
154        slot.generation_id = self.generation.into();
155        let key = Key {
156            idx: index.0,
157            generation: self.generation,
158        };
159
160        self.free_top = self.indices.free_list_next(index, offset);
161        self.generation.advance();
162        Some((key, element))
163    }
164
165    /// Try to insert a value into the map.
166    ///
167    /// This will fail if there is not enough space. Sugar wrapper around `reserve` for inserting
168    /// values. Note that on success, an old value stored in the backing slice will be overwritten.
169    /// Use `reserve` directly if it is vital that no old value is dropped.
170    pub fn insert(&mut self, value: T) -> Option<Key> {
171        // Insertion must work but we don't care about the value.
172        let (index, element) = self.reserve()?;
173        *element = value;
174        Some(index)
175    }
176
177    /// Remove an element.
178    ///
179    /// If successful, return a mutable reference to the removed element so that the caller can
180    /// swap it with a logically empty value. Returns `None` if the provided index did not refer to
181    /// an element that could be freed.
182    pub fn remove(&mut self, index: Key) -> Option<&mut T> {
183        if self.get(index).is_none() {
184            return None
185        }
186
187        // The slot can be freed.
188        let free = FreeIndex(index.idx);
189        let slot = self.slots.get_mut(index.idx).unwrap();
190        assert!(slot.generation_id.generation().is_ok());
191
192        let offset = self.indices.free_list_offset(free, self.free_top);
193        slot.generation_id = offset.into();
194        self.free_top = index.idx;
195
196        Some(&mut self.elements[index.idx])
197    }
198
199    /// Get the next free slot.
200    fn next_free_slot(&mut self) -> Option<FreeIndex> {
201        // If free_top is one-past-the-end marker one of those is going to fail. Note that this
202        // also means extracting one of these statements out of the function may change the
203        // semantics if `elements.len() != slots.len()`.
204
205        // Ensure the index refers to an element within the slice or try to allocate a new slot
206        // wherein we can fit the element.
207        let free = match self.slots.get_mut(self.free_top) {
208            // There is a free element in our free list.
209            Some(_) => {
210                // Ensure that there is also a real element there.
211                let _= self.elements.get_mut(self.free_top)?;
212                FreeIndex(self.free_top)
213            },
214            // Need to try an get a new element from the slot slice.
215            None => { // Try to get the next one
216                // Will not actually wrap if pushing is successful.
217                let new_index = self.slots.len();
218                // Ensure there is an element where we want to push to.
219                let _ = self.elements.get_mut(new_index)?;
220
221                let free_slot = self.slots.try_reserve()?;
222                let free_index = FreeIndex(new_index);
223                // New top is the new one-past-the-end.
224                let new_top = new_index.checked_add(1).unwrap();
225
226                let offset = self.indices.free_list_offset(free_index, new_top);
227                free_slot.generation_id = offset.into();
228                self.free_top = free_index.0;
229
230                free_index
231            }
232        };
233
234
235        // index refers to elements within the slices
236        Some(free)
237    }
238}
239
240impl<'a, T> SlotMap<'a, T> {
241    /// Create a slot map.
242    ///
243    /// The capacity is the minimum of the capacity of the element and slot slices.
244    pub fn new(elements: Slice<'a, T>, slots: Slice<'a, Slot>) -> Self {
245        let capacity = elements.len().min(slots.len());
246        SlotMap {
247            elements,
248            slots: Partial {
249                slice: slots,
250                next_idx: 0,
251            },
252            generation: Generation::default(),
253            free_top: 0,
254            indices: IndexComputer::from_capacity(capacity),
255        }
256    }
257}
258
259impl<'a, T> Partial<'a, T> {
260    fn get(&self, idx: usize) -> Option<&T> {
261        if idx >= self.next_idx {
262            None
263        } else {
264            Some(&self.slice[idx])
265        }
266    }
267
268    fn get_mut(&mut self, idx: usize) -> Option<&mut T> {
269        if idx >= self.next_idx {
270            None
271        } else {
272            Some(&mut self.slice[idx])
273        }
274    }
275
276    fn len(&self) -> usize {
277        self.next_idx
278    }
279
280    fn try_reserve(&mut self) -> Option<&mut T> {
281        if self.next_idx == self.slice.len() {
282            None
283        } else {
284            let idx = self.next_idx;
285            self.next_idx += 1;
286            Some(&mut self.slice[idx])
287        }
288    }
289}
290
291impl GenerationOrFreelink {
292    pub(crate) fn free_link(self) -> Result<Offset, Generation> {
293        if self.0 > 0 {
294            Err(Generation(self.0))
295        } else {
296            Ok(Offset(self.0))
297        }
298    }
299
300    pub(crate) fn generation(self) -> Result<Generation, Offset> {
301        match self.free_link() {
302            Ok(offset) => Err(offset),
303            Err(generation) => Ok(generation),
304        }
305    }
306}
307
308impl IndexComputer {
309    pub(crate) fn from_capacity(capacity: usize) -> Self {
310        assert!(capacity < isize::max_value() as usize);
311        IndexComputer(capacity)
312    }
313
314    /// Get the next free list entry.
315    /// This applies the offset to the base index, wrapping around if required.
316    ///
317    /// This is the reverse of `free_list_offset`.
318    fn free_list_next(&self, FreeIndex(base): FreeIndex, offset: Offset)
319        -> usize
320    {
321        let capacity = self.0;
322        let offset = offset.int_offset();
323
324        assert!(base < capacity);
325        assert!(offset <= capacity);
326        let base = base + 1;
327
328        if capacity - offset >= base {
329            offset + base // Fine within the range
330        } else {
331            // Mathematically, capacity < offset + base < 2*capacity
332            // Wrap once, mod (capacity + 1), result again in range
333            offset
334                .wrapping_add(base)
335                .wrapping_sub(capacity + 1)
336        }
337    }
338
339    /// Get the offset difference between the index and the next element.
340    /// Computes a potentially wrapping positive offset where zero is the element following the
341    /// base.
342    ///
343    /// This is the reverse of `free_list_next`.
344    fn free_list_offset(&self, FreeIndex(base): FreeIndex, to: usize)
345        -> Offset
346    {
347        let capacity = self.0;
348
349        assert!(base != to, "Cant offset element to itself");
350        assert!(base < capacity, "Should never have to offset the end-of-list marker");
351        assert!(to <= capacity, "Can only offset to the end-of-list marker");
352        let base = base + 1;
353
354        Offset::from_int_offset(if base <= to {
355            to - base
356        } else {
357            // Wrap once, mod (capacity + 1), result again in range
358            to
359                .wrapping_add(capacity + 1)
360                .wrapping_sub(base)
361        })
362    }
363}
364
365impl Generation {
366    pub(crate) fn advance(&mut self) {
367        assert!(self.0 > 0);
368        self.0 = self.0.wrapping_add(1).max(1)
369    }
370}
371
372impl Offset {
373    pub(crate) fn from_int_offset(offset: usize) -> Self {
374        assert!(offset < isize::max_value() as usize);
375        Offset((offset as isize).checked_neg().unwrap())
376    }
377
378    pub(crate) fn int_offset(self) -> usize {
379        self.0.checked_neg().unwrap() as usize
380    }
381}
382
383impl Default for Generation {
384    fn default() -> Self {
385        Generation(1)
386    }
387}
388
389impl From<Generation> for GenerationOrFreelink {
390    fn from(gen: Generation) -> GenerationOrFreelink {
391        GenerationOrFreelink(gen.0)
392    }
393}
394
395impl From<Offset> for GenerationOrFreelink {
396    fn from(offset: Offset) -> GenerationOrFreelink {
397        GenerationOrFreelink(offset.0)
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404    use crate::slice::ManagedSlice as Slice;
405
406    #[test]
407    fn simple() {
408        let mut elements = [0u32; 2];
409        let mut slots = [Slot::default(); 2];
410
411        let mut map = SlotMap::new(
412            Slice::Borrowed(&mut elements[..]),
413            Slice::Borrowed(&mut slots[..]));
414        let key42 = map.insert(42).unwrap();
415        let keylo = map.insert('K' as _).unwrap();
416
417        assert_eq!(map.insert(0x9999), None);
418        assert_eq!(map.get(key42).cloned(), Some(42));
419        assert_eq!(map.get(keylo).cloned(), Some('K' as _));
420
421        assert!(map.remove(key42).is_some());
422        assert_eq!(map.get(key42), None);
423
424        let lastkey = map.insert(0x9999).unwrap();
425        assert_eq!(map.get(lastkey).cloned(), Some(0x9999));
426
427        *map.remove(keylo).unwrap() = 0;
428        assert_eq!(map.get(lastkey).cloned(), Some(0x9999));
429        assert!(map.remove(lastkey).is_some());
430    }
431
432    #[test]
433    fn retained() {
434        let mut elements = [0u32; 1];
435        let mut slots = [Slot::default(); 1];
436
437        let mut map = SlotMap::new(
438            Slice::Borrowed(&mut elements[..]),
439            Slice::Borrowed(&mut slots[..]));
440        let key = map.insert(0xde).unwrap();
441        map.remove(key).unwrap();
442        assert_eq!(map.get(key), None);
443
444        let new_key = map.insert(0xad).unwrap();
445
446        assert_eq!(map.get(key), None);
447        assert_eq!(map.get(new_key).cloned(), Some(0xad));
448
449        assert_eq!(map.remove(key), None);
450        map.remove(new_key).unwrap();
451
452        assert_eq!(map.get(key), None);
453        assert_eq!(map.get(new_key), None);
454    }
455
456    #[test]
457    fn non_simple_free_list() {
458        // Check the free list implementation
459        let mut elements = [0u32; 3];
460        let mut slots = [Slot::default(); 3];
461
462        let mut map = SlotMap::new(
463            Slice::Borrowed(&mut elements[..]),
464            Slice::Borrowed(&mut slots[..]));
465
466        let key0 = map.insert(0).unwrap();
467        let key1 = map.insert(1).unwrap();
468        let key2 = map.insert(2).unwrap();
469
470        *map.remove(key1).unwrap() = 0xF;
471        assert_eq!(map.free_top, 1);
472        assert_eq!(map.get(key0).cloned(), Some(0));
473        assert_eq!(map.get(key2).cloned(), Some(2));
474
475        *map.remove(key2).unwrap() = 0xF;
476        assert_eq!(map.free_top, 2);
477        assert_eq!(map.get(key0).cloned(), Some(0));
478
479        *map.remove(key0).unwrap() = 0xF;
480        assert_eq!(map.free_top, 0);
481
482        let key0 = map.insert(0).unwrap();
483        assert_eq!(map.free_top, 2);
484        let key1 = map.insert(1).unwrap();
485        let key2 = map.insert(2).unwrap();
486        assert_eq!(map.get(key0).cloned(), Some(0));
487        assert_eq!(map.get(key1).cloned(), Some(1));
488        assert_eq!(map.get(key2).cloned(), Some(2));
489    }
490}