Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bevy_reflect: Trait casting #5001

Closed
wants to merge 15 commits into from
2 changes: 1 addition & 1 deletion crates/bevy_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use serde::{Deserialize, Serialize};
Reflect,
FromReflect,
)]
#[reflect_value(Serialize, Deserialize, PartialEq, Hash)]
#[reflect_value(Deserialize, PartialEq, Hash)]
pub enum HandleId {
Id(Uuid, u64),
AssetPathId(AssetPathId),
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_asset/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,19 @@ impl<'a> AssetPath<'a> {
#[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect,
)]
#[reflect_value(PartialEq, Hash, Serialize, Deserialize)]
#[reflect_value(PartialEq, Hash, Deserialize)]
pub struct AssetPathId(SourcePathId, LabelId);

#[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect,
)]
#[reflect_value(PartialEq, Hash, Serialize, Deserialize)]
#[reflect_value(PartialEq, Hash, Deserialize)]
pub struct SourcePathId(u64);

#[derive(
Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Reflect,
)]
#[reflect_value(PartialEq, Hash, Serialize, Deserialize)]
#[reflect_value(PartialEq, Hash, Deserialize)]
pub struct LabelId(u64);

impl<'a> From<&'a Path> for SourcePathId {
Expand Down
63 changes: 41 additions & 22 deletions crates/bevy_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ pub mod prelude {

use bevy_app::prelude::*;
use bevy_ecs::entity::Entity;
use bevy_utils::HashSet;
use std::ops::Range;
use bevy_reflect::TypeRegistryArc;

/// Adds core functionality to Apps.
#[derive(Default)]
Expand All @@ -34,29 +33,49 @@ impl Plugin for CorePlugin {

app.register_type::<Entity>().register_type::<Name>();

register_rust_types(app);
register_math_types(app);
let registry = app.world.resource::<TypeRegistryArc>();
let mut registry = registry.write();
rust_types::register_types(&mut registry);
math_types::register_types(&mut registry);
}
}

fn register_rust_types(app: &mut App) {
app.register_type::<Range<f32>>()
.register_type::<String>()
.register_type::<HashSet<String>>()
.register_type::<Option<String>>();
mod rust_types {
use bevy_reflect::erased_serde::Serialize;
use bevy_reflect::register_all;
use bevy_utils::HashSet;
use std::ops::Range;

register_all! {
traits: [Serialize],
types: [
String,
Option<String>,
Range<f32>,
HashSet<String>
]
}
}

fn register_math_types(app: &mut App) {
app.register_type::<bevy_math::IVec2>()
.register_type::<bevy_math::IVec3>()
.register_type::<bevy_math::IVec4>()
.register_type::<bevy_math::UVec2>()
.register_type::<bevy_math::UVec3>()
.register_type::<bevy_math::UVec4>()
.register_type::<bevy_math::Vec2>()
.register_type::<bevy_math::Vec3>()
.register_type::<bevy_math::Vec4>()
.register_type::<bevy_math::Mat3>()
.register_type::<bevy_math::Mat4>()
.register_type::<bevy_math::Quat>();
mod math_types {
use bevy_reflect::erased_serde::Serialize;
use bevy_reflect::register_all;

register_all! {
traits: [Serialize],
types: [
bevy_math::IVec2,
bevy_math::IVec3,
bevy_math::IVec4,
bevy_math::UVec2,
bevy_math::UVec3,
bevy_math::UVec4,
bevy_math::Vec2,
bevy_math::Vec3,
bevy_math::Vec4,
bevy_math::Mat3,
bevy_math::Mat4,
bevy_math::Quat,
]
}
}
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/reflect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl<C: Component + Reflect + FromWorld> FromType<C> for ReflectComponent {
}
}

impl_reflect_value!(Entity(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(Entity(Hash, PartialEq, Deserialize));
impl_from_reflect_value!(Entity);

#[derive(Clone)]
Expand Down
40 changes: 40 additions & 0 deletions crates/bevy_reflect/bevy_reflect_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,43 @@ pub fn impl_from_reflect_value(input: TokenStream) -> TokenStream {
let ty = &reflect_value_def.type_name;
from_reflect::impl_value(ty, &reflect_value_def.generics, &bevy_reflect_path)
}

/// A macro that allows for mass type, trait, and type data registration.
///
/// This will generate a public function with the signature: `fn register_types(&mut TypeRegistry)`.
/// You can then use this generated function to register the given types, traits, and type data.
///
/// The only required field is `types`. Both the `traits` and `data` fields can be omitted if
/// they are not needed.
///
/// To register a trait cast, add the trait to the `traits` list.
///
/// To register type data, add the type data to the `data` list.
///
/// Everything in the `traits` and `data` lists will be registered for all types in `types`
/// that are able to register them.
///
/// # Example
///
/// ```ignore
/// use bevy_reflect_derive::register_all;
///
/// trait MyTrait {}
/// struct MyType;
/// struct MyOtherType;
///
/// impl MyTrait for MyType {}
///
/// // Not all types need to implement all traits or type data
/// register_all! {
/// traits: [MyTrait],
/// types: [MyType, MyOtherType],
/// // You can also register type data as well:
/// // data: [ReflectMyOtherTrait]
/// }
///
/// ```
#[proc_macro]
pub fn register_all(item: TokenStream) -> TokenStream {
registration::register_all_internal(item)
}
152 changes: 151 additions & 1 deletion crates/bevy_reflect/bevy_reflect_derive/src/registration.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
//! Contains code related specifically to Bevy's type registration.

use bevy_macro_utils::BevyManifest;
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::{Iter, Punctuated};
use syn::{bracketed, parse_macro_input, Token, Type};
use syn::{Generics, Path};

/// Creates the `GetTypeRegistration` impl for the given type data.
Expand All @@ -23,3 +28,148 @@ pub(crate) fn impl_get_type_registration(
}
}
}

pub fn register_all_internal(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as RegisterAllData);

let bevy_reflect_path = BevyManifest::default().get_path("bevy_reflect");
let registry_ident = format_ident!("registry");

let empty = Punctuated::<Type, Token![,]>::default();
let registrations = input.types().map(|ty| {
let type_data = input.type_data().unwrap_or_else(|| empty.iter());
let trait_type = input.traits().unwrap_or_else(|| empty.iter());
quote! {{
// Get or create registration for type
let type_registration = match #registry_ident.get_mut(::std::any::TypeId::of::<#ty>()) {
Some(registration) => registration,
None => {
#registry_ident.register::<#ty>();
#registry_ident.get_mut(::std::any::TypeId::of::<#ty>()).unwrap()
}
};
// Register all possible trait casts
#(
if let Some(cast_fn) = #bevy_reflect_path::maybe_trait_cast!(#ty, #trait_type) {
type_registration.register_trait_cast::<dyn #trait_type>(cast_fn);
}
)*
// Register all possible type data
#(
if let Some(data) = #bevy_reflect_path::maybe_type_data!(#ty, #type_data) {
type_registration.insert(data);
}
)*
}}
});

