Thanks to visit codestin.com
Credit goes to docs.rs

bevy_ecs/storage/
blob_array.rs

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