Skip to content

Commit

Permalink
Add automatic reflection registration
Browse files Browse the repository at this point in the history
  • Loading branch information
MrGVSV committed Nov 10, 2022
1 parent 1914a3f commit 162c3d0
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 36 deletions.
78 changes: 56 additions & 22 deletions crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use quote::quote;
use crate::{utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Token, Variant};
use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Token, Type, Variant};

pub(crate) enum ReflectDerive<'a> {
Struct(ReflectStruct<'a>),
Expand Down Expand Up @@ -323,6 +323,7 @@ impl<'a> ReflectMeta<'a> {
self.traits.idents(),
self.generics,
None,
None::<std::iter::Empty<&Type>>,
)
}

Expand All @@ -347,22 +348,7 @@ impl<'a> ReflectStruct<'a> {
&self.serialization_denylist
}

/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
///
/// Returns a specific implementation for structs and this method should be preffered over the generic [`get_type_registration`](crate::ReflectMeta) method
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
let reflect_path = self.meta.bevy_reflect_path();

crate::registration::impl_get_type_registration(
self.meta.type_name(),
reflect_path,
self.meta.traits().idents(),
self.meta.generics(),
Some(&self.serialization_denylist),
)
}

/// Get a collection of types which are exposed to the reflection API
/// Get a collection of types which are exposed to the reflection API.
pub fn active_types(&self) -> Vec<syn::Type> {
self.fields
.iter()
Expand All @@ -371,25 +357,39 @@ impl<'a> ReflectStruct<'a> {
.collect::<Vec<_>>()
}

/// 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(move |field| field.attrs.ignore.is_active())
}

/// Get an iterator of fields which are ignored by the reflection API
/// 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(move |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
}

/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
///
/// Returns a specific implementation for structs.
/// This method should be preferred over the generic [`get_type_registration`](crate::ReflectMeta) method.
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
crate::registration::impl_get_type_registration(
self.meta.type_name,
&self.meta.bevy_reflect_path,
self.meta.traits.idents(),
self.meta.generics,
Some(&self.serialization_denylist),
Some(self.active_types().iter()),
)
}
}

impl<'a> ReflectEnum<'a> {
Expand All @@ -410,4 +410,38 @@ impl<'a> ReflectEnum<'a> {
pub fn variants(&self) -> &[EnumVariant<'a>] {
&self.variants
}

/// Returns the `GetTypeRegistration` impl as a `TokenStream`.
pub fn get_type_registration(&self) -> proc_macro2::TokenStream {
crate::registration::impl_get_type_registration(
self.meta.type_name,
&self.meta.bevy_reflect_path,
self.meta.traits.idents(),
self.meta.generics,
None,
Some(
self.variants()
.iter()
.flat_map(|variant| variant.active_fields())
.map(|field| &field.data.ty),
),
)
}
}

impl<'a> EnumVariant<'a> {
/// The complete set of fields in this variant.
pub fn fields(&self) -> &[StructField<'a>] {
match &self.fields {
EnumVariantFields::Named(fields) | EnumVariantFields::Unnamed(fields) => fields,
EnumVariantFields::Unit => &[],
}
}

/// Get an iterator of fields which are exposed to the reflection API
pub fn active_fields(&self) -> impl Iterator<Item = &StructField<'a>> {
self.fields()
.iter()
.filter(|field| !field.attrs.ignore.is_ignored())
}
}
2 changes: 1 addition & 1 deletion crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> TokenStream {
bevy_reflect_path,
);

let get_type_registration_impl = reflect_enum.meta().get_type_registration();
let get_type_registration_impl = reflect_enum.get_type_registration();
let (impl_generics, ty_generics, where_clause) =
reflect_enum.meta().generics().split_for_impl();

Expand Down
15 changes: 13 additions & 2 deletions crates/bevy_reflect/bevy_reflect_derive/src/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@
use bit_set::BitSet;
use proc_macro2::Ident;
use quote::quote;
use syn::{Generics, Path};
use syn::{Generics, Path, Type};

