From c1a62e9f74b9d0cd6b72bef3f19b170b2ff5f45d Mon Sep 17 00:00:00 2001 From: bstriker Date: Fri, 16 Jan 2026 00:38:16 -0500 Subject: [PATCH 1/8] feat: introduce `auto_plugin_custom` macro to register user-defined bevy App::build hooks --- Cargo.toml | 1 + .../bevy_auto_plugin_proc_macros/src/lib.rs | 5 ++ .../src/__private/auto_plugin_registry.rs | 5 ++ .../src/__private/expand/attr/mod.rs | 1 + .../attributes/actions/auto_plugin_custom.rs | 52 +++++++++++++ .../src/macro_api/attributes/actions/mod.rs | 2 + .../src/macro_api/context/macro_paths.rs | 8 ++ src/lib.rs | 2 + tests/e2e/actions/auto_plugin_custom.rs | 78 +++++++++++++++++++ tests/e2e/actions/mod.rs | 1 + 10 files changed, 155 insertions(+) create mode 100644 crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_custom.rs create mode 100644 tests/e2e/actions/auto_plugin_custom.rs diff --git a/Cargo.toml b/Cargo.toml index a7996613..5e3e9e7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ wasm-bindgen-test = { workspace = true } internal_test_util = { workspace = true } internal_test_proc_macro = { workspace = true } meta_merge = "0.1.1" +syn = { workspace = true } [build-dependencies] rustc_version = "0.4" diff --git a/crates/bevy_auto_plugin_proc_macros/src/lib.rs b/crates/bevy_auto_plugin_proc_macros/src/lib.rs index 84d9b058..93546e17 100644 --- a/crates/bevy_auto_plugin_proc_macros/src/lib.rs +++ b/crates/bevy_auto_plugin_proc_macros/src/lib.rs @@ -155,3 +155,8 @@ pub fn auto_run_on_build(attr: CompilerStream, input: CompilerStream) -> Compile pub fn auto_bind_plugin(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_bind_plugin::auto_bind_plugin_outer, attr, input) } + +#[proc_macro_attribute] +pub fn auto_plugin_custom(attr: CompilerStream, input: CompilerStream) -> CompilerStream { + handle_attribute(expand::attr::auto_plugin_custom, attr, input) +} diff --git a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs index 53abc5c6..d9d36822 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs @@ -312,6 +312,11 @@ macro_rules! _plugin_entry_after_build { }; } +pub trait AutoPluginCustom { + fn resolve_path() -> Path; + fn on_build(app: &mut bevy_app::App); +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs index 05d79be5..cd01cd80 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs @@ -42,6 +42,7 @@ gen_action_outers! { auto_add_observer => IaAddObserver, auto_add_plugin => IaAddPlugin, auto_configure_system_set => IaConfigureSystemSet, + auto_plugin_custom => IaAutoPluginCustom, } gen_rewrite_outers! { diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_custom.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_custom.rs new file mode 100644 index 00000000..56574b65 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_custom.rs @@ -0,0 +1,52 @@ +use crate::macro_api::prelude::*; +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::{ + ToTokens, + quote, +}; +use syn::Path; + +#[derive(FromMeta, Debug, Clone, PartialEq, Hash)] +#[darling(derive_syn_parse)] +pub struct AutoPluginCustomArgs { + custom: Path, +} + +impl AttributeIdent for AutoPluginCustomArgs { + const IDENT: &'static str = "auto_plugin_custom"; +} + +pub type IaAutoPluginCustom = ItemAttribute< + Composed, + AllowStructOrEnum, +>; +pub type AutoPluginCustomAppMutEmitter = AppMutationEmitter; +pub type AutoPluginCustomAttrEmitter = AttrEmitter; + +impl EmitAppMutationTokens for AutoPluginCustomAppMutEmitter { + fn to_app_mutation_tokens( + &self, + tokens: &mut TokenStream, + app_param: &syn::Ident, + ) -> syn::Result<()> { + let custom = &self.args.args.base.custom; + let concrete_paths = self.args.concrete_paths()?; + for concrete_path in concrete_paths { + tokens.extend(quote! { + <#custom as ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::AutoPluginCustom>::on_build::<#concrete_path>(#app_param); + }); + } + Ok(()) + } +} + +impl ToTokens for AutoPluginCustomAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); + tokens.extend(quote! { + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs index 137d11e9..94494386 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs @@ -8,6 +8,7 @@ mod auto_init_state; mod auto_init_sub_state; mod auto_insert_resource; mod auto_name; +mod auto_plugin_custom; mod auto_register_state_type; mod auto_register_type; mod auto_run_on_build; @@ -24,6 +25,7 @@ pub mod prelude { pub use auto_init_sub_state::*; pub use auto_insert_resource::*; pub use auto_name::*; + pub use auto_plugin_custom::*; pub use auto_register_state_type::*; pub use auto_register_type::*; pub use auto_run_on_build::*; diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs b/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs index a2e7150a..a6304d9b 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs @@ -32,6 +32,7 @@ pub struct MacroPaths { pub emit_auto_name_macro: syn::Path, /// resolved absolute path to `auto_configure_system_set` pub emit_configure_system_set_macro: syn::Path, + pub emit_auto_plugin_custom_macro: syn::Path, } impl Default for MacroPaths { @@ -51,6 +52,7 @@ impl Default for MacroPaths { emit_run_on_build_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_run_on_build ), emit_auto_name_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_name ), emit_configure_system_set_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_configure_system_set ), + emit_auto_plugin_custom_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_plugin_custom ), } } } @@ -136,3 +138,9 @@ impl MacroPathProvider for ConfigureSystemSetArgs { &context.macros.emit_configure_system_set_macro } } + +impl MacroPathProvider for AutoPluginCustomArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_auto_plugin_custom_macro + } +} diff --git a/src/lib.rs b/src/lib.rs index 0b4c45f1..135e459c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -189,4 +189,6 @@ pub mod prelude { #[doc = include_str!("../docs/proc_attributes/actions/auto_configure_system_set.md")] pub use bevy_auto_plugin_proc_macros::auto_configure_system_set; + + pub use bevy_auto_plugin_proc_macros::auto_plugin_custom; } diff --git a/tests/e2e/actions/auto_plugin_custom.rs b/tests/e2e/actions/auto_plugin_custom.rs new file mode 100644 index 00000000..aa15e9f2 --- /dev/null +++ b/tests/e2e/actions/auto_plugin_custom.rs @@ -0,0 +1,78 @@ +use bevy_app::prelude::*; +use bevy_auto_plugin::prelude::*; +use bevy_auto_plugin_shared::__private::auto_plugin_registry::AutoPluginCustom; +use bevy_ecs::prelude::*; +use internal_test_proc_macro::xtest; +use std::any::TypeId; +use syn::{ + Path, + parse_quote, +}; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct TestPlugin; + +#[derive(Resource, Debug, Default, PartialEq)] +struct Counter(Vec); + +struct MyCustomHookA; + +impl AutoPluginCustom for MyCustomHookA { + fn resolve_path() -> Path { + parse_quote!(::tests::e2e::actions::auto_plugin_custom::MyCustomHookA) + } + fn on_build(app: &mut App) { + app.world_mut().resource_mut::().0.push(format!("A {:?}", TypeId::of::())) + } +} + +struct MyCustomHookB; + +impl AutoPluginCustom for MyCustomHookB { + fn resolve_path() -> Path { + parse_quote!(::tests::e2e::actions::auto_plugin_custom::MyCustomHookB) + } + fn on_build(app: &mut App) { + app.world_mut().resource_mut::().0.push(format!("B {:?}", TypeId::of::())) + } +} + +#[derive(Debug)] +#[auto_plugin_custom(plugin = TestPlugin, custom = MyCustomHookA)] +#[auto_plugin_custom(plugin = TestPlugin, custom = MyCustomHookB)] +struct TestA; + +#[derive(Debug)] +#[auto_plugin_custom(plugin = TestPlugin, custom = MyCustomHookB)] +#[auto_plugin_custom(plugin = TestPlugin, custom = MyCustomHookA)] +struct TestB; + +fn app() -> App { + let mut app = internal_test_util::create_minimal_app(); + app.init_resource::(); + app.add_plugins(TestPlugin); + app +} + +#[xtest] +fn test_auto_plugin_custom() { + let app = app(); + fn hook_a(test: TypeId) -> String { + format!("A {:?}", test) + } + fn hook_b(test: TypeId) -> String { + format!("B {:?}", test) + } + let test_a = TypeId::of::(); + let test_b = TypeId::of::(); + assert_eq!( + app.world().get_resource::(), + Some(&Counter(vec![ + hook_a(test_a), + hook_a(test_b), + hook_b(test_b), + hook_b(test_a), + ])) + ); +} diff --git a/tests/e2e/actions/mod.rs b/tests/e2e/actions/mod.rs index 3a595627..c613520d 100644 --- a/tests/e2e/actions/mod.rs +++ b/tests/e2e/actions/mod.rs @@ -23,6 +23,7 @@ mod auto_insert_resource; mod auto_insert_resource_with_generics; mod auto_name; mod auto_name_with_generics; +mod auto_plugin_custom; mod auto_plugin_default_param; mod auto_plugin_default_param_method; mod auto_plugin_param; From 3e1947de8f673ef288c85879284c3126d8cf85b4 Mon Sep 17 00:00:00 2001 From: bstriker Date: Sat, 17 Jan 2026 13:24:20 -0500 Subject: [PATCH 2/8] refactor: remove unused `resolve_path` function from `AutoPluginCustom` trait --- .../src/__private/auto_plugin_registry.rs | 1 - tests/e2e/actions/auto_plugin_custom.rs | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs index d9d36822..4583914b 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs @@ -313,7 +313,6 @@ macro_rules! _plugin_entry_after_build { } pub trait AutoPluginCustom { - fn resolve_path() -> Path; fn on_build(app: &mut bevy_app::App); } diff --git a/tests/e2e/actions/auto_plugin_custom.rs b/tests/e2e/actions/auto_plugin_custom.rs index aa15e9f2..c6802ac9 100644 --- a/tests/e2e/actions/auto_plugin_custom.rs +++ b/tests/e2e/actions/auto_plugin_custom.rs @@ -4,10 +4,6 @@ use bevy_auto_plugin_shared::__private::auto_plugin_registry::AutoPluginCustom; use bevy_ecs::prelude::*; use internal_test_proc_macro::xtest; use std::any::TypeId; -use syn::{ - Path, - parse_quote, -}; #[derive(AutoPlugin)] #[auto_plugin(impl_plugin_trait)] @@ -19,9 +15,6 @@ struct Counter(Vec); struct MyCustomHookA; impl AutoPluginCustom for MyCustomHookA { - fn resolve_path() -> Path { - parse_quote!(::tests::e2e::actions::auto_plugin_custom::MyCustomHookA) - } fn on_build(app: &mut App) { app.world_mut().resource_mut::().0.push(format!("A {:?}", TypeId::of::())) } @@ -30,9 +23,6 @@ impl AutoPluginCustom for MyCustomHookA { struct MyCustomHookB; impl AutoPluginCustom for MyCustomHookB { - fn resolve_path() -> Path { - parse_quote!(::tests::e2e::actions::auto_plugin_custom::MyCustomHookB) - } fn on_build(app: &mut App) { app.world_mut().resource_mut::().0.push(format!("B {:?}", TypeId::of::())) } From 9280542ce8729f81d10ed8fd32c20b6b0c68d531 Mon Sep 17 00:00:00 2001 From: bstriker Date: Sat, 17 Jan 2026 13:29:38 -0500 Subject: [PATCH 3/8] refactor: rename `auto_plugin_custom` to `auto_plugin_build_hook` across macros, traits, and tests --- .../bevy_auto_plugin_proc_macros/src/lib.rs | 4 ++-- .../src/__private/auto_plugin_registry.rs | 2 +- .../src/__private/expand/attr/mod.rs | 2 +- ...in_custom.rs => auto_plugin_build_hook.rs} | 24 +++++++++---------- .../src/macro_api/attributes/actions/mod.rs | 4 ++-- .../src/macro_api/context/macro_paths.rs | 8 +++---- src/lib.rs | 2 +- ...in_custom.rs => auto_plugin_build_hook.rs} | 16 ++++++------- tests/e2e/actions/mod.rs | 2 +- 9 files changed, 32 insertions(+), 32 deletions(-) rename crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/{auto_plugin_custom.rs => auto_plugin_build_hook.rs} (55%) rename tests/e2e/actions/{auto_plugin_custom.rs => auto_plugin_build_hook.rs} (77%) diff --git a/crates/bevy_auto_plugin_proc_macros/src/lib.rs b/crates/bevy_auto_plugin_proc_macros/src/lib.rs index 93546e17..a7dd7e62 100644 --- a/crates/bevy_auto_plugin_proc_macros/src/lib.rs +++ b/crates/bevy_auto_plugin_proc_macros/src/lib.rs @@ -157,6 +157,6 @@ pub fn auto_bind_plugin(attr: CompilerStream, input: CompilerStream) -> Compiler } #[proc_macro_attribute] -pub fn auto_plugin_custom(attr: CompilerStream, input: CompilerStream) -> CompilerStream { - handle_attribute(expand::attr::auto_plugin_custom, attr, input) +pub fn auto_plugin_build_hook(attr: CompilerStream, input: CompilerStream) -> CompilerStream { + handle_attribute(expand::attr::auto_plugin_build_hook, attr, input) } diff --git a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs index 4583914b..f2905ae0 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs @@ -312,7 +312,7 @@ macro_rules! _plugin_entry_after_build { }; } -pub trait AutoPluginCustom { +pub trait AutoPluginBuildHook { fn on_build(app: &mut bevy_app::App); } diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs index cd01cd80..528c5ce0 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs @@ -42,7 +42,7 @@ gen_action_outers! { auto_add_observer => IaAddObserver, auto_add_plugin => IaAddPlugin, auto_configure_system_set => IaConfigureSystemSet, - auto_plugin_custom => IaAutoPluginCustom, + auto_plugin_build_hook => IaAutoPluginBuildHook, } gen_rewrite_outers! { diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_custom.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs similarity index 55% rename from crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_custom.rs rename to crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs index 56574b65..3dbc6e13 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_custom.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs @@ -9,39 +9,39 @@ use syn::Path; #[derive(FromMeta, Debug, Clone, PartialEq, Hash)] #[darling(derive_syn_parse)] -pub struct AutoPluginCustomArgs { - custom: Path, +pub struct AutoPluginBuildHookArgs { + hook: Path, } -impl AttributeIdent for AutoPluginCustomArgs { - const IDENT: &'static str = "auto_plugin_custom"; +impl AttributeIdent for AutoPluginBuildHookArgs { + const IDENT: &'static str = "auto_plugin_hook"; } -pub type IaAutoPluginCustom = ItemAttribute< - Composed, +pub type IaAutoPluginBuildHook = ItemAttribute< + Composed, AllowStructOrEnum, >; -pub type AutoPluginCustomAppMutEmitter = AppMutationEmitter; -pub type AutoPluginCustomAttrEmitter = AttrEmitter; +pub type AutoPluginBuildHookAppMutEmitter = AppMutationEmitter; +pub type AutoPluginBuildHookAttrEmitter = AttrEmitter; -impl EmitAppMutationTokens for AutoPluginCustomAppMutEmitter { +impl EmitAppMutationTokens for AutoPluginBuildHookAppMutEmitter { fn to_app_mutation_tokens( &self, tokens: &mut TokenStream, app_param: &syn::Ident, ) -> syn::Result<()> { - let custom = &self.args.args.base.custom; + let custom = &self.args.args.base.hook; let concrete_paths = self.args.concrete_paths()?; for concrete_path in concrete_paths { tokens.extend(quote! { - <#custom as ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::AutoPluginCustom>::on_build::<#concrete_path>(#app_param); + <#custom as ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::AutoPluginBuildHook>::on_build::<#concrete_path>(#app_param); }); } Ok(()) } } -impl ToTokens for AutoPluginCustomAttrEmitter { +impl ToTokens for AutoPluginBuildHookAttrEmitter { fn to_tokens(&self, tokens: &mut TokenStream) { let args = self.args.args.extra_args(); tokens.extend(quote! { diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs index 94494386..bbd3a320 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs @@ -8,7 +8,7 @@ mod auto_init_state; mod auto_init_sub_state; mod auto_insert_resource; mod auto_name; -mod auto_plugin_custom; +mod auto_plugin_build_hook; mod auto_register_state_type; mod auto_register_type; mod auto_run_on_build; @@ -25,7 +25,7 @@ pub mod prelude { pub use auto_init_sub_state::*; pub use auto_insert_resource::*; pub use auto_name::*; - pub use auto_plugin_custom::*; + pub use auto_plugin_build_hook::*; pub use auto_register_state_type::*; pub use auto_register_type::*; pub use auto_run_on_build::*; diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs b/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs index a6304d9b..758d5868 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs @@ -32,7 +32,7 @@ pub struct MacroPaths { pub emit_auto_name_macro: syn::Path, /// resolved absolute path to `auto_configure_system_set` pub emit_configure_system_set_macro: syn::Path, - pub emit_auto_plugin_custom_macro: syn::Path, + pub emit_auto_plugin_hook_macro: syn::Path, } impl Default for MacroPaths { @@ -52,7 +52,7 @@ impl Default for MacroPaths { emit_run_on_build_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_run_on_build ), emit_auto_name_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_name ), emit_configure_system_set_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_configure_system_set ), - emit_auto_plugin_custom_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_plugin_custom ), + emit_auto_plugin_hook_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_plugin_hook ), } } } @@ -139,8 +139,8 @@ impl MacroPathProvider for ConfigureSystemSetArgs { } } -impl MacroPathProvider for AutoPluginCustomArgs { +impl MacroPathProvider for AutoPluginBuildHookArgs { fn macro_path(context: &Context) -> &syn::Path { - &context.macros.emit_auto_plugin_custom_macro + &context.macros.emit_auto_plugin_hook_macro } } diff --git a/src/lib.rs b/src/lib.rs index 135e459c..78c03034 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,5 +190,5 @@ pub mod prelude { #[doc = include_str!("../docs/proc_attributes/actions/auto_configure_system_set.md")] pub use bevy_auto_plugin_proc_macros::auto_configure_system_set; - pub use bevy_auto_plugin_proc_macros::auto_plugin_custom; + pub use bevy_auto_plugin_proc_macros::auto_plugin_build_hook; } diff --git a/tests/e2e/actions/auto_plugin_custom.rs b/tests/e2e/actions/auto_plugin_build_hook.rs similarity index 77% rename from tests/e2e/actions/auto_plugin_custom.rs rename to tests/e2e/actions/auto_plugin_build_hook.rs index c6802ac9..324cc02c 100644 --- a/tests/e2e/actions/auto_plugin_custom.rs +++ b/tests/e2e/actions/auto_plugin_build_hook.rs @@ -1,6 +1,6 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_auto_plugin_shared::__private::auto_plugin_registry::AutoPluginCustom; +use bevy_auto_plugin_shared::__private::auto_plugin_registry::AutoPluginBuildHook; use bevy_ecs::prelude::*; use internal_test_proc_macro::xtest; use std::any::TypeId; @@ -14,7 +14,7 @@ struct Counter(Vec); struct MyCustomHookA; -impl AutoPluginCustom for MyCustomHookA { +impl AutoPluginBuildHook for MyCustomHookA { fn on_build(app: &mut App) { app.world_mut().resource_mut::().0.push(format!("A {:?}", TypeId::of::())) } @@ -22,20 +22,20 @@ impl AutoPluginCustom for MyCustomHookA { struct MyCustomHookB; -impl AutoPluginCustom for MyCustomHookB { +impl AutoPluginBuildHook for MyCustomHookB { fn on_build(app: &mut App) { app.world_mut().resource_mut::().0.push(format!("B {:?}", TypeId::of::())) } } #[derive(Debug)] -#[auto_plugin_custom(plugin = TestPlugin, custom = MyCustomHookA)] -#[auto_plugin_custom(plugin = TestPlugin, custom = MyCustomHookB)] +#[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookA)] +#[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookB)] struct TestA; #[derive(Debug)] -#[auto_plugin_custom(plugin = TestPlugin, custom = MyCustomHookB)] -#[auto_plugin_custom(plugin = TestPlugin, custom = MyCustomHookA)] +#[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookB)] +#[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookA)] struct TestB; fn app() -> App { @@ -46,7 +46,7 @@ fn app() -> App { } #[xtest] -fn test_auto_plugin_custom() { +fn test_auto_plugin_hook() { let app = app(); fn hook_a(test: TypeId) -> String { format!("A {:?}", test) diff --git a/tests/e2e/actions/mod.rs b/tests/e2e/actions/mod.rs index c613520d..97f03286 100644 --- a/tests/e2e/actions/mod.rs +++ b/tests/e2e/actions/mod.rs @@ -23,7 +23,7 @@ mod auto_insert_resource; mod auto_insert_resource_with_generics; mod auto_name; mod auto_name_with_generics; -mod auto_plugin_custom; +mod auto_plugin_build_hook; mod auto_plugin_default_param; mod auto_plugin_default_param_method; mod auto_plugin_param; From 408779671ad23835d8e6db0dd01ab757c727adb8 Mon Sep 17 00:00:00 2001 From: bstriker Date: Sat, 17 Jan 2026 13:36:17 -0500 Subject: [PATCH 4/8] refactor: relocate `AutoPluginBuildHook` trait to `lib.rs` and expose publicly in prelude --- .../src/__private/auto_plugin_registry.rs | 4 ---- crates/bevy_auto_plugin_shared/src/lib.rs | 4 ++++ .../macro_api/attributes/actions/auto_plugin_build_hook.rs | 2 +- src/lib.rs | 1 + tests/e2e/actions/auto_plugin_build_hook.rs | 1 - 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs index f2905ae0..53abc5c6 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs @@ -312,10 +312,6 @@ macro_rules! _plugin_entry_after_build { }; } -pub trait AutoPluginBuildHook { - fn on_build(app: &mut bevy_app::App); -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/bevy_auto_plugin_shared/src/lib.rs b/crates/bevy_auto_plugin_shared/src/lib.rs index ad233530..6c2857ff 100644 --- a/crates/bevy_auto_plugin_shared/src/lib.rs +++ b/crates/bevy_auto_plugin_shared/src/lib.rs @@ -20,3 +20,7 @@ pub fn _initialize() { __wasm_call_ctors(); } } + +pub trait AutoPluginBuildHook { + fn on_build(app: &mut bevy_app::App); +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs index 3dbc6e13..2fbba066 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs @@ -34,7 +34,7 @@ impl EmitAppMutationTokens for AutoPluginBuildHookAppMutEmitter { let concrete_paths = self.args.concrete_paths()?; for concrete_path in concrete_paths { tokens.extend(quote! { - <#custom as ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::AutoPluginBuildHook>::on_build::<#concrete_path>(#app_param); + <#custom as ::bevy_auto_plugin::__private::shared::AutoPluginBuildHook>::on_build::<#concrete_path>(#app_param); }); } Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 78c03034..4e036adf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,5 +190,6 @@ pub mod prelude { #[doc = include_str!("../docs/proc_attributes/actions/auto_configure_system_set.md")] pub use bevy_auto_plugin_proc_macros::auto_configure_system_set; + pub use super::__private::shared::AutoPluginBuildHook; pub use bevy_auto_plugin_proc_macros::auto_plugin_build_hook; } diff --git a/tests/e2e/actions/auto_plugin_build_hook.rs b/tests/e2e/actions/auto_plugin_build_hook.rs index 324cc02c..5dc76078 100644 --- a/tests/e2e/actions/auto_plugin_build_hook.rs +++ b/tests/e2e/actions/auto_plugin_build_hook.rs @@ -1,6 +1,5 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_auto_plugin_shared::__private::auto_plugin_registry::AutoPluginBuildHook; use bevy_ecs::prelude::*; use internal_test_proc_macro::xtest; use std::any::TypeId; From 4066a71a048c74ec4ee2d5582271bbe18fb83768 Mon Sep 17 00:00:00 2001 From: bstriker Date: Sat, 17 Jan 2026 16:02:45 -0500 Subject: [PATCH 5/8] refactor: update `AutoPluginBuildHook` trait to support generic type parameters and adjust implementations --- crates/bevy_auto_plugin_shared/src/lib.rs | 4 ++-- .../attributes/actions/auto_plugin_build_hook.rs | 2 +- tests/e2e/actions/auto_plugin_build_hook.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/bevy_auto_plugin_shared/src/lib.rs b/crates/bevy_auto_plugin_shared/src/lib.rs index 6c2857ff..5751ec68 100644 --- a/crates/bevy_auto_plugin_shared/src/lib.rs +++ b/crates/bevy_auto_plugin_shared/src/lib.rs @@ -21,6 +21,6 @@ pub fn _initialize() { } } -pub trait AutoPluginBuildHook { - fn on_build(app: &mut bevy_app::App); +pub trait AutoPluginBuildHook { + fn on_build(app: &mut bevy_app::App); } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs index 2fbba066..d2fcfc20 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs @@ -34,7 +34,7 @@ impl EmitAppMutationTokens for AutoPluginBuildHookAppMutEmitter { let concrete_paths = self.args.concrete_paths()?; for concrete_path in concrete_paths { tokens.extend(quote! { - <#custom as ::bevy_auto_plugin::__private::shared::AutoPluginBuildHook>::on_build::<#concrete_path>(#app_param); + < #custom as ::bevy_auto_plugin::__private::shared::AutoPluginBuildHook::< #concrete_path >>::on_build(#app_param); }); } Ok(()) diff --git a/tests/e2e/actions/auto_plugin_build_hook.rs b/tests/e2e/actions/auto_plugin_build_hook.rs index 5dc76078..9681a711 100644 --- a/tests/e2e/actions/auto_plugin_build_hook.rs +++ b/tests/e2e/actions/auto_plugin_build_hook.rs @@ -13,26 +13,26 @@ struct Counter(Vec); struct MyCustomHookA; -impl AutoPluginBuildHook for MyCustomHookA { - fn on_build(app: &mut App) { +impl AutoPluginBuildHook for MyCustomHookA { + fn on_build(app: &mut App) { app.world_mut().resource_mut::().0.push(format!("A {:?}", TypeId::of::())) } } struct MyCustomHookB; -impl AutoPluginBuildHook for MyCustomHookB { - fn on_build(app: &mut App) { +impl AutoPluginBuildHook for MyCustomHookB { + fn on_build(app: &mut App) { app.world_mut().resource_mut::().0.push(format!("B {:?}", TypeId::of::())) } } -#[derive(Debug)] +#[derive(Component, Debug)] #[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookA)] #[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookB)] struct TestA; -#[derive(Debug)] +#[derive(Component, Debug)] #[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookB)] #[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookA)] struct TestB; From 786588621512b1f3ed7b637ecd62e35e7dc7d188 Mon Sep 17 00:00:00 2001 From: bstriker Date: Sat, 17 Jan 2026 16:32:47 -0500 Subject: [PATCH 6/8] refactor: make `AutoPluginBuildHook` trait method instance-based and update macro to handle struct instances --- crates/bevy_auto_plugin_shared/src/lib.rs | 2 +- .../actions/auto_plugin_build_hook.rs | 7 +- tests/e2e/actions/auto_plugin_build_hook.rs | 68 ++++++++++++------- 3 files changed, 49 insertions(+), 28 deletions(-) diff --git a/crates/bevy_auto_plugin_shared/src/lib.rs b/crates/bevy_auto_plugin_shared/src/lib.rs index 5751ec68..ae105cf2 100644 --- a/crates/bevy_auto_plugin_shared/src/lib.rs +++ b/crates/bevy_auto_plugin_shared/src/lib.rs @@ -22,5 +22,5 @@ pub fn _initialize() { } pub trait AutoPluginBuildHook { - fn on_build(app: &mut bevy_app::App); + fn on_build(&self, app: &mut bevy_app::App); } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs index d2fcfc20..43d121aa 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_plugin_build_hook.rs @@ -5,12 +5,12 @@ use quote::{ ToTokens, quote, }; -use syn::Path; +use syn::Expr; #[derive(FromMeta, Debug, Clone, PartialEq, Hash)] #[darling(derive_syn_parse)] pub struct AutoPluginBuildHookArgs { - hook: Path, + hook: Expr, } impl AttributeIdent for AutoPluginBuildHookArgs { @@ -34,7 +34,8 @@ impl EmitAppMutationTokens for AutoPluginBuildHookAppMutEmitter { let concrete_paths = self.args.concrete_paths()?; for concrete_path in concrete_paths { tokens.extend(quote! { - < #custom as ::bevy_auto_plugin::__private::shared::AutoPluginBuildHook::< #concrete_path >>::on_build(#app_param); + let instance = #custom; + ::bevy_auto_plugin::__private::shared::AutoPluginBuildHook::< #concrete_path >::on_build(&instance, #app_param); }); } Ok(()) diff --git a/tests/e2e/actions/auto_plugin_build_hook.rs b/tests/e2e/actions/auto_plugin_build_hook.rs index 9681a711..694a4c1f 100644 --- a/tests/e2e/actions/auto_plugin_build_hook.rs +++ b/tests/e2e/actions/auto_plugin_build_hook.rs @@ -2,38 +2,54 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; use bevy_ecs::prelude::*; use internal_test_proc_macro::xtest; -use std::any::TypeId; +use std::{ + any::TypeId, + collections::{ + HashMap, + HashSet, + }, +}; #[derive(AutoPlugin)] #[auto_plugin(impl_plugin_trait)] struct TestPlugin; #[derive(Resource, Debug, Default, PartialEq)] -struct Counter(Vec); +struct Counter(HashMap<&'static str, HashMap>>); struct MyCustomHookA; impl AutoPluginBuildHook for MyCustomHookA { - fn on_build(app: &mut App) { - app.world_mut().resource_mut::().0.push(format!("A {:?}", TypeId::of::())) + fn on_build(&self, app: &mut App) { + app.world_mut() + .resource_mut::() + .0 + .entry("A") + .or_default() + .entry(TypeId::of::()) + .or_default() + .insert(std::any::type_name::()); } } -struct MyCustomHookB; +struct MyCustomHookB(&'static str); impl AutoPluginBuildHook for MyCustomHookB { - fn on_build(app: &mut App) { - app.world_mut().resource_mut::().0.push(format!("B {:?}", TypeId::of::())) + fn on_build(&self, app: &mut App) { + let mut counter = app.world_mut().resource_mut::(); + let set = counter.0.entry("B").or_default().entry(TypeId::of::()).or_default(); + set.insert(std::any::type_name::()); + set.insert(self.0); } } #[derive(Component, Debug)] #[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookA)] -#[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookB)] +#[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookB("foo"))] struct TestA; #[derive(Component, Debug)] -#[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookB)] +#[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookB("bar"))] #[auto_plugin_build_hook(plugin = TestPlugin, hook = MyCustomHookA)] struct TestB; @@ -47,21 +63,25 @@ fn app() -> App { #[xtest] fn test_auto_plugin_hook() { let app = app(); - fn hook_a(test: TypeId) -> String { - format!("A {:?}", test) - } - fn hook_b(test: TypeId) -> String { - format!("B {:?}", test) - } - let test_a = TypeId::of::(); - let test_b = TypeId::of::(); + let counter = app.world().get_resource::().expect("counter resource missing"); + + let hook_a_map = counter.0.get("A").expect("Hook A entry missing"); + assert_eq!( + hook_a_map.get(&TypeId::of::()).expect("Hook A - TestA entry missing"), + &HashSet::from([std::any::type_name::()]) + ); + assert_eq!( + hook_a_map.get(&TypeId::of::()).expect("Hook A - TestB entry missing"), + &HashSet::from([std::any::type_name::()]) + ); + + let hook_b_map = counter.0.get("B").expect("Hook B entry missing"); + assert_eq!( + hook_b_map.get(&TypeId::of::()).expect("Hook B - TestA entry missing"), + &HashSet::from([std::any::type_name::(), "foo"]) + ); assert_eq!( - app.world().get_resource::(), - Some(&Counter(vec![ - hook_a(test_a), - hook_a(test_b), - hook_b(test_b), - hook_b(test_a), - ])) + hook_b_map.get(&TypeId::of::()).expect("Hook B - TestB entry missing"), + &HashSet::from([std::any::type_name::(), "bar"]) ); } From bb32dfa3dad84de4288179261f3bee94009bdd5f Mon Sep 17 00:00:00 2001 From: bstriker Date: Sat, 17 Jan 2026 23:40:42 -0500 Subject: [PATCH 7/8] chore(deps): remove unused `syn` dependency from Cargo.toml --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5e3e9e7b..a7996613 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,6 @@ wasm-bindgen-test = { workspace = true } internal_test_util = { workspace = true } internal_test_proc_macro = { workspace = true } meta_merge = "0.1.1" -syn = { workspace = true } [build-dependencies] rustc_version = "0.4" From 51c787b93be523d9542414cc2bd7eb77169c714f Mon Sep 17 00:00:00 2001 From: bstriker Date: Thu, 5 Feb 2026 15:29:51 -0500 Subject: [PATCH 8/8] feat: document and expose `auto_plugin_build_hook` --- CHANGELOG.md | 3 +- README.md | 30 ++++++++++- .../bevy_auto_plugin_proc_macros/src/lib.rs | 1 + crates/bevy_auto_plugin_shared/Cargo.toml | 3 +- crates/bevy_auto_plugin_shared/src/lib.rs | 45 ++++++++++++++++ .../actions/auto_plugin_build_hook.md | 51 +++++++++++++++++++ src/lib.rs | 31 +++++++++++ 7 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 docs/proc_attributes/actions/auto_plugin_build_hook.md diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc1c660..b5be6ab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,4 +160,5 @@ ## v0.10.0 - Add deterministic ordering for auto plugin registry entries, preserving definition order. - Add optional `after_build` flag to all `auto_*` macros to enable injecting tokens at the end of plugin build function body instead of the start. -- Add support for `insert` in `auto_resource` \ No newline at end of file +- Add support for `insert` in `auto_resource` +- Add `auto_plugin_build_hook` and `AutoPluginBuildHook` to run custom hooks during plugin build. diff --git a/README.md b/README.md index 9bd9d756..a696854a 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,34 @@ There is `auto_plugin` arguments if your plugin has generics. See [tests](tests/e2e) for other examples +### Custom Build Hooks (Third-Party Integration) + +You can extend this pattern to third-party crates by using `#[auto_plugin_build_hook]` as a building block +for your own macros. For example, a user might want to automatically call `bevy_replicon` `app.replicate::()` for each annotated type. + +```rust,ignore +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; +use bevy_replicon::prelude::*; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct MyPlugin; + +struct ReplicateHook; + +impl AutoPluginBuildHook for ReplicateHook { + fn on_build(&self, app: &mut App) { + app.replicate::(); + } +} + +#[derive(Component)] +#[auto_plugin_build_hook(plugin = MyPlugin, hook = ReplicateHook)] +struct MyReplicatedThing; +``` + + ### Expanded If you were looking to cherry-pick certain functionality like `auto_name` or `auto_register_type` for example you could use them individually: @@ -260,4 +288,4 @@ at your option. This means you can select the license you prefer. ### Your Contributions Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual-licensed as above, without any additional terms or conditions. \ No newline at end of file +dual-licensed as above, without any additional terms or conditions. diff --git a/crates/bevy_auto_plugin_proc_macros/src/lib.rs b/crates/bevy_auto_plugin_proc_macros/src/lib.rs index a7dd7e62..4d8b9eae 100644 --- a/crates/bevy_auto_plugin_proc_macros/src/lib.rs +++ b/crates/bevy_auto_plugin_proc_macros/src/lib.rs @@ -156,6 +156,7 @@ pub fn auto_bind_plugin(attr: CompilerStream, input: CompilerStream) -> Compiler handle_attribute(expand::attr::auto_bind_plugin::auto_bind_plugin_outer, attr, input) } +#[doc = include_str!(concat!(env!("OUT_DIR"), "/docs/proc_attributes/actions/auto_plugin_build_hook.md"))] #[proc_macro_attribute] pub fn auto_plugin_build_hook(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_plugin_build_hook, attr, input) diff --git a/crates/bevy_auto_plugin_shared/Cargo.toml b/crates/bevy_auto_plugin_shared/Cargo.toml index f5aa45e9..7ff69a92 100644 --- a/crates/bevy_auto_plugin_shared/Cargo.toml +++ b/crates/bevy_auto_plugin_shared/Cargo.toml @@ -45,4 +45,5 @@ anyhow = { workspace = true } # used in crate resolve tests bevy_ecs = { workspace = true } bevy_reflect = { workspace = true } -bevy_state = { workspace = true } \ No newline at end of file +bevy_state = { workspace = true } +bevy_auto_plugin = { path = "../../.", default-features = false } \ No newline at end of file diff --git a/crates/bevy_auto_plugin_shared/src/lib.rs b/crates/bevy_auto_plugin_shared/src/lib.rs index ae105cf2..567df6d6 100644 --- a/crates/bevy_auto_plugin_shared/src/lib.rs +++ b/crates/bevy_auto_plugin_shared/src/lib.rs @@ -21,6 +21,51 @@ pub fn _initialize() { } } +/// Hook invoked during a plugin's build for a specific target type `T`. +/// +/// Register a hook by annotating the target type with [`bevy_auto_plugin::prelude::auto_plugin_build_hook`]. +/// +/// The `hook` expression is evaluated when the plugin builds. Use `after_build` on the +/// attribute to run the hook after the build body instead of before. +/// +/// # Example +/// ```rust +/// use bevy_app::prelude::*; +/// use bevy_ecs::prelude::*; +/// use bevy_auto_plugin::prelude::*; +/// +/// #[derive(AutoPlugin)] +/// #[auto_plugin(impl_plugin_trait)] +/// struct MyPlugin; +/// +/// struct SpawnComponentHook; +/// +/// impl AutoPluginBuildHook for SpawnComponentHook where T: Component + Default { +/// fn on_build(&self, app: &mut App) { +/// app.world_mut().spawn(T::default()); +/// } +/// } +/// +/// #[auto_component(plugin = MyPlugin, derive(Default))] +/// #[auto_plugin_build_hook(plugin = MyPlugin, hook = SpawnComponentHook)] +/// struct MyComponent; +/// ``` +/// Generates the equivalent to: +/// ```rust +/// use bevy_app::prelude::*; +/// use bevy_ecs::prelude::*; +/// +/// #[derive(Component, Default)] +/// struct MyComponent; +/// +/// struct MyPlugin; +/// impl Plugin for MyPlugin { +/// fn build(&self, app: &mut App) { +/// app.world_mut().spawn(MyComponent); +/// } +/// } +/// ``` pub trait AutoPluginBuildHook { + /// Called during plugin build for the target type `T`. fn on_build(&self, app: &mut bevy_app::App); } diff --git a/docs/proc_attributes/actions/auto_plugin_build_hook.md b/docs/proc_attributes/actions/auto_plugin_build_hook.md new file mode 100644 index 00000000..a876ecad --- /dev/null +++ b/docs/proc_attributes/actions/auto_plugin_build_hook.md @@ -0,0 +1,51 @@ +Registers a build hook to run custom logic for a type when a plugin builds. + +# Parameters +- `plugin = PluginType` - Required. Specifies which plugin should run this hook. +- `hook = Expr` - Required. Expression that constructs a value implementing `AutoPluginBuildHook` for the target type. +- `after_build` - Optional. Injects this macro's tokens at the end of the plugin build instead of the start. +- `generics(T1, T2, ...)` - Optional. Specifies concrete types for generic parameters. + When provided, the hook is run for each of these specific generic parameters. + Note: Clippy will complain if you have duplicate generic type names. For those you can use named generics: `generics(T1 = ..., T2 = ...)`. + +# Example +```rust +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct MyPlugin; + +struct MyHook; + +impl AutoPluginBuildHook for MyHook { + fn on_build(&self, _app: &mut App) { + // custom logic for T + } +} + +#[derive(Component)] +#[auto_plugin_build_hook(plugin = MyPlugin, hook = MyHook)] +struct Foo; +``` + +# Example (with generics) +```rust +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct MyPlugin; + +struct MyHook; + +impl AutoPluginBuildHook for MyHook { + fn on_build(&self, _app: &mut App) {} +} + +#[derive(Component)] +#[auto_plugin_build_hook(plugin = MyPlugin, hook = MyHook, generics(u32), generics(bool))] +struct Foo(T); +``` diff --git a/src/lib.rs b/src/lib.rs index 4e036adf..c6dd0640 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,34 @@ //! #[apply(CopyDefaultComponentTemplate!)] //! struct BarComponent; //! ``` +//! +//! ### Custom Build Hooks (Third-Party Integration) +//! You can use `#[auto_plugin_build_hook]` as a building block for third-party +//! APIs that require `App` calls (for example, `bevy_replicon`'s `app.replicate::()`). +//! This makes it easy to build your own macros for external crates without +//! writing boilerplate in every plugin build. +//! +//! ```rust,ignore +//! use bevy::prelude::*; +//! use bevy_auto_plugin::prelude::*; +//! use bevy_replicon::prelude::*; +//! +//! #[derive(AutoPlugin)] +//! #[auto_plugin(impl_plugin_trait)] +//! struct MyPlugin; +//! +//! struct ReplicateHook; +//! +//! impl AutoPluginBuildHook for ReplicateHook { +//! fn on_build(&self, app: &mut App) { +//! app.replicate::(); +//! } +//! } +//! +//! #[derive(Component)] +//! #[auto_plugin_build_hook(plugin = MyPlugin, hook = ReplicateHook)] +//! struct NetTransform; +//! ``` /// Private Re-exports #[doc(hidden)] @@ -190,6 +218,9 @@ pub mod prelude { #[doc = include_str!("../docs/proc_attributes/actions/auto_configure_system_set.md")] pub use bevy_auto_plugin_proc_macros::auto_configure_system_set; + #[doc(inline)] pub use super::__private::shared::AutoPluginBuildHook; + + #[doc = include_str!("../docs/proc_attributes/actions/auto_plugin_build_hook.md")] pub use bevy_auto_plugin_proc_macros::auto_plugin_build_hook; }