This commit is contained in:
eugineerd 2025-07-18 02:12:02 +02:00 committed by GitHub
commit 41e22806c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 652 additions and 8 deletions

View File

@ -28,6 +28,8 @@ members = [
"examples/mobile",
# Examples of using Bevy on no_std platforms.
"examples/no_std/*",
# Examples of compiling Bevy with automatic reflect type registration for platforms without `inventory` support.
"examples/reflection/auto_register_static",
# Benchmarks
"benches",
# Internal tools that are not published.
@ -159,6 +161,7 @@ default = [
"hdr",
"multi_threaded",
"png",
"reflect_auto_register",
"smaa_luts",
"sysinfo_plugin",
"tonemapping_luts",
@ -550,6 +553,12 @@ reflect_functions = ["bevy_internal/reflect_functions"]
# Enable documentation reflection
reflect_documentation = ["bevy_internal/reflect_documentation"]
# Enable automatic reflect registration
reflect_auto_register = ["bevy_internal/reflect_auto_register"]
# Enable automatic reflect registration without inventory. See `reflect::load_type_registrations` for more info.
reflect_auto_register_static = ["bevy_internal/reflect_auto_register_static"]
# Enable winit custom cursor support
custom_cursor = ["bevy_internal/custom_cursor"]
@ -2776,6 +2785,17 @@ description = "Demonstrates how to create and use type data"
category = "Reflection"
wasm = false
[[example]]
name = "auto_register_static"
path = "examples/reflection/auto_register_static/src/lib.rs"
doc-scrape-examples = true
[package.metadata.example.auto_register_static]
name = "Automatic types registration"
description = "Demonstrates how to set up automatic reflect types registration for platforms without `inventory` support"
category = "Reflection"
wasm = false
# Scene
[[example]]
name = "scene"

View File

@ -22,6 +22,11 @@ reflect_functions = [
"bevy_reflect/functions",
"bevy_ecs/reflect_functions",
]
reflect_auto_register = [
"bevy_reflect",
"bevy_reflect/auto_register",
"bevy_ecs/reflect_auto_register",
]
# Debugging Features

View File

@ -108,7 +108,12 @@ impl Default for App {
{
use bevy_ecs::observer::ObservedBy;
#[cfg(not(feature = "reflect_auto_register"))]
app.init_resource::<AppTypeRegistry>();
#[cfg(feature = "reflect_auto_register")]
app.insert_resource(AppTypeRegistry::new_with_derived_types());
app.register_type::<Name>();
app.register_type::<ChildOf>();
app.register_type::<Children>();

View File

@ -3,6 +3,8 @@ extern crate alloc;
use alloc::sync::Arc;
#[cfg(feature = "reflect_auto_register")]
use bevy_ecs::schedule::IntoScheduleConfigs;
use bevy_ecs::{event::EventWriter, HotPatched};
#[cfg(not(target_family = "wasm"))]
use dioxus_devtools::connect_subsecond;
@ -38,5 +40,14 @@ impl Plugin for HotPatchPlugin {
}
},
);
#[cfg(feature = "reflect_auto_register")]
app.add_systems(
crate::First,
(move |registry: bevy_ecs::system::Res<bevy_ecs::reflect::AppTypeRegistry>| {
registry.write().register_derived_types();
})
.run_if(bevy_ecs::schedule::common_conditions::on_event::<HotPatched>),
);
}
}

View File

@ -27,6 +27,7 @@ bevy_reflect = ["dep:bevy_reflect"]
## Extends reflection support to functions.
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
reflect_auto_register = ["bevy_reflect", "bevy_reflect/auto_register"]
## Enables automatic backtrace capturing in BevyError
backtrace = ["std"]

View File

