bevy_reflect: Recursive registration (#5781)

# Objective

Resolves #4154

Currently, registration must all be done manually:

```rust
#[derive(Reflect)]
struct Foo(Bar);

#[derive(Reflect)]
struct Bar(Baz);

#[derive(Reflect)]
struct Baz(usize);

fn main() {
  // ...
  app
    .register_type::<Foo>()
    .register_type::<Bar>()
    .register_type::<Baz>()
    // .register_type::<usize>() <- This one is handled by Bevy, thankfully
  // ...
}
```

This can grow really quickly and become very annoying to add, remove,
and update as types change. It would be great if we could help reduce
the number of types that a user must manually implement themselves.

## Solution

As suggested in #4154, this PR adds automatic recursive registration.
Essentially, when a type is registered, it may now also choose to
register additional types along with it using the new
`GetTypeRegistration::register_type_dependencies` trait method.

The `Reflect` derive macro now automatically does this for all fields in
structs, tuple structs, struct variants, and tuple variants. This is
also done for tuples, arrays, `Vec<T>`, `HashMap<K, V>`, and
`Option<T>`.

This allows us to simplify the code above like:

```rust
#[derive(Reflect)]
struct Foo(Bar);

#[derive(Reflect)]
struct Bar(Baz);

#[derive(Reflect)]
struct Baz(usize);

fn main() {
  // ...
  app.register_type::<Foo>()
  // ...
}
```

This automatic registration only occurs if the type has not yet been
registered. If it has been registered, we simply skip it and move to the
next one. This reduces the cost of registration and prevents overwriting
customized registrations.

## Considerations

While this does improve ergonomics on one front, it's important to look
at some of the arguments against adopting a PR like this.

#### Generic Bounds

~~Since we need to be able to register the fields individually, we need
those fields to implement `GetTypeRegistration`. This forces users to
then add this trait as a bound on their generic arguments. This
annoyance could be relieved with something like #5772.~~

This is no longer a major issue as the `Reflect` derive now adds the
`GetTypeRegistration` bound by default. This should technically be okay,
since we already add the `Reflect` bound.

However, this can also be considered a breaking change for manual
implementations that left out a `GetTypeRegistration` impl ~~or for
items that contain dynamic types (e.g. `DynamicStruct`) since those also
do not implement `GetTypeRegistration`~~.

#### Registration Assumptions

By automatically registering fields, users might inadvertently be
relying on certain types to be automatically registered. If `Foo`
auto-registers `Bar`, but `Foo` is later removed from the code, then
anywhere that previously used or relied on `Bar`'s registration would
now fail.

---

## Changelog

- Added recursive type registration to structs, tuple structs, struct
variants, tuple variants, tuples, arrays, `Vec<T>`, `HashMap<K, V>`, and
`Option<T>`
- Added a new trait in the hidden `bevy_reflect::__macro_exports` module
called `RegisterForReflection`
- Added `GetTypeRegistration` impl for
`bevy_render::render_asset::RenderAssetUsages`

## Migration Guide

All types that derive `Reflect` will now automatically add
`GetTypeRegistration` as a bound on all (unignored) fields. This means
that all reflected fields will need to also implement
`GetTypeRegistration`.

If all fields **derive** `Reflect` or are implemented in `bevy_reflect`,
this should not cause any issues. However, manual implementations of
`Reflect` that excluded a `GetTypeRegistration` impl for their type will
need to add one.

```rust
#[derive(Reflect)]
struct Foo<T: FromReflect> {
  data: MyCustomType<T>
}

// OLD
impl<T: FromReflect> Reflect for MyCustomType<T> {/* ... */}

// NEW
impl<T: FromReflect + GetTypeRegistration> Reflect for MyCustomType<T> {/* ... */}
impl<T: FromReflect + GetTypeRegistration> GetTypeRegistration for MyCustomType<T> {/* ... */}
```

---------

Co-authored-by: James Liu <contact@jamessliu.com>
Co-authored-by: radiish <cb.setho@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Gino Valente 2024-03-04 12:04:10 -07:00 committed by GitHub
parent d90cb57c2e
commit ccb9d0500f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 505 additions and 129 deletions

View file

@ -470,7 +470,12 @@ impl<'a> ReflectMeta<'a> {
&self,
where_clause_options: &WhereClauseOptions,
) -> proc_macro2::TokenStream {
crate::registration::impl_get_type_registration(self, where_clause_options, None)
crate::registration::impl_get_type_registration(
self,
where_clause_options,
None,
Option::<std::iter::Empty<&Type>>::None,
)
}
/// The collection of docstrings for this type, if any.
@ -502,6 +507,7 @@ impl<'a> ReflectStruct<'a> {
self.meta(),
where_clause_options,
self.serialization_data(),
Some(self.active_types().iter()),
)
}
@ -512,22 +518,21 @@ impl<'a> ReflectStruct<'a> {
.collect()
}
/// Get an iterator of fields which are exposed to the reflection API
/// Get an iterator of fields which are exposed to the reflection API.
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields
self.fields()
.iter()
.filter(|field| field.attrs.ignore.is_active())
}
/// Get an iterator of fields which are ignored by the reflection API
pub fn ignored_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields
self.fields()
.iter()
.filter(|field| field.attrs.ignore.is_ignored())
}
/// The complete set of fields in this struct.
#[allow(dead_code)]
pub fn fields(&self) -> &[StructField<'a>] {
&self.fields
}
@ -573,6 +578,21 @@ impl<'a> ReflectEnum<'a> {
pub fn where_clause_options(&self) -> WhereClauseOptions {
WhereClauseOptions::new_with_fields(self.meta(), self.active_types().into_boxed_slice())
}
/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
///
/// Returns a specific implementation for enums and this method should be preferred over the generic [`get_type_registration`](crate::ReflectMeta) method
pub fn get_type_registration(
&self,
where_clause_options: &WhereClauseOptions,
) -> proc_macro2::TokenStream {
crate::registration::impl_get_type_registration(
self.meta(),
where_clause_options,
None,
Some(self.active_fields().map(|field| &field.data.ty)),
)
}
}
impl<'a> EnumVariant<'a> {

View file

@ -84,9 +84,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
let type_path_impl = impl_type_path(reflect_enum.meta());
let get_type_registration_impl = reflect_enum
.meta()
.get_type_registration(&where_clause_options);
let get_type_registration_impl = reflect_enum.get_type_registration(&where_clause_options);
let (impl_generics, ty_generics, where_clause) =
reflect_enum.meta().type_path().generics().split_for_impl();

View file

@ -4,17 +4,29 @@ use crate::derive_data::ReflectMeta;
use crate::serialization::SerializationDataDef;
use crate::utility::WhereClauseOptions;
use quote::quote;
use syn::Type;
/// Creates the `GetTypeRegistration` impl for the given type data.
#[allow(clippy::too_many_arguments)]
pub(crate) fn impl_get_type_registration(
pub(crate) fn impl_get_type_registration<'a>(
meta: &ReflectMeta,
where_clause_options: &WhereClauseOptions,
serialization_data: Option<&SerializationDataDef>,
type_dependencies: Option<impl Iterator<Item = &'a Type>>,
) -> proc_macro2::TokenStream {
let type_path = meta.type_path();
let bevy_reflect_path = meta.bevy_reflect_path();
let registration_data = meta.attrs().idents();
let type_deps_fn = type_dependencies.map(|deps| {
quote! {
#[inline(never)]
fn register_type_dependencies(registry: &mut #bevy_reflect_path::TypeRegistry) {
#(<#deps as #bevy_reflect_path::__macro_exports::RegisterForReflection>::__register(registry);)*
}
}
});
let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
@ -44,6 +56,8 @@ pub(crate) fn impl_get_type_registration(
#(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::<Self>::from_type());)*
registration
}
#type_deps_fn
}
}
}