TokenStream::from(quote! {
pub fn register_types(#registry_ident: &mut #bevy_reflect_path::TypeRegistry) {
#(#registrations)*
}
})
}

/// Maps to the following invocation:
///
/// ```ignore
/// use bevy_reflect_derive::{register_all, reflect_trait};
///
/// trait MyTrait {}
/// struct MyType {}
/// #[reflect_trait]
/// trait MyData {}
///
/// register_all! {
/// types: [MyType],
/// traits: [MyTrait],
/// data: [ReflectMyData],
/// }
/// ```
///
/// > Note: The order of the `traits`, `data`, and `types` fields does not matter. Additionally,
/// > the commas (separating and trailing) may be omitted entirely.
///
/// The only required field in this macro is the `types` field. All others can be omitted if
/// desired.
struct RegisterAllData {
type_list: Punctuated<Type, Token![,]>,
trait_list: Option<Punctuated<Type, Token![,]>>,
data_list: Option<Punctuated<Type, Token![,]>>,
}

impl RegisterAllData {
/// Returns an iterator over the types to register.
fn types(&self) -> Iter<Type> {
self.type_list.iter()
}

/// Returns an iterator over the traits to register.
fn traits(&self) -> Option<Iter<Type>> {
self.trait_list.as_ref().map(|list| list.iter())
}

/// Returns an iterator over the type data to register.
fn type_data(&self) -> Option<Iter<Type>> {
self.data_list.as_ref().map(|list| list.iter())
}

/// Parse a list of types.
///
/// This is the portion _after_ the the respective keyword and consumes: `: [ Foo, Bar, Baz ]`
fn parse_list(input: &mut ParseStream) -> syn::Result<Punctuated<Type, Token![,]>> {
input.parse::<Token![:]>()?;
let list;
bracketed!(list in input);
let parsed = list.parse_terminated(Type::parse)?;
// Parse optional trailing comma
input.parse::<Option<Token![,]>>()?;
Ok(parsed)
}
}

impl Parse for RegisterAllData {
fn parse(mut input: ParseStream) -> syn::Result<Self> {
let mut trait_list = None;
let mut type_list = None;
let mut data_list = None;

while !input.is_empty() {
let lookahead = input.lookahead1();
if lookahead.peek(kw::traits) {
input.parse::<kw::traits>()?;
trait_list = Some(Self::parse_list(&mut input)?);
} else if lookahead.peek(kw::types) {
input.parse::<kw::types>()?;
type_list = Some(Self::parse_list(&mut input)?);
} else if lookahead.peek(kw::data) {
input.parse::<kw::data>()?;
data_list = Some(Self::parse_list(&mut input)?);
} else {
return Err(syn::Error::new(
input.span(),
"expected either 'traits', 'types', or 'data' field",
));
}
}

if let Some(type_list) = type_list {
Ok(Self {
trait_list,
type_list,
data_list,
})
} else {
Err(syn::Error::new(
input.span(),
"missing required field 'types'",
))
}
}
}

mod kw {
syn::custom_keyword!(traits);
syn::custom_keyword!(types);
syn::custom_keyword!(data);
}
Loading