bevy_ecs/storage/
resource.rs

1use crate::{
2    change_detection::{
3        CheckChangeTicks, ComponentTickCells, ComponentTicks, ComponentTicksMut, MaybeLocation,
4        MutUntyped, Tick,
5    },
6    component::{ComponentId, Components},
7    storage::{blob_array::BlobArray, SparseSet},
8};
9use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
10use bevy_utils::prelude::DebugName;
11use core::{cell::UnsafeCell, panic::Location};
12
13#[cfg(feature = "std")]
14use std::thread::ThreadId;
15
16/// The type-erased backing storage and metadata for a single resource within a [`World`].
17///
18/// If `SEND` is false, values of this type will panic if dropped from a different thread.
19///
20/// [`World`]: crate::world::World
21pub struct ResourceData<const SEND: bool> {
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<const SEND: bool> Drop for ResourceData<SEND> {
38    fn drop(&mut self) {
39        // For Non Send resources we need to validate that correct thread
40        // is dropping the resource. This validation is not needed in case
41        // of SEND resources. Or if there is no data.
42        if !SEND && self.is_present() {
43            // If this thread is already panicking, panicking again will cause
44            // the entire process to abort. In this case we choose to avoid
45            // dropping or checking this altogether and just leak the column.
46            #[cfg(feature = "std")]
47            if std::thread::panicking() {
48                return;
49            }
50            self.validate_access();
51        }
52        // SAFETY: Drop is only called once upon dropping the ResourceData
53        // and is inaccessible after this as the parent ResourceData has
54        // been dropped. The validate_access call above will check that the
55        // data is dropped on the thread it was inserted from.
56        unsafe {
57            self.data.drop(1, self.is_present().into());
58        }
59    }
60}
61
62impl<const SEND: bool> ResourceData<SEND> {
63    /// The only row in the underlying `BlobArray`.
64    const ROW: usize = 0;
65
66    /// Validates the access to `!Send` resources is only done on the thread they were created from.
67    ///
68    /// # Panics
69    /// If `SEND` is false, this will panic if called from a different thread than the one it was inserted from.
70    #[inline]
71    fn validate_access(&self) {
72        if !SEND {
73            #[cfg(feature = "std")]
74            if self.origin_thread_id != Some(std::thread::current().id()) {
75                // Panic in tests, as testing for aborting is nearly impossible
76                panic!(
77                    "Attempted to access or drop non-send resource {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.",
78                    self.type_name,
79                    self.origin_thread_id,
80                    std::thread::current().id()
81                );
82            }
83
84            // TODO: Handle no_std non-send.
85            // Currently, no_std is single-threaded only, so this is safe to ignore.
86            // To support no_std multithreading, an alternative will be required.
87            // Remove the #[expect] attribute above when this is addressed.
88        }
89    }
90
91    /// Returns true if the resource is populated.
92    #[inline]
93    pub fn is_present(&self) -> bool {
94        self.is_present
95    }
96
97    /// Returns a reference to the resource, if it exists.
98    ///
99    /// # Panics
100    /// If `SEND` is false, this will panic if a value is present and is not accessed from the
101    /// 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 resource'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 resource and its change ticks, if it exists.
125    ///
126    /// # Panics
127    /// If `SEND` is false, this will panic if a value is present and is not accessed from the
128    /// original thread it was inserted in.
129    #[inline]
130    pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, ComponentTickCells<'_>)> {
131        self.is_present().then(|| {
132            self.validate_access();
133            (
134                // SAFETY: We've already checked if a value is present, and there should only be one.
135                unsafe { self.data.get_unchecked(Self::ROW) },
136                ComponentTickCells {
137                    added: &self.added_ticks,
138                    changed: &self.changed_ticks,
139                    changed_by: self.changed_by.as_ref(),
140                },
141            )
142        })
143    }
144
145    /// Returns a mutable reference to the resource, if it exists.
146    ///
147    /// # Panics
148    /// If `SEND` is false, this will panic if a value is present and is not accessed from the
149    /// original thread it was inserted in.
150    pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option<MutUntyped<'_>> {
151        let (ptr, ticks) = self.get_with_ticks()?;
152        Some(MutUntyped {
153            // SAFETY: We have exclusive access to the underlying storage.
154            value: unsafe { ptr.assert_unique() },
155            // SAFETY: We have exclusive access to the underlying storage.
156            ticks: unsafe { ComponentTicksMut::from_tick_cells(ticks, last_run, this_run) },
157        })
158    }
159
160    /// Inserts a value into the resource. If a value is already present
161    /// it will be replaced.
162    ///
163    /// # Panics
164    /// If `SEND` is false, this will panic if a value is present and is not replaced from
165    /// the original thread it was inserted in.
166    ///
167    /// # Safety
168    /// - `value` must be valid for the underlying type for the resource.
169    #[inline]
170    pub(crate) unsafe fn insert(
171        &mut self,
172        value: OwningPtr<'_>,
173        change_tick: Tick,
174        caller: MaybeLocation,
175    ) {
176        if self.is_present() {
177            self.validate_access();
178            // SAFETY: The caller ensures that the provided value is valid for the underlying type and
179            // is properly initialized. We've ensured that a value is already present and previously
180            // initialized.
181            unsafe { self.data.replace_unchecked(Self::ROW, value) };
182        } else {
183            #[cfg(feature = "std")]
184            if !SEND {
185                self.origin_thread_id = Some(std::thread::current().id());
186            }
187            // SAFETY:
188            // - There is only one element, and it's always allocated.
189            // - The caller guarantees must be valid for the underlying type and thus its
190            //   layout must be identical.
191            // - The value was previously not present and thus must not have been initialized.
192            unsafe { self.data.initialize_unchecked(Self::ROW, value) };
193            *self.added_ticks.deref_mut() = change_tick;
194            self.is_present = true;
195        }
196        *self.changed_ticks.deref_mut() = change_tick;
197
198        self.changed_by
199            .as_ref()
200            .map(|changed_by| changed_by.deref_mut())
201            .assign(caller);
202    }
203
204    /// Inserts a value into the resource with a pre-existing change tick. If a
205    /// value is already present it will be replaced.
206    ///
207    /// # Panics
208    /// If `SEND` is false, this will panic if a value is present and is not replaced from
209    /// the original thread it was inserted in.
210    ///
211    /// # Safety
212    /// - `value` must be valid for the underlying type for the resource.
213    #[inline]
214    pub(crate) unsafe fn insert_with_ticks(
215        &mut self,
216        value: OwningPtr<'_>,
217        change_ticks: ComponentTicks,
218        caller: MaybeLocation,
219    ) {
220        if self.is_present() {
221            self.validate_access();
222            // SAFETY: The caller ensures that the provided value is valid for the underlying type and
223            // is properly initialized. We've ensured that a value is already present and previously
224            // initialized.
225            unsafe { self.data.replace_unchecked(Self::ROW, value) };
226        } else {
227            #[cfg(feature = "std")]
228            if !SEND {
229                self.origin_thread_id = Some(std::thread::current().id());
230            }
231            // SAFETY:
232            // - There is only one element, and it's always allocated.
233            // - The caller guarantees must be valid for the underlying type and thus its
234            //   layout must be identical.
235            // - The value was previously not present and thus must not have been initialized.
236            unsafe { self.data.initialize_unchecked(Self::ROW, value) };
237            self.is_present = true;
238        }
239        *self.added_ticks.deref_mut() = change_ticks.added;
240        *self.changed_ticks.deref_mut() = change_ticks.changed;
241        self.changed_by
242            .as_ref()
243            .map(|changed_by| changed_by.deref_mut())
244            .assign(caller);
245    }
246
247    /// Removes a value from the resource, if present.
248    ///
249    /// # Panics
250    /// If `SEND` is false, this will panic if a value is present and is not removed from the
251    /// original thread it was inserted from.
252    #[inline]
253    #[must_use = "The returned pointer to the removed component should be used or dropped"]
254    pub(crate) fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks, MaybeLocation)> {
255        if !self.is_present() {
256            return None;
257        }
258        if !SEND {
259            self.validate_access();
260        }
261
262        self.is_present = false;
263
264        // SAFETY:
265        // - There is always only one row in the `BlobArray` created during initialization.
266        // - This function has validated that the row is present with the check of `self.is_present`.
267        // - The caller is to take ownership of the value, returned as a `OwningPtr`.
268        let res = unsafe { self.data.get_unchecked_mut(Self::ROW).promote() };
269
270        let caller = self
271            .changed_by
272            .as_ref()
273            // SAFETY: This function is being called through an exclusive mutable reference to Self
274            .map(|changed_by| unsafe { *changed_by.deref_mut() });
275
276        // SAFETY: This function is being called through an exclusive mutable reference to Self, which
277        // makes it sound to read these ticks.
278        unsafe {
279            Some((
280                res,
281                ComponentTicks {
282                    added: self.added_ticks.read(),
283                    changed: self.changed_ticks.read(),
284                },
285                caller,
286            ))
287        }
288    }
289
290    /// Removes a value from the resource, if present, and drops it.
291    ///
292    /// # Panics
293    /// If `SEND` is false, this will panic if a value is present and is not
294    /// accessed from the original thread it was inserted in.
295    #[inline]
296    pub(crate) fn remove_and_drop(&mut self) {
297        if self.is_present() {
298            self.validate_access();
299            // SAFETY: There is only one element, and it's always allocated.
300            unsafe { self.data.drop_last_element(Self::ROW) };
301            self.is_present = false;
302        }
303    }
304
305    pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {
306        self.added_ticks.get_mut().check_tick(check);
307        self.changed_ticks.get_mut().check_tick(check);
308    }
309}
310
311/// The backing store for all [`Resource`]s stored in the [`World`].
312///
313/// [`Resource`]: crate::resource::Resource
314/// [`World`]: crate::world::World
315#[derive(Default)]
316pub struct Resources<const SEND: bool> {
317    resources: SparseSet<ComponentId, ResourceData<SEND>>,
318}
319
320impl<const SEND: bool> Resources<SEND> {
321    /// The total number of resources stored in the [`World`]
322    ///
323    /// [`World`]: crate::world::World
324    #[inline]
325    pub fn len(&self) -> usize {
326        self.resources.len()
327    }
328
329    /// Iterate over all resources that have been initialized, i.e. given a [`ComponentId`]
330    pub fn iter(&self) -> impl Iterator<Item = (ComponentId, &ResourceData<SEND>)> {
331        self.resources.iter().map(|(id, data)| (*id, data))
332    }
333
334    /// Returns true if there are no resources stored in the [`World`],
335    /// false otherwise.
336    ///
337    /// [`World`]: crate::world::World
338    #[inline]
339    pub fn is_empty(&self) -> bool {
340        self.resources.is_empty()
341    }
342
343    /// Gets read-only access to a resource, if it exists.
344    #[inline]
345    pub fn get(&self, component_id: ComponentId) -> Option<&ResourceData<SEND>> {
346        self.resources.get(component_id)
347    }
348
349    /// Clears all resources.
350    #[inline]
351    pub fn clear(&mut self) {
352        self.resources.clear();
353    }
354
355    /// Gets mutable access to a resource, if it exists.
356    #[inline]
357    pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ResourceData<SEND>> {
358        self.resources.get_mut(component_id)
359    }
360
361    /// Fetches or initializes a new resource and returns back its underlying column.
362    ///
363    /// # Panics
364    /// Will panic if `component_id` is not valid for the provided `components`
365    /// If `SEND` is true, this will panic if `component_id`'s `ComponentInfo` is not registered as being `Send` + `Sync`.
366    pub(crate) fn initialize_with(
367        &mut self,
368        component_id: ComponentId,
369        components: &Components,
370    ) -> &mut ResourceData<SEND> {
371        self.resources.get_or_insert_with(component_id, || {
372            let component_info = components.get_info(component_id).unwrap();
373            if SEND {
374                assert!(
375                    component_info.is_send_and_sync(),
376                    "Send + Sync resource {} initialized as non_send. It may have been inserted via World::insert_non_send_resource by accident. Try using World::insert_resource instead.",
377                    component_info.name(),
378                );
379            }
380            // SAFETY: component_info.drop() is valid for the types that will be inserted.
381            let data = unsafe {
382                BlobArray::with_capacity(
383                    component_info.layout(),
384                    component_info.drop(),
385                    1
386                )
387            };
388            ResourceData {
389                data,
390                is_present: false,
391                added_ticks: UnsafeCell::new(Tick::new(0)),
392                changed_ticks: UnsafeCell::new(Tick::new(0)),
393                type_name: component_info.name(),
394                #[cfg(feature = "std")]
395                origin_thread_id: None,
396                changed_by: MaybeLocation::caller().map(UnsafeCell::new),
397            }
398        })
399    }
400
401    pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {
402        for info in self.resources.values_mut() {
403            info.check_change_ticks(check);
404        }
405    }
406}