Skip to main content

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}