Skip to content

Commit

Permalink
bevy_reflect: Nested TypeInfo getters (#13321)
Browse files Browse the repository at this point in the history
# Objective

Right now, `TypeInfo` can be accessed directly from a type using either
`Typed::type_info` or `Reflect::get_represented_type_info`.

However, once that `TypeInfo` is accessed, any nested types must be
accessed via the `TypeRegistry`.

```rust
#[derive(Reflect)]
struct Foo {
  bar: usize
}

let registry = TypeRegistry::default();

let TypeInfo::Struct(type_info) = Foo::type_info() else {
  panic!("expected struct info");
};

let field = type_info.field("bar").unwrap();

let field_info = registry.get_type_info(field.type_id()).unwrap();
assert!(field_info.is::<usize>());;
```

## Solution

Enable nested types within a `TypeInfo` to be retrieved directly.

```rust
#[derive(Reflect)]
struct Foo {
  bar: usize
}

let TypeInfo::Struct(type_info) = Foo::type_info() else {
  panic!("expected struct info");
};

let field = type_info.field("bar").unwrap();

let field_info = field.type_info().unwrap();
assert!(field_info.is::<usize>());;
```

The particular implementation was chosen for two reasons.

Firstly, we can't just store `TypeInfo` inside another `TypeInfo`
directly. This is because some types are recursive and would result in a
deadlock when trying to create the `TypeInfo` (i.e. it has to create the
`TypeInfo` before it can use it, but it also needs the `TypeInfo` before
it can create it). Therefore, we must instead store the function so it
can be retrieved lazily.

I had considered also using a `OnceLock` or something to lazily cache
the info, but I figured we can look into optimizations later. The API
should remain the same with or without the `OnceLock`.

Secondly, a new wrapper trait had to be introduced: `MaybeTyped`. Like
`RegisterForReflection`, this trait is `#[doc(hidden)]` and only exists
so that we can properly handle dynamic type fields without requiring
them to implement `Typed`. We don't want dynamic types to implement
`Typed` due to the fact that it would make the return type
`Option<&'static TypeInfo>` for all types even though only the dynamic
types ever need to return `None` (see #6971 for details).

Users should never have to interact with this trait as it has a blanket
impl for all `Typed` types. And `Typed` is automatically implemented
when deriving `Reflect` (as it is required).

The one downside is we do need to return `Option<&'static TypeInfo>`
from all these new methods so that we can handle the dynamic cases. If
we didn't have to, we'd be able to get rid of the `Option` entirely. But
I think that's an okay tradeoff for this one part of the API, and keeps
the other APIs intact.

## Testing

This PR contains tests to verify everything works as expected. You can
test locally by running:

```
cargo test --package bevy_reflect
```

---

## Changelog

### Public Changes

- Added `ArrayInfo::item_info` method
- Added `NamedField::type_info` method
- Added `UnnamedField::type_info` method
- Added `ListInfo::item_info` method
- Added `MapInfo::key_info` method
- Added `MapInfo::value_info` method
- All active fields now have a `Typed` bound (remember that this is
automatically satisfied for all types that derive `Reflect`)

### Internal Changes

- Added `MaybeTyped` trait

## Migration Guide

All active fields for reflected types (including lists, maps, tuples,
etc.), must implement `Typed`. For the majority of users this won't have
any visible impact.

However, users implementing `Reflect` manually may need to update their
types to implement `Typed` if they weren't already.

Additionally, custom dynamic types will need to implement the new hidden
`MaybeTyped` trait.
  • Loading branch information
MrGVSV authored Jul 15, 2024
1 parent 8e67aef commit aa24167
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct NoReflect(f32);
fn main() {
let mut foo: Box<dyn Struct> = Box::new(Foo::<NoReflect> { a: NoReflect(42.0) });
//~^ ERROR: `NoReflect` does not provide type registration information
//~| ERROR: `NoReflect` can not provide type information through reflection

// foo doesn't implement Reflect because NoReflect doesn't implement Reflect
foo.get_field::<NoReflect>("a").unwrap();
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_reflect/derive/src/utility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
quote!(
#ty : #reflect_bound
+ #bevy_reflect_path::TypePath
// Needed for `Typed` impls
+ #bevy_reflect_path::MaybeTyped
// Needed for `GetTypeRegistration` impls
+ #bevy_reflect_path::__macro_exports::RegisterForReflection
)
}))
Expand Down
18 changes: 15 additions & 3 deletions crates/bevy_reflect/src/array.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
self as bevy_reflect, utility::reflect_hasher, ApplyError, Reflect, ReflectKind, ReflectMut,
ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable,
self as bevy_reflect, utility::reflect_hasher, ApplyError, MaybeTyped, Reflect, ReflectKind,
ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable,
};
use bevy_reflect_derive::impl_type_path;
use std::{
Expand Down Expand Up @@ -79,6 +79,7 @@ pub trait Array: Reflect {
pub struct ArrayInfo {
type_path: TypePathTable,
type_id: TypeId,
item_info: fn() -> Option<&'static TypeInfo>,
item_type_path: TypePathTable,
item_type_id: TypeId,
capacity: usize,
Expand All @@ -93,10 +94,13 @@ impl ArrayInfo {
///
/// * `capacity`: The maximum capacity of the underlying array.
///
pub fn new<TArray: Array + TypePath, TItem: Reflect + TypePath>(capacity: usize) -> Self {
pub fn new<TArray: Array + TypePath, TItem: Reflect + MaybeTyped + TypePath>(
capacity: usize,
) -> Self {
Self {
type_path: TypePathTable::of::<TArray>(),
type_id: TypeId::of::<TArray>(),
item_info: TItem::maybe_type_info,
item_type_path: TypePathTable::of::<TItem>(),
item_type_id: TypeId::of::<TItem>(),
capacity,
Expand Down Expand Up @@ -143,6 +147,14 @@ impl ArrayInfo {
TypeId::of::<T>() == self.type_id
}

/// The [`TypeInfo`] of the array item.
///
/// Returns `None` if the array item does not contain static type information,
/// such as for dynamic types.
pub fn item_info(&self) -> Option<&'static TypeInfo> {
(self.item_info)()
}

/// A representation of the type path of the array item.
///
/// Provides dynamic access to all methods on [`TypePath`].
Expand Down
18 changes: 18 additions & 0 deletions crates/bevy_reflect/src/enums/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ mod tests {
if let VariantInfo::Tuple(variant) = info.variant("B").unwrap() {
assert!(variant.field_at(0).unwrap().is::<usize>());
assert!(variant.field_at(1).unwrap().is::<i32>());
assert!(variant
.field_at(0)
.unwrap()
.type_info()
.unwrap()
.is::<usize>());
assert!(variant
.field_at(1)
.unwrap()
.type_info()
.unwrap()
.is::<i32>());
} else {
panic!("Expected `VariantInfo::Tuple`");
}
Expand All @@ -60,6 +72,12 @@ mod tests {
if let VariantInfo::Struct(variant) = info.variant("C").unwrap() {
assert!(variant.field_at(0).unwrap().is::<f32>());
assert!(variant.field("foo").unwrap().is::<f32>());
assert!(variant
.field("foo")
.unwrap()
.type_info()
.unwrap()
.is::<f32>());
} else {
panic!("Expected `VariantInfo::Struct`");
}
Expand Down
28 changes: 25 additions & 3 deletions crates/bevy_reflect/src/fields.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::attributes::{impl_custom_attribute_methods, CustomAttributes};
use crate::{Reflect, TypePath, TypePathTable};
use crate::{MaybeTyped, Reflect, TypeInfo, TypePath, TypePathTable};
use std::any::{Any, TypeId};
use std::sync::Arc;

/// The named field of a reflected struct.
#[derive(Clone, Debug)]
pub struct NamedField {
name: &'static str,
type_info: fn() -> Option<&'static TypeInfo>,
type_path: TypePathTable,
type_id: TypeId,
custom_attributes: Arc<CustomAttributes>,
Expand All @@ -16,9 +17,10 @@ pub struct NamedField {

impl NamedField {
/// Create a new [`NamedField`].
pub fn new<T: Reflect + TypePath>(name: &'static str) -> Self {
pub fn new<T: Reflect + MaybeTyped + TypePath>(name: &'static str) -> Self {
Self {
name,
type_info: T::maybe_type_info,
type_path: TypePathTable::of::<T>(),
type_id: TypeId::of::<T>(),
custom_attributes: Arc::new(CustomAttributes::default()),
Expand Down Expand Up @@ -46,6 +48,15 @@ impl NamedField {
self.name
}

/// The [`TypeInfo`] of the field.
///
///
/// Returns `None` if the field does not contain static type information,
/// such as for dynamic types.
pub fn type_info(&self) -> Option<&'static TypeInfo> {
(self.type_info)()
}

/// A representation of the type path of the field.
///
/// Provides dynamic access to all methods on [`TypePath`].
Expand Down Expand Up @@ -86,6 +97,7 @@ impl NamedField {
#[derive(Clone, Debug)]
pub struct UnnamedField {
index: usize,
type_info: fn() -> Option<&'static TypeInfo>,
type_path: TypePathTable,
type_id: TypeId,
custom_attributes: Arc<CustomAttributes>,
Expand All @@ -94,9 +106,10 @@ pub struct UnnamedField {
}

impl UnnamedField {
pub fn new<T: Reflect + TypePath>(index: usize) -> Self {
pub fn new<T: Reflect + MaybeTyped + TypePath>(index: usize) -> Self {
Self {
index,
type_info: T::maybe_type_info,
type_path: TypePathTable::of::<T>(),
type_id: TypeId::of::<T>(),
custom_attributes: Arc::new(CustomAttributes::default()),
Expand Down Expand Up @@ -124,6 +137,15 @@ impl UnnamedField {
self.index
}

/// The [`TypeInfo`] of the field.
///
///
/// Returns `None` if the field does not contain static type information,
/// such as for dynamic types.
pub fn type_info(&self) -> Option<&'static TypeInfo> {
(self.type_info)()
}

/// A representation of the type path of the field.
///
/// Provides dynamic access to all methods on [`TypePath`].
Expand Down
16 changes: 8 additions & 8 deletions crates/bevy_reflect/src/impls/smallvec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ use std::any::Any;
use crate::utility::GenericTypeInfoCell;
use crate::{
self as bevy_reflect, ApplyError, FromReflect, FromType, GetTypeRegistration, List, ListInfo,
ListIter, Reflect, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo,
TypePath, TypeRegistration, Typed,
ListIter, MaybeTyped, Reflect, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned,
ReflectRef, TypeInfo, TypePath, TypeRegistration, Typed,
};

impl<T: SmallArray + TypePath + Send + Sync> List for SmallVec<T>
where
T::Item: FromReflect + TypePath,
T::Item: FromReflect + MaybeTyped + TypePath,
{
fn get(&self, index: usize) -> Option<&dyn Reflect> {
if index < SmallVec::len(self) {
Expand Down Expand Up @@ -79,7 +79,7 @@ where

impl<T: SmallArray + TypePath + Send + Sync> Reflect for SmallVec<T>
where
T::Item: FromReflect + TypePath,
T::Item: FromReflect + MaybeTyped + TypePath,
{
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
Expand Down Expand Up @@ -149,7 +149,7 @@ where

impl<T: SmallArray + TypePath + Send + Sync + 'static> Typed for SmallVec<T>
where
T::Item: FromReflect + TypePath,
T::Item: FromReflect + MaybeTyped + TypePath,
{
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
Expand All @@ -161,7 +161,7 @@ impl_type_path!(::smallvec::SmallVec<T: SmallArray>);

impl<T: SmallArray + TypePath + Send + Sync> FromReflect for SmallVec<T>
where
T::Item: FromReflect + TypePath,
T::Item: FromReflect + MaybeTyped + TypePath,
{
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
if let ReflectRef::List(ref_list) = reflect.reflect_ref() {
Expand All @@ -178,7 +178,7 @@ where

impl<T: SmallArray + TypePath + Send + Sync> GetTypeRegistration for SmallVec<T>
where
T::Item: FromReflect + TypePath,
T::Item: FromReflect + MaybeTyped + TypePath,
{
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<SmallVec<T>>();
Expand All @@ -188,4 +188,4 @@ where
}

#[cfg(feature = "functions")]
crate::func::macros::impl_function_traits!(SmallVec<T>; <T: SmallArray + TypePath + Send + Sync> where T::Item: FromReflect + TypePath);
crate::func::macros::impl_function_traits!(SmallVec<T>; <T: SmallArray + TypePath + Send + Sync> where T::Item: FromReflect + MaybeTyped + TypePath);
Loading

0 comments on commit aa24167

Please sign in to comment.