bevy_ecs/storage/thin_array_ptr.rs
1use crate::query::DebugCheckedUnwrap;
2use alloc::{
3 alloc::{alloc, handle_alloc_error, realloc},
4 boxed::Box,
5};
6use core::{
7 alloc::Layout,
8 mem::{needs_drop, size_of},
9 num::NonZeroUsize,
10 ptr::{self, NonNull},
11};
12
13/// Similar to [`Vec<T>`], but with the capacity and length cut out for performance reasons.
14///
15/// This type can be treated as a `ManuallyDrop<Box<[T]>>` without a built in length. To avoid
16/// memory leaks, [`drop`](Self::drop) must be called when no longer in use.
17///
18/// [`Vec<T>`]: alloc::vec::Vec
19pub struct ThinArrayPtr<T> {
20 data: NonNull<T>,
21 #[cfg(debug_assertions)]
22 capacity: usize,
23}
24
25impl<T> ThinArrayPtr<T> {
26 fn empty() -> Self {
27 #[cfg(debug_assertions)]
28 {
29 Self {
30 data: NonNull::dangling(),
31 capacity: 0,
32 }
33 }
34 #[cfg(not(debug_assertions))]
35 {
36 Self {
37 data: NonNull::dangling(),
38 }
39 }
40 }
41
42 #[inline(always)]
43 fn set_capacity(&mut self, _capacity: usize) {
44 #[cfg(debug_assertions)]
45 {
46 self.capacity = _capacity;
47 }
48 }
49
50 /// Create a new [`ThinArrayPtr`] with a given capacity. If the `capacity` is 0, this will no allocate any memory.
51 #[inline]
52 pub fn with_capacity(capacity: usize) -> Self {
53 let mut arr = Self::empty();
54 if capacity > 0 {
55 // SAFETY:
56 // - The `current_capacity` is 0 because it was just created
57 unsafe { arr.alloc(NonZeroUsize::new_unchecked(capacity)) };
58 }
59 arr
60 }
61
62 /// Allocate memory for the array, this should only be used if not previous allocation has been made (capacity = 0)
63 /// The caller should update their saved `capacity` value to reflect the fact that it was changed
64 ///
65 /// # Panics
66 /// - Panics if the new capacity overflows `usize`
67 pub fn alloc(&mut self, capacity: NonZeroUsize) {
68 self.set_capacity(capacity.get());
69 if size_of::<T>() != 0 {
70 let new_layout = Layout::array::<T>(capacity.get())
71 .expect("layout should be valid (arithmetic overflow)");
72 // SAFETY:
73 // - layout has non-zero size, `capacity` > 0, `size` > 0 (`size_of::<T>() != 0`)
74 self.data = NonNull::new(unsafe { alloc(new_layout) })
75 .unwrap_or_else(|| handle_alloc_error(new_layout))
76 .cast();
77 }
78 }
79
80 /// Reallocate memory for the array, this should only be used if a previous allocation for this array has been made (capacity > 0).
81 ///
82 /// # Panics
83 /// - Panics if the new capacity overflows `usize`
84 ///
85 /// # Safety
86 /// - The current capacity is indeed greater than 0
87 /// - The caller should update their saved `capacity` value to reflect the fact that it was changed
88 pub unsafe fn realloc(&mut self, current_capacity: NonZeroUsize, new_capacity: NonZeroUsize) {
89 #[cfg(debug_assertions)]
90 assert_eq!(self.capacity, current_capacity.get());
91 self.set_capacity(new_capacity.get());
92 if size_of::<T>() != 0 {
93 let new_layout =
94 Layout::array::<T>(new_capacity.get()).expect("overflow while allocating memory");
95 // SAFETY:
96 // - ptr was be allocated via this allocator
97 // - the layout of the array is the same as `Layout::array::<T>(current_capacity)`
98 // - the size of `T` is non 0, and `new_capacity` > 0
99 // - "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)",
100 // since the item size is always a multiple of its align, the rounding cannot happen
101 // here and the overflow is handled in `Layout::array`
102 self.data = NonNull::new(unsafe {
103 realloc(
104 self.data.cast().as_ptr(),
105 // We can use `unwrap_unchecked` because this is the Layout of the current allocation, it must be valid
106 Layout::array::<T>(current_capacity.get()).debug_checked_unwrap(),
107 new_layout.size(),
108 )
109 })
110 .unwrap_or_else(|| handle_alloc_error(new_layout))
111 .cast();
112 }
113 }
114
115 /// Initializes the value at `index` to `value`. This function does not do any bounds checking.
116 ///
117 /// # Safety
118 /// `index` must be in bounds i.e. within the `capacity`.
119 /// if `index` = `len` the caller should update their saved `len` value to reflect the fact that it was changed
120 #[inline]
121 pub unsafe fn initialize_unchecked(&mut self, index: usize, value: T) {
122 // SAFETY: `index` is in bounds
123 let ptr = unsafe { self.get_unchecked_raw(index) };
124 // SAFETY: `index` is in bounds, therefore the pointer to that location in the array is valid, and aligned.
125 unsafe { ptr::write(ptr, value) };
126 }
127
128 /// Get a raw pointer to the element at `index`. This method doesn't do any bounds checking.
129 ///
130 /// # Safety
131 /// - `index` must be safe to access.
132 #[inline]
133 pub unsafe fn get_unchecked_raw(&mut self, index: usize) -> *mut T {
134 // SAFETY:
135 // - `self.data` and the resulting pointer are in the same allocated object
136 // - the memory address of the last element doesn't overflow `isize`, so if `index` is in bounds, it won't overflow either
137 unsafe { self.data.as_ptr().add(index) }
138 }
139
140 /// Get a reference to the element at `index`. This method doesn't do any bounds checking.
141 ///
142 /// # Safety
143 /// - `index` must be safe to read.
144 #[inline]
145 pub unsafe fn get_unchecked(&self, index: usize) -> &'_ T {
146 // SAFETY:
147 // - `self.data` and the resulting pointer are in the same allocated object
148 // - the memory address of the last element doesn't overflow `isize`, so if `index` is in bounds, it won't overflow either
149 let ptr = unsafe { self.data.as_ptr().add(index) };
150 // SAFETY:
151 // - The pointer is properly aligned
152 // - It is dereferenceable (all in the same allocation)
153 // - `index` < `len` and the element is safe to write to, so its valid
154 // - We have a reference to self, so no other mutable accesses to the element can occur
155 unsafe {
156 ptr.as_ref()
157 // SAFETY: We can use `unwarp_unchecked` because the pointer isn't null)
158 .debug_checked_unwrap()
159 }
160 }
161
162 /// Get a mutable reference to the element at `index`. This method doesn't do any bounds checking.
163 ///
164 /// # Safety
165 /// - `index` must be safe to write to.
166 #[inline]
167 pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> &'_ mut T {
168 // SAFETY:
169 // - `self.data` and the resulting pointer are in the same allocated object
170 // - the memory address of the last element doesn't overflow `isize`, so if `index` is in bounds, it won't overflow either
171 let ptr = unsafe { self.data.as_ptr().add(index) };
172 // SAFETY:
173 // - The pointer is properly aligned
174 // - It is dereferenceable (all in the same allocation)
175 // - `index` < `len` and the element is safe to write to, so its valid
176 // - We have a mutable reference to `self`
177 unsafe {
178 ptr.as_mut()
179 // SAFETY: We can use `unwarp_unchecked` because the pointer isn't null)
180 .unwrap_unchecked()
181 }
182 }
183
184 /// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and return the removed value.
185 ///
186 /// # Safety
187 /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
188 /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
189 /// - `index_to_remove` != `index_to_keep`
190 /// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
191 /// 1) initialize a different value in `index_to_keep`
192 /// 2) update the saved length of the array if `index_to_keep` was the last element.
193 #[inline]
194 pub unsafe fn swap_remove_unchecked_nonoverlapping(
195 &mut self,
196 index_to_remove: usize,
197 index_to_keep: usize,
198 ) -> T {
199 #[cfg(debug_assertions)]
200 {
201 debug_assert!(self.capacity > index_to_keep);
202 debug_assert!(self.capacity > index_to_remove);
203 debug_assert_ne!(index_to_keep, index_to_remove);
204 }
205 let base_ptr = self.data.as_ptr();
206 let value = ptr::read(base_ptr.add(index_to_remove));
207 ptr::copy_nonoverlapping(
208 base_ptr.add(index_to_keep),
209 base_ptr.add(index_to_remove),
210 1,
211 );
212 value
213 }
214
215 /// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and return the removed value.
216 ///
217 /// # Safety
218 /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
219 /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
220 /// - `index_to_remove` != `index_to_keep`
221 /// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
222 /// 1) initialize a different value in `index_to_keep`
223 /// 2) update the saved length of the array if `index_to_keep` was the last element.
224 #[inline]
225 pub unsafe fn swap_remove_unchecked(
226 &mut self,
227 index_to_remove: usize,
228 index_to_keep: usize,
229 ) -> T {
230 if index_to_remove != index_to_keep {
231 return self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep);
232 }
233 ptr::read(self.data.as_ptr().add(index_to_remove))
234 }
235
236 /// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and drop the removed value.
237 ///
238 /// # Safety
239 /// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
240 /// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
241 /// - `index_to_remove` != `index_to_keep`
242 /// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
243 /// 1) initialize a different value in `index_to_keep`
244 /// 2) update the saved length of the array if `index_to_keep` was the last element.
245 #[inline]
246 pub unsafe fn swap_remove_and_drop_unchecked(
247 &mut self,
248 index_to_remove: usize,
249 index_to_keep: usize,
250 ) {
251 let val = &mut self.swap_remove_unchecked(index_to_remove, index_to_keep);
252 ptr::drop_in_place(ptr::from_mut(val));
253 }
254
255 /// Get a raw pointer to the last element of the array, return `None` if the length is 0
256 ///
257 /// # Safety
258 /// - ensure that `current_len` is indeed the len of the array
259 #[inline]
260 unsafe fn last_element(&mut self, current_len: usize) -> Option<*mut T> {
261 (current_len != 0).then_some(self.data.as_ptr().add(current_len - 1))
262 }
263
264 /// Clears the array, removing (and dropping) Note that this method has no effect on the allocated capacity of the vector.
265 ///
266 /// # Safety
267 /// - `current_len` is indeed the length of the array
268 /// - The caller should update their saved length value
269 pub unsafe fn clear_elements(&mut self, mut current_len: usize) {
270 if needs_drop::<T>() {
271 while let Some(to_drop) = self.last_element(current_len) {
272 ptr::drop_in_place(to_drop);
273 current_len -= 1;
274 }
275 }
276 }
277
278 /// Drop the entire array and all its elements.
279 ///
280 /// # Safety
281 /// - `current_len` is indeed the length of the array
282 /// - `current_capacity` is indeed the capacity of the array
283 /// - The caller must not use this `ThinArrayPtr` in any way after calling this function
284 pub unsafe fn drop(&mut self, current_capacity: usize, current_len: usize) {
285 #[cfg(debug_assertions)]
286 assert_eq!(self.capacity, current_capacity);
287 if current_capacity != 0 {
288 self.clear_elements(current_len);
289 let layout = Layout::array::<T>(current_capacity).expect("layout should be valid");
290 alloc::alloc::dealloc(self.data.as_ptr().cast(), layout);
291 }
292 self.set_capacity(0);
293 }
294
295 /// Get the [`ThinArrayPtr`] as a slice with a given length.
296 ///
297 /// # Safety
298 /// - `slice_len` must match the actual length of the array
299 #[inline]
300 pub unsafe fn as_slice(&self, slice_len: usize) -> &[T] {
301 // SAFETY:
302 // - the data is valid - allocated with the same allocator
303 // - non-null and well-aligned
304 // - we have a shared reference to self - the data will not be mutated during 'a
305 unsafe { core::slice::from_raw_parts(self.data.as_ptr(), slice_len) }
306 }
307}
308
309impl<T> From<Box<[T]>> for ThinArrayPtr<T> {
310 fn from(value: Box<[T]>) -> Self {
311 let _len = value.len();
312 let slice_ptr = Box::<[T]>::into_raw(value);
313 // SAFETY: We just got the pointer from a reference
314 let first_element_ptr = unsafe { (*slice_ptr).as_mut_ptr() };
315 Self {
316 // SAFETY: The pointer can't be null, it came from a reference
317 data: unsafe { NonNull::new_unchecked(first_element_ptr) },
318 #[cfg(debug_assertions)]
319 capacity: _len,
320 }
321 }
322}