/// Creates the `GetTypeRegistration` impl for the given type data.
pub(crate) fn impl_get_type_registration(
pub(crate) fn impl_get_type_registration<'a>(
type_name: &Ident,
bevy_reflect_path: &Path,
registration_data: &[Ident],
generics: &Generics,
serialization_denylist: Option<&BitSet<u32>>,
type_dependencies: Option<impl Iterator<Item = &'a Type>>,
) -> proc_macro2::TokenStream {
let type_deps_fn = type_dependencies.map(|deps| {
quote! {
fn register_type_dependencies(registry: &mut #bevy_reflect_path::__macro_exports::TypeRegistry) {
#(registry.try_register::<#deps>();)*
}
}
});

let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let serialization_data = serialization_denylist.map(|denylist| {
let denylist = denylist.into_iter();
Expand All @@ -32,6 +41,8 @@ pub(crate) fn impl_get_type_registration(
#(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::<#type_name #ty_generics>::from_type());)*
registration
}

#type_deps_fn
}
}
}
2 changes: 1 addition & 1 deletion crates/bevy_reflect/src/enums/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ mod tests {
#[test]
fn enum_should_allow_generics() {
#[derive(Reflect, Debug, PartialEq)]
enum TestEnum<T: FromReflect> {
enum TestEnum<T: FromReflect + GetTypeRegistration> {
A,
B(T),
C { value: T },
Expand Down
29 changes: 23 additions & 6 deletions crates/bevy_reflect/src/impls/std.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::std_traits::ReflectDefault;
use crate::{self as bevy_reflect, ReflectFromPtr, ReflectOwned};
use crate::{self as bevy_reflect, ReflectFromPtr, ReflectOwned, TypeRegistry};
use crate::{
map_apply, map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicEnum, DynamicMap, Enum,
EnumInfo, FromReflect, FromType, GetTypeRegistration, List, ListInfo, Map, MapInfo, MapIter,
Expand Down Expand Up @@ -298,12 +298,16 @@ impl<T: FromReflect> Typed for Vec<T> {
}
}

impl<T: FromReflect> GetTypeRegistration for Vec<T> {
impl<T: FromReflect + GetTypeRegistration> GetTypeRegistration for Vec<T> {
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Vec<T>>();
registration.insert::<ReflectFromPtr>(FromType::<Vec<T>>::from_type());
registration
}

fn register_type_dependencies(registry: &mut TypeRegistry) {
registry.try_register::<T>();
}
}

impl<T: FromReflect> FromReflect for Vec<T> {
Expand Down Expand Up @@ -469,14 +473,19 @@ impl<K: FromReflect + Eq + Hash, V: FromReflect> Typed for HashMap<K, V> {

impl<K, V> GetTypeRegistration for HashMap<K, V>
where
K: FromReflect + Eq + Hash,
V: FromReflect,
K: FromReflect + GetTypeRegistration + Eq + Hash,
V: FromReflect + GetTypeRegistration,
{
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<HashMap<K, V>>();
registration.insert::<ReflectFromPtr>(FromType::<HashMap<K, V>>::from_type());
registration
}

fn register_type_dependencies(registry: &mut TypeRegistry) {
registry.try_register::<K>();
registry.try_register::<V>();
}
}

impl<K: FromReflect + Eq + Hash, V: FromReflect> FromReflect for HashMap<K, V> {
Expand Down Expand Up @@ -638,10 +647,14 @@ impl<T: Reflect, const N: usize> Typed for [T; N] {
macro_rules! impl_array_get_type_registration {
($($N:expr)+) => {
$(
impl<T: Reflect > GetTypeRegistration for [T; $N] {
impl<T: Reflect + GetTypeRegistration> GetTypeRegistration for [T; $N] {
fn get_type_registration() -> TypeRegistration {
TypeRegistration::of::<[T; $N]>()
}

fn register_type_dependencies(registry: &mut TypeRegistry) {
registry.try_register::<T>();
}
}
)+
};
Expand Down Expand Up @@ -734,10 +747,14 @@ impl Reflect for Cow<'static, str> {
}
}

impl<T: FromReflect> GetTypeRegistration for Option<T> {
impl<T: FromReflect + GetTypeRegistration> GetTypeRegistration for Option<T> {
fn get_type_registration() -> TypeRegistration {
TypeRegistration::of::<Option<T>>()
}

fn register_type_dependencies(registry: &mut TypeRegistry) {
registry.try_register::<T>();
}
}

impl<T: FromReflect> Enum for Option<T> {
Expand Down
98 changes: 97 additions & 1 deletion crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub use erased_serde;
pub mod __macro_exports {
use crate::Uuid;

pub use crate::TypeRegistry;

/// Generates a new UUID from the given UUIDs `a` and `b`,
/// where the bytes are generated by a bitwise `a ^ b.rotate_right(1)`.
/// The generated UUID will be a `UUIDv4` (meaning that the bytes should be random, not e.g. derived from the system time).
Expand Down Expand Up @@ -103,6 +105,7 @@ mod tests {
ser::{to_string_pretty, PrettyConfig},
Deserializer,
};
use std::any::TypeId;
use std::fmt::{Debug, Formatter};

use super::prelude::*;
Expand Down Expand Up @@ -440,6 +443,99 @@ mod tests {
assert_eq!(new_foo, expected_new_foo);
}

#[test]
fn should_auto_register_fields() {
#[derive(Reflect, FromReflect)]
struct Foo {
bar: Bar,
}

#[derive(Reflect, FromReflect)]
enum Bar {
Variant(Baz),
}

#[derive(Reflect, FromReflect)]
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_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)]
Expand Down Expand Up @@ -661,7 +757,7 @@ mod tests {

// Struct (generic)
#[derive(Reflect)]
struct MyGenericStruct<T: Reflect> {
struct MyGenericStruct<T: Reflect + GetTypeRegistration> {
foo: T,
bar: usize,
}
Expand Down
Loading

0 comments on commit 162c3d0

Please sign in to comment.