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:🆕:<T>()];
      let info = TupleStructInfo:🆕:<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 (aa5178e773) and main (c309acd432).

<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>
This commit is contained in:
Gino Valente 2022-06-09 21:18:15 +00:00
parent 1679a99738
commit e6f34ba47f
16 changed files with 1410 additions and 39 deletions

View file

@ -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 }

View file

@ -32,6 +32,10 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
.unwrap_or_else(|| Member::Unnamed(Index::from(field.index)))
})
.collect::<Vec<_>>();
let field_types = derive_data
.active_fields()
.map(|field| field.data.ty.clone())
.collect::<Vec<_>>();
let field_count = field_idents.len();
let field_indices = (0..field_count).collect::<Vec<usize>>();
@ -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::<Self>(&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::<Self>()
}
#[inline]
fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo {
<Self as #bevy_reflect_path::Typed>::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::<Vec<_>>();
let field_types = derive_data
.active_fields()
.map(|field| field.data.ty.clone())
.collect::<Vec<_>>();
let field_count = field_idents.len();
let field_indices = (0..field_count).collect::<Vec<usize>>();
@ -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::<Self>(&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::<Self>()
}
#[inline]
fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo {
<Self as #bevy_reflect_path::Typed>::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::<Self>();
#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::<Self>()
}
#[inline]
fn get_type_info(&self) -> &'static #bevy_reflect_path::TypeInfo {
<Self as #bevy_reflect_path::Typed>::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::<Self, _>(|| {
#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
}
}
}
}

View file

@ -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<TArray: Array, TItem: Reflect>(capacity: usize) -> Self {
Self {
type_name: std::any::type_name::<TArray>(),
type_id: TypeId::of::<TArray>(),
item_type_name: std::any::type_name::<TItem>(),
item_type_id: TypeId::of::<TItem>(),
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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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 {
<Self as Typed>::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::<Self>()))
}
}
/// An iterator over an [`Array`].
pub struct ArrayIter<'a> {
pub(crate) array: &'a dyn Array,

View file

@ -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<T: Reflect, TName: Into<Cow<'static, str>>>(name: TName) -> Self {
Self {
name: name.into(),
type_name: std::any::type_name::<T>(),
type_id: TypeId::of::<T>(),
}
}
/// 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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<T: Reflect>(index: usize) -> Self {
Self {
index,
type_name: std::any::type_name::<T>(),
type_id: TypeId::of::<T>(),
}
}
/// 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == self.type_id
}
}

View file

@ -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<T: smallvec::Array + Send + Sync + 'static> Array for SmallVec<T>
where
@ -61,6 +64,10 @@ where
std::any::type_name::<Self>()
}
fn get_type_info(&self) -> &'static TypeInfo {
<Self as Typed>::type_info()
}
fn any(&self) -> &dyn Any {
self
}
@ -103,6 +110,16 @@ where
}
}
impl<T: smallvec::Array + Send + Sync + 'static> Typed for SmallVec<T>
where
T::Item: FromReflect + Clone,
{
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
CELL.get_or_insert::<Self, _>(|| TypeInfo::List(ListInfo::new::<Self, T::Item>()))
}
}
impl<T: smallvec::Array + Send + Sync + 'static> FromReflect for SmallVec<T>
where
T::Item: FromReflect + Clone,

View file

@ -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<T: FromReflect> Reflect for Vec<T> {
std::any::type_name::<Self>()
}
fn get_type_info(&self) -> &'static TypeInfo {
<Self as Typed>::type_info()
}
fn any(&self) -> &dyn Any {
self
}
@ -160,6 +165,13 @@ unsafe impl<T: FromReflect> Reflect for Vec<T> {
}
}
impl<T: FromReflect> Typed for Vec<T> {
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
CELL.get_or_insert::<Self, _>(|| TypeInfo::List(ListInfo::new::<Self, T>()))
}
}
impl<T: FromReflect + for<'de> Deserialize<'de>> GetTypeRegistration for Vec<T> {
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Vec<T>>();
@ -228,6 +240,10 @@ unsafe impl<K: Reflect + Eq + Hash, V: Reflect> Reflect for HashMap<K, V> {
std::any::type_name::<Self>()
}
fn get_type_info(&self) -> &'static TypeInfo {
<Self as Typed>::type_info()
}
fn any(&self) -> &dyn Any {
self
}
@ -278,6 +294,13 @@ unsafe impl<K: Reflect + Eq + Hash, V: Reflect> Reflect for HashMap<K, V> {
}
}
impl<K: Reflect + Eq + Hash, V: Reflect> Typed for HashMap<K, V> {
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
CELL.get_or_insert::<Self, _>(|| TypeInfo::Map(MapInfo::new::<Self, K, V>()))
}
}
impl<K, V> GetTypeRegistration for HashMap<K, V>
where
K: Reflect + Clone + Eq + Hash + for<'de> Deserialize<'de>,
@ -338,6 +361,10 @@ unsafe impl<T: Reflect, const N: usize> Reflect for [T; N] {
std::any::type_name::<Self>()
}
fn get_type_info(&self) -> &'static TypeInfo {
<Self as Typed>::type_info()
}
#[inline]
fn any(&self) -> &dyn Any {
self
@ -414,6 +441,13 @@ impl<T: FromReflect, const N: usize> FromReflect for [T; N] {
}
}
impl<T: Reflect, const N: usize> Typed for [T; N] {
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
CELL.get_or_insert::<Self, _>(|| TypeInfo::Array(ArrayInfo::new::<Self, T>(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::<Self>()
}
fn get_type_info(&self) -> &'static TypeInfo {
<Self as Typed>::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::<Self>()))
}
}
impl GetTypeRegistration for Cow<'static, str> {
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Cow<'static, str>>();

