Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/storage/resource.rs
6604 views
1
use crate::{
2
change_detection::{MaybeLocation, MutUntyped, TicksMut},
3
component::{CheckChangeTicks, ComponentId, ComponentTicks, Components, Tick, TickCells},
4
storage::{blob_vec::BlobVec, SparseSet},
5
};
6
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
7
use bevy_utils::prelude::DebugName;
8
use core::{cell::UnsafeCell, mem::ManuallyDrop, panic::Location};
9
10
#[cfg(feature = "std")]
11
use 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
18
pub 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
32
impl<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
57
impl<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)]
303
pub struct Resources<const SEND: bool> {
304
resources: SparseSet<ComponentId, ResourceData<SEND>>,
305
}
306
307
impl<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
}
393
394