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

Reflect auto registration #15030

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4b48e23
minimal example auto registration for reflect types
eugineerd Sep 2, 2024
89c52dd
implement auto registration for the rest of supported `#[derive(Refle…
eugineerd Sep 3, 2024
f3614b8
add `no_auto_register` reflect attribute to allow opting out of autom…
eugineerd Sep 3, 2024
7367345
added `reflect_auto_register` feature to allow enabling/disabling aut…
eugineerd Sep 3, 2024
16ee7cf
reduce wasm overhead
eugineerd Sep 4, 2024
35f0591
run `cargo run -p build-templated-pages -- update features`
eugineerd Sep 4, 2024
119a598
fix not registering `TypeData`
eugineerd Sep 4, 2024
53696f7
add doc test and remove feature-gating
eugineerd Sep 5, 2024
7d761e1
remove `dbg!`
eugineerd Sep 5, 2024
d8eb597
update examples for automatic reflect type registration
eugineerd Sep 5, 2024
1da577a
fix typo
eugineerd Sep 5, 2024
4485798
remove outdated comment from reflect Cargo.toml
eugineerd Sep 5, 2024
ef6bb2d
remove needless borrow
eugineerd Sep 5, 2024
5fa1572
fix from check-doc
eugineerd Sep 5, 2024
d2d3290
move calling automatic type registration to `AppTypeRegistry`.
eugineerd Sep 5, 2024
878e8d3
more clippy fixes
eugineerd Sep 5, 2024
b518d5e
Move automatic types registration to app creation
eugineerd Sep 5, 2024
edf5f87
revert changes to examples that use `TypeRegistry`
eugineerd Sep 6, 2024
e9f67f0
hide automatic reflect registration in `__macro_exports`
eugineerd Sep 6, 2024
301b60a
add note about `no_auto_register` to `Reflect` derive doc
eugineerd Sep 6, 2024
6ad0a23
made `inventory` and `wasm-init` platform specific deps
eugineerd Sep 7, 2024
1279d62
tried to abstract platform-dependent code away
eugineerd Sep 7, 2024
74337f7
update `bevy_reflect`'s module-level doc's "Manual Registration" section
eugineerd Sep 7, 2024
7723de5
Merge remote-tracking branch 'upstream/main' into reflect-auto-regist…
eugineerd Oct 2, 2024
b18b645
Merge remote-tracking branch 'upstream/main' into reflect-auto-regist…
eugineerd Oct 20, 2024
257af97
Merge branch 'main' into reflect-auto-registration
eugineerd Dec 1, 2024
943dde9
added test for ignored auto reflect registration
eugineerd Dec 1, 2024
36f49f0
clippy
eugineerd Dec 1, 2024
e50610b
apply suggested doc fix
eugineerd Dec 1, 2024
b0f2438
implement reflect auto register for opaque types
eugineerd Dec 1, 2024
2ae4bb9
add test for auto reflect registration on all supported types
eugineerd Dec 1, 2024
7c7bd79
put automatic type registration behind feature gate
eugineerd Dec 1, 2024
03ce543
add reason to `no_auto_register` allow
eugineerd Dec 1, 2024
67f4d02
use `impl_is_generic` instead of converting to token stream.
eugineerd Dec 1, 2024
1548654
Merge remote-tracking branch 'upstream/main' into reflect-auto-regist…
eugineerd Dec 8, 2024
69bbc17
fix missing `[package]` in `bevy_reflect/Cargo.toml` (How did that ev…
eugineerd Dec 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl Default for App {
app.sub_apps.main.update_schedule = Some(Main.intern());

#[cfg(feature = "bevy_reflect")]
app.init_resource::<AppTypeRegistry>();
app.insert_resource(AppTypeRegistry::new_with_derived_types());
eugineerd marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(feature = "reflect_functions")]
app.init_resource::<AppFunctionRegistry>();
Expand Down
11 changes: 11 additions & 0 deletions crates/bevy_ecs/src/reflect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ impl DerefMut for AppTypeRegistry {
}
}

impl AppTypeRegistry {
/// Creates [`AppTypeRegistry`] and calls [`register_derived_types`](TypeRegistry::register_derived_types) on it.
///
/// See [`register_derived_types`](TypeRegistry::register_derived_types) for more details.
eugineerd marked this conversation as resolved.
Show resolved Hide resolved
pub fn new_with_derived_types() -> Self {
let app_registry = AppTypeRegistry::default();
app_registry.write().register_derived_types();
app_registry
}
}

/// A [`Resource`] storing [`FunctionRegistry`] for
/// function registrations relevant to a whole app.
///
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_reflect/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ glam = { version = "0.28", features = ["serde"], optional = true }
petgraph = { version = "0.6", features = ["serde-1"], optional = true }
smol_str = { version = "0.2.0", optional = true }
uuid = { version = "1.0", optional = true, features = ["v4", "serde"] }
inventory = "0.3"
wasm-init = "0.2"
eugineerd marked this conversation as resolved.
Show resolved Hide resolved

[dev-dependencies]
ron = "0.8.0"
Expand Down
19 changes: 19 additions & 0 deletions crates/bevy_reflect/derive/src/container_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mod kw {
syn::custom_keyword!(PartialEq);
syn::custom_keyword!(Hash);
syn::custom_keyword!(no_field_bounds);
syn::custom_keyword!(no_auto_register);
}

// The "special" trait idents that are used internally for reflection.
Expand Down Expand Up @@ -188,6 +189,7 @@ pub(crate) struct ContainerAttributes {
type_path_attrs: TypePathAttrs,
custom_where: Option<WhereClause>,
no_field_bounds: bool,
no_auto_register: bool,
custom_attributes: CustomAttributes,
idents: Vec<Ident>,
}
Expand Down Expand Up @@ -239,6 +241,8 @@ impl ContainerAttributes {
self.parse_type_path(input, trait_)
} else if lookahead.peek(kw::no_field_bounds) {
self.parse_no_field_bounds(input)
} else if lookahead.peek(kw::no_auto_register) {
self.parse_no_auto_register(input)
} else if lookahead.peek(kw::Debug) {
self.parse_debug(input)
} else if lookahead.peek(kw::PartialEq) {
Expand Down Expand Up @@ -347,6 +351,16 @@ impl ContainerAttributes {
Ok(())
}

/// Parse `no_auto_register` attribute.
///
/// Examples:
/// - `#[reflect(no_auto_register)]`
fn parse_no_auto_register(&mut self, input: ParseStream) -> syn::Result<()> {
input.parse::<kw::no_auto_register>()?;
self.no_auto_register = true;
Ok(())
}