@ -47,6 +47,18 @@ impl DerefMut for AppTypeRegistry {
}
}
impl AppTypeRegistry {
/// Creates [`AppTypeRegistry`] and automatically registers all types deriving [`Reflect`].
///
/// See [`TypeRegistry::register_derived_types`] for more details.
#[cfg(feature = "reflect_auto_register")]
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.
///

View File

@ -293,6 +293,20 @@ reflect_functions = [
"bevy_ecs/reflect_functions",
]
# Enable automatic reflect registration using inventory.
reflect_auto_register = [
"bevy_reflect/auto_register_inventory",
"bevy_app/reflect_auto_register",
"bevy_ecs/reflect_auto_register",
]
# Enable automatic reflect registration without inventory. See `reflect::load_type_registrations` for more info.
reflect_auto_register_static = [
"bevy_reflect/auto_register_static",
"bevy_app/reflect_auto_register",
"bevy_ecs/reflect_auto_register",
]
# Enable documentation reflection
reflect_documentation = ["bevy_reflect/documentation"]

View File

@ -10,7 +10,7 @@ keywords = ["bevy"]
rust-version = "1.85.0"
[features]
default = ["std", "smallvec", "debug"]
default = ["std", "smallvec", "debug", "auto_register_inventory"]
# Features
@ -68,6 +68,22 @@ std = [
## on all platforms, including `no_std`.
critical-section = ["bevy_platform/critical-section"]
# Enables automatic reflect registration. Does nothing by itself,
# must select `auto_register_inventory` or `auto_register_static` to make it work.
auto_register = []
## Enables automatic reflect registration using inventory. Not supported on all platforms.
auto_register_inventory = [
"auto_register",
"bevy_reflect_derive/auto_register_inventory",
"dep:inventory",
]
## Enable automatic reflect registration without inventory. This feature has precedence over `auto_register_inventory`.
## See `load_type_registrations` for more info.
auto_register_static = [
"auto_register",
"bevy_reflect_derive/auto_register_static",
]
## Enables use of browser APIs.
## Note this is currently only applicable on `wasm32` architectures.
web = ["bevy_platform/web", "uuid?/js"]
@ -113,6 +129,9 @@ wgpu-types = { version = "25", features = [
"serde",
], optional = true, default-features = false }
# deps for automatic type registration
inventory = { version = "0.3", optional = true }
[dev-dependencies]
ron = "0.10"
rmp-serde = "1.1"

View File

@ -17,6 +17,13 @@ default = []
documentation = []
# Enables macro logic related to function reflection
functions = []
# Enables automatic reflect registration. Does nothing by itself,
# must select `auto_register_inventory` or `auto_register_static` to make it work.
auto_register = []
# Enables automatic reflection using inventory. Not supported on all platforms.
auto_register_inventory = ["auto_register"]
# Enables automatic reflection on platforms not supported by inventory. See `load_type_registrations` for more info.
auto_register_static = ["auto_register", "dep:uuid"]
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
@ -24,6 +31,9 @@ indexmap = "2.0"
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full", "extra-traits"] }
uuid = { version = "1.13.1", default-features = false, features = [
"v4",
], optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.

View File

@ -25,6 +25,7 @@ mod kw {
syn::custom_keyword!(Hash);
syn::custom_keyword!(Clone);
syn::custom_keyword!(no_field_bounds);
syn::custom_keyword!(no_auto_register);
syn::custom_keyword!(opaque);
}
@ -184,6 +185,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>,
@ -240,6 +242,8 @@ impl ContainerAttributes {
self.parse_no_field_bounds(input)
} else if lookahead.peek(kw::Clone) {
self.parse_clone(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::Hash) {
@ -378,6 +382,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:
@ -583,6 +597,12 @@ impl ContainerAttributes {
self.no_field_bounds
}
/// Returns true if the `no_auto_register` attribute was found on this type.
#[cfg(feature = "auto_register")]
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

View File

@ -154,3 +154,88 @@ pub fn common_partial_reflect_methods(
#debug_fn
}
}
#[cfg(feature = "auto_register")]
pub fn reflect_auto_registration(meta: &ReflectMeta) -> Option<proc_macro2::TokenStream> {
if meta.attrs().no_auto_register() {
return None;
}
let bevy_reflect_path = meta.bevy_reflect_path();
let type_path = meta.type_path();
if type_path.impl_is_generic() {
return None;
};
#[cfg(feature = "auto_register_static")]
{
use std::{
env, fs,
io::Write,
path::PathBuf,
sync::{LazyLock, Mutex},
};
// Skip unless env var is set, otherwise this might slow down rust-analyzer
if env::var("BEVY_REFLECT_AUTO_REGISTER_STATIC").is_err() {
return None;
}
// Names of registrations functions will be stored in this file.
// To allow writing to this file from multiple threads during compilation it is protected by mutex.
// This static is valid for the duration of compilation of one crate and we have one file per crate,
// so it is enough to protect compilation threads from overwriting each other.
// This file is reset on every crate recompilation.
//
// It might make sense to replace the mutex with File::lock when file_lock feature becomes stable.
static REGISTRATION_FNS_EXPORT: LazyLock<Mutex<fs::File>> = LazyLock::new(|| {
let path = PathBuf::from("target").join("bevy_reflect_type_registrations");
fs::DirBuilder::new()
.recursive(true)
.create(&path)
.unwrap_or_else(|_| panic!("Failed to create {path:?}"));
let file_path = path.join(env::var("CARGO_CRATE_NAME").unwrap());
let file = fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&file_path)
.unwrap_or_else(|_| panic!("Failed to create {file_path:?}"));
Mutex::new(file)
});
let export_name = format!("_bevy_reflect_register_{}", uuid::Uuid::new_v4().as_u128());
{
let mut file = REGISTRATION_FNS_EXPORT.lock().unwrap();
writeln!(file, "{export_name}")
.unwrap_or_else(|_| panic!("Failed to write registration function {export_name}"));
// We must sync_data to ensure all content is written before releasing the lock.
file.sync_data().unwrap();
};
Some(quote! {
/// # Safety
/// This function must only be used by the `load_type_registrations` macro.
#[unsafe(export_name=#export_name)]
pub unsafe extern "Rust" fn bevy_register_type(registry: &mut #bevy_reflect_path::TypeRegistry) {
<#type_path as #bevy_reflect_path::__macro_exports::RegisterForReflection>::__register(registry);
}
})
}
#[cfg(all(
feature = "auto_register_inventory",
not(feature = "auto_register_static")
))]
{
Some(quote! {
#bevy_reflect_path::__macro_exports::auto_register::inventory::submit!{
#bevy_reflect_path::__macro_exports::auto_register::AutomaticReflectRegistrations(
<#type_path as #bevy_reflect_path::__macro_exports::auto_register::RegisterForReflection>::__register
)
}
})
}
}

