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}