/// Parse `where` attribute.
///
/// Examples:
Expand Down Expand Up @@ -526,6 +540,11 @@ impl ContainerAttributes {
pub fn no_field_bounds(&self) -> bool {
self.no_field_bounds
}

/// Returns true if the `no_auto_register` attribute was found on this type.
pub fn no_auto_register(&self) -> bool {
self.no_auto_register
}
}

/// Adds an identifier to a vector of identifiers if it is not already present.
Expand Down
32 changes: 31 additions & 1 deletion crates/bevy_reflect/derive/src/impls/common.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bevy_macro_utils::fq_std::{FQAny, FQBox, FQOption, FQResult};

use quote::quote;
use quote::{quote, ToTokens};

use crate::{derive_data::ReflectMeta, utility::WhereClauseOptions};

Expand Down Expand Up @@ -156,3 +156,33 @@ pub fn common_partial_reflect_methods(
#debug_fn
}
}

pub fn reflect_auto_registration(meta: &ReflectMeta) -> Option<proc_macro2::TokenStream> {
eugineerd marked this conversation as resolved.
Show resolved Hide resolved
if meta.attrs().no_auto_register() {
return None;
}

let bevy_reflect_path = meta.bevy_reflect_path();
let type_path = meta.type_path();
let generics = meta.type_path().generics();

if !generics.into_token_stream().is_empty() {
return None;
};
eugineerd marked this conversation as resolved.
Show resolved Hide resolved

Some(quote! {
#[cfg(target_family = "wasm")]
#bevy_reflect_path::__macro_exports::wasm_init::wasm_init!{
#bevy_reflect_path::__macro_exports::AUTOMATIC_REFLECT_REGISTRATIONS
.write()
.expect("Failed to get write lock for automatic reflect type registration")
.push(<#type_path as #bevy_reflect_path::__macro_exports::RegisterForReflection>::__register);
}
eugineerd marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(not(target_family = "wasm"))]
#bevy_reflect_path::__macro_exports::inventory::submit!(
#bevy_reflect_path::__macro_exports::AUTOMATIC_REFLECT_REGISTRATIONS(
<#type_path as #bevy_reflect_path::__macro_exports::RegisterForReflection>::__register
)
);
})
}
9 changes: 8 additions & 1 deletion crates/bevy_reflect/derive/src/impls/enums.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::derive_data::{EnumVariantFields, ReflectEnum, StructField};
use crate::enum_utility::{EnumVariantOutputData, TryApplyVariantBuilder, VariantBuilder};
use crate::impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed};
use crate::impls::{
common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed,
reflect_auto_registration,
};
use bevy_macro_utils::fq_std::{FQBox, FQOption, FQResult};
use proc_macro2::{Ident, Span};
use quote::quote;
Expand Down Expand Up @@ -80,6 +83,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
let (impl_generics, ty_generics, where_clause) =
reflect_enum.meta().type_path().generics().split_for_impl();

