Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_asset/src/reflect.rs
6601 views
1
use alloc::boxed::Box;
2
use core::any::{Any, TypeId};
3
4
use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, World};
5
use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect};
6
7
use crate::{
8
Asset, AssetId, Assets, Handle, InvalidGenerationError, UntypedAssetId, UntypedHandle,
9
};
10
11
/// Type data for the [`TypeRegistry`](bevy_reflect::TypeRegistry) used to operate on reflected [`Asset`]s.
12
///
13
/// This type provides similar methods to [`Assets<T>`] like [`get`](ReflectAsset::get),
14
/// [`add`](ReflectAsset::add) and [`remove`](ReflectAsset::remove), but can be used in situations where you don't know which asset type `T` you want
15
/// until runtime.
16
///
17
/// [`ReflectAsset`] can be obtained via [`TypeRegistration::data`](bevy_reflect::TypeRegistration::data) if the asset was registered using [`register_asset_reflect`](crate::AssetApp::register_asset_reflect).
18
#[derive(Clone)]
19
pub struct ReflectAsset {
20
handle_type_id: TypeId,
21
assets_resource_type_id: TypeId,
22
23
get: fn(&World, UntypedAssetId) -> Option<&dyn Reflect>,
24
// SAFETY:
25
// - may only be called with an [`UnsafeWorldCell`] which can be used to access the corresponding `Assets<T>` resource mutably
26
// - may only be used to access **at most one** access at once
27
get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedAssetId) -> Option<&mut dyn Reflect>,
28
add: fn(&mut World, &dyn PartialReflect) -> UntypedHandle,
29
insert:
30
fn(&mut World, UntypedAssetId, &dyn PartialReflect) -> Result<(), InvalidGenerationError>,
31
len: fn(&World) -> usize,
32
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = UntypedAssetId> + 'w>,
33
remove: fn(&mut World, UntypedAssetId) -> Option<Box<dyn Reflect>>,
34
}
35
36
impl ReflectAsset {
37
/// The [`TypeId`] of the [`Handle<T>`] for this asset
38
pub fn handle_type_id(&self) -> TypeId {
39
self.handle_type_id
40
}
41
42
/// The [`TypeId`] of the [`Assets<T>`] resource
43
pub fn assets_resource_type_id(&self) -> TypeId {
44
self.assets_resource_type_id
45
}
46
47
/// Equivalent of [`Assets::get`]
48
pub fn get<'w>(
49
&self,
50
world: &'w World,
51
asset_id: impl Into<UntypedAssetId>,
52
) -> Option<&'w dyn Reflect> {
53
(self.get)(world, asset_id.into())
54
}
55
56
/// Equivalent of [`Assets::get_mut`]
57
pub fn get_mut<'w>(
58
&self,
59
world: &'w mut World,
60
asset_id: impl Into<UntypedAssetId>,
61
) -> Option<&'w mut dyn Reflect> {
62
// SAFETY: unique world access
63
#[expect(
64
unsafe_code,
65
reason = "Use of unsafe `Self::get_unchecked_mut()` function."
66
)]
67
unsafe {
68
(self.get_unchecked_mut)(world.as_unsafe_world_cell(), asset_id.into())
69
}
70
}
71
72
/// Equivalent of [`Assets::get_mut`], but works with an [`UnsafeWorldCell`].
73
///
74
/// Only use this method when you have ensured that you are the *only* one with access to the [`Assets`] resource of the asset type.
75
/// Furthermore, this does *not* allow you to have look up two distinct handles,
76
/// you can only have at most one alive at the same time.
77
/// This means that this is *not allowed*:
78
/// ```no_run
79
/// # use bevy_asset::{ReflectAsset, UntypedHandle};
80
/// # use bevy_ecs::prelude::World;
81
/// # let reflect_asset: ReflectAsset = unimplemented!();
82
/// # let mut world: World = unimplemented!();
83
/// # let handle_1: UntypedHandle = unimplemented!();
84
/// # let handle_2: UntypedHandle = unimplemented!();
85
/// let unsafe_world_cell = world.as_unsafe_world_cell();
86
/// let a = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, &handle_1).unwrap() };
87
/// let b = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, &handle_2).unwrap() };
88
/// // ^ not allowed, two mutable references through the same asset resource, even though the
89
/// // handles are distinct
90
///
91
/// println!("a = {a:?}, b = {b:?}");
92
/// ```
93
///
94
/// # Safety
95
/// This method does not prevent you from having two mutable pointers to the same data,
96
/// violating Rust's aliasing rules. To avoid this:
97
/// * Only call this method if you know that the [`UnsafeWorldCell`] may be used to access the corresponding `Assets<T>`
98
/// * Don't call this method more than once in the same scope.
99
#[expect(
100
unsafe_code,
101
reason = "This function calls unsafe code and has safety requirements."
102
)]
103
pub unsafe fn get_unchecked_mut<'w>(
104
&self,
105
world: UnsafeWorldCell<'w>,
106
asset_id: impl Into<UntypedAssetId>,
107
) -> Option<&'w mut dyn Reflect> {
108
// SAFETY: requirements are deferred to the caller
109
unsafe { (self.get_unchecked_mut)(world, asset_id.into()) }
110
}
111
112
/// Equivalent of [`Assets::add`]
113
pub fn add(&self, world: &mut World, value: &dyn PartialReflect) -> UntypedHandle {
114
(self.add)(world, value)
115
}
116
/// Equivalent of [`Assets::insert`]
117
pub fn insert(
118
&self,
119
world: &mut World,
120
asset_id: impl Into<UntypedAssetId>,
121
value: &dyn PartialReflect,
122
) -> Result<(), InvalidGenerationError> {
123
(self.insert)(world, asset_id.into(), value)
124
}
125
126
/// Equivalent of [`Assets::remove`]
127
pub fn remove(
128
&self,
129
world: &mut World,
130
asset_id: impl Into<UntypedAssetId>,
131
) -> Option<Box<dyn Reflect>> {
132
(self.remove)(world, asset_id.into())
133
}
134
135
/// Equivalent of [`Assets::len`]
136
pub fn len(&self, world: &World) -> usize {
137
(self.len)(world)
138
}
139
140
/// Equivalent of [`Assets::is_empty`]
141
pub fn is_empty(&self, world: &World) -> bool {
142
self.len(world) == 0
143
}
144
145
/// Equivalent of [`Assets::ids`]
146
pub fn ids<'w>(&self, world: &'w World) -> impl Iterator<Item = UntypedAssetId> + 'w {
147
(self.ids)(world)
148
}
149
}
150
151
impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
152
fn from_type() -> Self {
153
ReflectAsset {
154
handle_type_id: TypeId::of::<Handle<A>>(),
155
assets_resource_type_id: TypeId::of::<Assets<A>>(),
156
get: |world, asset_id| {
157
let assets = world.resource::<Assets<A>>();
158
let asset = assets.get(asset_id.typed_debug_checked());
159
asset.map(|asset| asset as &dyn Reflect)
160
},
161
get_unchecked_mut: |world, asset_id| {
162
// SAFETY: `get_unchecked_mut` must be called with `UnsafeWorldCell` having access to `Assets<A>`,
163
// and must ensure to only have at most one reference to it live at all times.
164
#[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
165
let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
166
let asset = assets.get_mut(asset_id.typed_debug_checked());
167
asset.map(|asset| asset as &mut dyn Reflect)
168
},
169
add: |world, value| {
170
let mut assets = world.resource_mut::<Assets<A>>();
171
let value: A = FromReflect::from_reflect(value)
172
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::add`");
173
assets.add(value).untyped()
174
},
175
insert: |world, asset_id, value| {
176
let mut assets = world.resource_mut::<Assets<A>>();
177
let value: A = FromReflect::from_reflect(value)
178
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::set`");
179
assets.insert(asset_id.typed_debug_checked(), value)
180
},
181
len: |world| {
182
let assets = world.resource::<Assets<A>>();
183
assets.len()
184
},
185
ids: |world| {
186
let assets = world.resource::<Assets<A>>();
187
Box::new(assets.ids().map(AssetId::untyped))
188
},
189
remove: |world, asset_id| {
190
let mut assets = world.resource_mut::<Assets<A>>();
191
let value = assets.remove(asset_id.typed_debug_checked());
192
value.map(|value| Box::new(value) as Box<dyn Reflect>)
193
},
194
}
195
}
196
}
197
198
/// Reflect type data struct relating a [`Handle<T>`] back to the `T` asset type.
199
///
200
/// Say you want to look up the asset values of a list of handles when you have access to their `&dyn Reflect` form.
201
/// Assets can be looked up in the world using [`ReflectAsset`], but how do you determine which [`ReflectAsset`] to use when
202
/// only looking at the handle? [`ReflectHandle`] is stored in the type registry on each `Handle<T>` type, so you can use [`ReflectHandle::asset_type_id`] to look up
203
/// the [`ReflectAsset`] type data on the corresponding `T` asset type:
204
///
205
///
206
/// ```no_run
207
/// # use bevy_reflect::{TypeRegistry, prelude::*};
208
/// # use bevy_ecs::prelude::*;
209
/// use bevy_asset::{ReflectHandle, ReflectAsset};
210
///
211
/// # let world: &World = unimplemented!();
212
/// # let type_registry: TypeRegistry = unimplemented!();
213
/// let handles: Vec<&dyn Reflect> = unimplemented!();
214
/// for handle in handles {
215
/// let reflect_handle = type_registry.get_type_data::<ReflectHandle>(handle.type_id()).unwrap();
216
/// let reflect_asset = type_registry.get_type_data::<ReflectAsset>(reflect_handle.asset_type_id()).unwrap();
217
///
218
/// let handle = reflect_handle.downcast_handle_untyped(handle.as_any()).unwrap();
219
/// let value = reflect_asset.get(world, &handle).unwrap();
220
/// println!("{value:?}");
221
/// }
222
/// ```
223
#[derive(Clone)]
224
pub struct ReflectHandle {
225
asset_type_id: TypeId,
226
downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
227
typed: fn(UntypedHandle) -> Box<dyn Reflect>,
228
}
229
230
impl ReflectHandle {
231
/// The [`TypeId`] of the asset
232
pub fn asset_type_id(&self) -> TypeId {
233
self.asset_type_id
234
}
235
236
/// A way to go from a [`Handle<T>`] in a `dyn Any` to a [`UntypedHandle`]
237
pub fn downcast_handle_untyped(&self, handle: &dyn Any) -> Option<UntypedHandle> {
238
(self.downcast_handle_untyped)(handle)
239
}
240
241
/// A way to go from a [`UntypedHandle`] to a [`Handle<T>`] in a `Box<dyn Reflect>`.
242
/// Equivalent of [`UntypedHandle::typed`].
243
pub fn typed(&self, handle: UntypedHandle) -> Box<dyn Reflect> {
244
(self.typed)(handle)
245
}
246
}
247
248
impl<A: Asset> FromType<Handle<A>> for ReflectHandle {
249
fn from_type() -> Self {
250
ReflectHandle {
251
asset_type_id: TypeId::of::<A>(),
252
downcast_handle_untyped: |handle: &dyn Any| {
253
handle
254
.downcast_ref::<Handle<A>>()
255
.map(|h| h.clone().untyped())
256
},
257
typed: |handle: UntypedHandle| Box::new(handle.typed_debug_checked::<A>()),
258
}
259
}
260
}
261
262
#[cfg(test)]
263
mod tests {
264
use alloc::{string::String, vec::Vec};
265
use core::any::TypeId;
266
267
use crate::{Asset, AssetApp, AssetPlugin, ReflectAsset};
268
use bevy_app::App;
269
use bevy_ecs::reflect::AppTypeRegistry;
270
use bevy_reflect::Reflect;
271
272
#[derive(Asset, Reflect)]
273
struct AssetType {
274
field: String,
275
}
276
277
#[test]
278
fn test_reflect_asset_operations() {
279
let mut app = App::new();
280
app.add_plugins(AssetPlugin::default())
281
.init_asset::<AssetType>()
282
.register_asset_reflect::<AssetType>();
283
284
let reflect_asset = {
285
let type_registry = app.world().resource::<AppTypeRegistry>();
286
let type_registry = type_registry.read();
287
288
type_registry
289
.get_type_data::<ReflectAsset>(TypeId::of::<AssetType>())
290
.unwrap()
291
.clone()
292
};
293
294
let value = AssetType {
295
field: "test".into(),
296
};
297
298
let handle = reflect_asset.add(app.world_mut(), &value);
299
// struct is a reserved keyword, so we can't use it here
300
let strukt = reflect_asset
301
.get_mut(app.world_mut(), &handle)
302
.unwrap()
303
.reflect_mut()
304
.as_struct()
305
.unwrap();
306
strukt
307
.field_mut("field")
308
.unwrap()
309
.apply(&String::from("edited"));
310
311
assert_eq!(reflect_asset.len(app.world()), 1);
312
let ids: Vec<_> = reflect_asset.ids(app.world()).collect();
313
assert_eq!(ids.len(), 1);
314
let id = ids[0];
315
316
let asset = reflect_asset.get(app.world(), id).unwrap();
317
assert_eq!(asset.downcast_ref::<AssetType>().unwrap().field, "edited");
318
319
reflect_asset.remove(app.world_mut(), id).unwrap();
320
assert_eq!(reflect_asset.len(app.world()), 0);
321
}
322
}
323
324