engine/collections/
vec.rs

1// SPDX-FileCopyrightText: 2024 Jens Pitkänen <jens.pitkanen@helsinki.fi>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5use core::{
6    fmt::Debug,
7    mem::{needs_drop, transmute, MaybeUninit},
8    ops::{Deref, DerefMut},
9    slice,
10};
11
12use bytemuck::{fill_zeroes, Zeroable};
13
14use crate::allocators::LinearAllocator;
15
16/// A fixed-capacity contiguous growable array type.
17///
18/// Named like Vec since it's used similarly, but this type does *not* allocate
19/// more memory as needed. Very cheap to create and push to. Unlike
20/// [`ArrayVec`](arrayvec::ArrayVec), the capacity can be picked at runtime, and
21/// the backing memory does not need to be initialized until it's actually used.
22/// This means that creating a [`FixedVec`] is very fast, and you only pay in
23/// page faults for the memory you actually use.
24pub struct FixedVec<'a, T> {
25    uninit_slice: &'a mut [MaybeUninit<T>],
26    initialized_len: usize,
27}
28
29impl<T> FixedVec<'_, T> {
30    /// Creates a new [`FixedVec`] with zero capacity, but also no need for an
31    /// allocator.
32    pub fn empty() -> FixedVec<'static, T> {
33        FixedVec {
34            uninit_slice: &mut [],
35            initialized_len: 0,
36        }
37    }
38
39    /// Creates a new [`FixedVec`] with enough space for `capacity` elements of
40    /// type `T`. Returns None if the allocator does not have enough free space.
41    pub fn new<'a>(allocator: &'a LinearAllocator, capacity: usize) -> Option<FixedVec<'a, T>> {
42        let uninit_slice: &'a mut [MaybeUninit<T>] =
43            allocator.try_alloc_uninit_slice::<T>(capacity, None)?;
44        Some(FixedVec {
45            uninit_slice,
46            initialized_len: 0,
47        })
48    }
49
50    /// Like [`FixedVec::new`], but with a manually specified alignment.
51    ///
52    /// The given alignment must be a valid alignment for `T`.
53    pub fn with_alignment<'a>(
54        allocator: &'a LinearAllocator,
55        capacity: usize,
56        alignment: usize,
57    ) -> Option<FixedVec<'a, T>> {
58        let uninit_slice: &'a mut [MaybeUninit<T>] =
59            allocator.try_alloc_uninit_slice::<T>(capacity, Some(alignment))?;
60        Some(FixedVec {
61            uninit_slice,
62            initialized_len: 0,
63        })
64    }
65
66    /// Appends the value to the back of the array. If there's no capacity left,
67    /// returns the given value back wrapped in a [`Result::Err`].
68    ///
69    /// If `T` doesn't implement [`Debug`] and you want to unwrap the result,
70    /// use [`Result::ok`] and then unwrap.
71    pub fn push(&mut self, value: T) -> Result<(), T> {
72        // Pick index, check it fits:
73        let i = self.initialized_len;
74        let Some(uninit_at_i) = self.uninit_slice.get_mut(i) else {
75            return Err(value);
76        };
77
78        // Notes on this write:
79        // - The "existing value" (`uninit`, uninitialized memory) does not get
80        //   dropped here. Dropping uninitialized memory would be bad.
81        //   - To avoid leaks, we should be sure that `uninit` here is actually
82        //     uninitialized: if `uninit` is actually initialized here, it will
83        //     never be dropped, i.e. it will be leaked. `uninit` is definitely
84        //     uninitialized the first time we use any specific index, since we
85        //     specifically allocate a slice of uninitialized MaybeUninits. On
86        //     the subsequent times, we rely on the fact that all the functions
87        //     that remove values from the array also drop the values.
88        uninit_at_i.write(value);
89
90        // The value at `i` is now initialized, update length:
91        self.initialized_len = i + 1;
92
93        Ok(())
94    }
95
96    /// If non-empty, returns the final element and shortens the array by one.
97    pub fn pop(&mut self) -> Option<T> {
98        if self.initialized_len == 0 {
99            return None;
100        }
101        let i = self.initialized_len - 1;
102
103        // Safety: since i < initialized_len, the MaybeUninit at that index is
104        // definitely initialized. Double-reads (thus double-drops) are avoided
105        // by decrementing initialized_len right after, which means that the
106        // previous value in the slice won't be used as if it were initialized.
107        let value = unsafe { self.uninit_slice[i].assume_init_read() };
108        self.initialized_len -= 1;
109
110        Some(value)
111    }
112
113    /// Empties out the array, dropping the currently contained values.
114    pub fn clear(&mut self) {
115        self.truncate(0);
116    }
117
118    /// Shortens the array to be the given length if it's currently longer. Any
119    /// values past the new length are dropped.
120    pub fn truncate(&mut self, new_len: usize) {
121        if new_len >= self.initialized_len {
122            return;
123        }
124
125        if needs_drop::<T>() {
126            for initialized_value in &mut self.uninit_slice[new_len..self.initialized_len] {
127                // Safety: since we're iterating only up to
128                // `self.initialized_len`, which only gets incremented as the
129                // values are initialized, all of these [MaybeUninit]s must be
130                // initialized.
131                unsafe { initialized_value.assume_init_drop() };
132            }
133        }
134
135        self.initialized_len = new_len;
136    }
137
138    /// Returns `true` if there's no more capacity for additional elements.
139    pub fn is_full(&self) -> bool {
140        self.initialized_len == self.uninit_slice.len()
141    }
142
143    /// Returns the amount of elements that could be pushed into this array
144    /// before the capacity is reached.
145    pub fn spare_capacity(&self) -> usize {
146        self.uninit_slice.len() - self.initialized_len
147    }
148}
149
150impl<T: Copy> FixedVec<'_, T> {
151    /// Appends the values from the slice to the back of the array in order. If
152    /// there's not enough capacity to extend by the whole slice, no values are
153    /// copied over, and this function returns `false`.
154    pub fn extend_from_slice(&mut self, slice: &[T]) -> bool {
155        // NOTE: The implementation of this function does the same steps as
156        // `FixedVec::push`, with the same safety implications, just for many
157        // values at a time.
158
159        if self.initialized_len + slice.len() > self.uninit_slice.len() {
160            return false;
161        }
162
163        for (src, dst) in (slice.iter()).zip(&mut self.uninit_slice[self.initialized_len..]) {
164            dst.write(*src);
165            self.initialized_len += 1;
166        }
167
168        true
169    }
170}
171
172impl<T: Zeroable> FixedVec<'_, T> {
173    /// Fills out the rest of the array's capacity with zeroed values.
174    pub fn fill_with_zeroes(&mut self) {
175        fill_zeroes(&mut self.uninit_slice[self.initialized_len..]);
176        // Safety: everything up until `self.initialized_len` must've already
177        // been initialized, and now the rest is zeroed, and zeroed memory is
178        // valid for T (because it's Zeroable) => the whole slice is
179        // initialized.
180        self.initialized_len = self.uninit_slice.len();
181    }
182}
183
184impl<'a, T> FixedVec<'a, T> {
185    /// Splits the vec into two, returning the part with `length`.
186    ///
187    /// If `length` is greater than this array's capacity, returns `None`.
188    pub fn split_off_head(&mut self, length: usize) -> Option<FixedVec<'a, T>> {
189        if length > self.uninit_slice.len() {
190            return None;
191        }
192        let mid = length;
193        let len = self.uninit_slice.len();
194        let head_ptr = self.uninit_slice.as_mut_ptr();
195        // Safety: head_ptr is an aligned pointer to a valid slice, and mid is
196        // within that slice, so the resulting slice is a subslice of the
197        // original one from the start.
198        let head = unsafe { slice::from_raw_parts_mut(head_ptr, mid) };
199        // Safety: tail_ptr is an aligned pointer to a valid slice, offset by
200        // mid from the start of the slice, and len is the length of the slice,
201        // so the resulting slice is a subslice of the original one from the
202        // end. It also does not overlap with head, as the start pointer is
203        // offset by its length.
204        let tail = unsafe { slice::from_raw_parts_mut(head_ptr.add(mid), len - mid) };
205        let head_initialized = self.initialized_len.min(mid);
206        let tail_initialized = self.initialized_len.saturating_sub(mid);
207
208        self.initialized_len = tail_initialized;
209        self.uninit_slice = tail;
210        Some(FixedVec {
211            uninit_slice: head,
212            initialized_len: head_initialized,
213        })
214    }
215}
216
217impl<T> Drop for FixedVec<'_, T> {
218    fn drop(&mut self) {
219        self.clear();
220    }
221}
222
223impl<T> Deref for FixedVec<'_, T> {
224    type Target = [T];
225
226    fn deref<'a>(&'a self) -> &'a Self::Target {
227        let initialized_slice = &self.uninit_slice[..self.initialized_len];
228        // Safety: `MaybeUninit<T>` is identical to `T` except that it might be
229        // uninitialized, and all values up to `self.initialized_len` are
230        // initialized.
231        unsafe { transmute::<&'a [MaybeUninit<T>], &'a [T]>(initialized_slice) }
232    }
233}
234
235impl<T> DerefMut for FixedVec<'_, T> {
236    fn deref_mut<'a>(&'a mut self) -> &'a mut Self::Target {
237        let initialized_slice = &mut self.uninit_slice[..self.initialized_len];
238        // Safety: `MaybeUninit<T>` is identical to `T` except that it might be
239        // uninitialized, and all values up to `self.initialized_len` are
240        // initialized.
241        unsafe { transmute::<&'a mut [MaybeUninit<T>], &'a mut [T]>(initialized_slice) }
242    }
243}
244
245impl<T: Debug> Debug for FixedVec<'_, T> {
246    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
247        let slice: &[T] = self;
248        f.debug_list().entries(slice).finish()
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use core::{
255        str::FromStr,
256        sync::atomic::{AtomicI32, Ordering},
257    };
258
259    use arrayvec::ArrayString;
260
261    use crate::{
262        allocators::{static_allocator, LinearAllocator},
263        collections::FixedVec,
264    };
265
266    #[test]
267    fn does_not_leak() {
268        const COUNT: usize = 100;
269        static ELEMENT_COUNT: AtomicI32 = AtomicI32::new(0);
270
271        #[derive(Debug)]
272        struct Element {
273            _foo: bool,
274            _bar: ArrayString<100>,
275        }
276        impl Element {
277            pub fn create_and_count() -> Element {
278                ELEMENT_COUNT.fetch_add(1, Ordering::Relaxed);
279                Element {
280                    _foo: true,
281                    _bar: ArrayString::from_str("Bar").unwrap(),
282                }
283            }
284        }
285        impl Drop for Element {
286            fn drop(&mut self) {
287                ELEMENT_COUNT.fetch_add(-1, Ordering::Relaxed);
288            }
289        }
290
291        const ALLOCATOR_SIZE: usize = size_of::<Element>() * COUNT + align_of::<Element>() - 1;
292        static ARENA: &LinearAllocator = static_allocator!(ALLOCATOR_SIZE);
293        let mut vec: FixedVec<Element> = FixedVec::new(ARENA, COUNT).unwrap();
294
295        // Fill once:
296        assert_eq!(0, ELEMENT_COUNT.load(Ordering::Relaxed));
297        for _ in 0..COUNT / 2 {
298            vec.push(Element::create_and_count()).unwrap();
299        }
300        assert_eq!(COUNT as i32 / 2, ELEMENT_COUNT.load(Ordering::Relaxed));
301
302        // Clear:
303        vec.clear();
304        assert_eq!(0, ELEMENT_COUNT.load(Ordering::Relaxed));
305
306        // Refill:
307        for _ in 0..COUNT {
308            vec.push(Element::create_and_count()).unwrap();
309        }
310        assert_eq!(COUNT as i32, ELEMENT_COUNT.load(Ordering::Relaxed));
311        assert!(
312            vec.push(Element::create_and_count()).is_err(),
313            "vec should be full already"
314        );
315        assert_eq!(COUNT as i32, ELEMENT_COUNT.load(Ordering::Relaxed));
316
317        // Drop:
318        drop(vec);
319        assert_eq!(0, ELEMENT_COUNT.load(Ordering::Relaxed));
320    }
321
322    #[test]
323    fn zst_elements_work() {
324        #[derive(Debug, PartialEq)]
325        struct Zst;
326
327        static ARENA: &LinearAllocator = static_allocator!(0);
328        let alloc = LinearAllocator::new(ARENA, 0).unwrap();
329        let mut vec: FixedVec<Zst> = FixedVec::new(&alloc, 2).unwrap();
330
331        vec.push(Zst).unwrap();
332        vec.push(Zst).unwrap();
333        assert_eq!(Err(Zst), vec.push(Zst));
334        assert_eq!(2, vec.len());
335
336        vec.clear();
337        assert!(vec.is_empty());
338    }
339}