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