View file

@ -217,11 +217,15 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
// `TypePath` is always required for active fields since they are used to
// construct `NamedField` and `UnnamedField` instances for the `Typed` impl.
Some(
self.active_fields
.iter()
.map(move |ty| quote!(#ty : #reflect_bound + #bevy_reflect_path::TypePath)),
)
// Likewise, `GetTypeRegistration` is always required for active fields since
// they are used to register the type's dependencies.
Some(self.active_fields.iter().map(move |ty| {
quote!(
#ty : #reflect_bound
+ #bevy_reflect_path::TypePath
+ #bevy_reflect_path::__macro_exports::RegisterForReflection
)
}))
}
}

View file

@ -1,5 +1,5 @@
use crate::std_traits::ReflectDefault;
use crate::{self as bevy_reflect, ReflectFromPtr, ReflectFromReflect, ReflectOwned};
use crate::{self as bevy_reflect, ReflectFromPtr, ReflectFromReflect, ReflectOwned, TypeRegistry};
use crate::{
impl_type_path, map_apply, map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicEnum,
DynamicMap, Enum, EnumInfo, FromReflect, FromType, GetTypeRegistration, List, ListInfo,
@ -221,7 +221,7 @@ impl_reflect_value!(::std::ffi::OsString(Debug, Hash, PartialEq));
macro_rules! impl_reflect_for_veclike {
($ty:path, $insert:expr, $remove:expr, $push:expr, $pop:expr, $sub:ty) => {
impl<T: FromReflect + TypePath> List for $ty {
impl<T: FromReflect + TypePath + GetTypeRegistration> List for $ty {
#[inline]
fn get(&self, index: usize) -> Option<&dyn Reflect> {
<$sub>::get(self, index).map(|value| value as &dyn Reflect)
@ -280,7 +280,7 @@ macro_rules! impl_reflect_for_veclike {
}
}
impl<T: FromReflect + TypePath> Reflect for $ty {
impl<T: FromReflect + TypePath + GetTypeRegistration> Reflect for $ty {
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
}
@ -347,7 +347,7 @@ macro_rules! impl_reflect_for_veclike {
}
}
impl<T: FromReflect + TypePath> Typed for $ty {
impl<T: FromReflect + TypePath + GetTypeRegistration> Typed for $ty {
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
CELL.get_or_insert::<Self, _>(|| TypeInfo::List(ListInfo::new::<Self, T>()))
@ -356,15 +356,19 @@ macro_rules! impl_reflect_for_veclike {
impl_type_path!($ty);
impl<T: FromReflect + TypePath> GetTypeRegistration for $ty {
impl<T: FromReflect + TypePath + GetTypeRegistration> GetTypeRegistration for $ty {
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<$ty>();
registration.insert::<ReflectFromPtr>(FromType::<$ty>::from_type());
registration
}
fn register_type_dependencies(registry: &mut TypeRegistry) {
registry.register::<T>();
}
}
impl<T: FromReflect + TypePath> FromReflect for $ty {
impl<T: FromReflect + TypePath + GetTypeRegistration> FromReflect for $ty {
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
if let ReflectRef::List(ref_list) = reflect.reflect_ref() {
let mut new_list = Self::with_capacity(ref_list.len());
@ -401,8 +405,8 @@ macro_rules! impl_reflect_for_hashmap {
($ty:path) => {
impl<K, V, S> Map for $ty
where
K: FromReflect + TypePath + Eq + Hash,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Send + Sync,
{
fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> {
@ -498,8 +502,8 @@ macro_rules! impl_reflect_for_hashmap {
impl<K, V, S> Reflect for $ty
where
K: FromReflect + TypePath + Eq + Hash,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Send + Sync,
{
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
@ -567,8 +571,8 @@ macro_rules! impl_reflect_for_hashmap {
impl<K, V, S> Typed for $ty
where
K: FromReflect + TypePath + Eq + Hash,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Send + Sync,
{
fn type_info() -> &'static TypeInfo {
@ -579,8 +583,8 @@ macro_rules! impl_reflect_for_hashmap {
impl<K, V, S> GetTypeRegistration for $ty
where
K: FromReflect + TypePath + Eq + Hash,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Send + Sync,
{
fn get_type_registration() -> TypeRegistration {
@ -588,12 +592,17 @@ macro_rules! impl_reflect_for_hashmap {
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
registration
}
fn register_type_dependencies(registry: &mut TypeRegistry) {
registry.register::<K>();
registry.register::<V>();
}
}
impl<K, V, S> FromReflect for $ty
where
K: FromReflect + TypePath + Eq + Hash,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash,
V: FromReflect + TypePath + GetTypeRegistration,
S: TypePath + BuildHasher + Default + Send + Sync,
{
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
@ -624,8 +633,8 @@ impl_type_path!(::bevy_utils::hashbrown::HashMap<K, V, S>);
impl<K, V> Map for ::std::collections::BTreeMap<K, V>
where
K: FromReflect + TypePath + Eq + Ord,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord,
V: FromReflect + TypePath + GetTypeRegistration,
{
fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> {
key.downcast_ref::<K>()
@ -720,8 +729,8 @@ where
impl<K, V> Reflect for ::std::collections::BTreeMap<K, V>
where
K: FromReflect + TypePath + Eq + Ord,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord,
V: FromReflect + TypePath + GetTypeRegistration,
{
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
@ -788,8 +797,8 @@ where
impl<K, V> Typed for ::std::collections::BTreeMap<K, V>
where
K: FromReflect + TypePath + Eq + Ord,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord,
V: FromReflect + TypePath + GetTypeRegistration,
{
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
@ -799,8 +808,8 @@ where
impl<K, V> GetTypeRegistration for ::std::collections::BTreeMap<K, V>
where
K: FromReflect + TypePath + Eq + Ord,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord,
V: FromReflect + TypePath + GetTypeRegistration,
{
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Self>();
@ -811,8 +820,8 @@ where
impl<K, V> FromReflect for ::std::collections::BTreeMap<K, V>
where
K: FromReflect + TypePath + Eq + Ord,
V: FromReflect + TypePath,
K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord,
V: FromReflect + TypePath + GetTypeRegistration,
{
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
if let ReflectRef::Map(ref_map) = reflect.reflect_ref() {
@ -831,7 +840,7 @@ where
impl_type_path!(::std::collections::BTreeMap<K, V>);
impl<T: Reflect + TypePath, const N: usize> Array for [T; N] {
impl<T: Reflect + TypePath + GetTypeRegistration, const N: usize> Array for [T; N] {
#[inline]
fn get(&self, index: usize) -> Option<&dyn Reflect> {
<[T]>::get(self, index).map(|value| value as &dyn Reflect)
@ -860,7 +869,7 @@ impl<T: Reflect + TypePath, const N: usize> Array for [T; N] {
}
}
impl<T: Reflect + TypePath, const N: usize> Reflect for [T; N] {
impl<T: Reflect + TypePath + GetTypeRegistration, const N: usize> Reflect for [T; N] {
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
}
@ -942,7 +951,7 @@ impl<T: Reflect + TypePath, const N: usize> Reflect for [T; N] {
}
}
impl<T: FromReflect + TypePath, const N: usize> FromReflect for [T; N] {
impl<T: FromReflect + TypePath + GetTypeRegistration, const N: usize> FromReflect for [T; N] {
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
if let ReflectRef::Array(ref_array) = reflect.reflect_ref() {
let mut temp_vec = Vec::with_capacity(ref_array.len());
@ -956,7 +965,7 @@ impl<T: FromReflect + TypePath, const N: usize> FromReflect for [T; N] {
}
}
impl<T: Reflect + TypePath, const N: usize> Typed for [T; N] {
impl<T: Reflect + TypePath + GetTypeRegistration, 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)))
@ -975,37 +984,27 @@ impl<T: TypePath, const N: usize> TypePath for [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.
// This can be changed to use const generics once serde utilizes const generics for arrays.
// Tracking issue: https://github.com/serde-rs/serde/issues/1937
macro_rules! impl_array_get_type_registration {
($($N:expr)+) => {
$(
impl<T: Reflect + TypePath> GetTypeRegistration for [T; $N] {
fn get_type_registration() -> TypeRegistration {
TypeRegistration::of::<[T; $N]>()
}
}
)+
};
}
impl_array_get_type_registration! {
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32
}
impl<T: FromReflect + TypePath> GetTypeRegistration for Option<T> {
impl<T: Reflect + TypePath + GetTypeRegistration, const N: usize> GetTypeRegistration for [T; N] {
fn get_type_registration() -> TypeRegistration {
TypeRegistration::of::<Option<T>>()
TypeRegistration::of::<[T; N]>()
}
fn register_type_dependencies(registry: &mut TypeRegistry) {
registry.register::<T>();
}
}
impl<T: FromReflect + TypePath> Enum for Option<T> {
impl<T: FromReflect + TypePath + GetTypeRegistration> GetTypeRegistration for Option<T> {
fn get_type_registration() -> TypeRegistration {
TypeRegistration::of::<Option<T>>()
}
fn register_type_dependencies(registry: &mut TypeRegistry) {
registry.register::<T>();
}
}
impl<T: FromReflect + TypePath + GetTypeRegistration> Enum for Option<T> {
fn field(&self, _name: &str) -> Option<&dyn Reflect> {
None
}
@ -1076,7 +1075,7 @@ impl<T: FromReflect + TypePath> Enum for Option<T> {
}
}
impl<T: FromReflect + TypePath> Reflect for Option<T> {
impl<T: FromReflect + TypePath + GetTypeRegistration> Reflect for Option<T> {
#[inline]
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
@ -1189,7 +1188,7 @@ impl<T: FromReflect + TypePath> Reflect for Option<T> {
}
}
impl<T: FromReflect + TypePath> FromReflect for Option<T> {
impl<T: FromReflect + TypePath + GetTypeRegistration> FromReflect for Option<T> {
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
if let ReflectRef::Enum(dyn_enum) = reflect.reflect_ref() {
match dyn_enum.variant_name() {
@ -1227,7 +1226,7 @@ impl<T: FromReflect + TypePath> FromReflect for Option<T> {
}
}
impl<T: FromReflect + TypePath> Typed for Option<T> {
impl<T: FromReflect + TypePath + GetTypeRegistration> Typed for Option<T> {
fn type_info() -> &'static TypeInfo {
static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new();
CELL.get_or_insert::<Self, _>(|| {
@ -1392,7 +1391,7 @@ where
}
}
impl<T: FromReflect + Clone + TypePath> List for Cow<'static, [T]> {
impl<T: FromReflect + Clone + TypePath + GetTypeRegistration> List for Cow<'static, [T]> {
fn get(&self, index: usize) -> Option<&dyn Reflect> {
self.as_ref().get(index).map(|x| x as &dyn Reflect)
}
@ -1451,7 +1450,7 @@ impl<T: FromReflect + Clone + TypePath> List for Cow<'static, [T]> {
}
}
impl<T: FromReflect + Clone + TypePath> Reflect for Cow<'static, [T]> {
impl<T: FromReflect + Clone + TypePath + GetTypeRegistration> Reflect for Cow<'static, [T]> {
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
}
@ -1518,20 +1517,26 @@ impl<T: FromReflect + Clone + TypePath> Reflect for Cow<'static, [T]> {
}
}
impl<T: FromReflect + Clone + TypePath> Typed for Cow<'static, [T]> {
impl<T: FromReflect + Clone + TypePath + GetTypeRegistration> Typed for Cow<'static, [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 + Clone + TypePath> GetTypeRegistration for Cow<'static, [T]> {
impl<T: FromReflect + Clone + TypePath + GetTypeRegistration> GetTypeRegistration
for Cow<'static, [T]>
{
fn get_type_registration() -> TypeRegistration {
TypeRegistration::of::<Cow<'static, [T]>>()
}
fn register_type_dependencies(registry: &mut TypeRegistry) {
registry.register::<T>();
}
}
impl<T: FromReflect + Clone + TypePath> FromReflect for Cow<'static, [T]> {
impl<T: FromReflect + Clone + TypePath + GetTypeRegistration> FromReflect for Cow<'static, [T]> {
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
if let ReflectRef::List(ref_list) = reflect.reflect_ref() {
let mut temp_vec = Vec::with_capacity(ref_list.len());

View file

@ -481,9 +481,11 @@ mod tuple_struct;
mod type_info;
mod type_path;
mod type_registry;
mod impls {
#[cfg(feature = "glam")]
mod glam;
#[cfg(feature = "bevy_math")]
mod math {
mod direction;
@ -491,6 +493,7 @@ mod impls {
mod primitives3d;
mod rect;
}
#[cfg(feature = "smallvec")]
mod smallvec;
#[cfg(feature = "smol_str")]
@ -535,9 +538,48 @@ pub use erased_serde;
extern crate alloc;
/// Exports used by the reflection macros.
///
/// These are not meant to be used directly and are subject to breaking changes.
#[doc(hidden)]
pub mod __macro_exports {
pub use bevy_utils::uuid::generate_composite_uuid;
use crate::{
DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicStruct, DynamicTuple,
DynamicTupleStruct, GetTypeRegistration, TypeRegistry,
};
/// A wrapper trait around [`GetTypeRegistration`].
///
/// This trait is used by the derive macro to recursively register all type dependencies.
/// It's used instead of `GetTypeRegistration` directly to avoid making dynamic types also
/// implement `GetTypeRegistration` in order to be used as active fields.
///
/// This trait has a blanket implementation for all types that implement `GetTypeRegistration`
/// and manual implementations for all dynamic types (which simply do nothing).
pub trait RegisterForReflection {
#[allow(unused_variables)]
fn __register(registry: &mut TypeRegistry) {}
}
impl<T: GetTypeRegistration> RegisterForReflection for T {
fn __register(registry: &mut TypeRegistry) {
registry.register::<T>();
}
}
impl RegisterForReflection for DynamicEnum {}
impl RegisterForReflection for DynamicTupleStruct {}
impl RegisterForReflection for DynamicStruct {}
impl RegisterForReflection for DynamicMap {}
impl RegisterForReflection for DynamicList {}
impl RegisterForReflection for DynamicArray {}
impl RegisterForReflection for DynamicTuple {}
}
#[cfg(test)]
@ -1002,6 +1044,124 @@ mod tests {
assert_eq!(new_foo, expected_new_foo);
}
#[test]
fn should_auto_register_fields() {
#[derive(Reflect)]
struct Foo {
bar: Bar,
}
#[derive(Reflect)]
enum Bar {
Variant(Baz),
}
#[derive(Reflect)]
struct Baz(usize);
// === Basic === //
let mut registry = TypeRegistry::empty();
registry.register::<Foo>();
assert!(
registry.contains(TypeId::of::<Bar>()),
"registry should contain auto-registered `Bar` from `Foo`"
);
// === Option === //
let mut registry = TypeRegistry::empty();
registry.register::<Option<Foo>>();
assert!(
registry.contains(TypeId::of::<Bar>()),
"registry should contain auto-registered `Bar` from `Option<Foo>`"
);
// === Tuple === //
let mut registry = TypeRegistry::empty();
registry.register::<(Foo, Foo)>();
assert!(
registry.contains(TypeId::of::<Bar>()),
"registry should contain auto-registered `Bar` from `(Foo, Foo)`"
);
// === Array === //
let mut registry = TypeRegistry::empty();
registry.register::<[Foo; 3]>();
assert!(
registry.contains(TypeId::of::<Bar>()),
"registry should contain auto-registered `Bar` from `[Foo; 3]`"
);
// === Vec === //
let mut registry = TypeRegistry::empty();
registry.register::<Vec<Foo>>();
assert!(
registry.contains(TypeId::of::<Bar>()),
"registry should contain auto-registered `Bar` from `Vec<Foo>`"
);
// === HashMap === //
let mut registry = TypeRegistry::empty();
registry.register::<HashMap<i32, Foo>>();
assert!(
registry.contains(TypeId::of::<Bar>()),
"registry should contain auto-registered `Bar` from `HashMap<i32, Foo>`"
);
}
#[test]
fn should_allow_dynamic_fields() {
#[derive(Reflect)]
#[reflect(from_reflect = false)]
struct MyStruct(
DynamicEnum,
DynamicTupleStruct,
DynamicStruct,
DynamicMap,
DynamicList,
DynamicArray,
DynamicTuple,
i32,
);
assert_impl_all!(MyStruct: Reflect, GetTypeRegistration);
let mut registry = TypeRegistry::empty();
registry.register::<MyStruct>();
assert_eq!(2, registry.iter().count());
assert!(registry.contains(TypeId::of::<MyStruct>()));
assert!(registry.contains(TypeId::of::<i32>()));
}
#[test]
fn should_not_auto_register_existing_types() {
#[derive(Reflect)]
struct Foo {
bar: Bar,
}
#[derive(Reflect, Default)]
struct Bar(usize);
let mut registry = TypeRegistry::empty();
registry.register::<Bar>();
registry.register_type_data::<Bar, ReflectDefault>();
registry.register::<Foo>();
assert!(
registry
.get_type_data::<ReflectDefault>(TypeId::of::<Bar>())
.is_some(),
"registry should contain existing registration for `Bar`"
);
}
#[test]
fn reflect_serialize() {
#[derive(Reflect)]
@ -1981,6 +2141,39 @@ bevy_reflect::tests::Test {
let _ = <RecurseB as TypePath>::type_path();
}
#[test]
fn recursive_registration_does_not_hang() {
#[derive(Reflect)]
struct Recurse<T>(T);
let mut registry = TypeRegistry::empty();
registry.register::<Recurse<Recurse<()>>>();
#[derive(Reflect)]
#[reflect(no_field_bounds)]
struct SelfRecurse {
recurse: Vec<SelfRecurse>,
}
registry.register::<SelfRecurse>();
#[derive(Reflect)]
#[reflect(no_field_bounds)]
enum RecurseA {
Recurse(RecurseB),
}
#[derive(Reflect)]
struct RecurseB {
vector: Vec<RecurseA>,
}
registry.register::<RecurseA>();
assert!(registry.contains(TypeId::of::<RecurseA>()));
assert!(registry.contains(TypeId::of::<RecurseB>()));
}
#[test]
fn can_opt_out_type_path() {
#[derive(Reflect)]

View file

@ -3,8 +3,8 @@ use bevy_utils::all_tuples;
use crate::{
self as bevy_reflect, utility::GenericTypePathCell, FromReflect, GetTypeRegistration, Reflect,
ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypeRegistration, Typed,
UnnamedField,
ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypeRegistration, TypeRegistry,
Typed, UnnamedField,
};
use crate::{ReflectKind, TypePathTable};
use std::any::{Any, TypeId};
@ -461,7 +461,7 @@ pub fn tuple_debug(dyn_tuple: &dyn Tuple, f: &mut Formatter<'_>) -> std::fmt::Re
macro_rules! impl_reflect_tuple {
{$($index:tt : $name:tt),*} => {
impl<$($name: Reflect + TypePath),*> Tuple for ($($name,)*) {
impl<$($name: Reflect + TypePath + GetTypeRegistration),*> Tuple for ($($name,)*) {
#[inline]
fn field(&self, index: usize) -> Option<&dyn Reflect> {
match index {
@ -512,7 +512,7 @@ macro_rules! impl_reflect_tuple {
}
}
impl<$($name: Reflect + TypePath),*> Reflect for ($($name,)*) {
impl<$($name: Reflect + TypePath + GetTypeRegistration),*> Reflect for ($($name,)*) {
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
}
@ -575,7 +575,7 @@ macro_rules! impl_reflect_tuple {
}
}
impl <$($name: Reflect + TypePath),*> Typed for ($($name,)*) {
impl <$($name: Reflect + TypePath + GetTypeRegistration),*> Typed for ($($name,)*) {
fn type_info() -> &'static TypeInfo {
static CELL: $crate::utility::GenericTypeInfoCell = $crate::utility::GenericTypeInfoCell::new();
CELL.get_or_insert::<Self, _>(|| {
@ -588,14 +588,17 @@ macro_rules! impl_reflect_tuple {
}
}
impl<$($name: Reflect + TypePath),*> GetTypeRegistration for ($($name,)*) {
impl<$($name: Reflect + TypePath + GetTypeRegistration),*> GetTypeRegistration for ($($name,)*) {
fn get_type_registration() -> TypeRegistration {
TypeRegistration::of::<($($name,)*)>()
}
fn register_type_dependencies(_registry: &mut TypeRegistry) {
$(_registry.register::<$name>();)*
}
}
impl<$($name: FromReflect + TypePath),*> FromReflect for ($($name,)*)
impl<$($name: FromReflect + TypePath + GetTypeRegistration),*> FromReflect for ($($name,)*)
{
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
if let ReflectRef::Tuple(_ref_tuple) = reflect.reflect_ref() {

View file

@ -56,8 +56,15 @@ impl Debug for TypeRegistryArc {
/// See the [crate-level documentation] for more information on type registration.
///
/// [crate-level documentation]: crate
pub trait GetTypeRegistration {
pub trait GetTypeRegistration: 'static {
/// Returns the default [`TypeRegistration`] for this type.
fn get_type_registration() -> TypeRegistration;
/// Registers other types needed by this type.
///
/// This method is called by [`TypeRegistry::register`] to register any other required types.
/// Often, this is done for fields of structs and enum variants to ensure all types are properly registered.
#[allow(unused_variables)]
fn register_type_dependencies(registry: &mut TypeRegistry) {}
}
impl Default for TypeRegistry {
@ -100,41 +107,134 @@ impl TypeRegistry {
registry
}
/// Registers the type `T`, adding reflect data as specified in the [`Reflect`] derive:
/// ```ignore (Neither bevy_ecs nor serde "derive" are available.)
/// #[derive(Component, serde::Serialize, serde::Deserialize, Reflect)]
/// #[reflect(Component, Serialize, Deserialize)] // will register ReflectComponent, ReflectSerialize, ReflectDeserialize
/// Attempts to register the type `T` if it has not yet been registered already.
///
/// This will also recursively register any type dependencies as specified by [`GetTypeRegistration::register_type_dependencies`].
/// When deriving `Reflect`, this will generally be all the fields of the struct or enum variant.
/// As with any type registration, these type dependencies will not be registered more than once.
///
/// If the registration for type `T` already exists, it will not be registered again and neither will its type dependencies.
/// To register the type, overwriting any existing registration, use [register](Self::overwrite_registration) instead.
///
/// Additionally, this will add any reflect [type data](TypeData) as specified in the [`Reflect`] derive.
///
/// # Example
///
/// ```
/// # use std::any::TypeId;
/// # use bevy_reflect::{Reflect, TypeRegistry, std_traits::ReflectDefault};
/// #[derive(Reflect, Default)]
/// #[reflect(Default)]
/// struct Foo {
/// name: Option<String>,
/// value: i32
/// }
///
/// let mut type_registry = TypeRegistry::default();
///
/// type_registry.register::<Foo>();
///
/// // The main type
/// assert!(type_registry.contains(TypeId::of::<Foo>()));
///
/// // Its type dependencies
/// assert!(type_registry.contains(TypeId::of::<Option<String>>()));
/// assert!(type_registry.contains(TypeId::of::<i32>()));
///
/// // Its type data
/// assert!(type_registry.get_type_data::<ReflectDefault>(TypeId::of::<Foo>()).is_some());
/// ```
pub fn register<T>(&mut self)
where
T: GetTypeRegistration,
{
self.add_registration(T::get_type_registration());
if self.register_internal(TypeId::of::<T>(), T::get_type_registration) {
T::register_type_dependencies(self);
}
}
/// Attempts to register the type described by `registration`.
///
/// If the registration for the type already exists, it will not be registered again.
///
/// To forcibly register the type, overwriting any existing registration, use the
/// [`overwrite_registration`](Self::overwrite_registration) method instead.
///
/// This method will _not_ register type dependencies.
/// Use [`register`](Self::register) to register a type with its dependencies.
///
/// Returns `true` if the registration was added and `false` if it already exists.
pub fn add_registration(&mut self, registration: TypeRegistration) -> bool {
let type_id = registration.type_id();
self.register_internal(type_id, || registration)
}
/// Registers the type described by `registration`.
pub fn add_registration(&mut self, registration: TypeRegistration) {
if self.registrations.contains_key(&registration.type_id()) {
return;
}
let short_name = registration.type_info().type_path_table().short_path();
if self.short_path_to_id.contains_key(short_name)
|| self.ambiguous_names.contains(short_name)
{
// name is ambiguous. fall back to long names for all ambiguous types
self.short_path_to_id.remove(short_name);
self.ambiguous_names.insert(short_name);
} else {
self.short_path_to_id
.insert(short_name, registration.type_id());
}
self.type_path_to_id
.insert(registration.type_info().type_path(), registration.type_id());
///
/// If the registration for the type already exists, it will be overwritten.
///
/// To avoid overwriting existing registrations, it's recommended to use the
/// [`register`](Self::register) or [`add_registration`](Self::add_registration) methods instead.
///
/// This method will _not_ register type dependencies.
/// Use [`register`](Self::register) to register a type with its dependencies.
pub fn overwrite_registration(&mut self, registration: TypeRegistration) {
Self::update_registration_indices(
&registration,
&mut self.short_path_to_id,
&mut self.type_path_to_id,
&mut self.ambiguous_names,
);
self.registrations
.insert(registration.type_id(), registration);
}
/// Internal method to register a type with a given [`TypeId`] and [`TypeRegistration`].
///
/// By using this method, we are able to reduce the number of `TypeId` hashes and lookups needed
/// to register a type.
///
/// This method is internal to prevent users from accidentally registering a type with a `TypeId`
/// that does not match the type in the `TypeRegistration`.
fn register_internal(
&mut self,
type_id: TypeId,
get_registration: impl FnOnce() -> TypeRegistration,
) -> bool {
match self.registrations.entry(type_id) {
bevy_utils::hashbrown::hash_map::Entry::Occupied(_) => false,
bevy_utils::hashbrown::hash_map::Entry::Vacant(entry) => {
let registration = get_registration();
Self::update_registration_indices(
&registration,
&mut self.short_path_to_id,
&mut self.type_path_to_id,
&mut self.ambiguous_names,
);
entry.insert(registration);
true
}
}
}
/// Internal method to register additional lookups for a given [`TypeRegistration`].
fn update_registration_indices(
registration: &TypeRegistration,
short_path_to_id: &mut HashMap<&'static str, TypeId>,
type_path_to_id: &mut HashMap<&'static str, TypeId>,
ambiguous_names: &mut HashSet<&'static str>,
) {
let short_name = registration.type_info().type_path_table().short_path();
if short_path_to_id.contains_key(short_name) || ambiguous_names.contains(short_name) {
// name is ambiguous. fall back to long names for all ambiguous types
short_path_to_id.remove(short_name);
ambiguous_names.insert(short_name);
} else {
short_path_to_id.insert(short_name, registration.type_id());
}
type_path_to_id.insert(registration.type_info().type_path(), registration.type_id());
}
/// Registers the type data `D` for type `T`.
///
/// Most of the time [`TypeRegistry::register`] can be used instead to register a type you derived [`Reflect`] for.
@ -162,6 +262,10 @@ impl TypeRegistry {
data.insert(D::from_type());
}
pub fn contains(&self, type_id: TypeId) -> bool {
self.registrations.contains_key(&type_id)
}
/// Returns a reference to the [`TypeRegistration`] of the type with the
/// given [`TypeId`].
///

View file

@ -1,4 +1,4 @@
use bevy_reflect::{Reflect, TypePath};
use bevy_reflect::{GetField, Reflect, Struct, TypePath};
#[derive(Reflect)]
#[reflect(from_reflect = false)]
@ -11,7 +11,7 @@ struct Foo<T> {
struct NoReflect(f32);
fn main() {
let mut foo: Box<dyn Reflect> = Box::new(Foo::<NoReflect> { a: NoReflect(42.0) });
let mut foo: Box<dyn Struct> = Box::new(Foo::<NoReflect> { a: NoReflect(42.0) });
// foo doesn't implement Reflect because NoReflect doesn't implement Reflect
foo.get_field::<NoReflect>("a").unwrap();
}

View file

@ -1,16 +1,34 @@
error[E0599]: no method named `get_field` found for struct `Box<(dyn Reflect + 'static)>` in the current scope
--> tests/reflect_derive/generics.fail.rs:16:9
|
16 | foo.get_field::<NoReflect>("a").unwrap();
| ^^^^^^^^^ method not found in `Box<dyn Reflect>`
error[E0277]: the trait bound `NoReflect: Reflect` is not satisfied
--> tests/reflect_derive/generics.fail.rs:14:37
--> tests/reflect_derive/generics.fail.rs:16:21
|
16 | foo.get_field::<NoReflect>("a").unwrap();
| --------- ^^^^^^^^^ the trait `Reflect` is not implemented for `NoReflect`
| |
| required by a bound introduced by this call
|
= help: the following other types implement trait `Reflect`:
bool
char
isize
i8
i16
i32
i64
i128
and $N others
note: required by a bound in `bevy_reflect::GetField::get_field`
--> /home/runner/work/bevy/bevy/crates/bevy_reflect/src/struct_trait.rs:242:21
|
242 | fn get_field<T: Reflect>(&self, name: &str) -> Option<&T>;
| ^^^^^^^ required by this bound in `GetField::get_field`
error[E0277]: the trait bound `NoReflect: GetTypeRegistration` is not satisfied
--> tests/reflect_derive/generics.fail.rs:14:36
|
14 | let mut foo: Box<dyn Reflect> = Box::new(Foo::<NoReflect> { a: NoReflect(42.0) });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Reflect` is not implemented for `NoReflect`
14 | let mut foo: Box<dyn Struct> = Box::new(Foo::<NoReflect> { a: NoReflect(42.0) });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GetTypeRegistration` is not implemented for `NoReflect`
|
= help: the following other types implement trait `Reflect`:
= help: the following other types implement trait `GetTypeRegistration`:
bool
char
isize
@ -20,7 +38,8 @@ error[E0277]: the trait bound `NoReflect: Reflect` is not satisfied
i64
i128
and $N others
note: required for `Foo<NoReflect>` to implement `Reflect`
= note: required for `NoReflect` to implement `RegisterForReflection`
note: required for `Foo<NoReflect>` to implement `bevy_reflect::Struct`
--> tests/reflect_derive/generics.fail.rs:3:10
|
3 | #[derive(Reflect)]
@ -28,5 +47,5 @@ note: required for `Foo<NoReflect>` to implement `Reflect`
4 | #[reflect(from_reflect = false)]
5 | struct Foo<T> {
| ^^^^^^
= note: required for the cast from `Box<Foo<NoReflect>>` to `Box<(dyn Reflect + 'static)>`
= note: required for the cast from `Box<Foo<NoReflect>>` to `Box<(dyn bevy_reflect::Struct + 'static)>`
= note: this error originates in the derive macro `Reflect` (in Nightly builds, run with -Z macro-backtrace for more info)

View file

@ -7,10 +7,12 @@ use bevy_ecs::{
system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
world::{FromWorld, Mut},
};
use bevy_reflect::std_traits::ReflectDefault;
use bevy_reflect::{
utility::{reflect_hasher, NonGenericTypeInfoCell},
FromReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath,
Typed, ValueInfo,
FromReflect, FromType, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectFromPtr,
ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize,
TypeInfo, TypePath, TypeRegistration, Typed, ValueInfo,
};
use bevy_utils::{thiserror::Error, HashMap, HashSet};
use serde::{Deserialize, Serialize};
@ -162,6 +164,18 @@ impl Reflect for RenderAssetUsages {
}
}
impl GetTypeRegistration for RenderAssetUsages {
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Self>();
registration.insert::<ReflectSerialize>(FromType::<Self>::from_type());
registration.insert::<ReflectDeserialize>(FromType::<Self>::from_type());
registration.insert::<ReflectDefault>(FromType::<Self>::from_type());
registration.insert::<ReflectFromReflect>(FromType::<Self>::from_type());
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
registration
}
}
impl FromReflect for RenderAssetUsages {
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
let raw_value = *reflect.as_any().downcast_ref::<u8>()?;

View file

@ -12,8 +12,10 @@ fn main() {
.run();
}
/// The `#[derive(Reflect)]` macro will automatically add any required bounds to `T`,
/// such as `Reflect` and `GetTypeRegistration`.
#[derive(Reflect)]
struct MyType<T: Reflect> {
struct MyType<T> {
value: T,
}