bevy/crates/bevy_reflect/src/utility.rs

143 lines
5.2 KiB
Rust
Raw Normal View History

bevy_reflect: Add statically available type info for reflected types (#4042) # Objective > Resolves #4504 It can be helpful to have access to type information without requiring an instance of that type. Especially for `Reflect`, a lot of the gathered type information is known at compile-time and should not necessarily require an instance. ## Solution Created a dedicated `TypeInfo` enum to store static type information. All types that derive `Reflect` now also implement the newly created `Typed` trait: ```rust pub trait Typed: Reflect { fn type_info() -> &'static TypeInfo; } ``` > Note: This trait was made separate from `Reflect` due to `Sized` restrictions. If you only have access to a `dyn Reflect`, just call `.get_type_info()` on it. This new trait method on `Reflect` should return the same value as if you had called it statically. If all you have is a `TypeId` or type name, you can get the `TypeInfo` directly from the registry using the `TypeRegistry::get_type_info` method (assuming it was registered). ### Usage Below is an example of working with `TypeInfo`. As you can see, we don't have to generate an instance of `MyTupleStruct` in order to get this information. ```rust #[derive(Reflect)] struct MyTupleStruct(usize, i32, MyStruct); let info = MyTupleStruct::type_info(); if let TypeInfo::TupleStruct(info) = info { assert!(info.is::<MyTupleStruct>()); assert_eq!(std::any::type_name::<MyTupleStruct>(), info.type_name()); assert!(info.field_at(1).unwrap().is::<i32>()); } else { panic!("Expected `TypeInfo::TupleStruct`"); } ``` ### Manual Implementations It's not recommended to manually implement `Typed` yourself, but if you must, you can use the `TypeInfoCell` to automatically create and manage the static `TypeInfo`s for you (which is very helpful for blanket/generic impls): ```rust use bevy_reflect::{Reflect, TupleStructInfo, TypeInfo, UnnamedField}; use bevy_reflect::utility::TypeInfoCell; struct Foo<T: Reflect>(T); impl<T: Reflect> Typed for Foo<T> { fn type_info() -> &'static TypeInfo { static CELL: TypeInfoCell = TypeInfoCell::generic(); CELL.get_or_insert::<Self, _>(|| { let fields = [UnnamedField::new::<T>()]; let info = TupleStructInfo::new::<Self>(&fields); TypeInfo::TupleStruct(info) }) } } ``` ## Benefits One major benefit is that this opens the door to other serialization methods. Since we can get all the type info at compile time, we can know how to properly deserialize something like: ```rust #[derive(Reflect)] struct MyType { foo: usize, bar: Vec<String> } // RON to be deserialized: ( type: "my_crate::MyType", // <- We now know how to deserialize the rest of this object value: { // "foo" is a value type matching "usize" "foo": 123, // "bar" is a list type matching "Vec<String>" with item type "String" "bar": ["a", "b", "c"] } ) ``` Not only is this more compact, but it has better compatibility (we can change the type of `"foo"` to `i32` without having to update our serialized data). Of course, serialization/deserialization strategies like this may need to be discussed and fully considered before possibly making a change. However, we will be better equipped to do that now that we can access type information right from the registry. ## Discussion Some items to discuss: 1. Duplication. There's a bit of overlap with the existing traits/structs since they require an instance of the type while the type info structs do not (for example, `Struct::field_at(&self, index: usize)` and `StructInfo::field_at(&self, index: usize)`, though only `StructInfo` is accessible without an instance object). Is this okay, or do we want to handle it in another way? 2. Should `TypeInfo::Dynamic` be removed? Since the dynamic types don't have type information available at runtime, we could consider them `TypeInfo::Value`s (or just even just `TypeInfo::Struct`). The intention with `TypeInfo::Dynamic` was to keep the distinction from these dynamic types and actual structs/values since users might incorrectly believe the methods of the dynamic type's info struct would map to some contained data (which isn't possible statically). 4. General usefulness of this change, including missing/unnecessary parts. 5. Possible changes to the scene format? (One possible issue with changing it like in the example above might be that we'd have to be careful when handling generic or trait object types.) ## Compile Tests I ran a few tests to compare compile times (as suggested [here](https://github.com/bevyengine/bevy/pull/4042#discussion_r876408143)). I toggled `Reflect` and `FromReflect` derive macros using `cfg_attr` for both this PR (aa5178e7736a6f8252e10e543e52722107649d3f) and main (c309acd4322b1c3b2089e247a2d28b938eb7b56d). <details> <summary>See More</summary> The test project included 250 of the following structs (as well as a few other structs): ```rust #[derive(Default)] #[cfg_attr(feature = "reflect", derive(Reflect))] #[cfg_attr(feature = "from_reflect", derive(FromReflect))] pub struct Big001 { inventory: Inventory, foo: usize, bar: String, baz: ItemDescriptor, items: [Item; 20], hello: Option<String>, world: HashMap<i32, String>, okay: (isize, usize, /* wesize */), nope: ((String, String), (f32, f32)), blah: Cow<'static, str>, } ``` > I don't know if the compiler can optimize all these duplicate structs away, but I think it's fine either way. We're comparing times, not finding the absolute worst-case time. I only ran each build 3 times using `cargo build --timings` (thank you @devil-ira), each of which were preceeded by a `cargo clean --package bevy_reflect_compile_test`. Here are the times I got: | Test | Test 1 | Test 2 | Test 3 | Average | | -------------------------------- | ------ | ------ | ------ | ------- | | Main | 1.7s | 3.1s | 1.9s | 2.33s | | Main + `Reflect` | 8.3s | 8.6s | 8.1s | 8.33s | | Main + `Reflect` + `FromReflect` | 11.6s | 11.8s | 13.8s | 12.4s | | PR | 3.5s | 1.8s | 1.9s | 2.4s | | PR + `Reflect` | 9.2s | 8.8s | 9.3s | 9.1s | | PR + `Reflect` + `FromReflect` | 12.9s | 12.3s | 12.5s | 12.56s | </details> --- ## Future Work Even though everything could probably be made `const`, we unfortunately can't. This is because `TypeId::of::<T>()` is not yet `const` (see https://github.com/rust-lang/rust/issues/77125). When it does get stabilized, it would probably be worth coming back and making things `const`. Co-authored-by: MrGVSV <49806985+MrGVSV@users.noreply.github.com>
2022-06-09 21:18:15 +00:00
//! Helpers for working with Bevy reflection.
use crate::TypeInfo;
use bevy_utils::HashMap;
use once_cell::race::OnceBox;
use parking_lot::RwLock;
use std::any::{Any, TypeId};
/// A container for [`TypeInfo`] over non-generic types, allowing instances to be stored statically.
///
/// This is specifically meant for use with _non_-generic types. If your type _is_ generic,
/// then use [`GenericTypeInfoCell`] instead. Otherwise, it will not take into account all
/// monomorphizations of your type.
///
/// ## Example
///
/// ```
/// # use std::any::Any;
/// # use bevy_reflect::{NamedField, Reflect, ReflectMut, ReflectRef, StructInfo, Typed, TypeInfo};
/// use bevy_reflect::utility::NonGenericTypeInfoCell;
///
/// struct Foo {
/// bar: i32
/// }
///
/// impl Typed for Foo {
/// fn type_info() -> &'static TypeInfo {
/// static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
/// CELL.get_or_set(|| {
/// let fields = [NamedField::new::<i32, _>("bar")];
/// let info = StructInfo::new::<Self>(&fields);
/// TypeInfo::Struct(info)
/// })
/// }
/// }
/// #
/// # unsafe impl Reflect for Foo {
/// # fn type_name(&self) -> &str { todo!() }
/// # fn get_type_info(&self) -> &'static TypeInfo { todo!() }
/// # fn any(&self) -> &dyn Any { todo!() }
/// # fn any_mut(&mut self) -> &mut dyn Any { todo!() }
/// # fn as_reflect(&self) -> &dyn Reflect { todo!() }
/// # fn as_reflect_mut(&mut self) -> &mut dyn Reflect { todo!() }
/// # fn apply(&mut self, value: &dyn Reflect) { todo!() }
/// # fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> { todo!() }
/// # fn reflect_ref(&self) -> ReflectRef { todo!() }
/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() }
/// # fn clone_value(&self) -> Box<dyn Reflect> { todo!() }
/// # }
/// ```
pub struct NonGenericTypeInfoCell(OnceBox<TypeInfo>);
impl NonGenericTypeInfoCell {
/// Initialize a [`NonGenericTypeInfoCell`] for non-generic types.
pub const fn new() -> Self {
Self(OnceBox::new())
}
/// Returns a reference to the [`TypeInfo`] stored in the cell.
///
/// If there is no [`TypeInfo`] found, a new one will be generated from the given function.
///
/// [`TypeInfos`]: TypeInfo
pub fn get_or_set<F>(&self, f: F) -> &TypeInfo
where
F: FnOnce() -> TypeInfo,
{
self.0.get_or_init(|| Box::new(f()))
}
}
/// A container for [`TypeInfo`] over generic types, allowing instances to be stored statically.
///
/// This is specifically meant for use with generic types. If your type isn't generic,
/// then use [`NonGenericTypeInfoCell`] instead as it should be much more performant.
///
/// ## Example
///
/// ```
/// # use std::any::Any;
/// # use bevy_reflect::{Reflect, ReflectMut, ReflectRef, TupleStructInfo, Typed, TypeInfo, UnnamedField};
/// use bevy_reflect::utility::GenericTypeInfoCell;
///
/// struct Foo<T: Reflect>(T);
///
/// impl<T: Reflect> Typed for Foo<T> {
/// fn type_info() -> &'static TypeInfo {
/// static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
/// CELL.get_or_insert::<Self, _>(|| {
/// let fields = [UnnamedField::new::<T>(0)];
/// let info = TupleStructInfo::new::<Self>(&fields);
/// TypeInfo::TupleStruct(info)
/// })
/// }
/// }
/// #
/// # unsafe impl<T: Reflect> Reflect for Foo<T> {
/// # fn type_name(&self) -> &str { todo!() }
/// # fn get_type_info(&self) -> &'static TypeInfo { todo!() }
/// # fn any(&self) -> &dyn Any { todo!() }
/// # fn any_mut(&mut self) -> &mut dyn Any { todo!() }
/// # fn as_reflect(&self) -> &dyn Reflect { todo!() }
/// # fn as_reflect_mut(&mut self) -> &mut dyn Reflect { todo!() }
/// # fn apply(&mut self, value: &dyn Reflect) { todo!() }
/// # fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> { todo!() }
/// # fn reflect_ref(&self) -> ReflectRef { todo!() }
/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() }
/// # fn clone_value(&self) -> Box<dyn Reflect> { todo!() }
/// # }
/// ```
pub struct GenericTypeInfoCell(OnceBox<RwLock<HashMap<TypeId, &'static TypeInfo>>>);
impl GenericTypeInfoCell {
/// Initialize a [`GenericTypeInfoCell`] for generic types.
pub const fn new() -> Self {
Self(OnceBox::new())
}
/// Returns a reference to the [`TypeInfo`] stored in the cell.
///
/// This method will then return the correct [`TypeInfo`] reference for the given type `T`.
/// If there is no [`TypeInfo`] found, a new one will be generated from the given function.
pub fn get_or_insert<T, F>(&self, f: F) -> &TypeInfo
where
T: Any + ?Sized,
F: FnOnce() -> TypeInfo,
{
let type_id = TypeId::of::<T>();
let mapping = self.0.get_or_init(|| Box::new(RwLock::default()));
if let Some(info) = mapping.read().get(&type_id) {
return info;
}
mapping.write().entry(type_id).or_insert_with(|| {
// We leak here in order to obtain a `&'static` reference.
// Otherwise, we won't be able to return a reference due to the `RwLock`.
// This should be okay, though, since we expect it to remain statically
// available over the course of the application.
Box::leak(Box::new(f()))
})
}
}