stable_id/tomb_vec/
mod.rs

1mod tomb_vec_tests;
2
3use std::fmt::Debug;
4
5use std::cmp::Reverse;
6use std::collections::BinaryHeap;
7
8use std::{
9    mem,
10    ops::{Index, IndexMut},
11};
12
13use stable_id_traits::{CastUsize, Maximum};
14
15use crate::{Slot, Tec};
16
17impl<IndexT, DataT> Default for Tec<IndexT, DataT>
18where
19    IndexT: Maximum,
20{
21    fn default() -> Self {
22        Self {
23            vec: Default::default(),
24            next_free: Maximum::max_value(),
25            count: 0,
26        }
27    }
28}
29
30impl<IndexT, DataT> Tec<IndexT, DataT>
31where
32    IndexT: CastUsize + Ord + Copy + Maximum,
33{
34    fn set_sentinal(&mut self) {
35        self.next_free = Maximum::max_value();
36    }
37
38    fn check_free_link_invariant(&self, link: IndexT) -> bool {
39        let n = link.cast_to();
40        let m = IndexT::max_value().cast_to();
41
42        // either the free list link is pointing to a valid spot in memory
43        // or it's pointing to the sentinal
44        n <= self.capacity() || n == m
45    }
46
47    pub fn with_capacity(capacity: usize) -> Self {
48        Self {
49            vec: Vec::with_capacity(capacity),
50            ..Self::default()
51        }
52    }
53
54    /// Number of items in this data structure.
55    pub fn len(&self) -> usize {
56        debug_assert_eq!(
57            self.iter().count(),
58            self.count,
59            "number of living items doesn't match self.count"
60        );
61        self.count
62    }
63
64    pub fn is_empty(&self) -> bool {
65        self.len() == 0
66    }
67
68    pub fn clear(&mut self) {
69        self.vec.clear();
70        self.count = 0;
71        self.set_sentinal();
72    }
73
74    /**
75    Allocates an id from the given `data`.
76    Note: can store at most IndexT::max_value() - 1 elements, because
77    the next free node needs to be count + 1.
78    */
79    pub fn alloc(&mut self, data: DataT) -> IndexT {
80        let original_free_index = self.next_free;
81
82        let next_slot = self.vec.get_mut(original_free_index.cast_to());
83
84        let result_index = if let Some(slot) = next_slot {
85            match slot {
86                Slot::Alive(..) => unimplemented!("next free slot is already occupied"),
87                Slot::Dead { next_free } => {
88                    self.next_free = *next_free;
89                    *slot = Slot::Alive(data);
90                }
91            }
92            original_free_index
93        } else {
94            let result_index = self.capacity();
95
96            assert!(
97                result_index < IndexT::max_value().cast_to(),
98                "exceed storage limit"
99            );
100
101            self.vec.push(Slot::Alive(data));
102            self.set_sentinal();
103            IndexT::cast_from(result_index)
104        };
105
106        self.count += 1;
107
108        debug_assert!(self.check_consistency());
109
110        result_index
111    }
112
113    /** Panic if index is invalid */
114    pub fn remove(&mut self, index: IndexT) -> DataT {
115        assert!(!self.is_empty(), "removing an item from an empty container");
116
117        // invariants: the free index must be either
118        //      - pointer some dead slot within the vec
119        //      - or the end of the vector
120
121        // we're doing panic! over Option, so just do the bookkeeping here since we don't need to recover anything
122        self.count -= 1;
123
124        let index_usize = index.cast_to();
125        let removal_candidate = &mut self.vec[index_usize];
126
127        let data = match removal_candidate {
128            Slot::Alive(_) => {
129                // create a dead slot and then swap it with the candidate
130                let mut temp_dead_slot = Slot::Dead {
131                    next_free: self.next_free,
132                };
133                mem::swap(&mut temp_dead_slot, removal_candidate);
134
135                // the temporary slot now has the removed item
136
137                self.next_free = index;
138
139                match temp_dead_slot {
140                    Slot::Alive(data) => data,
141                    Slot::Dead { .. } => unreachable!("cannot unwrap a dead item"),
142                }
143            }
144            Slot::Dead { .. } => panic!("removing a dead item"),
145        };
146
147        data
148    }
149
150    pub fn get(&self, index: IndexT) -> Option<&DataT> {
151        self.vec.get(index.cast_to()).and_then(|slot| match slot {
152            Slot::Alive(data) => Some(data),
153            Slot::Dead { .. } => None,
154        })
155    }
156
157    pub fn get_mut(&mut self, index: IndexT) -> Option<&mut DataT> {
158        self.vec
159            .get_mut(index.cast_to())
160            .and_then(|slot| match slot {
161                Slot::Alive(data) => Some(data),
162                Slot::Dead { .. } => None,
163            })
164    }
165
166    pub fn iter(&self) -> impl Iterator<Item = &DataT> + DoubleEndedIterator {
167        self.vec.iter().filter_map(|data| match data {
168            Slot::Alive(data) => Some(data),
169            Slot::Dead { .. } => None,
170        })
171    }
172
173    pub fn iter_with_id(&self) -> impl Iterator<Item = (IndexT, &DataT)> + DoubleEndedIterator {
174        self.vec
175            .iter()
176            .enumerate()
177            .filter_map(|(id, data)| match data {
178                Slot::Alive(data) => Some((IndexT::cast_from(id), data)),
179                Slot::Dead { .. } => None,
180            })
181    }
182
183    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut DataT> + DoubleEndedIterator {
184        self.vec.iter_mut().filter_map(|data| match data {
185            Slot::Alive(data) => Some(data),
186            Slot::Dead { .. } => None,
187        })
188    }
189
190    pub fn iter_mut_with_id(
191        &mut self,
192    ) -> impl Iterator<Item = (IndexT, &mut DataT)> + DoubleEndedIterator {
193        self.vec
194            .iter_mut()
195            .enumerate()
196            .filter_map(|(id, data)| match data {
197                Slot::Alive(data) => Some((CastUsize::cast_from(id), data)),
198                Slot::Dead { .. } => None,
199            })
200    }
201
202    pub fn into_iter_with_id(self) -> impl Iterator<Item = (IndexT, DataT)> + DoubleEndedIterator {
203        self.vec
204            .into_iter()
205            .enumerate()
206            .filter_map(|(id, data)| match data {
207                Slot::Alive(data) => Some((CastUsize::cast_from(id), data)),
208                Slot::Dead { .. } => None,
209            })
210    }
211
212    /// The amount of occupied space in the underlying `vec`.
213    /// Note:
214    /// ```compile_fail
215    /// self.len() <= self.capacity() == self.vec.len() <= self.vec.capacity()
216    /// ```
217    pub fn capacity(&self) -> usize {
218        self.vec.len()
219    }
220
221    fn get_free_list(&self) -> Vec<IndexT> {
222        let max = Maximum::max_value();
223        let capacity = self.capacity();
224        let len = self.len();
225        assert!(capacity >= len);
226
227        let mut cur = self.next_free;
228        let mut acc = Vec::with_capacity(capacity - len);
229
230        loop {
231            if cur == max {
232                break;
233            }
234
235            if let Slot::Dead { next_free } = &self.vec[cur.cast_to()] {
236                acc.push(cur);
237                cur = *next_free;
238            } else {
239                unreachable!("found a living slot in free list")
240            }
241        }
242        acc
243    }
244
245    /**
246    Coalescing using the typical typical 2 direction trick, and then return the number of items being removed.
247    - FORWARD: we need to backfill dead slots in increasing order, using a binary heap
248    - another cursor traverse from the back of the memory block to scan for living slots and do the swap
249
250    However, if you can bound the number of dead slots to k=log(n), then you can bound this to O(log n). Analysis:
251    - forward cusor: it uses a binary heap to iterate, which takes `k log k` = `log(n) * log(log(n))` comparisons, which is O(log(n)), calculation thanks to symbolic calculator.
252    - backward cursor: either it gets k living members, or it has loop through at most k dead members to get the k living memebers, so O(k) = O(log(n))
253    */
254    fn heap_based_coalesce<F>(&mut self, mut f: F) -> usize
255    where
256        F: FnMut(IndexT, IndexT),
257    {
258        let mut free_heap = {
259            let free_list: Vec<_> = self.get_free_list().into_iter().map(Reverse).collect();
260
261            BinaryHeap::from(free_list)
262        };
263        let removed_len = free_heap.len();
264
265        let mut backward_cursor = self.capacity() - 1;
266        let max = Maximum::max_value();
267        'main_loop: while let Some(Reverse(forward_cursor)) = free_heap.pop() {
268            // find a living slot from the back
269            let mut living_target = loop {
270                let swap_target = &mut self.vec[backward_cursor];
271
272                let forward_cursor_usize = forward_cursor.cast_to();
273                if forward_cursor_usize >= backward_cursor {
274                    break 'main_loop;
275                }
276
277                if matches!(swap_target, Slot::Alive(_)) {
278                    // Let's swap the target out of the vec and replace with garbage data.
279                    // Later self.remove_trailing_dead_slots() will drop them.
280                    let mut dummy = Slot::Dead { next_free: max };
281                    mem::swap(swap_target, &mut dummy);
282                    break dummy;
283                }
284
285                backward_cursor -= 1;
286
287                // note: we have at least 1 living slot otherwise the code would short circuit in the base case
288                debug_assert!(backward_cursor != 0);
289            };
290
291            let dead_target = &mut self.vec[forward_cursor.cast_to()];
292            debug_assert!(matches!(dead_target, Slot::Dead { .. }));
293
294            // i.e. doing a remove and swap
295            mem::swap(&mut living_target, dead_target);
296            f(IndexT::cast_from(backward_cursor), forward_cursor);
297        }
298
299        removed_len
300    }
301
302    /**
303    Coalesce the data by removing the dead slots. Takes a function `f(old_id, new_id)`
304    that allows you to deal with changes made by the process, i.e. say in your game model,
305    you have an entity which occupied `old_id`, you would need to change all references
306    to use the `new_id`.
307    This is intended to be used before saving a game.
308
309    Note: this algorithm is O(n lg n) due to the use of binary heap.
310    */
311    pub fn coalesce<F>(&mut self, f: F)
312    where
313        F: FnMut(IndexT, IndexT),
314    {
315        let next_usize = self.next_free.cast_to();
316        let capacity = self.capacity();
317        if next_usize >= capacity {
318            return;
319        } else {
320            // this implies there is at least 1 living item
321            debug_assert!(!self.is_empty());
322        }
323
324        let removed_len = self.heap_based_coalesce(f);
325
326        // pop out all trailing dead slots
327        self.vec.truncate(capacity - removed_len);
328
329        // edge-case: at this point the memory is compact, so we're pointing the free-list to the sentinel value
330        self.set_sentinal();
331
332        debug_assert_eq!(self.len(), self.capacity());
333    }
334
335    fn check_consistency(&self) -> bool {
336        use std::collections::HashSet;
337
338        debug_assert!(self.check_free_link_invariant(self.next_free));
339
340        if self.is_empty() {
341            debug_assert!(self.next_free == IndexT::max_value());
342            debug_assert!(self.vec.is_empty());
343            return true;
344        }
345
346        // indices of all dead slots
347        let dead_set: HashSet<usize> = self
348            .vec
349            .iter()
350            .enumerate()
351            .filter(|(_, slot)| matches!(slot, Slot::Dead { .. }))
352            .map(|(i, _)| i)
353            .collect();
354
355        let linked_dead_set = self
356            .get_free_list()
357            .into_iter()
358            .map(CastUsize::cast_to)
359            .collect();
360
361        // we're double-counting:
362        // - dead_set is based on linear scan of the whole memory
363        // - linked_dead_set is based on linked-list traversal from self.next_free
364        assert_eq!(dead_set, linked_dead_set);
365
366        true
367    }
368}
369
370impl<IndexT, DataT> Tec<IndexT, DataT>
371where
372    IndexT: CastUsize + Ord + Copy + Maximum,
373    DataT: Clone,
374{
375    /**
376    Populate `count` number of items by cloning the given `data`.
377    */
378    pub fn populate(data: DataT, count: usize) -> Self {
379        let vec = vec![Slot::Alive(data); count];
380        let count = vec.len();
381
382        Self {
383            vec,
384            next_free: Maximum::max_value(),
385            count,
386        }
387    }
388}
389
390impl<IndexT, DataT> Tec<IndexT, DataT>
391where
392    IndexT: CastUsize + Ord + Copy + Maximum,
393    DataT: Clone + Default,
394{
395    /**
396    Populate `count` number of items with the default value.
397    */
398    pub fn populate_defaults(count: usize) -> Self {
399        Self::populate(Default::default(), count)
400    }
401}
402
403impl<IndexT, DataT> Tec<IndexT, DataT>
404where
405    IndexT: CastUsize + Ord + Copy + Maximum,
406    DataT: Default,
407{
408    pub fn alloc_default(&mut self) -> IndexT {
409        self.alloc(Default::default())
410    }
411}
412
413impl<IndexT, DataT> Index<IndexT> for Tec<IndexT, DataT>
414where
415    IndexT: CastUsize + Ord + Copy + Maximum,
416{
417    type Output = DataT;
418
419    fn index(&self, index: IndexT) -> &Self::Output {
420        self.get(index).expect("element not exist")
421    }
422}
423
424impl<IndexT, DataT> IndexMut<IndexT> for Tec<IndexT, DataT>
425where
426    IndexT: CastUsize + Ord + Copy + Maximum,
427{
428    fn index_mut(&mut self, index: IndexT) -> &mut Self::Output {
429        self.get_mut(index).expect("element not exist")
430    }
431}
432
433impl<IndexT, DataT> Debug for Tec<IndexT, DataT>
434where
435    IndexT: Debug,
436    DataT: Debug,
437{
438    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439        f.debug_struct("Tec")
440            .field("vec", &self.vec)
441            .field("next_free", &self.next_free)
442            .field("count", &self.count)
443            .finish()
444    }
445}