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