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 28 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 @@ -92,7 +92,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 @@ -49,6 +49,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
6 changes: 6 additions & 0 deletions crates/bevy_reflect/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ smol_str = { version = "0.2.0", features = ["serde"], optional = true }
uuid = { version = "1.0", optional = true, features = ["v4", "serde"] }
wgpu-types = { version = "23", features = ["serde"], optional = true }

# deps for automatic type registration
[target.'cfg(not(target_family = "wasm"))'.dependencies]
inventory = "0.3"
[target.'cfg(target_family = "wasm")'.dependencies]
wasm-init = "0.2"

[dev-dependencies]
ron = "0.8.0"
rmp-serde = "1.1"
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);
syn::custom_keyword!(opaque);
}

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,
is_opaque: bool,
idents: Vec<Ident>,
Expand Down Expand Up @@ -242,6 +244,8 @@ impl ContainerAttributes {
self.parse_opaque(input)
} 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 @@ -360,6 +364,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 @@ -543,6 +557,11 @@ impl ContainerAttributes {
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
}

/// Returns true if the `opaque` attribute was found on this type.
pub fn is_opaque(&self) -> bool {
self.is_opaque
Expand Down
24 changes: 23 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, where_clause_options::WhereClauseOptions};

Expand Down Expand Up @@ -156,3 +156,25 @@ 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! {
#bevy_reflect_path::__macro_exports::auto_register_function!{
#bevy_reflect_path::__macro_exports::AutomaticReflectRegistrations::add(
<#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,7 +1,10 @@
use crate::{
derive_data::{EnumVariantFields, ReflectEnum, StructField},
enum_utility::{EnumVariantOutputData, TryApplyVariantBuilder, VariantBuilder},
impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed},
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};
Expand Down Expand Up @@ -82,6 +85,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 @@ -95,6 +100,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 tuple_structs;
mod typed;

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,5 +1,8 @@
use crate::{
impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed},
impls::{
common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed,
reflect_auto_registration,
},
struct_utility::FieldAccessors,
ReflectStruct,
};
Expand Down Expand Up @@ -62,6 +65,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 @@ -75,6 +80,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,5 +1,8 @@
use crate::{
impls::{common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed},
impls::{
common_partial_reflect_methods, impl_full_reflect, impl_type_path, impl_typed,
reflect_auto_registration,
},
struct_utility::FieldAccessors,
ReflectStruct,
};
Expand Down Expand Up @@ -50,6 +53,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 @@ -63,6 +68,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
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 @@ -315,6 +315,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
94 changes: 89 additions & 5 deletions crates/bevy_reflect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,12 +460,11 @@
//! typically require manual monomorphization (i.e. manually specifying the types the generic method can
//! take).
//!
//! ## Manual Registration
//! ## Manual Registration of Generic Types
//!
//! Since Rust doesn't provide built-in support for running initialization code before `main`,
//! there is no way for `bevy_reflect` to automatically register types into the [type registry].
//! This means types must manually be registered, including their desired monomorphized
//! representations if generic.
//! `bevy_reflect` automatically collects all types that derive [`Reflect`] on startup,
//! and [`TypeRegistry::register_derived_types`] can be used to register these types at any point in the program.
//! However, this does not apply to types with generics: their desired monomorphized representations must be registered manually.
//!
//! # Features
//!
Expand Down Expand Up @@ -687,6 +686,77 @@ pub mod __macro_exports {
impl RegisterForReflection for DynamicArray {}

impl RegisterForReflection for DynamicTuple {}

/// Stores registration functions of all reflect types that can be automatically registered.
///
/// Intended to be used as follows:
/// ```rs
/// // Adding a type
/// auto_register_function!{
/// AutomaticReflectRegistrations::add(<type_registration_function>)
/// }
///
/// // Registering collected types
/// let mut registry = TypeRegistry::default();
/// AutomaticReflectRegistrations::register(&mut registry);
/// ```
pub struct AutomaticReflectRegistrations;

#[cfg(not(target_family = "wasm"))]
mod __automatic_type_registration_impl {
use super::*;

pub use inventory::submit as auto_register_function;

pub struct AutomaticReflectRegistrationsImpl(fn(&mut TypeRegistry));

impl AutomaticReflectRegistrations {
// Must be const to allow usage in static context
pub const fn add(func: fn(&mut TypeRegistry)) -> AutomaticReflectRegistrationsImpl {
AutomaticReflectRegistrationsImpl(func)
}
pub fn register(registry: &mut TypeRegistry) {
for registration_fn in inventory::iter::<AutomaticReflectRegistrationsImpl> {
registration_fn.0(registry);
}
}
}

inventory::collect!(AutomaticReflectRegistrationsImpl);
}

#[cfg(target_family = "wasm")]
mod __automatic_type_registration_impl {
use super::*;
pub use wasm_init::wasm_init as auto_register_function;

static AUTOMATIC_REFLECT_REGISTRATIONS: std::sync::RwLock<Vec<fn(&mut TypeRegistry)>> =
std::sync::RwLock::new(Vec::new());

impl AutomaticReflectRegistrations {
pub fn add(func: fn(&mut TypeRegistry)) {
AUTOMATIC_REFLECT_REGISTRATIONS
.write()
.expect("Failed to get write lock for automatic reflect type registration")
.push(func);
}
pub fn register(registry: &mut TypeRegistry) {
// wasm_init must be called at least once to run all init code.
// Calling it multiple times is ok and doesn't do anything.
wasm_init::wasm_init();

for registration_fn in AUTOMATIC_REFLECT_REGISTRATIONS
.read()
.expect("Failed to get read lock for automatic reflect type registration")
.iter()
{
registration_fn(registry);
}
}
}
}

pub use __automatic_type_registration_impl::*;
}

#[cfg(test)]
Expand Down Expand Up @@ -2975,6 +3045,20 @@ bevy_reflect::tests::Test {
);
}

#[test]
fn should_ignore_auto_reflect_registration() {
#[derive(Reflect)]
#[reflect(no_auto_register)]
struct NoAutomaticStruct {
a: usize,
}

let mut registry = TypeRegistry::default();
registry.register_derived_types();

assert!(!registry.contains(TypeId::of::<NoAutomaticStruct>()));
}

#[cfg(feature = "glam")]
mod glam {
use super::*;
Expand Down
37 changes: 37 additions & 0 deletions crates/bevy_reflect/src/type_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,43 @@ 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) {
crate::__macro_exports::AutomaticReflectRegistrations::register(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
Loading
Loading