let auto_register = reflect_auto_registration(reflect_enum.meta());

let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);

quote! {
Expand All @@ -93,6 +98,8 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream

#function_impls

#auto_register

impl #impl_generics #bevy_reflect_path::Enum for #enum_path #ty_generics #where_reflect_clause {
fn field(&self, #ref_name: &str) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> {
match #match_this {
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_reflect/derive/src/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ mod typed;
mod values;

pub(crate) use assertions::impl_assertions;
pub(crate) use common::{common_partial_reflect_methods, impl_full_reflect};
pub(crate) use common::{
common_partial_reflect_methods, impl_full_reflect, reflect_auto_registration,
};
pub(crate) use enums::impl_enum;
#[cfg(feature = "functions")]
pub(crate) use func::impl_function_traits;
Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_reflect/derive/src/impls/structs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed};
use crate::impls::{
common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed,
reflect_auto_registration,
};
use crate::struct_utility::FieldAccessors;
use crate::ReflectStruct;
use bevy_macro_utils::fq_std::{FQBox, FQDefault, FQOption, FQResult};
Expand Down Expand Up @@ -60,6 +63,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
.generics()
.split_for_impl();

let auto_register = reflect_auto_registration(reflect_struct.meta());

let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);

quote! {
Expand All @@ -73,6 +78,8 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS

#function_impls

#auto_register

impl #impl_generics #bevy_reflect_path::Struct for #struct_path #ty_generics #where_reflect_clause {
fn field(&self, name: &str) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> {
match name {
Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_reflect/derive/src/impls/tuple_structs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed};
use crate::impls::{
common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed,
reflect_auto_registration,
};
use crate::struct_utility::FieldAccessors;
use crate::ReflectStruct;
use bevy_macro_utils::fq_std::{FQBox, FQDefault, FQOption, FQResult};
Expand Down Expand Up @@ -48,6 +51,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
.generics()
.split_for_impl();

let auto_register = reflect_auto_registration(reflect_struct.meta());

let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);

quote! {
Expand All @@ -61,6 +66,8 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::

#function_impls

#auto_register

impl #impl_generics #bevy_reflect_path::TupleStruct for #struct_path #ty_generics #where_reflect_clause {
fn field(&self, index: usize) -> #FQOption<&dyn #bevy_reflect_path::PartialReflect> {
match index {
Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_reflect/derive/src/impls/values.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed};
use crate::impls::{
common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed,
reflect_auto_registration,
};
use crate::utility::WhereClauseOptions;
use crate::ReflectMeta;
use bevy_macro_utils::fq_std::{FQBox, FQClone, FQOption, FQResult};
Expand Down Expand Up @@ -57,6 +60,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
let get_type_registration_impl = meta.get_type_registration(&where_clause_options);

let auto_register = reflect_auto_registration(meta);

quote! {
#get_type_registration_impl

Expand All @@ -68,6 +73,8 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {

#function_impls

#auto_register

impl #impl_generics #bevy_reflect_path::PartialReflect for #type_path #ty_generics #where_reflect_clause {
#[inline]
fn get_represented_type_info(&self) -> #FQOption<&'static #bevy_reflect_path::TypeInfo> {
Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_reflect/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre
/// #[reflect(@Required, @EditorTooltip::new("An ID is required!"))]
/// struct Id(u8);
/// ```
/// ## `#[reflect(no_auto_register)]`
///
/// This attribute will opt-out of the automatic reflect type registration.
///
/// All non-generic types annotated with `#[derive(Reflect)]` are usually automatically registered on app startup.
/// If this behavior is not desired, this attribute may be used to disable it for the annotated type.
///
/// # Field Attributes
///
Expand Down
13 changes: 13 additions & 0 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,9 @@ extern crate alloc;
/// These are not meant to be used directly and are subject to breaking changes.
#[doc(hidden)]
pub mod __macro_exports {
pub extern crate inventory;
pub extern crate wasm_init;

use crate::{
DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicStruct, DynamicTuple,
DynamicTupleStruct, GetTypeRegistration, TypeRegistry,
Expand Down Expand Up @@ -641,6 +644,16 @@ pub mod __macro_exports {
impl RegisterForReflection for DynamicArray {}

impl RegisterForReflection for DynamicTuple {}

/// Stores registration functions of all reflect types that can be automatically registered.
#[cfg(not(target_family = "wasm"))]
#[allow(non_camel_case_types)]
pub struct AUTOMATIC_REFLECT_REGISTRATIONS(pub fn(&mut TypeRegistry));
#[cfg(not(target_family = "wasm"))]
inventory::collect!(AUTOMATIC_REFLECT_REGISTRATIONS);
eugineerd marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(target_family = "wasm")]
pub static AUTOMATIC_REFLECT_REGISTRATIONS: std::sync::RwLock<Vec<fn(&mut TypeRegistry)>> =
std::sync::RwLock::new(Vec::new());
}

#[cfg(test)]
Expand Down
56 changes: 56 additions & 0 deletions crates/bevy_reflect/src/type_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,62 @@ impl TypeRegistry {
registry
}

/// Register all non-generic types annotated with `#[derive(Reflect)]`.
///
/// Calling this method is equivalent to calling [`register`](Self::register) on all types without generic parameters
/// that derived [`Reflect`] trait.
///
/// This method is supported on Linux, macOS, iOS, Android and Windows via the `inventory` crate,
/// and on wasm via the `wasm-init` crate. It does nothing on platforms not supported by either of those crates.
///
/// # 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::empty();
/// type_registry.register_derived_types();
///
/// // 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_derived_types(&mut self) {
// wasm_init must be called at least once to run all init code.
// Calling it multiple times is ok and doesn't do anything.
#[cfg(target_family = "wasm")]
wasm_init::wasm_init();

#[cfg(target_family = "wasm")]
for registration_fn in crate::__macro_exports::AUTOMATIC_REFLECT_REGISTRATIONS
.read()
.expect("Failed to get read lock for automatic reflect type registration")
.iter()
{
registration_fn(self);
}

#[cfg(not(target_family = "wasm"))]
for registration_fn in
inventory::iter::<crate::__macro_exports::AUTOMATIC_REFLECT_REGISTRATIONS>
{
registration_fn.0(self);
}
}

/// 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`].
Expand Down
4 changes: 1 addition & 3 deletions examples/reflection/reflection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,13 @@ use serde::de::DeserializeSeed;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// Bar will be automatically registered as it's a dependency of Foo
.register_type::<Foo>()
.add_systems(Startup, setup)
.run();
}

/// Deriving `Reflect` implements the relevant reflection traits. In this case, it implements the
/// `Reflect` trait and the `Struct` trait `derive(Reflect)` assumes that all fields also implement
/// Reflect.
/// Reflect. All types without generics that `derive(Reflect)` are automatically registered.
///
/// All fields in a reflected item will need to be `Reflect` as well. You can opt a field out of
/// reflection by using the `#[reflect(ignore)]` attribute.
Expand Down
3 changes: 0 additions & 3 deletions examples/scene/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ use std::{fs::File, io::Write};
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.register_type::<ComponentA>()
.register_type::<ComponentB>()
.register_type::<ResourceA>()
.add_systems(
Startup,
(save_scene_system, load_scene_system, infotext_system),
Expand Down