diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 8d74f36495520d..c96b605e4b7d49 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -22,6 +22,7 @@ erased-serde = "0.3" downcast-rs = "1.2" parking_lot = "0.11.0" thiserror = "1.0" +once_cell = "1.11" serde = "1" smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true } glam = { version = "0.20.0", features = ["serde"], optional = true } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls.rs index abec509425a64b..95b8d6f0ac7519 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls.rs @@ -32,6 +32,10 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream { .unwrap_or_else(|| Member::Unnamed(Index::from(field.index))) }) .collect::>(); + let field_types = derive_data + .active_fields() + .map(|field| field.data.ty.clone()) + .collect::>(); let field_count = field_idents.len(); let field_indices = (0..field_count).collect::>(); @@ -49,12 +53,27 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream { }); let debug_fn = derive_data.traits().get_debug_impl(); + let typed_impl = impl_typed( + struct_name, + derive_data.generics(), + quote! { + let fields: [#bevy_reflect_path::NamedField; #field_count] = [ + #(#bevy_reflect_path::NamedField::new::<#field_types, _>(#field_names),)* + ]; + let info = #bevy_reflect_path::StructInfo::new::(&fields); + #bevy_reflect_path::TypeInfo::Struct(info) + }, + bevy_reflect_path, + ); + let get_type_registration_impl = derive_data.get_type_registration(); let (impl_generics, ty_generics, where_clause) = derive_data.generics().split_for_impl(); TokenStream::from(quote! { #get_type_registration_impl + #typed_impl + impl #impl_generics #bevy_reflect_path::Struct for #struct_name #ty_generics #where_clause { fn field(&self, name: &str) -> Option<&dyn #bevy_reflect_path::Reflect> { match name { @@ -114,6 +133,11 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream { std::any::type_name::() } + #[inline] + fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo { + ::type_info() + } + #[inline] fn any(&self) -> &dyn std::any::Any { self @@ -184,6 +208,10 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream .active_fields() .map(|field| Member::Unnamed(Index::from(field.index))) .collect::>(); + let field_types = derive_data + .active_fields() + .map(|field| field.data.ty.clone()) + .collect::>(); let field_count = field_idents.len(); let field_indices = (0..field_count).collect::>(); @@ -201,10 +229,25 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream }); let debug_fn = derive_data.traits().get_debug_impl(); + let typed_impl = impl_typed( + struct_name, + derive_data.generics(), + quote! { + let fields: [#bevy_reflect_path::UnnamedField; #field_count] = [ + #(#bevy_reflect_path::UnnamedField::new::<#field_types>(#field_indices),)* + ]; + let info = #bevy_reflect_path::TupleStructInfo::new::(&fields); + #bevy_reflect_path::TypeInfo::TupleStruct(info) + }, + bevy_reflect_path, + ); + let (impl_generics, ty_generics, where_clause) = derive_data.generics().split_for_impl(); TokenStream::from(quote! { #get_type_registration_impl + #typed_impl + impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_name #ty_generics #where_clause { fn field(&self, index: usize) -> Option<&dyn #bevy_reflect_path::Reflect> { match index { @@ -243,6 +286,11 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream std::any::type_name::() } + #[inline] + fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo { + ::type_info() + } + #[inline] fn any(&self) -> &dyn std::any::Any { self @@ -315,10 +363,22 @@ pub(crate) fn impl_value( let partial_eq_fn = reflect_traits.get_partial_eq_impl(bevy_reflect_path); let debug_fn = reflect_traits.get_debug_impl(); + let typed_impl = impl_typed( + type_name, + generics, + quote! { + let info = #bevy_reflect_path::ValueInfo::new::(); + #bevy_reflect_path::TypeInfo::Value(info) + }, + bevy_reflect_path, + ); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); TokenStream::from(quote! { #get_type_registration_impl + #typed_impl + // SAFE: any and any_mut both return self unsafe impl #impl_generics #bevy_reflect_path::Reflect for #type_name #ty_generics #where_clause { #[inline] @@ -326,6 +386,11 @@ pub(crate) fn impl_value( std::any::type_name::() } + #[inline] + fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo { + ::type_info() + } + #[inline] fn any(&self) -> &dyn std::any::Any { self @@ -385,3 +450,38 @@ pub(crate) fn impl_value( } }) } + +fn impl_typed( + type_name: &Ident, + generics: &Generics, + generator: proc_macro2::TokenStream, + bevy_reflect_path: &Path, +) -> proc_macro2::TokenStream { + let is_generic = !generics.params.is_empty(); + + let static_generator = if is_generic { + quote! { + static CELL: #bevy_reflect_path::utility::GenericTypeInfoCell = #bevy_reflect_path::utility::GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| { + #generator + }) + } + } else { + quote! { + static CELL: #bevy_reflect_path::utility::NonGenericTypeInfoCell = #bevy_reflect_path::utility::NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| { + #generator + }) + } + }; + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics #bevy_reflect_path::Typed for #type_name #ty_generics #where_clause { + fn type_info() -> &'static #bevy_reflect_path::TypeInfo { + #static_generator + } + } + } +} diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index 59ecc8949d9880..f94e0e6cceaa4a 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -1,6 +1,9 @@ -use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef}; +use crate::{ + serde::Serializable, utility::NonGenericTypeInfoCell, DynamicInfo, Reflect, ReflectMut, + ReflectRef, TypeInfo, Typed, +}; use std::{ - any::Any, + any::{Any, TypeId}, fmt::Debug, hash::{Hash, Hasher}, }; @@ -37,6 +40,73 @@ pub trait Array: Reflect { } } +/// A container for compile-time array info. +#[derive(Clone, Debug)] +pub struct ArrayInfo { + type_name: &'static str, + type_id: TypeId, + item_type_name: &'static str, + item_type_id: TypeId, + capacity: usize, +} + +impl ArrayInfo { + /// Create a new [`ArrayInfo`]. + /// + /// # Arguments + /// + /// * `capacity`: The maximum capacity of the underlying array. + /// + pub fn new(capacity: usize) -> Self { + Self { + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + item_type_name: std::any::type_name::(), + item_type_id: TypeId::of::(), + capacity, + } + } + + /// The compile-time capacity of the array. + pub fn capacity(&self) -> usize { + self.capacity + } + + /// The [type name] of the array. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the array. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the array type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } + + /// The [type name] of the array item. + /// + /// [type name]: std::any::type_name + pub fn item_type_name(&self) -> &'static str { + self.item_type_name + } + + /// The [`TypeId`] of the array item. + pub fn item_type_id(&self) -> TypeId { + self.item_type_id + } + + /// Check if the given type matches the array item type. + pub fn item_is(&self) -> bool { + TypeId::of::() == self.item_type_id + } +} + /// A fixed-size list of reflected values. /// /// This differs from [`DynamicList`] in that the size of the [`DynamicArray`] @@ -89,6 +159,11 @@ unsafe impl Reflect for DynamicArray { self.name.as_str() } + #[inline] + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + #[inline] fn any(&self) -> &dyn Any { self @@ -185,6 +260,13 @@ impl Array for DynamicArray { } } +impl Typed for DynamicArray { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) + } +} + /// An iterator over an [`Array`]. pub struct ArrayIter<'a> { pub(crate) array: &'a dyn Array, diff --git a/crates/bevy_reflect/src/fields.rs b/crates/bevy_reflect/src/fields.rs new file mode 100644 index 00000000000000..21dc9ec5f75e20 --- /dev/null +++ b/crates/bevy_reflect/src/fields.rs @@ -0,0 +1,84 @@ +use crate::Reflect; +use std::any::{Any, TypeId}; +use std::borrow::Cow; + +/// The named field of a reflected struct. +#[derive(Clone, Debug)] +pub struct NamedField { + name: Cow<'static, str>, + type_name: &'static str, + type_id: TypeId, +} + +impl NamedField { + /// Create a new [`NamedField`]. + pub fn new>>(name: TName) -> Self { + Self { + name: name.into(), + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + } + } + + /// The name of the field. + pub fn name(&self) -> &Cow<'static, str> { + &self.name + } + + /// The [type name] of the field. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the field. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the field type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } +} + +/// The unnamed field of a reflected tuple or tuple struct. +#[derive(Clone, Debug)] +pub struct UnnamedField { + index: usize, + type_name: &'static str, + type_id: TypeId, +} + +impl UnnamedField { + pub fn new(index: usize) -> Self { + Self { + index, + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + } + } + + /// Returns the index of the field. + pub fn index(&self) -> usize { + self.index + } + + /// The [type name] of the field. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the field. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the field type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } +} diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index adec1ca92a8f0a..2ab1f97869c1e1 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -1,7 +1,10 @@ use smallvec::SmallVec; use std::any::Any; -use crate::{Array, ArrayIter, FromReflect, List, Reflect, ReflectMut, ReflectRef}; +use crate::utility::GenericTypeInfoCell; +use crate::{ + Array, ArrayIter, FromReflect, List, ListInfo, Reflect, ReflectMut, ReflectRef, TypeInfo, Typed, +}; impl Array for SmallVec where @@ -61,6 +64,10 @@ where std::any::type_name::() } + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + fn any(&self) -> &dyn Any { self } @@ -103,6 +110,16 @@ where } } +impl Typed for SmallVec +where + T::Item: FromReflect + Clone, +{ + fn type_info() -> &'static TypeInfo { + static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) + } +} + impl FromReflect for SmallVec where T::Item: FromReflect + Clone, diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 6b465a05b75178..c5fed20da26ec8 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -1,10 +1,11 @@ use crate as bevy_reflect; use crate::{ - map_partial_eq, serde::Serializable, Array, ArrayIter, DynamicMap, FromReflect, FromType, - GetTypeRegistration, List, Map, MapIter, Reflect, ReflectDeserialize, ReflectMut, ReflectRef, - TypeRegistration, + map_partial_eq, serde::Serializable, Array, ArrayInfo, ArrayIter, DynamicMap, FromReflect, + FromType, GetTypeRegistration, List, ListInfo, Map, MapInfo, MapIter, Reflect, + ReflectDeserialize, ReflectMut, ReflectRef, TypeInfo, TypeRegistration, Typed, ValueInfo, }; +use crate::utility::{GenericTypeInfoCell, NonGenericTypeInfoCell}; use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_value}; use bevy_utils::{Duration, HashMap, HashSet}; use serde::{Deserialize, Serialize}; @@ -110,6 +111,10 @@ unsafe impl Reflect for Vec { std::any::type_name::() } + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + fn any(&self) -> &dyn Any { self } @@ -160,6 +165,13 @@ unsafe impl Reflect for Vec { } } +impl Typed for Vec { + fn type_info() -> &'static TypeInfo { + static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) + } +} + impl Deserialize<'de>> GetTypeRegistration for Vec { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::>(); @@ -228,6 +240,10 @@ unsafe impl Reflect for HashMap { std::any::type_name::() } + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + fn any(&self) -> &dyn Any { self } @@ -278,6 +294,13 @@ unsafe impl Reflect for HashMap { } } +impl Typed for HashMap { + fn type_info() -> &'static TypeInfo { + static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| TypeInfo::Map(MapInfo::new::())) + } +} + impl GetTypeRegistration for HashMap where K: Reflect + Clone + Eq + Hash + for<'de> Deserialize<'de>, @@ -338,6 +361,10 @@ unsafe impl Reflect for [T; N] { std::any::type_name::() } + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + #[inline] fn any(&self) -> &dyn Any { self @@ -414,6 +441,13 @@ impl FromReflect for [T; N] { } } +impl Typed for [T; N] { + fn type_info() -> &'static TypeInfo { + static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| TypeInfo::Array(ArrayInfo::new::(N))) + } +} + // TODO: // `FromType::from_type` requires `Deserialize<'de>` to be implemented for `T`. // Currently serde only supports `Deserialize<'de>` for arrays up to size 32. @@ -446,6 +480,10 @@ unsafe impl Reflect for Cow<'static, str> { std::any::type_name::() } + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + fn any(&self) -> &dyn Any { self } @@ -509,6 +547,13 @@ unsafe impl Reflect for Cow<'static, str> { } } +impl Typed for Cow<'static, str> { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) + } +} + impl GetTypeRegistration for Cow<'static, str> { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::>(); diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index d82ba808412e4a..5519b8031841cb 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -1,6 +1,7 @@ #![doc = include_str!("../README.md")] mod array; +mod fields; mod list; mod map; mod path; @@ -8,6 +9,7 @@ mod reflect; mod struct_trait; mod tuple; mod tuple_struct; +mod type_info; mod type_registry; mod type_uuid; mod impls { @@ -26,6 +28,7 @@ mod impls { pub mod serde; pub mod std_traits; +pub mod utility; pub mod prelude { pub use crate::std_traits::*; @@ -37,6 +40,7 @@ pub mod prelude { } pub use array::*; +pub use fields::*; pub use impls::*; pub use list::*; pub use map::*; @@ -45,6 +49,7 @@ pub use reflect::*; pub use struct_trait::*; pub use tuple::*; pub use tuple_struct::*; +pub use type_info::*; pub use type_registry::*; pub use type_uuid::*; @@ -536,6 +541,237 @@ mod tests { ); } + #[test] + fn reflect_type_info() { + // TypeInfo + let info = i32::type_info(); + assert_eq!(std::any::type_name::(), info.type_name()); + assert_eq!(std::any::TypeId::of::(), info.type_id()); + + // TypeInfo (unsized) + assert_eq!( + std::any::TypeId::of::(), + ::type_info().type_id() + ); + + // TypeInfo (instance) + let value: &dyn Reflect = &123_i32; + let info = value.get_type_info(); + assert!(info.is::()); + + // Struct + #[derive(Reflect)] + struct MyStruct { + foo: i32, + bar: usize, + } + + let info = MyStruct::type_info(); + if let TypeInfo::Struct(info) = info { + assert!(info.is::()); + assert_eq!(std::any::type_name::(), info.type_name()); + assert_eq!( + std::any::type_name::(), + info.field("foo").unwrap().type_name() + ); + assert_eq!( + std::any::TypeId::of::(), + info.field("foo").unwrap().type_id() + ); + assert!(info.field("foo").unwrap().is::()); + assert_eq!("foo", info.field("foo").unwrap().name()); + assert_eq!( + std::any::type_name::(), + info.field_at(1).unwrap().type_name() + ); + } else { + panic!("Expected `TypeInfo::Struct`"); + } + + let value: &dyn Reflect = &MyStruct { foo: 123, bar: 321 }; + let info = value.get_type_info(); + assert!(info.is::()); + + // Struct (generic) + #[derive(Reflect)] + struct MyGenericStruct { + foo: T, + bar: usize, + } + + let info = >::type_info(); + if let TypeInfo::Struct(info) = info { + assert!(info.is::>()); + assert_eq!( + std::any::type_name::>(), + info.type_name() + ); + assert_eq!( + std::any::type_name::(), + info.field("foo").unwrap().type_name() + ); + assert_eq!("foo", info.field("foo").unwrap().name()); + assert_eq!( + std::any::type_name::(), + info.field_at(1).unwrap().type_name() + ); + } else { + panic!("Expected `TypeInfo::Struct`"); + } + + let value: &dyn Reflect = &MyGenericStruct { + foo: String::from("Hello!"), + bar: 321, + }; + let info = value.get_type_info(); + assert!(info.is::>()); + + // Tuple Struct + #[derive(Reflect)] + struct MyTupleStruct(usize, i32, MyStruct); + + let info = MyTupleStruct::type_info(); + if let TypeInfo::TupleStruct(info) = info { + assert!(info.is::()); + assert_eq!(std::any::type_name::(), info.type_name()); + assert_eq!( + std::any::type_name::(), + info.field_at(1).unwrap().type_name() + ); + assert!(info.field_at(1).unwrap().is::()); + } else { + panic!("Expected `TypeInfo::TupleStruct`"); + } + + let value: &dyn Reflect = &MyTupleStruct(123, 321, MyStruct { foo: 123, bar: 321 }); + let info = value.get_type_info(); + assert!(info.is::()); + + // Tuple + type MyTuple = (u32, f32, String); + + let info = MyTuple::type_info(); + if let TypeInfo::Tuple(info) = info { + assert!(info.is::()); + assert_eq!(std::any::type_name::(), info.type_name()); + assert_eq!( + std::any::type_name::(), + info.field_at(1).unwrap().type_name() + ); + } else { + panic!("Expected `TypeInfo::Tuple`"); + } + + let value: &dyn Reflect = &(123_u32, 1.23_f32, String::from("Hello!")); + let info = value.get_type_info(); + assert!(info.is::()); + + // List + type MyList = Vec; + + let info = MyList::type_info(); + if let TypeInfo::List(info) = info { + assert!(info.is::()); + assert!(info.item_is::()); + assert_eq!(std::any::type_name::(), info.type_name()); + assert_eq!(std::any::type_name::(), info.item_type_name()); + } else { + panic!("Expected `TypeInfo::List`"); + } + + let value: &dyn Reflect = &vec![123_usize]; + let info = value.get_type_info(); + assert!(info.is::()); + + // List (SmallVec) + #[cfg(feature = "smallvec")] + { + type MySmallVec = smallvec::SmallVec<[String; 2]>; + + let info = MySmallVec::type_info(); + if let TypeInfo::List(info) = info { + assert!(info.is::()); + assert!(info.item_is::()); + assert_eq!(std::any::type_name::(), info.type_name()); + assert_eq!(std::any::type_name::(), info.item_type_name()); + } else { + panic!("Expected `TypeInfo::List`"); + } + + let value: MySmallVec = smallvec::smallvec![String::default(); 2]; + let value: &dyn Reflect = &value; + let info = value.get_type_info(); + assert!(info.is::()); + } + + // Array + type MyArray = [usize; 3]; + + let info = MyArray::type_info(); + if let TypeInfo::Array(info) = info { + assert!(info.is::()); + assert!(info.item_is::()); + assert_eq!(std::any::type_name::(), info.type_name()); + assert_eq!(std::any::type_name::(), info.item_type_name()); + assert_eq!(3, info.capacity()); + } else { + panic!("Expected `TypeInfo::Array`"); + } + + let value: &dyn Reflect = &[1usize, 2usize, 3usize]; + let info = value.get_type_info(); + assert!(info.is::()); + + // Map + type MyMap = HashMap; + + let info = MyMap::type_info(); + if let TypeInfo::Map(info) = info { + assert!(info.is::()); + assert!(info.key_is::()); + assert!(info.value_is::()); + assert_eq!(std::any::type_name::(), info.type_name()); + assert_eq!(std::any::type_name::(), info.key_type_name()); + assert_eq!(std::any::type_name::(), info.value_type_name()); + } else { + panic!("Expected `TypeInfo::Map`"); + } + + let value: &dyn Reflect = &MyMap::new(); + let info = value.get_type_info(); + assert!(info.is::()); + + // Value + type MyValue = String; + + let info = MyValue::type_info(); + if let TypeInfo::Value(info) = info { + assert!(info.is::()); + assert_eq!(std::any::type_name::(), info.type_name()); + } else { + panic!("Expected `TypeInfo::Value`"); + } + + let value: &dyn Reflect = &String::from("Hello!"); + let info = value.get_type_info(); + assert!(info.is::()); + + // Dynamic + type MyDynamic = DynamicList; + + let info = MyDynamic::type_info(); + if let TypeInfo::Dynamic(info) = info { + assert!(info.is::()); + assert_eq!(std::any::type_name::(), info.type_name()); + } else { + panic!("Expected `TypeInfo::Dynamic`"); + } + + let value: &dyn Reflect = &DynamicList::default(); + let info = value.get_type_info(); + assert!(info.is::()); + } + #[test] fn as_reflect() { trait TestTrait: Reflect {} diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 3ea8d6050f666a..66d3816707adb9 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -1,7 +1,11 @@ -use std::any::Any; +use std::any::{Any, TypeId}; use std::fmt::{Debug, Formatter}; -use crate::{serde::Serializable, Array, ArrayIter, DynamicArray, Reflect, ReflectMut, ReflectRef}; +use crate::utility::NonGenericTypeInfoCell; +use crate::{ + serde::Serializable, Array, ArrayIter, DynamicArray, DynamicInfo, FromReflect, Reflect, + ReflectMut, ReflectRef, TypeInfo, Typed, +}; /// An ordered, mutable list of [Reflect] items. This corresponds to types like [`std::vec::Vec`]. /// @@ -20,6 +24,61 @@ pub trait List: Reflect + Array { } } +/// A container for compile-time list info. +#[derive(Clone, Debug)] +pub struct ListInfo { + type_name: &'static str, + type_id: TypeId, + item_type_name: &'static str, + item_type_id: TypeId, +} + +impl ListInfo { + /// Create a new [`ListInfo`]. + pub fn new() -> Self { + Self { + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + item_type_name: std::any::type_name::(), + item_type_id: TypeId::of::(), + } + } + + /// The [type name] of the list. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the list. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the list type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } + + /// The [type name] of the list item. + /// + /// [type name]: std::any::type_name + pub fn item_type_name(&self) -> &'static str { + self.item_type_name + } + + /// The [`TypeId`] of the list item. + pub fn item_type_id(&self) -> TypeId { + self.item_type_id + } + + /// Check if the given type matches the list item type. + pub fn item_is(&self) -> bool { + TypeId::of::() == self.item_type_id + } +} + /// A list of reflected values. #[derive(Default)] pub struct DynamicList { @@ -111,6 +170,11 @@ unsafe impl Reflect for DynamicList { self.name.as_str() } + #[inline] + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + #[inline] fn any(&self) -> &dyn Any { self @@ -182,6 +246,13 @@ impl Debug for DynamicList { } } +impl Typed for DynamicList { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) + } +} + impl IntoIterator for DynamicList { type Item = Box; type IntoIter = std::vec::IntoIter; diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index 988e723ef24e61..88f0f63a1c85fa 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -1,9 +1,11 @@ -use std::any::Any; +use std::any::{Any, TypeId}; use std::fmt::{Debug, Formatter}; +use std::hash::Hash; use bevy_utils::{Entry, HashMap}; -use crate::{Reflect, ReflectMut, ReflectRef}; +use crate::utility::NonGenericTypeInfoCell; +use crate::{DynamicInfo, Reflect, ReflectMut, ReflectRef, TypeInfo, Typed}; /// An ordered mapping between [`Reflect`] values. /// @@ -44,6 +46,82 @@ pub trait Map: Reflect { fn clone_dynamic(&self) -> DynamicMap; } +/// A container for compile-time map info. +#[derive(Clone, Debug)] +pub struct MapInfo { + type_name: &'static str, + type_id: TypeId, + key_type_name: &'static str, + key_type_id: TypeId, + value_type_name: &'static str, + value_type_id: TypeId, +} + +impl MapInfo { + /// Create a new [`MapInfo`]. + pub fn new() -> Self { + Self { + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + key_type_name: std::any::type_name::(), + key_type_id: TypeId::of::(), + value_type_name: std::any::type_name::(), + value_type_id: TypeId::of::(), + } + } + + /// The [type name] of the map. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the map. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the map type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } + + /// The [type name] of the key. + /// + /// [type name]: std::any::type_name + pub fn key_type_name(&self) -> &'static str { + self.key_type_name + } + + /// The [`TypeId`] of the key. + pub fn key_type_id(&self) -> TypeId { + self.key_type_id + } + + /// Check if the given type matches the key type. + pub fn key_is(&self) -> bool { + TypeId::of::() == self.key_type_id + } + + /// The [type name] of the value. + /// + /// [type name]: std::any::type_name + pub fn value_type_name(&self) -> &'static str { + self.value_type_name + } + + /// The [`TypeId`] of the value. + pub fn value_type_id(&self) -> TypeId { + self.value_type_id + } + + /// Check if the given type matches the value type. + pub fn value_is(&self) -> bool { + TypeId::of::() == self.value_type_id + } +} + const HASH_ERROR: &str = "the given key does not support hashing"; /// An ordered mapping between reflected values. @@ -140,6 +218,11 @@ unsafe impl Reflect for DynamicMap { &self.name } + #[inline] + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + fn any(&self) -> &dyn Any { self } @@ -204,6 +287,13 @@ impl Debug for DynamicMap { } } +impl Typed for DynamicMap { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) + } +} + /// An iterator over the key-value pairs of a [`Map`]. pub struct MapIter<'a> { pub(crate) map: &'a dyn Map, diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 1a7bb63973fe27..a0da96c189e5f6 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -1,10 +1,10 @@ use crate::{ array_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug, - tuple_struct_debug, Array, List, Map, Struct, Tuple, TupleStruct, + tuple_struct_debug, Array, List, Map, Struct, Tuple, TupleStruct, TypeInfo, Typed, ValueInfo, }; - use std::{any::Any, fmt::Debug}; +use crate::utility::NonGenericTypeInfoCell; pub use bevy_utils::AHasher as ReflectHasher; /// An immutable enumeration of "kinds" of reflected type. @@ -57,6 +57,16 @@ pub unsafe trait Reflect: Any + Send + Sync { /// [type name]: std::any::type_name fn type_name(&self) -> &str; + /// Returns the [`TypeInfo`] of the underlying type. + /// + /// This method is great if you have an instance of a type or a `dyn Reflect`, + /// and want to access its [`TypeInfo`]. However, if this method is to be called + /// frequently, consider using [`TypeRegistry::get_type_info`] as it can be more + /// performant for such use cases. + /// + /// [`TypeRegistry::get_type_info`]: crate::TypeRegistry::get_type_info + fn get_type_info(&self) -> &'static TypeInfo; + /// Returns the value as a [`&dyn Any`][std::any::Any]. fn any(&self) -> &dyn Any; @@ -193,6 +203,13 @@ impl Debug for dyn Reflect { } } +impl Typed for dyn Reflect { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::())) + } +} + impl dyn Reflect { /// Downcasts the value to type `T`, consuming the trait object. /// diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index 74415d09c28571..ac06e4a5b04693 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -1,7 +1,12 @@ -use crate::{Reflect, ReflectMut, ReflectRef}; +use crate::utility::NonGenericTypeInfoCell; +use crate::{DynamicInfo, NamedField, Reflect, ReflectMut, ReflectRef, TypeInfo, Typed}; use bevy_utils::{Entry, HashMap}; use std::fmt::{Debug, Formatter}; -use std::{any::Any, borrow::Cow}; +use std::{ + any::{Any, TypeId}, + borrow::Cow, + slice::Iter, +}; /// A reflected Rust regular struct type. /// @@ -61,6 +66,85 @@ pub trait Struct: Reflect { fn clone_dynamic(&self) -> DynamicStruct; } +/// A container for compile-time struct info. +#[derive(Clone, Debug)] +pub struct StructInfo { + type_name: &'static str, + type_id: TypeId, + fields: Box<[NamedField]>, + field_indices: HashMap, usize>, +} + +impl StructInfo { + /// Create a new [`StructInfo`]. + /// + /// # Arguments + /// + /// * `fields`: The fields of this struct in the order they are defined + /// + pub fn new(fields: &[NamedField]) -> Self { + let field_indices = fields + .iter() + .enumerate() + .map(|(index, field)| { + let name = field.name().clone(); + (name, index) + }) + .collect::>(); + + Self { + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + fields: fields.to_vec().into_boxed_slice(), + field_indices, + } + } + + /// Get the field with the given name. + pub fn field(&self, name: &str) -> Option<&NamedField> { + self.field_indices + .get(name) + .map(|index| &self.fields[*index]) + } + + /// Get the field at the given index. + pub fn field_at(&self, index: usize) -> Option<&NamedField> { + self.fields.get(index) + } + + /// Get the index of the field with the given name. + pub fn index_of(&self, name: &str) -> Option { + self.field_indices.get(name).copied() + } + + /// Iterate over the fields of this struct. + pub fn iter(&self) -> Iter<'_, NamedField> { + self.fields.iter() + } + + /// The total number of fields in this struct. + pub fn field_len(&self) -> usize { + self.fields.len() + } + + /// The [type name] of the struct. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the struct. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the struct type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } +} + /// An iterator over the field values of a struct. pub struct FieldIter<'a> { pub(crate) struct_val: &'a dyn Struct, @@ -260,6 +344,11 @@ unsafe impl Reflect for DynamicStruct { &self.name } + #[inline] + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + #[inline] fn any(&self) -> &dyn Any { self @@ -330,6 +419,13 @@ impl Debug for DynamicStruct { } } +impl Typed for DynamicStruct { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) + } +} + /// Compares a [`Struct`] with a [`Reflect`] value. /// /// Returns true if and only if all of the following are true: diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 3d5439171461ee..bab117e5e5d6a4 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -1,10 +1,12 @@ +use crate::utility::NonGenericTypeInfoCell; use crate::{ - FromReflect, FromType, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectMut, - ReflectRef, TypeRegistration, + DynamicInfo, FromReflect, FromType, GetTypeRegistration, Reflect, ReflectDeserialize, + ReflectMut, ReflectRef, TypeInfo, TypeRegistration, Typed, UnnamedField, }; use serde::Deserialize; -use std::any::Any; +use std::any::{Any, TypeId}; use std::fmt::{Debug, Formatter}; +use std::slice::Iter; /// A reflected Rust tuple. /// @@ -124,6 +126,62 @@ impl GetTupleField for dyn Tuple { } } +/// A container for compile-time tuple info. +#[derive(Clone, Debug)] +pub struct TupleInfo { + type_name: &'static str, + type_id: TypeId, + fields: Box<[UnnamedField]>, +} + +impl TupleInfo { + /// Create a new [`TupleInfo`]. + /// + /// # Arguments + /// + /// * `fields`: The fields of this tuple in the order they are defined + /// + pub fn new(fields: &[UnnamedField]) -> Self { + Self { + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + fields: fields.to_vec().into_boxed_slice(), + } + } + + /// Get the field at the given index. + pub fn field_at(&self, index: usize) -> Option<&UnnamedField> { + self.fields.get(index) + } + + /// Iterate over the fields of this tuple. + pub fn iter(&self) -> Iter<'_, UnnamedField> { + self.fields.iter() + } + + /// The total number of fields in this tuple. + pub fn field_len(&self) -> usize { + self.fields.len() + } + + /// The [type name] of the tuple. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the tuple. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the tuple type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } +} + /// A tuple which allows fields to be added at runtime. #[derive(Default)] pub struct DynamicTuple { @@ -216,6 +274,11 @@ unsafe impl Reflect for DynamicTuple { self.name() } + #[inline] + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + #[inline] fn any(&self) -> &dyn Any { self @@ -271,6 +334,13 @@ unsafe impl Reflect for DynamicTuple { } } +impl Typed for DynamicTuple { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) + } +} + /// Applies the elements of `b` to the corresponding elements of `a`. /// /// # Panics @@ -396,6 +466,10 @@ macro_rules! impl_reflect_tuple { std::any::type_name::() } + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + fn any(&self) -> &dyn Any { self } @@ -438,7 +512,20 @@ macro_rules! impl_reflect_tuple { } } - impl<$($name: Reflect + for<'de> Deserialize<'de>),*> GetTypeRegistration for ($($name,)*) { + impl <$($name: Reflect),*> Typed for ($($name,)*) { + fn type_info() -> &'static TypeInfo { + static CELL: $crate::utility::GenericTypeInfoCell = $crate::utility::GenericTypeInfoCell::new(); + CELL.get_or_insert::(|| { + let fields = [ + $(UnnamedField::new::<$name>($index),)* + ]; + let info = TupleInfo::new::(&fields); + TypeInfo::Tuple(info) + }) + } + } + + impl<$($name: Reflect + Typed + for<'de> Deserialize<'de>),*> GetTypeRegistration for ($($name,)*) { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::<($($name,)*)>(); registration.insert::(FromType::<($($name,)*)>::from_type()); diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index 27e60b12d67bfe..e3a8afa8a8bb6b 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -1,6 +1,8 @@ -use crate::{Reflect, ReflectMut, ReflectRef}; -use std::any::Any; +use crate::utility::NonGenericTypeInfoCell; +use crate::{DynamicInfo, Reflect, ReflectMut, ReflectRef, TypeInfo, Typed, UnnamedField}; +use std::any::{Any, TypeId}; use std::fmt::{Debug, Formatter}; +use std::slice::Iter; /// A reflected Rust tuple struct. /// @@ -44,6 +46,62 @@ pub trait TupleStruct: Reflect { fn clone_dynamic(&self) -> DynamicTupleStruct; } +/// A container for compile-time tuple struct info. +#[derive(Clone, Debug)] +pub struct TupleStructInfo { + type_name: &'static str, + type_id: TypeId, + fields: Box<[UnnamedField]>, +} + +impl TupleStructInfo { + /// Create a new [`TupleStructInfo`]. + /// + /// # Arguments + /// + /// * `fields`: The fields of this struct in the order they are defined + /// + pub fn new(fields: &[UnnamedField]) -> Self { + Self { + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + fields: fields.to_vec().into_boxed_slice(), + } + } + + /// Get the field at the given index. + pub fn field_at(&self, index: usize) -> Option<&UnnamedField> { + self.fields.get(index) + } + + /// Iterate over the fields of this struct. + pub fn iter(&self) -> Iter<'_, UnnamedField> { + self.fields.iter() + } + + /// The total number of fields in this struct. + pub fn field_len(&self) -> usize { + self.fields.len() + } + + /// The [type name] of the tuple struct. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the tuple struct. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the tuple struct type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } +} + /// An iterator over the field values of a tuple struct. pub struct TupleStructFieldIter<'a> { pub(crate) tuple_struct: &'a dyn TupleStruct, @@ -200,6 +258,11 @@ unsafe impl Reflect for DynamicTupleStruct { self.name.as_str() } + #[inline] + fn get_type_info(&self) -> &'static TypeInfo { + ::type_info() + } + #[inline] fn any(&self) -> &dyn Any { self @@ -269,6 +332,13 @@ impl Debug for DynamicTupleStruct { } } +impl Typed for DynamicTupleStruct { + fn type_info() -> &'static TypeInfo { + static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); + CELL.get_or_set(|| TypeInfo::Dynamic(DynamicInfo::new::())) + } +} + /// Compares a [`TupleStruct`] with a [`Reflect`] value. /// /// Returns true if and only if all of the following are true: diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs new file mode 100644 index 00000000000000..2d1d1f53275257 --- /dev/null +++ b/crates/bevy_reflect/src/type_info.rs @@ -0,0 +1,223 @@ +use crate::{ArrayInfo, ListInfo, MapInfo, Reflect, StructInfo, TupleInfo, TupleStructInfo}; +use std::any::{Any, TypeId}; + +/// A static accessor to compile-time type information. +/// +/// This trait is automatically implemented by the `#[derive(Reflect)]` macro +/// and allows type information to be processed without an instance of that type. +/// +/// # Implementing +/// +/// While it is recommended to leave implementing this trait to the `#[derive(Reflect)]` macro, +/// it is possible to implement this trait manually. If a manual implementation is needed, +/// you _must_ ensure that the information you provide is correct, otherwise various systems that +/// rely on this trait may fail in unexpected ways. +/// +/// Implementors may have difficulty in generating a reference to [`TypeInfo`] with a static +/// lifetime. Luckily, this crate comes with some [utility] structs, to make generating these +/// statics much simpler. +/// +/// # Example +/// +/// ``` +/// # use std::any::Any; +/// # use bevy_reflect::{NamedField, Reflect, ReflectMut, ReflectRef, StructInfo, TypeInfo, ValueInfo}; +/// # use bevy_reflect::utility::NonGenericTypeInfoCell; +/// use bevy_reflect::Typed; +/// +/// struct MyStruct { +/// foo: usize, +/// bar: (f32, f32) +/// } +/// +/// impl Typed for MyStruct { +/// fn type_info() -> &'static TypeInfo { +/// static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); +/// CELL.get_or_set(|| { +/// let fields = [ +/// NamedField::new::("foo"), +/// NamedField::new::<(f32, f32), _>("bar"), +/// ]; +/// let info = StructInfo::new::(&fields); +/// TypeInfo::Struct(info) +/// }) +/// } +/// } +/// +/// # +/// # unsafe impl Reflect for MyStruct { +/// # 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) -> Result<(), Box> { todo!() } +/// # fn reflect_ref(&self) -> ReflectRef { todo!() } +/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() } +/// # fn clone_value(&self) -> Box { todo!() } +/// # } +/// ``` +/// +/// [utility]: crate::utility +pub trait Typed: Reflect { + /// Returns the compile-time [info] for the underlying type. + /// + /// [info]: TypeInfo + fn type_info() -> &'static TypeInfo; +} + +/// Compile-time type information for various reflected types. +/// +/// Generally, for any given type, this value can be retrieved one of three ways: +/// +/// 1. [`Typed::type_info`] +/// 2. [`Reflect::get_type_info`] +/// 3. [`TypeRegistry::get_type_info`] +/// +/// Each return a static reference to [`TypeInfo`], but they all have their own use cases. +/// For example, if you know the type at compile time, [`Typed::type_info`] is probably +/// the simplest. If all you have is a `dyn Reflect`, you'll probably want [`Reflect::get_type_info`]. +/// Lastly, if all you have is a [`TypeId`] or [type name], you will need to go through +/// [`TypeRegistry::get_type_info`]. +/// +/// You may also opt to use [`TypeRegistry::get_type_info`] in place of the other methods simply because +/// it can be more performant. This is because those other methods may require attaining a lock on +/// the static [`TypeInfo`], while the registry simply checks a map. +/// +/// [`Reflect::get_type_info`]: crate::Reflect::get_type_info +/// [`TypeRegistry::get_type_info`]: crate::TypeRegistry::get_type_info +/// [`TypeId`]: std::any::TypeId +/// [type name]: std::any::type_name +#[derive(Debug, Clone)] +pub enum TypeInfo { + Struct(StructInfo), + TupleStruct(TupleStructInfo), + Tuple(TupleInfo), + List(ListInfo), + Array(ArrayInfo), + Map(MapInfo), + Value(ValueInfo), + /// Type information for "dynamic" types whose metadata can't be known at compile-time. + /// + /// This includes structs like [`DynamicStruct`](crate::DynamicStruct) and [`DynamicList`](crate::DynamicList). + Dynamic(DynamicInfo), +} + +impl TypeInfo { + /// The [`TypeId`] of the underlying type. + pub fn type_id(&self) -> TypeId { + match self { + Self::Struct(info) => info.type_id(), + Self::TupleStruct(info) => info.type_id(), + Self::Tuple(info) => info.type_id(), + Self::List(info) => info.type_id(), + Self::Array(info) => info.type_id(), + Self::Map(info) => info.type_id(), + Self::Value(info) => info.type_id(), + Self::Dynamic(info) => info.type_id(), + } + } + + /// The [name] of the underlying type. + /// + /// [name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + match self { + Self::Struct(info) => info.type_name(), + Self::TupleStruct(info) => info.type_name(), + Self::Tuple(info) => info.type_name(), + Self::List(info) => info.type_name(), + Self::Array(info) => info.type_name(), + Self::Map(info) => info.type_name(), + Self::Value(info) => info.type_name(), + Self::Dynamic(info) => info.type_name(), + } + } + + /// Check if the given type matches the underlying type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id() + } +} + +/// A container for compile-time info related to general value types, including primitives. +/// +/// This typically represents a type which cannot be broken down any further. This is often +/// due to technical reasons (or by definition), but it can also be a purposeful choice. +/// +/// For example, [`i32`] cannot be broken down any further, so it is represented by a [`ValueInfo`]. +/// And while [`String`] itself is a struct, it's fields are private, so we don't really treat +/// it _as_ a struct. It therefore makes more sense to represent it as a [`ValueInfo`]. +#[derive(Debug, Clone)] +pub struct ValueInfo { + type_name: &'static str, + type_id: TypeId, +} + +impl ValueInfo { + pub fn new() -> Self { + Self { + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + } + } + + /// The [type name] of the value. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the value. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the value type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } +} + +/// A container for compile-time info related to Bevy's _dynamic_ types, including primitives. +/// +/// This is functionally the same as [`ValueInfo`], however, semantically it refers to dynamic +/// types such as [`DynamicStruct`], [`DynamicTuple`], [`DynamicList`], etc. +/// +/// [`DynamicStruct`]: crate::DynamicStruct +/// [`DynamicTuple`]: crate::DynamicTuple +/// [`DynamicList`]: crate::DynamicList +#[derive(Debug, Clone)] +pub struct DynamicInfo { + type_name: &'static str, + type_id: TypeId, +} + +impl DynamicInfo { + pub fn new() -> Self { + Self { + type_name: std::any::type_name::(), + type_id: TypeId::of::(), + } + } + + /// The [type name] of the dynamic value. + /// + /// [type name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_name + } + + /// The [`TypeId`] of the dynamic value. + pub fn type_id(&self) -> TypeId { + self.type_id + } + + /// Check if the given type matches the dynamic value type. + pub fn is(&self) -> bool { + TypeId::of::() == self.type_id + } +} diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index c663cae6decdfe..60c29a88556262 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -1,4 +1,4 @@ -use crate::Reflect; +use crate::{Reflect, TypeInfo, Typed}; use bevy_utils::{HashMap, HashSet}; use downcast_rs::{impl_downcast, Downcast}; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -91,12 +91,12 @@ impl TypeRegistry { self.ambiguous_names.insert(short_name); } else { self.short_name_to_id - .insert(short_name, registration.type_id); + .insert(short_name, registration.type_id()); } self.full_name_to_id - .insert(registration.name.to_string(), registration.type_id); + .insert(registration.type_name().to_string(), registration.type_id()); self.registrations - .insert(registration.type_id, registration); + .insert(registration.type_id(), registration); } /// Returns a reference to the [`TypeRegistration`] of the type with the @@ -188,6 +188,14 @@ impl TypeRegistry { .and_then(|registration| registration.data_mut::()) } + /// Returns the [`TypeInfo`] associated with the given `TypeId`. + /// + /// If the specified type has not been registered, returns `None`. + pub fn get_type_info(&self, type_id: TypeId) -> Option<&'static TypeInfo> { + self.get(type_id) + .map(|registration| registration.type_info()) + } + /// Returns an iterator over the [`TypeRegistration`]s of the registered /// types. pub fn iter(&self) -> impl Iterator { @@ -215,23 +223,21 @@ impl TypeRegistryArc { /// A record of data about a type. /// -/// This contains the [`TypeId`], [name], and [short name] of the type. +/// This contains the [`TypeInfo`] of the type, as well as its [short name]. /// /// For each trait specified by the [`#[reflect(_)]`][0] attribute of /// [`#[derive(Reflect)]`][1] on the registered type, this record also contains /// a [`TypeData`] which can be used to downcast [`Reflect`] trait objects of /// this type to trait objects of the relevant trait. /// -/// [`TypeId`]: std::any::TypeId -/// [name]: std::any::type_name /// [short name]: TypeRegistration::get_short_name +/// [`TypeInfo`]: crate::TypeInfo /// [0]: crate::Reflect /// [1]: crate::Reflect pub struct TypeRegistration { - type_id: TypeId, short_name: String, - name: &'static str, data: HashMap>, + type_info: &'static TypeInfo, } impl TypeRegistration { @@ -240,7 +246,7 @@ impl TypeRegistration { /// [`TypeId`]: std::any::TypeId #[inline] pub fn type_id(&self) -> TypeId { - self.type_id + self.type_info.type_id() } /// Returns a reference to the value of type `T` in this registration's type @@ -263,6 +269,11 @@ impl TypeRegistration { .and_then(|value| value.downcast_mut()) } + /// Returns a reference to the registration's [`TypeInfo`] + pub fn type_info(&self) -> &'static TypeInfo { + self.type_info + } + /// Inserts an instance of `T` into this registration's type data. /// /// If another instance of `T` was previously inserted, it is replaced. @@ -271,14 +282,12 @@ impl TypeRegistration { } /// Creates type registration information for `T`. - pub fn of() -> Self { - let ty = TypeId::of::(); + pub fn of() -> Self { let type_name = std::any::type_name::(); Self { - type_id: ty, data: HashMap::default(), - name: type_name, short_name: Self::get_short_name(type_name), + type_info: T::type_info(), } } @@ -289,9 +298,11 @@ impl TypeRegistration { &self.short_name } - /// Returns the name of the type. - pub fn name(&self) -> &'static str { - self.name + /// Returns the [name] of the type. + /// + /// [name]: std::any::type_name + pub fn type_name(&self) -> &'static str { + self.type_info.type_name() } /// Calculates the short name of a type. @@ -347,9 +358,8 @@ impl Clone for TypeRegistration { TypeRegistration { data, - name: self.name, short_name: self.short_name.clone(), - type_id: self.type_id, + type_info: self.type_info, } } } diff --git a/crates/bevy_reflect/src/utility.rs b/crates/bevy_reflect/src/utility.rs new file mode 100644 index 00000000000000..a597539afdb021 --- /dev/null +++ b/crates/bevy_reflect/src/utility.rs @@ -0,0 +1,142 @@ +//! 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::("bar")]; +/// let info = StructInfo::new::(&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) -> Result<(), Box> { todo!() } +/// # fn reflect_ref(&self) -> ReflectRef { todo!() } +/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() } +/// # fn clone_value(&self) -> Box { todo!() } +/// # } +/// ``` +pub struct NonGenericTypeInfoCell(OnceBox); + +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(&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); +/// +/// impl Typed for Foo { +/// fn type_info() -> &'static TypeInfo { +/// static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); +/// CELL.get_or_insert::(|| { +/// let fields = [UnnamedField::new::(0)]; +/// let info = TupleStructInfo::new::(&fields); +/// TypeInfo::TupleStruct(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) -> Result<(), Box> { todo!() } +/// # fn reflect_ref(&self) -> ReflectRef { todo!() } +/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() } +/// # fn clone_value(&self) -> Box { todo!() } +/// # } +/// ``` +pub struct GenericTypeInfoCell(OnceBox>>); + +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(&self, f: F) -> &TypeInfo + where + T: Any + ?Sized, + F: FnOnce() -> TypeInfo, + { + let type_id = TypeId::of::(); + 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())) + }) + } +}