View file

@ -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::<i32>(), info.type_name());
assert_eq!(std::any::TypeId::of::<i32>(), info.type_id());
// TypeInfo (unsized)
assert_eq!(
std::any::TypeId::of::<dyn Reflect>(),
<dyn Reflect as Typed>::type_info().type_id()
);
// TypeInfo (instance)
let value: &dyn Reflect = &123_i32;
let info = value.get_type_info();
assert!(info.is::<i32>());
// Struct
#[derive(Reflect)]
struct MyStruct {
foo: i32,
bar: usize,
}
let info = MyStruct::type_info();
if let TypeInfo::Struct(info) = info {
assert!(info.is::<MyStruct>());
assert_eq!(std::any::type_name::<MyStruct>(), info.type_name());
assert_eq!(
std::any::type_name::<i32>(),
info.field("foo").unwrap().type_name()
);
assert_eq!(
std::any::TypeId::of::<i32>(),
info.field("foo").unwrap().type_id()
);
assert!(info.field("foo").unwrap().is::<i32>());
assert_eq!("foo", info.field("foo").unwrap().name());
assert_eq!(
std::any::type_name::<usize>(),
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::<MyStruct>());
// Struct (generic)
#[derive(Reflect)]
struct MyGenericStruct<T: Reflect> {
foo: T,
bar: usize,
}
let info = <MyGenericStruct<i32>>::type_info();
if let TypeInfo::Struct(info) = info {
assert!(info.is::<MyGenericStruct<i32>>());
assert_eq!(
std::any::type_name::<MyGenericStruct<i32>>(),
info.type_name()
);
assert_eq!(
std::any::type_name::<i32>(),
info.field("foo").unwrap().type_name()
);
assert_eq!("foo", info.field("foo").unwrap().name());
assert_eq!(
std::any::type_name::<usize>(),
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::<MyGenericStruct<String>>());
// Tuple Struct
#[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_eq!(
std::any::type_name::<i32>(),
info.field_at(1).unwrap().type_name()
);
assert!(info.field_at(1).unwrap().is::<i32>());
} 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::<MyTupleStruct>());
// Tuple
type MyTuple = (u32, f32, String);
let info = MyTuple::type_info();
if let TypeInfo::Tuple(info) = info {
assert!(info.is::<MyTuple>());
assert_eq!(std::any::type_name::<MyTuple>(), info.type_name());
assert_eq!(
std::any::type_name::<f32>(),
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::<MyTuple>());
// List
type MyList = Vec<usize>;
let info = MyList::type_info();
if let TypeInfo::List(info) = info {
assert!(info.is::<MyList>());
assert!(info.item_is::<usize>());
assert_eq!(std::any::type_name::<MyList>(), info.type_name());
assert_eq!(std::any::type_name::<usize>(), 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::<MyList>());
// 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::<MySmallVec>());
assert!(info.item_is::<String>());
assert_eq!(std::any::type_name::<MySmallVec>(), info.type_name());
assert_eq!(std::any::type_name::<String>(), 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::<MySmallVec>());
}
// Array
type MyArray = [usize; 3];
let info = MyArray::type_info();
if let TypeInfo::Array(info) = info {
assert!(info.is::<MyArray>());
assert!(info.item_is::<usize>());
assert_eq!(std::any::type_name::<MyArray>(), info.type_name());
assert_eq!(std::any::type_name::<usize>(), 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::<MyArray>());
// Map
type MyMap = HashMap<usize, f32>;
let info = MyMap::type_info();
if let TypeInfo::Map(info) = info {
assert!(info.is::<MyMap>());
assert!(info.key_is::<usize>());
assert!(info.value_is::<f32>());
assert_eq!(std::any::type_name::<MyMap>(), info.type_name());
assert_eq!(std::any::type_name::<usize>(), info.key_type_name());
assert_eq!(std::any::type_name::<f32>(), info.value_type_name());
} else {
panic!("Expected `TypeInfo::Map`");
}
let value: &dyn Reflect = &MyMap::new();
let info = value.get_type_info();
assert!(info.is::<MyMap>());
// Value
type MyValue = String;
let info = MyValue::type_info();
if let TypeInfo::Value(info) = info {
assert!(info.is::<MyValue>());
assert_eq!(std::any::type_name::<MyValue>(), info.type_name());
} else {
panic!("Expected `TypeInfo::Value`");
}
let value: &dyn Reflect = &String::from("Hello!");
let info = value.get_type_info();
assert!(info.is::<MyValue>());
// Dynamic
type MyDynamic = DynamicList;
let info = MyDynamic::type_info();
if let TypeInfo::Dynamic(info) = info {
assert!(info.is::<MyDynamic>());
assert_eq!(std::any::type_name::<MyDynamic>(), info.type_name());
} else {
panic!("Expected `TypeInfo::Dynamic`");
}
let value: &dyn Reflect = &DynamicList::default();
let info = value.get_type_info();
assert!(info.is::<MyDynamic>());
}
#[test]
fn as_reflect() {
trait TestTrait: Reflect {}

View file

@ -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<TList: List, TItem: FromReflect>() -> Self {
Self {
type_name: std::any::type_name::<TList>(),
type_id: TypeId::of::<TList>(),
item_type_name: std::any::type_name::<TItem>(),
item_type_id: TypeId::of::<TItem>(),
}
}
/// 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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 {
<Self as Typed>::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::<Self>()))
}
}
impl IntoIterator for DynamicList {
type Item = Box<dyn Reflect>;
type IntoIter = std::vec::IntoIter<Self::Item>;

View file

@ -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<TMap: Map, TKey: Hash + Reflect, TValue: Reflect>() -> Self {
Self {
type_name: std::any::type_name::<TMap>(),
type_id: TypeId::of::<TMap>(),
key_type_name: std::any::type_name::<TKey>(),
key_type_id: TypeId::of::<TKey>(),
value_type_name: std::any::type_name::<TValue>(),
value_type_id: TypeId::of::<TValue>(),
}
}
/// 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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 {
<Self as Typed>::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::<Self>()))
}
}
/// An iterator over the key-value pairs of a [`Map`].
pub struct MapIter<'a> {
pub(crate) map: &'a dyn Map,

