Thanks to visit codestin.com
Credit goes to docs.rs

bevy_ecs/storage/
resource.rs

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