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}