Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
- Add support for `insert` in `auto_resource`
- Add `auto_plugin_build_hook` and `AutoPluginBuildHook` to run custom hooks during plugin build.
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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::<T>()` 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<T: Component + 'static> AutoPluginBuildHook<T> for ReplicateHook {
fn on_build(&self, app: &mut App) {
app.replicate::<T>();
}
}

#[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:
Expand Down Expand Up @@ -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.
dual-licensed as above, without any additional terms or conditions.
6 changes: 6 additions & 0 deletions crates/bevy_auto_plugin_proc_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,9 @@ 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)
}

#[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)
}
3 changes: 2 additions & 1 deletion crates/bevy_auto_plugin_shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ anyhow = { workspace = true }
# used in crate resolve tests
bevy_ecs = { workspace = true }
bevy_reflect = { workspace = true }
bevy_state = { workspace = true }
bevy_state = { workspace = true }
bevy_auto_plugin = { path = "../../.", default-features = false }
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ gen_action_outers! {
auto_add_observer => IaAddObserver,
auto_add_plugin => IaAddPlugin,
auto_configure_system_set => IaConfigureSystemSet,
auto_plugin_build_hook => IaAutoPluginBuildHook,
}

gen_rewrite_outers! {
Expand Down
49 changes: 49 additions & 0 deletions crates/bevy_auto_plugin_shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,52 @@ pub fn _initialize() {
__wasm_call_ctors();
}
}

/// 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<T: 'static> AutoPluginBuildHook<T> 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<T: 'static> {
/// Called during plugin build for the target type `T`.
fn on_build(&self, app: &mut bevy_app::App);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate::macro_api::prelude::*;
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::{
ToTokens,
quote,
};
use syn::Expr;

#[derive(FromMeta, Debug, Clone, PartialEq, Hash)]
#[darling(derive_syn_parse)]
pub struct AutoPluginBuildHookArgs {
hook: Expr,
}

impl AttributeIdent for AutoPluginBuildHookArgs {
const IDENT: &'static str = "auto_plugin_hook";
}

pub type IaAutoPluginBuildHook = ItemAttribute<
Composed<AutoPluginBuildHookArgs, WithPlugin, WithZeroOrManyGenerics>,
AllowStructOrEnum,
>;
pub type AutoPluginBuildHookAppMutEmitter = AppMutationEmitter<IaAutoPluginBuildHook>;
pub type AutoPluginBuildHookAttrEmitter = AttrEmitter<IaAutoPluginBuildHook>;

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.hook;
let concrete_paths = self.args.concrete_paths()?;
for concrete_path in concrete_paths {
tokens.extend(quote! {
let instance = #custom;
::bevy_auto_plugin::__private::shared::AutoPluginBuildHook::< #concrete_path >::on_build(&instance, #app_param);
});
}
Ok(())
}
}

impl ToTokens for AutoPluginBuildHookAttrEmitter {
fn to_tokens(&self, tokens: &mut TokenStream) {
let args = self.args.args.extra_args();
tokens.extend(quote! {
#(#args),*
});
*tokens = self.wrap_as_attr(tokens);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod auto_init_state;
mod auto_init_sub_state;
mod auto_insert_resource;
mod auto_name;
mod auto_plugin_build_hook;
mod auto_register_state_type;
mod auto_register_type;
mod auto_run_on_build;
Expand All @@ -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_build_hook::*;
pub use auto_register_state_type::*;
pub use auto_register_type::*;
pub use auto_run_on_build::*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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_hook_macro: syn::Path,
}

impl Default for MacroPaths {
Expand All @@ -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_hook_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_plugin_hook ),
}
}
}
Expand Down Expand Up @@ -136,3 +138,9 @@ impl MacroPathProvider for ConfigureSystemSetArgs {
&context.macros.emit_configure_system_set_macro
}
}

impl MacroPathProvider for AutoPluginBuildHookArgs {
fn macro_path(context: &Context) -> &syn::Path {
&context.macros.emit_auto_plugin_hook_macro
}
}
51 changes: 51 additions & 0 deletions docs/proc_attributes/actions/auto_plugin_build_hook.md
Original file line number Diff line number Diff line change
@@ -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<T>` 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<T: 'static> AutoPluginBuildHook<T> 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<T: 'static> AutoPluginBuildHook<T> 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>(T);
```
34 changes: 34 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<T>()`).
//! 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<T: Component + 'static> AutoPluginBuildHook<T> for ReplicateHook {
//! fn on_build(&self, app: &mut App) {
//! app.replicate::<T>();
//! }
//! }
//!
//! #[derive(Component)]
//! #[auto_plugin_build_hook(plugin = MyPlugin, hook = ReplicateHook)]
//! struct NetTransform;
//! ```

/// Private Re-exports
#[doc(hidden)]
Expand Down Expand Up @@ -189,4 +217,10 @@ 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;
}
Loading