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 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            {
    ::core::panicking::panic_fmt(format_args!("Attempted to access or drop non-send resource {0} from thread {1:?} on a thread {2:?}. This is not allowed. Aborting.",
            self.type_name, self.origin_thread_id,
            std::thread::current().id()));
};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(#[automatically_derived]
impl ::core::default::Default for NonSends {
    #[inline]
    fn default() -> NonSends {
        NonSends { non_sends: ::core::default::Default::default() }
    }
}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}