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 // SAFETY: `new_capacity` can't overflow usize
262 let new_layout =
263 unsafe { array_layout_unchecked(&self.item_layout, new_capacity.get()) };
264 // SAFETY:
265 // - ptr was be allocated via this allocator
266 // - the layout used to previously allocate this array is equivalent to `array_layout(&self.item_layout, current_capacity.get())`
267 // - `item_layout.size() > 0` (`self.is_zst`==false) and `new_capacity > 0`, so the layout size is non-zero
268 // - "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)",
269 // since the item size is always a multiple of its align, the rounding cannot happen
270 // here and the overflow is handled in `array_layout`
271 let new_data = unsafe {
272 alloc::alloc::realloc(
273 self.get_ptr_mut().as_ptr(),
274 // 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
275 array_layout_unchecked(&self.item_layout, current_capacity.get()),
276 new_layout.size(),
277 )
278 };
279 self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout));
280 }
281 #[cfg(debug_assertions)]
282 {
283 self.capacity = new_capacity.into();
284 }
285 }
286
287 /// Initializes the value at `index` to `value`. This function does not do any bounds checking.
288 ///
289 /// # Safety
290 /// - `index` must be in bounds (`index` < capacity)
291 /// - The [`Layout`] of the value must match the layout of the blobs stored in this array,
292 /// and it must be safe to use the `drop` function of this [`BlobArray`] to drop `value`.
293 /// - `value` must not point to the same value that is being initialized.
294 #[inline]
295 pub unsafe fn initialize_unchecked(&mut self, index: usize, value: OwningPtr<'_>) {
296 #[cfg(debug_assertions)]
297 debug_assert!(self.capacity > index);
298 let size = self.item_layout.size();
299 let dst = self.get_unchecked_mut(index);
300 core::ptr::copy::<u8>(value.as_ptr(), dst.as_ptr(), size);
301 }
302
303 /// Replaces the value at `index` with `value`. This function does not do any bounds checking.
304 ///
305 /// # Safety
306 /// - Index must be in-bounds (`index` < `len`)
307 /// - `value`'s [`Layout`] must match this [`BlobArray`]'s `item_layout`,
308 /// and it must be safe to use the `drop` function of this [`BlobArray`] to drop `value`.
309 /// - `value` must not point to the same value that is being replaced.
310 pub unsafe fn replace_unchecked(&mut self, index: usize, value: OwningPtr<'_>) {
311 #[cfg(debug_assertions)]
312 debug_assert!(self.capacity > index);
313 // Pointer to the value in the vector that will get replaced.
314 // SAFETY: The caller ensures that `index` fits in this vector.
315 let destination = NonNull::from(unsafe { self.get_unchecked_mut(index) });
316 let source = value.as_ptr();
317
318 if let Some(drop) = self.drop {
319 // We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't
320 // accidentally drop elements twice in the event of a drop impl panicking.
321 self.drop = None;
322
323 // Transfer ownership of the old value out of the vector, so it can be dropped.
324 // SAFETY:
325 // - `destination` was obtained from a `PtrMut` in this vector, which ensures it is non-null,
326 // well-aligned for the underlying type, and has proper provenance.
327 // - The storage location will get overwritten with `value` later, which ensures
328 // that the element will not get observed or double dropped later.
329 // - If a panic occurs, `self.len` will remain `0`, which ensures a double-drop
330 // does not occur. Instead, all elements will be forgotten.
331 let old_value = unsafe { OwningPtr::new(destination) };
332
333 // This closure will run in case `drop()` panics,
334 // which ensures that `value` does not get forgotten.
335 let on_unwind = OnDrop::new(|| drop(value));
336
337 drop(old_value);
338
339 // If the above code does not panic, make sure that `value` doesn't get dropped.
340 core::mem::forget(on_unwind);
341
342 self.drop = Some(drop);
343 }
344
345 // Copy the new value into the vector, overwriting the previous value.
346 // SAFETY:
347 // - `source` and `destination` were obtained from `OwningPtr`s, which ensures they are
348 // valid for both reads and writes.
349 // - The value behind `source` will only be dropped if the above branch panics,
350 // so it must still be initialized and it is safe to transfer ownership into the vector.
351 // - `source` and `destination` were obtained from different memory locations,
352 // both of which we have exclusive access to, so they are guaranteed not to overlap.
353 unsafe {
354 core::ptr::copy_nonoverlapping::<u8>(
355 source,
356 destination.as_ptr(),
357 self.item_layout.size(),
358 );
359 }
360 }
361
362 /// This method will swap two elements in the array, and return the one at `index_to_remove`.
363 /// It is the caller's responsibility to drop the returned pointer, if that is desirable.
364 ///
365 /// # Safety
366 /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
367 /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
368 /// - `index_to_remove` != `index_to_keep`
369 /// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
370 /// 1) initialize a different value in `index_to_keep`
371 /// 2) update the saved length of the array if `index_to_keep` was the last element.
372 #[inline]
373 #[must_use = "The returned pointer should be used to drop the removed element"]
374 pub unsafe fn swap_remove_unchecked(
375 &mut self,
376 index_to_remove: usize,
377 index_to_keep: usize,
378 ) -> OwningPtr<'_> {
379 #[cfg(debug_assertions)]
380 {
381 debug_assert!(self.capacity > index_to_keep);
382 debug_assert!(self.capacity > index_to_remove);
383 }
384 if index_to_remove != index_to_keep {
385 return self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep);
386 }
387 // Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap)
388 // If we are storing ZSTs than the index doesn't actually matter because the size is 0.
389 self.get_unchecked_mut(index_to_keep).promote()
390 }
391
392 /// The same as [`Self::swap_remove_unchecked`] but the two elements must non-overlapping.
393 ///
394 /// # Safety
395 /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
396 /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
397 /// - `index_to_remove` != `index_to_keep`
398 /// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
399 /// 1) initialize a different value in `index_to_keep`
400 /// 2) update the saved length of the array if `index_to_keep` was the last element.
401 #[inline]
402 pub unsafe fn swap_remove_unchecked_nonoverlapping(
403 &mut self,
404 index_to_remove: usize,
405 index_to_keep: usize,
406 ) -> OwningPtr<'_> {
407 #[cfg(debug_assertions)]
408 {
409 debug_assert!(self.capacity > index_to_keep);
410 debug_assert!(self.capacity > index_to_remove);
411 debug_assert_ne!(index_to_keep, index_to_remove);
412 }
413 debug_assert_ne!(index_to_keep, index_to_remove);
414 core::ptr::swap_nonoverlapping::<u8>(
415 self.get_unchecked_mut(index_to_keep).as_ptr(),
416 self.get_unchecked_mut(index_to_remove).as_ptr(),
417 self.item_layout.size(),
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 /// This method will call [`Self::swap_remove_unchecked`] and drop the result.
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_and_drop_unchecked(
435 &mut self,
436 index_to_remove: usize,
437 index_to_keep: usize,
438 ) {
439 #[cfg(debug_assertions)]
440 {
441 debug_assert!(self.capacity > index_to_keep);
442 debug_assert!(self.capacity > index_to_remove);
443 }
444 let drop = self.drop;
445 let value = self.swap_remove_unchecked(index_to_remove, index_to_keep);
446 if let Some(drop) = drop {
447 drop(value);
448 }
449 }
450
451 /// The same as [`Self::swap_remove_and_drop_unchecked`] but the two elements must non-overlapping.
452 ///
453 /// # Safety
454 /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
455 /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
456 /// - `index_to_remove` != `index_to_keep`
457 /// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
458 /// 1) initialize a different value in `index_to_keep`
459 /// 2) update the saved length of the array if `index_to_keep` was the last element.
460 #[inline]
461 pub unsafe fn swap_remove_and_drop_unchecked_nonoverlapping(
462 &mut self,
463 index_to_remove: usize,
464 index_to_keep: usize,
465 ) {
466 #[cfg(debug_assertions)]
467 {
468 debug_assert!(self.capacity > index_to_keep);
469 debug_assert!(self.capacity > index_to_remove);
470 debug_assert_ne!(index_to_keep, index_to_remove);
471 }
472 let drop = self.drop;
473 let value = self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep);
474 if let Some(drop) = drop {
475 drop(value);
476 }
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use bevy_ecs::prelude::*;
483
484 #[derive(Component)]
485 struct PanicOnDrop;
486
487 impl Drop for PanicOnDrop {
488 fn drop(&mut self) {
489 panic!("PanicOnDrop is being Dropped");
490 }
491 }
492
493 #[test]
494 #[should_panic(expected = "PanicOnDrop is being Dropped")]
495 fn make_sure_zst_components_get_dropped() {
496 let mut world = World::new();
497
498 world.spawn(PanicOnDrop);
499 }
500}