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