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