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