View file

@ -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::<Self>()))
}
}
impl dyn Reflect {
/// Downcasts the value to type `T`, consuming the trait object.
///

View file

@ -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<Cow<'static, str>, usize>,
}
impl StructInfo {
/// Create a new [`StructInfo`].
///
/// # Arguments
///
/// * `fields`: The fields of this struct in the order they are defined
///
pub fn new<T: Reflect>(fields: &[NamedField]) -> Self {
let field_indices = fields
.iter()
.enumerate()
.map(|(index, field)| {
let name = field.name().clone();
(name, index)
})
.collect::<HashMap<_, _>>();
Self {
type_name: std::any::type_name::<T>(),
type_id: TypeId::of::<T>(),
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<usize> {
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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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 {
<Self as Typed>::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::<Self>()))
}
}
/// Compares a [`Struct`] with a [`Reflect`] value.
///
/// Returns true if and only if all of the following are true:

View file

@ -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<T: Reflect>(fields: &[UnnamedField]) -> Self {
Self {
type_name: std::any::type_name::<T>(),
type_id: TypeId::of::<T>(),
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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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 {
<Self as Typed>::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::<Self>()))
}
}
/// 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::<Self>()
}
fn get_type_info(&self) -> &'static TypeInfo {
<Self as Typed>::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::<Self, _>(|| {
let fields = [
$(UnnamedField::new::<$name>($index),)*
];
let info = TupleInfo::new::<Self>(&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::<ReflectDeserialize>(FromType::<($($name,)*)>::from_type());

View file

@ -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<T: Reflect>(fields: &[UnnamedField]) -> Self {
Self {
type_name: std::any::type_name::<T>(),
type_id: TypeId::of::<T>(),
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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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 {
<Self as Typed>::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::<Self>()))
}
}
/// Compares a [`TupleStruct`] with a [`Reflect`] value.
///
/// Returns true if and only if all of the following are true:

View file

@ -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::<usize, _>("foo"),
/// NamedField::new::<(f32, f32), _>("bar"),
/// ];
/// let info = StructInfo::new::<Self>(&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<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!() }
/// # }
/// ```
///
/// [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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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<T: Reflect + ?Sized>() -> Self {
Self {
type_name: std::any::type_name::<T>(),
type_id: TypeId::of::<T>(),
}
}
/// 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == 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<T: Reflect>() -> Self {
Self {
type_name: std::any::type_name::<T>(),
type_id: TypeId::of::<T>(),
}
}
/// 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<T: Any>(&self) -> bool {
TypeId::of::<T>() == self.type_id
}
}

View file

@ -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::<T>())
}
/// 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<Item = &TypeRegistration> {
@ -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<TypeId, Box<dyn TypeData>>,
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<T: Reflect>() -> Self {
let ty = TypeId::of::<T>();
pub fn of<T: Reflect + Typed>() -> Self {
let type_name = std::any::type_name::<T>();
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,
}
}
}

View file

@ -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::<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()))
})
}
}