View File

@ -77,6 +77,11 @@ 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();
#[cfg(not(feature = "auto_register"))]
let auto_register = None::<proc_macro2::TokenStream>;
#[cfg(feature = "auto_register")]
let auto_register = crate::impls::reflect_auto_registration(reflect_enum.meta());
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
quote! {
@ -90,6 +95,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 {

View File

@ -9,6 +9,8 @@ mod tuple_structs;
mod typed;
pub(crate) use assertions::impl_assertions;
#[cfg(feature = "auto_register")]
pub(crate) use common::reflect_auto_registration;
pub(crate) use common::{common_partial_reflect_methods, impl_full_reflect};
pub(crate) use enums::impl_enum;
#[cfg(feature = "functions")]

View File

@ -55,6 +55,11 @@ pub(crate) fn impl_opaque(meta: &ReflectMeta) -> proc_macro2::TokenStream {
#[cfg(feature = "functions")]
let function_impls = crate::impls::impl_function_traits(&where_clause_options);
#[cfg(not(feature = "auto_register"))]
let auto_register = None::<proc_macro2::TokenStream>;
#[cfg(feature = "auto_register")]
let auto_register = crate::impls::reflect_auto_registration(meta);
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);
let get_type_registration_impl = meta.get_type_registration(&where_clause_options);
@ -70,6 +75,8 @@ pub(crate) fn impl_opaque(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> {

View File

@ -58,6 +58,11 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
.generics()
.split_for_impl();
#[cfg(not(feature = "auto_register"))]
let auto_register = None::<proc_macro2::TokenStream>;
#[cfg(feature = "auto_register")]
let auto_register = crate::impls::reflect_auto_registration(reflect_struct.meta());
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
quote! {
@ -71,6 +76,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 {

View File

@ -46,6 +46,11 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
.generics()
.split_for_impl();
#[cfg(not(feature = "auto_register"))]
let auto_register = None::<proc_macro2::TokenStream>;
#[cfg(feature = "auto_register")]
let auto_register = crate::impls::reflect_auto_registration(reflect_struct.meta());
let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);
quote! {
@ -59,6 +64,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 {

View File

@ -40,6 +40,8 @@ mod trait_reflection;
mod type_path;
mod where_clause_options;
use std::{fs, io::Read, path::PathBuf};
use crate::derive_data::{ReflectDerive, ReflectMeta, ReflectStruct};
use container_attributes::ContainerAttributes;
use derive_data::{ReflectImplSource, ReflectProvenance, ReflectTraitToImpl, ReflectTypePath};
@ -320,6 +322,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
///
@ -843,3 +851,53 @@ pub fn impl_type_path(input: TokenStream) -> TokenStream {
};
})
}
/// Collects and loads type registrations when using `auto_register_static` feature.
///
/// Correctly using this macro requires following:
/// 1. This macro must be called **last** during compilation. This can be achieved by putting your main function
/// in a separate crate or restructuring your project to be separated into `bin` and `lib`, and putting this macro in `bin`.
/// Any automatic type registrations using `#[derive(Reflect)]` within the same crate as this macro are not guaranteed to run.
/// 2. Your project must be compiled with `auto_register_static` feature **and** `BEVY_REFLECT_AUTO_REGISTER_STATIC=1` env variable.
/// Enabling the feature generates registration functions while setting the variable enables export and
/// caching of registration function names.
/// 3. Must be called before creating `App` or using `TypeRegistry::register_derived_types`.
///
/// If you're experiencing linking issues try running `cargo clean` before rebuilding.
#[proc_macro]
pub fn load_type_registrations(_input: TokenStream) -> TokenStream {
if !cfg!(feature = "auto_register_static") {
return TokenStream::new();
}
let Ok(dir) = fs::read_dir(PathBuf::from("target").join("bevy_reflect_type_registrations"))
else {
return TokenStream::new();
};
let mut str_buf = String::new();
let mut registration_fns = Vec::new();
for file_path in dir {
let mut file = fs::OpenOptions::new()
.read(true)
.open(file_path.unwrap().path())
.unwrap();
file.read_to_string(&mut str_buf).unwrap();
registration_fns.extend(str_buf.lines().filter(|s| !s.is_empty()).map(|s| {
s.parse::<proc_macro2::TokenStream>()
.expect("Unexpected function name")
}));
str_buf.clear();
}
let bevy_reflect_path = meta::get_bevy_reflect_path();
TokenStream::from(quote! {
{
fn _register_types(){
unsafe extern "Rust" {
#( safe fn #registration_fns(registry_ptr: &mut #bevy_reflect_path::TypeRegistry); )*
};
#( #bevy_reflect_path::__macro_exports::auto_register::push_registration_fn(#registration_fns); )*
}
_register_types();
}
})
}

View File

@ -463,13 +463,6 @@
//! typically require manual monomorphization (i.e. manually specifying the types the generic method can
//! take).
//!
//! ## Manual Registration
//!
//! 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.
//!
//! # Features
//!
//! ## `bevy`
@ -519,6 +512,24 @@
//! which enables capturing the type stack when serializing or deserializing a type
//! and displaying it in error messages.
//!
//! ## `auto_register_inventory`/`auto_register_static`
//!
//! | Default | Dependencies |
//! | :-----: | :-------------------------------: |
//! | ✅ | [`bevy_reflect_derive/auto_register_inventory`] |
//! | ❌ | [`bevy_reflect_derive/auto_register_static`] |
//!
//! These features enable automatic registration of types that derive [`Reflect`].
//!
//! - `auto_register_inventory` uses `inventory` to collect types on supported platforms (Linux, macOS, iOS, FreeBSD, Android, Windows, WebAssembly).
//! - `auto_register_static` uses platform-independent way to collect types, but requires additional setup and might
//! slow down compilation, so it should only be used on platforms not supported by `inventory`.
//! See documentation for [`load_type_registrations`] macro for more info
//!
//! When this feature is enabled `bevy_reflect` will automatically collects all types that derive [`Reflect`] on app 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.
//!
//! [Reflection]: https://en.wikipedia.org/wiki/Reflective_programming
//! [Bevy]: https://bevy.org/
//! [limitations]: #limitations
@ -723,6 +734,91 @@ pub mod __macro_exports {
impl RegisterForReflection for DynamicArray {}
impl RegisterForReflection for DynamicTuple {}
/// Automatic reflect registration implementation
#[cfg(feature = "auto_register")]
pub mod auto_register {
pub use super::*;
/// inventory impl
#[cfg(all(
not(feature = "auto_register_static"),
feature = "auto_register_inventory"
))]
mod __automatic_type_registration_impl {
use super::*;
pub use inventory;
/// Stores type registration functions
pub struct AutomaticReflectRegistrations(pub fn(&mut TypeRegistry));
/// Registers all collected types.
pub fn register_types(registry: &mut TypeRegistry) {
#[cfg(target_family = "wasm")]
wasm_support::init();
for registration_fn in inventory::iter::<AutomaticReflectRegistrations> {
registration_fn.0(registry);
}
}
inventory::collect!(AutomaticReflectRegistrations);
#[cfg(target_family = "wasm")]
mod wasm_support {
use bevy_platform::sync::atomic::{AtomicBool, Ordering};
static INIT_DONE: AtomicBool = AtomicBool::new(false);
#[expect(unsafe_code, reason = "This function is generated by linker.")]
unsafe extern "C" {
fn __wasm_call_ctors();
}
/// This function must be called before using [`inventory::iter`] on [`AutomaticReflectRegistrations`] to run constructors on all platforms.
pub fn init() {
if INIT_DONE.swap(true, Ordering::Relaxed) {
return;
};
// SAFETY:
// This will call constructors on wasm platforms at most once (as long as `init` is the only function that calls `__wasm_call_ctors`).
//
// For more information see: https://docs.rs/inventory/latest/inventory/#webassembly-and-constructors
#[expect(
unsafe_code,
reason = "This function must be called to use inventory on wasm."
)]
unsafe {
__wasm_call_ctors();
}
}
}
}
/// static impl
#[cfg(feature = "auto_register_static")]
mod __automatic_type_registration_impl {
use super::*;
use alloc::vec::Vec;
use bevy_platform::sync::Mutex;
static REGISTRATION_FNS: Mutex<Vec<fn(&mut TypeRegistry)>> = Mutex::new(Vec::new());
/// Adds adds a new registration function for [`TypeRegistry`]
pub fn push_registration_fn(registration_fn: fn(&mut TypeRegistry)) {
REGISTRATION_FNS.lock().unwrap().push(registration_fn);
}
/// Registers all collected types.
pub fn register_types(registry: &mut TypeRegistry) {
for func in REGISTRATION_FNS.lock().unwrap().iter() {
(func)(registry);
}
}
}
pub use __automatic_type_registration_impl::*;
}
}
#[cfg(test)]
@ -3369,6 +3465,76 @@ bevy_reflect::tests::Test {
);
}
#[cfg(feature = "auto_register")]
mod auto_register_reflect {
use super::*;
#[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>()));
}
#[test]
fn should_auto_register_reflect_for_all_supported_types() {
// Struct
#[derive(Reflect)]
struct StructReflect {
a: usize,
}
// ZST struct
#[derive(Reflect)]
struct ZSTStructReflect;
// Tuple struct
#[derive(Reflect)]
struct TupleStructReflect(pub u32);
// Enum
#[derive(Reflect)]
enum EnumReflect {
A,
B,
}
// ZST enum
#[derive(Reflect)]
enum ZSTEnumReflect {}
// Opaque struct
#[derive(Reflect, Clone)]
#[reflect(opaque)]
struct OpaqueStructReflect {
_a: usize,
}
// ZST opaque struct
#[derive(Reflect, Clone)]
#[reflect(opaque)]
struct ZSTOpaqueStructReflect;
let mut registry = TypeRegistry::default();
registry.register_derived_types();
assert!(registry.contains(TypeId::of::<StructReflect>()));
assert!(registry.contains(TypeId::of::<ZSTStructReflect>()));
assert!(registry.contains(TypeId::of::<TupleStructReflect>()));
assert!(registry.contains(TypeId::of::<EnumReflect>()));
assert!(registry.contains(TypeId::of::<ZSTEnumReflect>()));
assert!(registry.contains(TypeId::of::<OpaqueStructReflect>()));
assert!(registry.contains(TypeId::of::<ZSTOpaqueStructReflect>()));
}
}
#[cfg(feature = "glam")]
mod glam {
use super::*;

View File

@ -120,6 +120,44 @@ 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());
/// ```
#[cfg(feature = "auto_register")]
pub fn register_derived_types(&mut self) {
crate::__macro_exports::auto_register::register_types(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`].

View File

@ -48,6 +48,7 @@ The default feature set enables most of the expected features of a game engine,
|ktx2|KTX2 compressed texture support|
|multi_threaded|Enables multithreaded parallelism in the engine. Disabling it forces all engine tasks to run on a single thread.|
|png|PNG image format support|
|reflect_auto_register|Enable automatic reflect registration|
|smaa_luts|Include SMAA Look Up Tables KTX2 Files|
|std|Allows access to the `std` crate.|
|sysinfo_plugin|Enables system information diagnostic plugin|
@ -108,6 +109,7 @@ The default feature set enables most of the expected features of a game engine,
|pbr_transmission_textures|Enable support for transmission-related textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs|
|pnm|PNM image format support, includes pam, pbm, pgm and ppm|
|qoi|QOI image format support|
|reflect_auto_register_static|Enable automatic reflect registration without inventory. See `reflect::load_type_registrations` for more info.|
|reflect_documentation|Enable documentation reflection|
|reflect_functions|Enable function reflection|
|serialize|Enable serialization support through serde|

View File

@ -424,6 +424,7 @@ Example | Description
Example | Description
--- | ---
[Automatic types registration](../examples/reflection/auto_register_static/src/lib.rs) | Demonstrates how to set up automatic reflect types registration for platforms without `inventory` support
[Custom Attributes](../examples/reflection/custom_attributes.rs) | Registering and accessing custom attributes on reflected types
[Dynamic Types](../examples/reflection/dynamic_types.rs) | How dynamic types are used with reflection
[Function Reflection](../examples/reflection/function_reflection.rs) | Demonstrates how functions can be called dynamically using reflection

View File

@ -0,0 +1,17 @@
[package]
name = "auto_register_static"
version = "0.0.0"
edition = "2024"
publish = false
license = "MIT OR Apache-2.0"
[lib]
# Our app must be a lib for static auto registration to work.
crate-type = ["lib"]
name = "auto_register_static"
[dependencies]
bevy = { path = "../../../", default-features = false, features = ["trace"] }
[lints]
workspace = true

View File

@ -0,0 +1,4 @@
.PHONEY: run
run:
BEVY_REFLECT_AUTO_REGISTER_STATIC=1 cargo run --features bevy/reflect_auto_register_static

View File

@ -0,0 +1,19 @@
# Automatic registration example for platforms without inventory support
This example illustrates how to use automatic type registration of `bevy_reflect` on platforms that don't support `inventory`.
To run the example, use the provided `Makefile` with `make run` or run manually by setting env var and enabling the required feature:
```sh
BEVY_REFLECT_AUTO_REGISTER_STATIC=1 cargo run --features bevy/reflect_auto_register_static
```
This approach should generally work on all platforms, however it is less convenient and slows down linking. It's recommended to use it only as a fallback.
Here's a list of caveats of this approach:
1. `load_type_registrations!` macro must be called before constructing `App` or using `TypeRegistry::register_derived_types`.
2. All of the types to be automatically registered must be declared in a separate from `load_type_registrations!` crate. This is why this example uses separate `lib` and `bin` setup.
3. Registration function names are cached in `target/type_registrations`. Due to incremental compilation the only way to rebuild this cache is to build with `bevy/reflect_auto_register_static` (or `auto_register_static` if just using `bevy_reflect`) feature disabled, then delete `target/type_registrations` and rebuild again with this feature enabled and `BEVY_REFLECT_AUTO_REGISTER_STATIC=1` environment variable set. Running `cargo clean` before recompiling is also an option, but it is even slower to do.
If you're experiencing linking issues try running `cargo clean` before rebuilding.

View File

@ -0,0 +1,10 @@
//! Demonstrates how to set up automatic reflect types registration for platforms without `inventory` support
use auto_register_static::main as lib_main;
use bevy::reflect::load_type_registrations;
fn main() {
// This must be called before our main to collect all type registration functions.
load_type_registrations!();
// After running load_type_registrations! we just forward to our main.
lib_main();
}

View File

@ -0,0 +1,44 @@
//! Demonstrates how to set up automatic reflect types registration for platforms without `inventory` support
use bevy::prelude::*;
// The type that should be automatically registered.
// All types subject to automatic registration must be defined not be define in the same crate as `load_type_registrations!``.
// Any `#[derive(Reflect)]` types within the `bin` crate are not guaranteed to be registered automatically.
#[derive(Reflect)]
struct Struct {
a: i32,
}
mod private {
mod very_private {
use bevy::prelude::*;
// Works with private types too!
#[derive(Reflect)]
struct PrivateStruct {
a: i32,
}
}
}
/// This is the main entrypoint, bin just forwards to it.
pub fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, startup)
.run();
}
fn startup(reg: Res<AppTypeRegistry>) {
let registry = reg.read();
info!(
"Is `Struct` registered? {}",
registry.contains(core::any::TypeId::of::<Struct>())
);
info!(
"Type info of `PrivateStruct`: {:?}",
registry
.get_with_short_type_path("PrivateStruct")
.expect("Not registered")
);
}

