Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/storage/non_send.rs
30636 views
1
use crate::{
2
change_detection::{CheckChangeTicks, ComponentTickCells, ComponentTicks, MaybeLocation, Tick},
3
component::{ComponentId, Components},
4
storage::{blob_array::BlobArray, SparseSet},
5
};
6
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
7
use bevy_utils::prelude::DebugName;
8
use core::{cell::UnsafeCell, panic::Location};
9
10
#[cfg(feature = "std")]
11
use std::thread::ThreadId;
12
13
/// The type-erased backing storage and metadata for one instance of non send data within a [`World`].
14
///
15
/// Values of this type will panic if dropped from a different thread.
16
///
17
/// [`World`]: crate::world::World
18
pub struct NonSendData {
19
/// Capacity is 1, length is 1 if `is_present` and 0 otherwise.
20
data: BlobArray,
21
is_present: bool,
22
added_ticks: UnsafeCell<Tick>,
23
changed_ticks: UnsafeCell<Tick>,
24
#[cfg_attr(
25
not(feature = "std"),
26
expect(dead_code, reason = "currently only used with the std feature")
27
)]
28
type_name: DebugName,
29
#[cfg(feature = "std")]
30
origin_thread_id: Option<ThreadId>,
31
changed_by: MaybeLocation<UnsafeCell<&'static Location<'static>>>,
32
}
33
34
impl Drop for NonSendData {
35
fn drop(&mut self) {
36
// We need to validate that correct thread is dropping the data.
37
// This validation is not needed if there is no data.
38
if self.is_present() {
39
// If this thread is already panicking, panicking again will cause
40
// the entire process to abort. In this case we choose to avoid
41
// dropping or checking this altogether and just leak the column.
42
#[cfg(feature = "std")]
43
if std::thread::panicking() {
44
return;
45
}
46
self.validate_access();
47
}
48
// SAFETY: Drop is only called once upon dropping the NonSendData
49
// and is inaccessible after this as the parent NonSendData has
50
// been dropped. The validate_access call above will check that the
51
// data is dropped on the thread it was inserted from.
52
unsafe {
53
self.data.drop(1, self.is_present().into());
54
}
55
}
56
}
57
58
impl NonSendData {
59
/// The only row in the underlying `BlobArray`.
60
const ROW: usize = 0;
61
62
/// Validates that the access to `NonSendData` is only done on the thread they were created from.
63
///
64
/// # Panics
65
/// This will panic if called from a different thread than the one it was inserted from.
66
#[inline]
67
fn validate_access(&self) {
68
#[cfg(feature = "std")]
69
if self.origin_thread_id != Some(std::thread::current().id()) {
70
// Panic in tests, as testing for aborting is nearly impossible
71
panic!(
72
"Attempted to access or drop non-send data {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.",
73
self.type_name,
74
self.origin_thread_id,
75
std::thread::current().id()
76
);
77
}
78
79
// TODO: Handle no_std non-send.
80
// Currently, no_std is single-threaded only, so this is safe to ignore.
81
// To support no_std multithreading, an alternative will be required.
82
// Remove the #[expect] attribute above when this is addressed.
83
}
84
85
/// Returns true if the data is populated.
86
#[inline]
87
pub fn is_present(&self) -> bool {
88
self.is_present
89
}
90
91
/// Returns a reference to the data, if it exists.
92
///
93
/// # Panics
94
/// This will panic if a value is present and is not accessed from the original thread it was inserted from.
95
#[inline]
96
pub fn get_data(&self) -> Option<Ptr<'_>> {
97
self.is_present().then(|| {
98
self.validate_access();
99
// SAFETY: We've already checked if a value is present, and there should only be one.
100
unsafe { self.data.get_unchecked(Self::ROW) }
101
})
102
}
103
104
/// Returns a reference to the data's change ticks, if it exists.
105
#[inline]
106
pub fn get_ticks(&self) -> Option<ComponentTicks> {
107
// SAFETY: This is being fetched through a read-only reference to Self, so no other mutable references
108
// to the ticks can exist.
109
unsafe {
110
self.is_present().then(|| ComponentTicks {
111
added: self.added_ticks.read(),
112
changed: self.changed_ticks.read(),
113
})
114
}
115
}
116
117
/// Returns references to the data and its change ticks, if it exists.
118
///
119
/// # Panics
120
/// This will panic if a value is present and is not accessed from the original thread it was inserted in.
121
#[inline]
122
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, ComponentTickCells<'_>)> {
123
self.is_present().then(|| {
124
self.validate_access();
125
(
126
// SAFETY: We've already checked if a value is present, and there should only be one.
127
unsafe { self.data.get_unchecked(Self::ROW) },
128
ComponentTickCells {
129
added: &self.added_ticks,
130
changed: &self.changed_ticks,
131
changed_by: self.changed_by.as_ref(),
132
},
133
)
134
})
135
}
136
137
/// Inserts a value into the non-send data. If a value is already present
138
/// it will be replaced.
139
///
140
/// # Panics
141
/// This will panic if a value is present and is not replaced from the original thread it was inserted in.
142
///
143
/// # Safety
144
/// - `value` must be valid for the underlying type for the data.
145
#[inline]
146
pub(crate) unsafe fn insert(
147
&mut self,
148
value: OwningPtr<'_>,
149
change_tick: Tick,
150
caller: MaybeLocation,
151
) {
152
if self.is_present() {
153
self.validate_access();
154
// SAFETY: The caller ensures that the provided value is valid for the underlying type and
155
// is properly initialized. We've ensured that a value is already present and previously
156
// initialized.
157
unsafe { self.data.replace_unchecked(Self::ROW, value) };
158
} else {
159
#[cfg(feature = "std")]
160
{
161
self.origin_thread_id = Some(std::thread::current().id());
162
}
163
// SAFETY:
164
// - There is only one element, and it's always allocated.
165
// - The caller guarantees must be valid for the underlying type and thus its
166
// layout must be identical.
167
// - The value was previously not present and thus must not have been initialized.
168
unsafe { self.data.initialize_unchecked(Self::ROW, value) };
169
*self.added_ticks.deref_mut() = change_tick;
170
self.is_present = true;
171
}
172
*self.changed_ticks.deref_mut() = change_tick;
173
174
self.changed_by
175
.as_ref()
176
.map(|changed_by| changed_by.deref_mut())
177
.assign(caller);
178
}
179
180
/// Removes a value from the data, if present.
181
///
182
/// # Panics
183
/// This will panic if a value is present and is not removed from the original thread it was inserted from.
184
#[inline]
185
#[must_use = "The returned pointer to the removed component should be used or dropped"]
186
pub(crate) fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks, MaybeLocation)> {
187
if !self.is_present() {
188
return None;
189
}
190
self.validate_access();
191
192
self.is_present = false;
193
194
// SAFETY:
195
// - There is always only one row in the `BlobArray` created during initialization.
196
// - This function has validated that the row is present with the check of `self.is_present`.
197
// - The caller is to take ownership of the value, returned as a `OwningPtr`.
198
let res = unsafe { self.data.get_unchecked_mut(Self::ROW).promote() };
199
200
let caller = self
201
.changed_by
202
.as_ref()
203
// SAFETY: This function is being called through an exclusive mutable reference to Self
204
.map(|changed_by| unsafe { *changed_by.deref_mut() });
205
206
// SAFETY: This function is being called through an exclusive mutable reference to Self, which
207
// makes it sound to read these ticks.
208
unsafe {
209
Some((
210
res,
211
ComponentTicks {
212
added: self.added_ticks.read(),
213
changed: self.changed_ticks.read(),
214
},
215
caller,
216
))
217
}
218
}
219
220
/// Removes a value from the data, if present, and drops it.
221
///
222
/// # Panics
223
/// This will panic if a value is present and is not accessed from the original thread it was inserted in.
224
#[inline]
225
pub(crate) fn remove_and_drop(&mut self) {
226
if self.is_present() {
227
self.validate_access();
228
// SAFETY: There is only one element, and it's always allocated.
229
unsafe { self.data.drop_last_element(Self::ROW) };
230
self.is_present = false;
231
}
232
}
233
234
pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {
235
self.added_ticks.get_mut().check_tick(check);
236
self.changed_ticks.get_mut().check_tick(check);
237
}
238
}
239
240
/// The backing store for all non send data stored in the [`World`].
241
///
242
/// [`World`]: crate::world::World
243
#[derive(Default)]
244
pub struct NonSends {
245
non_sends: SparseSet<ComponentId, NonSendData>,
246
}
247
248
impl NonSends {
249
/// The total amount of `!Send` data stored in the [`World`]
250
///
251
/// [`World`]: crate::world::World
252
#[inline]
253
pub fn len(&self) -> usize {
254
self.non_sends.len()
255
}
256
257
/// Iterate over all non send data that have been initialized, i.e. given a [`ComponentId`]
258
pub fn iter(&self) -> impl Iterator<Item = (ComponentId, &NonSendData)> {
259
self.non_sends.iter().map(|(id, data)| (*id, data))
260
}
261
262
/// Returns true if there is no `!Send` data stored in the [`World`],
263
/// false otherwise.
264
///
265
/// [`World`]: crate::world::World
266
#[inline]
267
pub fn is_empty(&self) -> bool {
268
self.non_sends.is_empty()
269
}
270
271
/// Gets read-only access to some `!Send` data, if it exists.
272
#[inline]
273
pub fn get(&self, component_id: ComponentId) -> Option<&NonSendData> {
274
self.non_sends.get(component_id)
275
}
276
277
/// Clears all non send data.
278
#[inline]
279
pub fn clear(&mut self) {
280
self.non_sends.clear();
281
}
282
283
/// Gets mutable access to `!Send` data, if it exists.
284
#[inline]
285
pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut NonSendData> {
286
self.non_sends.get_mut(component_id)
287
}
288
289
/// Fetches or initializes new `!Send` data and returns back its underlying column.
290
///
291
/// # Panics
292
/// Will panic if `component_id` is not valid for the provided `components`
293
pub(crate) fn initialize_with(
294
&mut self,
295
component_id: ComponentId,
296
components: &Components,
297
) -> &mut NonSendData {
298
self.non_sends.get_or_insert_with(component_id, || {
299
let component_info = components.get_info(component_id).unwrap();
300
// SAFETY:
301
// * component_info.drop() is valid for the types that will be inserted.
302
// * `ComponentInfo` ensures that `layout().size()` is a multiple of `layout().align()`
303
let data = unsafe {
304
BlobArray::with_capacity(component_info.layout(), component_info.drop(), 1)
305
};
306
NonSendData {
307
data,
308
is_present: false,
309
added_ticks: UnsafeCell::new(Tick::new(0)),
310
changed_ticks: UnsafeCell::new(Tick::new(0)),
311
type_name: component_info.name(),
312
#[cfg(feature = "std")]
313
origin_thread_id: None,
314
changed_by: MaybeLocation::caller().map(UnsafeCell::new),
315
}
316
})
317
}
318
319
pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {
320
for info in self.non_sends.values_mut() {
321
info.check_change_ticks(check);
322
}
323
}
324
}
325
326