Skip to main content

bevy_ecs/storage/
blob_array.rs

1use alloc::alloc::handle_alloc_error;
2use bevy_ptr::{OwningPtr, Ptr, PtrMut};
3use bevy_utils::OnDrop;
4use core::{alloc::Layout, cell::UnsafeCell, num::NonZeroUsize, ptr::NonNull};
5
6use crate::query::DebugCheckedUnwrap;
7
8/// A flat, type-erased data storage type.
9///
10/// Used to densely store homogeneous ECS data. A blob is usually just an arbitrary block of contiguous memory without any identity, and
11/// could be used to represent any arbitrary data (i.e. string, arrays, etc). This type only stores meta-data about the blob that it stores,
12/// and a pointer to the location of the start of the array, similar to a C-style `void*` array.
13///
14/// This type is reliant on its owning type to store the capacity and length information.
15#[derive(Debug)]
16pub(super) struct BlobArray {
17    /// The layout of the data.
18    /// This always has `size()` as a multiple of `align()`,
19    /// meaning we can use `repeat_packed` for layout and can
20    /// index the array by multiplying `size()` by the index.
21    item_layout: Layout,
22    // the `data` ptr's layout is always `array_layout(item_layout, capacity)`
23    data: NonNull<u8>,
24    // None if the underlying type doesn't need to be dropped
25    pub drop: Option<unsafe fn(OwningPtr<'_>)>,
26    #[cfg(debug_assertions)]
27    capacity: usize,
28}
29
30impl BlobArray {
31    /// Create a new [`BlobArray`] with a specified `capacity`.
32    /// If `capacity` is 0, no allocations will be made.
33    ///
34    /// `drop` is an optional function pointer that is meant to be invoked when any element in the [`BlobArray`]
35    /// should be dropped. For all Rust-based types, this should match 1:1 with the implementation of [`Drop`]
36    /// if present, and should be `None` if `T: !Drop`. For non-Rust based types, this should match any cleanup
37    /// processes typically associated with the stored element.
38    ///
39    /// # Safety
40    /// - `drop` should be safe to call with an [`OwningPtr`] pointing to any item that's been placed into this [`BlobArray`].
41    ///   If `drop` is `None`, the items will be leaked. This should generally be set as None based on [`needs_drop`].
42    /// - `item_layout.size()` must be a multiple of `item_layout.align()`.
43    ///   Note that this is true for all rust types, but not all `Layout` values.
44    ///
45    /// [`needs_drop`]: std::mem::needs_drop
46    pub unsafe fn with_capacity(
47        item_layout: Layout,
48        drop_fn: Option<unsafe fn(OwningPtr<'_>)>,
49        capacity: usize,
50    ) -> Self {
51        if capacity == 0 {
52            let align = NonZeroUsize::new(item_layout.align()).expect("alignment must be > 0");
53            // Indexing operations require that the size be a multiple of the alignment
54            debug_assert_eq!(
55                item_layout.pad_to_align(),
56                item_layout,
57                "Layout size must be a multiple of its alignment"
58            );
59
60            // Create a dangling pointer with the given alignment.
61            let data = NonNull::without_provenance(align);
62
63            Self {
64                item_layout,
65                drop: drop_fn,
66                data,
67                #[cfg(debug_assertions)]
68                capacity,
69            }
70        } else {
71            // SAFETY: Upheld by caller
72            let mut arr = unsafe { Self::with_capacity(item_layout, drop_fn, 0) };
73            // SAFETY: `capacity` > 0
74            unsafe { arr.alloc(NonZeroUsize::new_unchecked(capacity)) }
75            arr
76        }
77    }
78
79    /// Returns the [`Layout`] of the element type stored in the vector.
80    #[inline]
81    pub fn layout(&self) -> Layout {
82        self.item_layout
83    }
84
85    /// Return `true` if this [`BlobArray`] stores `ZSTs`.
86    pub fn is_zst(&self) -> bool {
87        self.item_layout.size() == 0
88    }
89
90    /// Returns the drop function for values stored in the vector,
91    /// or `None` if they don't need to be dropped.
92    #[inline]
93    pub fn get_drop(&self) -> Option<unsafe fn(OwningPtr<'_>)> {
94        self.drop
95    }
96
97    /// Returns a reference to the element at `index`, without doing bounds checking.
98    ///
99    /// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
100    /// Just like [`Vec::len`].*
101    ///
102    /// # Safety
103    /// - The element at index `index` is safe to access.
104    ///   (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `index` < `len`)
105    ///
106    /// [`Vec::len`]: alloc::vec::Vec::len
107    #[inline]
108    pub unsafe fn get_unchecked(&self, index: usize) -> Ptr<'_> {
109        #[cfg(debug_assertions)]
110        debug_assert!(index < self.capacity);
111        let size = self.item_layout.size();
112        // SAFETY:
113        // - The caller ensures that `index` fits in this array,
114        //   so this operation will not overflow the original allocation.
115        // - `size` is a multiple of the erased type's alignment,
116        //   so adding a multiple of `size` will preserve alignment.
117        unsafe { self.get_ptr().byte_add(index * size) }
118    }
119
120    /// Returns a mutable reference to the element at `index`, without doing bounds checking.
121    ///
122    /// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
123    /// Just like [`Vec::len`].*
124    ///
125    /// # Safety
126    /// - The element with at index `index` is safe to access.
127    ///   (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `index` < `len`)
128    ///
129    /// [`Vec::len`]: alloc::vec::Vec::len
130    #[inline]
131    pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> PtrMut<'_> {
132        #[cfg(debug_assertions)]
133        debug_assert!(index < self.capacity);
134        let size = self.item_layout.size();
135        // SAFETY:
136        // - The caller ensures that `index` fits in this vector,
137        //   so this operation will not overflow the original allocation.
138        // - `size` is a multiple of the erased type's alignment,
139        //  so adding a multiple of `size` will preserve alignment.
140        unsafe { self.get_ptr_mut().byte_add(index * size) }
141    }
142
143    /// Gets a [`Ptr`] to the start of the array
144    #[inline]
145    pub fn get_ptr(&self) -> Ptr<'_> {
146        // SAFETY: the inner data will remain valid for as long as 'self.
147        unsafe { Ptr::new(self.data) }
148    }
149
150    /// Gets a [`PtrMut`] to the start of the array
151    #[inline]
152    pub fn get_ptr_mut(&mut self) -> PtrMut<'_> {
153        // SAFETY: the inner data will remain valid for as long as 'self.
154        unsafe { PtrMut::new(self.data) }
155    }
156
157    /// Get a slice of the first `slice_len` elements in [`BlobArray`] as if it were an array with elements of type `T`
158    /// To get a slice to the entire array, the caller must plug `len` in `slice_len`.
159    ///
160    /// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
161    /// Just like [`Vec::len`].*
162    ///
163    /// # Safety
164    /// - The type `T` must be the type of the items in this [`BlobArray`].
165    /// - `slice_len` <= `len`
166    ///
167    /// [`Vec::len`]: alloc::vec::Vec::len
168    pub unsafe fn get_sub_slice<T>(&self, slice_len: usize) -> &[UnsafeCell<T>] {
169        #[cfg(debug_assertions)]
170        debug_assert!(slice_len <= self.capacity);
171        // SAFETY: the inner data will remain valid for as long as 'self.
172        unsafe {
173            core::slice::from_raw_parts(self.data.as_ptr() as *const UnsafeCell<T>, slice_len)
174        }
175    }
176
177    /// Clears the array, i.e. removing (and dropping) all of the elements.
178    /// Note that this method has no effect on the allocated capacity of the vector.
179    ///
180    /// Note that this method will behave exactly the same as [`Vec::clear`].
181    ///
182    /// # Safety
183    /// - For every element with index `i`, if `i` < `len`: It must be safe to call [`Self::get_unchecked_mut`] with `i`.
184    ///   (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `len` is correct.)
185    ///
186    /// [`Vec::clear`]: alloc::vec::Vec::clear
187    pub unsafe fn clear(&mut self, len: usize) {
188        #[cfg(debug_assertions)]
189        debug_assert!(self.capacity >= len);
190        if let Some(drop) = self.drop {
191            // We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't
192            // accidentally drop elements twice in the event of a drop impl panicking.
193            self.drop = None;
194            let size = self.item_layout.size();
195            for i in 0..len {
196                // SAFETY:
197                // * 0 <= `i` < `len`, so `i * size` must be in bounds for the allocation.
198                // * `size` is a multiple of the erased type's alignment,
199                //   so adding a multiple of `size` will preserve alignment.
200                // * The item is left unreachable so it can be safely promoted to an `OwningPtr`.
201                let item = unsafe { self.get_ptr_mut().byte_add(i * size).promote() };
202                // SAFETY: `item` was obtained from this `BlobArray`, so its underlying type must match `drop`.
203                unsafe { drop(item) };
204            }
205            self.drop = Some(drop);
206        }
207    }
208
209    /// Because this method needs parameters, it can't be the implementation of the `Drop` trait.
210    /// The owner of this [`BlobArray`] must call this method with the correct information.
211    ///
212    /// # Safety
213    /// - `cap` and `len` are indeed the capacity and length of this [`BlobArray`]
214    /// - This [`BlobArray`] mustn't be used after calling this method.
215    pub unsafe fn drop(&mut self, cap: usize, len: usize) {
216        #[cfg(debug_assertions)]
217        debug_assert_eq!(self.capacity, cap);
218        if cap != 0 {
219            self.clear(len);
220            if !self.is_zst() {
221                let layout = self.item_layout.repeat_packed(cap);
222                let layout = layout.expect("array layout should be valid");
223                alloc::alloc::dealloc(self.data.as_ptr().cast(), layout);
224            }
225            #[cfg(debug_assertions)]
226            {
227                self.capacity = 0;
228            }
229        }
230    }
231
232    /// Drops the last element in this [`BlobArray`].
233    ///
234    /// # Safety
235    // - `last_element_index` must correspond to the last element in the array.
236    // - After this method is called, the last element must not be used
237    // unless [`Self::initialize_unchecked`] is called to set the value of the last element.
238    pub unsafe fn drop_last_element(&mut self, last_element_index: usize) {
239        #[cfg(debug_assertions)]
240        debug_assert!(self.capacity > last_element_index);
241        if let Some(drop) = self.drop {
242            // We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't
243            // accidentally drop elements twice in the event of a drop impl panicking.
244            self.drop = None;
245            // SAFETY:
246            let item = self.get_unchecked_mut(last_element_index).promote();
247            // SAFETY:
248            unsafe { drop(item) };
249            self.drop = Some(drop);
250        }
251    }
252
253    /// Allocate a block of memory for the array. This should be used to initialize the array, do not use this
254    /// method if there are already elements stored in the array - use [`Self::realloc`] instead.
255    ///
256    /// # Panics
257    /// - Panics if the new capacity overflows `isize::MAX` bytes.
258    /// - Panics if the allocation causes an out-of-memory error.
259    pub(super) fn alloc(&mut self, capacity: NonZeroUsize) {
260        #[cfg(debug_assertions)]
261        debug_assert_eq!(self.capacity, 0);
262        if !self.is_zst() {
263            let new_layout = self.item_layout.repeat_packed(capacity.get());
264            let new_layout = new_layout.expect("array layout should be valid");
265            // SAFETY: layout has non-zero size because capacity > 0, and the blob isn't ZST (`self.is_zst` == false)
266            let new_data = unsafe { alloc::alloc::alloc(new_layout) };
267            self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout));
268        }
269        #[cfg(debug_assertions)]
270        {
271            self.capacity = capacity.into();
272        }
273    }
274
275    /// Reallocate memory for this array.
276    /// For example, if the length (number of stored elements) reached the capacity (number of elements the current allocation can store),
277    /// you might want to use this method to increase the allocation, so more data can be stored in the array.
278    ///
279    /// # Panics
280    /// - Panics if the new capacity overflows `isize::MAX` bytes.
281    /// - Panics if the allocation causes an out-of-memory error.
282    ///
283    /// # Safety
284    /// - `current_capacity` is indeed the current capacity of this array.
285    /// - After calling this method, the caller must update their saved capacity to reflect the change.
286    pub(super) unsafe fn realloc(
287        &mut self,
288        current_capacity: NonZeroUsize,
289        new_capacity: NonZeroUsize,
290    ) {
291        #[cfg(debug_assertions)]
292        debug_assert_eq!(self.capacity, current_capacity.get());
293        if !self.is_zst() {
294            let new_layout = self.item_layout.repeat_packed(new_capacity.get());
295            let new_layout = new_layout.expect("array layout should be valid");
296            let layout = self.item_layout.repeat_packed(current_capacity.get());
297            // SAFETY:
298            // - ptr was be allocated via this allocator
299            // - the layout used to previously allocate this array is equivalent to `self.item_layout.repeat_packed(current_capacity.get())`
300            // - `item_layout.size() > 0` (`self.is_zst`==false) and `new_capacity > 0`, so the layout size is non-zero
301            // - "new_size, when rounded up to the nearest multiple of layout.align(), must not overflow (i.e., the rounded value must be less than usize::MAX)",
302            // since the item size is always a multiple of its align, the rounding cannot happen
303            // here and the overflow is handled in `Layout::repeat_packed`
304            let new_data = unsafe {
305                alloc::alloc::realloc(
306                    self.get_ptr_mut().as_ptr(),
307                    // SAFETY: This is the Layout of the current array, it must be valid, if it hadn't have been, there would have been a panic on a previous allocation
308                    layout.debug_checked_unwrap(),
309                    new_layout.size(),
310                )
311            };
312            self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout));
313        }
314        #[cfg(debug_assertions)]
315        {
316            self.capacity = new_capacity.into();
317        }
318    }
319
320    /// Initializes the value at `index` to `value`. This function does not do any bounds checking.
321    ///
322    /// # Safety
323    /// - `index` must be in bounds (`index` < capacity)
324    /// - The [`Layout`] of the value must match the layout of the blobs stored in this array,
325    ///   and it must be safe to use the `drop` function of this [`BlobArray`] to drop `value`.
326    /// - `value` must not point to the same value that is being initialized.
327    #[inline]
328    pub unsafe fn initialize_unchecked(&mut self, index: usize, value: OwningPtr<'_>) {
329        #[cfg(debug_assertions)]
330        debug_assert!(self.capacity > index);
331        let size = self.item_layout.size();
332        let dst = self.get_unchecked_mut(index);
333        core::ptr::copy::<u8>(value.as_ptr(), dst.as_ptr(), size);
334    }
335
336    /// Replaces the value at `index` with `value`. This function does not do any bounds checking.
337    ///
338    /// # Safety
339    /// - Index must be in-bounds (`index` < `len`)
340    /// - `value`'s [`Layout`] must match this [`BlobArray`]'s `item_layout`,
341    ///   and it must be safe to use the `drop` function of this [`BlobArray`] to drop `value`.
342    /// - `value` must not point to the same value that is being replaced.
343    pub unsafe fn replace_unchecked(&mut self, index: usize, value: OwningPtr<'_>) {
344        #[cfg(debug_assertions)]
345        debug_assert!(self.capacity > index);
346        // Pointer to the value in the vector that will get replaced.
347        // SAFETY: The caller ensures that `index` fits in this vector.
348        let destination = NonNull::from(unsafe { self.get_unchecked_mut(index) });
349        let source = value.as_ptr();
350
351        if let Some(drop) = self.drop {
352            // We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't
353            // accidentally drop elements twice in the event of a drop impl panicking.
354            self.drop = None;
355
356            // Transfer ownership of the old value out of the vector, so it can be dropped.
357            // SAFETY:
358            // - `destination` was obtained from a `PtrMut` in this vector, which ensures it is non-null,
359            //   well-aligned for the underlying type, and has proper provenance.
360            // - The storage location will get overwritten with `value` later, which ensures
361            //   that the element will not get observed or double dropped later.
362            // - If a panic occurs, `self.len` will remain `0`, which ensures a double-drop
363            //   does not occur. Instead, all elements will be forgotten.
364            let old_value = unsafe { OwningPtr::new(destination) };
365
366            // This closure will run in case `drop()` panics,
367            // which ensures that `value` does not get forgotten.
368            let on_unwind = OnDrop::new(|| drop(value));
369
370            drop(old_value);
371
372            // If the above code does not panic, make sure that `value` doesn't get dropped.
373            core::mem::forget(on_unwind);
374
375            self.drop = Some(drop);
376        }
377
378        // Copy the new value into the vector, overwriting the previous value.
379        // SAFETY:
380        // - `source` and `destination` were obtained from `OwningPtr`s, which ensures they are
381        //   valid for both reads and writes.
382        // - The value behind `source` will only be dropped if the above branch panics,
383        //   so it must still be initialized and it is safe to transfer ownership into the vector.
384        // - `source` and `destination` were obtained from different memory locations,
385        //   both of which we have exclusive access to, so they are guaranteed not to overlap.
386        unsafe {
387            core::ptr::copy_nonoverlapping::<u8>(
388                source,
389                destination.as_ptr(),
390                self.item_layout.size(),
391            );
392        }
393    }
394
395    /// This method will swap two elements in the array, and return the one at `index_to_remove`.
396    /// It is the caller's responsibility to drop the returned pointer, if that is desirable.
397    ///
398    /// # Safety
399    /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
400    /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
401    /// -  The caller should address the inconsistent state of the array that has occurred after the swap, either:
402    ///     1) initialize a different value in `index_to_keep`
403    ///     2) update the saved length of the array if `index_to_keep` was the last element.
404    #[inline]
405    #[must_use = "The returned pointer should be used to drop the removed element"]
406    pub unsafe fn swap_remove_unchecked(
407        &mut self,
408        index_to_remove: usize,
409        index_to_keep: usize,
410    ) -> OwningPtr<'_> {
411        #[cfg(debug_assertions)]
412        {
413            debug_assert!(self.capacity > index_to_keep);
414            debug_assert!(self.capacity > index_to_remove);
415        }
416        if index_to_remove != index_to_keep {
417            return self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep);
418        }
419        // Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap)
420        // If we are storing ZSTs than the index doesn't actually matter because the size is 0.
421        self.get_unchecked_mut(index_to_keep).promote()
422    }
423
424    /// The same as [`Self::swap_remove_unchecked`] but the two elements must non-overlapping.
425    ///
426    /// # Safety
427    /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
428    /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
429    /// - `index_to_remove` != `index_to_keep`
430    /// -  The caller should address the inconsistent state of the array that has occurred after the swap, either:
431    ///     1) initialize a different value in `index_to_keep`
432    ///     2) update the saved length of the array if `index_to_keep` was the last element.
433    #[inline]
434    pub unsafe fn swap_remove_unchecked_nonoverlapping(
435        &mut self,
436        index_to_remove: usize,
437        index_to_keep: usize,
438    ) -> OwningPtr<'_> {
439        #[cfg(debug_assertions)]
440        {
441            debug_assert!(self.capacity > index_to_keep);
442            debug_assert!(self.capacity > index_to_remove);
443            debug_assert_ne!(index_to_keep, index_to_remove);
444        }
445        debug_assert_ne!(index_to_keep, index_to_remove);
446        core::ptr::swap_nonoverlapping::<u8>(
447            self.get_unchecked_mut(index_to_keep).as_ptr(),
448            self.get_unchecked_mut(index_to_remove).as_ptr(),
449            self.item_layout.size(),
450        );
451        // Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap)
452        // If we are storing ZSTs than the index doesn't actually matter because the size is 0.
453        self.get_unchecked_mut(index_to_keep).promote()
454    }
455
456    /// This method will call [`Self::swap_remove_unchecked`] and drop the result.
457    ///
458    /// # Safety
459    /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
460    /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
461    /// -  The caller should address the inconsistent state of the array that has occurred after the swap, either:
462    ///     1) initialize a different value in `index_to_keep`
463    ///     2) update the saved length of the array if `index_to_keep` was the last element.
464    #[inline]
465    pub unsafe fn swap_remove_and_drop_unchecked(
466        &mut self,
467        index_to_remove: usize,
468        index_to_keep: usize,
469    ) {
470        #[cfg(debug_assertions)]
471        {
472            debug_assert!(self.capacity > index_to_keep);
473            debug_assert!(self.capacity > index_to_remove);
474        }
475        let drop = self.drop;
476        let value = self.swap_remove_unchecked(index_to_remove, index_to_keep);
477        if let Some(drop) = drop {
478            drop(value);
479        }
480    }
481
482    /// The same as [`Self::swap_remove_and_drop_unchecked`] but the two elements must non-overlapping.
483    ///
484    /// # Safety
485    /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
486    /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
487    /// - `index_to_remove` != `index_to_keep`
488    /// -  The caller should address the inconsistent state of the array that has occurred after the swap, either:
489    ///     1) initialize a different value in `index_to_keep`
490    ///     2) update the saved length of the array if `index_to_keep` was the last element.
491    #[inline]
492    pub unsafe fn swap_remove_and_drop_unchecked_nonoverlapping(
493        &mut self,
494        index_to_remove: usize,
495        index_to_keep: usize,
496    ) {
497        #[cfg(debug_assertions)]
498        {
499            debug_assert!(self.capacity > index_to_keep);
500            debug_assert!(self.capacity > index_to_remove);
501            debug_assert_ne!(index_to_keep, index_to_remove);
502        }
503        let drop = self.drop;
504        let value = self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep);
505        if let Some(drop) = drop {
506            drop(value);
507        }
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use bevy_ecs::prelude::*;
514
515    #[derive(Component)]
516    struct PanicOnDrop;
517
518    impl Drop for PanicOnDrop {
519        fn drop(&mut self) {
520            panic!("PanicOnDrop is being Dropped");
521        }
522    }
523
524    #[test]
525    #[should_panic(expected = "PanicOnDrop is being Dropped")]
526    fn make_sure_zst_components_get_dropped() {
527        let mut world = World::new();
528
529        world.spawn(PanicOnDrop);
530    }
531}