View File

@ -0,0 +1,53 @@
---
title: Reflect auto registration
authors: ["@eugineerd"]
pull_requests: [15030]
---
## Automatic [`Reflect`] registration
Deriving [`Reflect`] on types opts into **Bevy's** runtime reflection infrastructure, which is used to power systems like component runtime inspection and serialization. Before **Bevy 0.17**, any top-level
types that derive [`Reflect`] (not used as a field in some other [`Reflect`]-ed type) had to be manually registered using [`register_type`] for the runtime reflection to work with them. With this release,
all types that [`#[derive(Reflect)]`] are now automatically registered! This works for any types without generic type parameters and should reduce the boilerplate needed when adding functionality that depends on [`Reflect`].
```rs
fn main() {
// No need to manually call .register_type::<Foo>()
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
#[derive(Reflect)]
pub struct Foo {
a: usize,
}
fn setup(type_registry: Res<AppTypeRegistry>) {
let type_registry = type_registry.read();
assert!(type_registry.contains(TypeId::of::<Foo>()));
}
```
In cases where automatic registration is undesirable, it can be opted-out of by adding #[reflect(no_auto_register)] reflect attribute to a type:
```rs
#[derive(Reflect)]
#[reflect(no_auto_register)]
pub struct Foo {
a: usize,
}
```
## Unsupported platforms
This feature relies on the [`inventory`] crate to collect all type registrations at compile-time. However, not all platforms are supported by [`inventory`], and while it would be best for
any unsupported platforms to be supported upstream, sometimes it might not be possible. For this reason, there is a different implementation of this feature that works on all platforms.
It comes with some caveats with regards to project structure and might increase compile time, so it is better used as a backup solution. The detailed instructions on how to use this feature
can be found in this [`example`].
[`Reflect`]: https://docs.rs/bevy/0.17.0/bevy/prelude/trait.Reflect.html
[`inventory`]: https://github.com/dtolnay/inventory
[`example`]: https://github.com/bevyengine/bevy/tree/release-0.17.0/examples/reflection/auto_register_static
[`register_type`]: https://docs.rs/bevy/0.17.0/bevy/prelude/struct.App.html#method.register_type