mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Component Hook functions as attributes for Component derive macro (#14005)
# Objective Fixes https://github.com/bevyengine/bevy/issues/13972 ## Solution Added 3 new attributes to the `Component` macro. ## Testing Added `component_hook_order_spawn_despawn_with_macro_hooks`, that makes the same as `component_hook_order_spawn_despawn` but uses a struct, that defines it's hooks with the `Component` macro. --- --------- Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
This commit is contained in:
parent
f009d37fe5
commit
330911f1bf
4 changed files with 113 additions and 2 deletions
|
@ -1,7 +1,7 @@
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, parse_quote, DeriveInput, Ident, LitStr, Path, Result};
|
||||
use syn::{parse_macro_input, parse_quote, DeriveInput, ExprPath, Ident, LitStr, Path, Result};
|
||||
|
||||
pub fn derive_event(input: TokenStream) -> TokenStream {
|
||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||
|
@ -54,6 +54,10 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||
|
||||
let storage = storage_path(&bevy_ecs_path, attrs.storage);
|
||||
|
||||
let on_add = hook_register_function_call(quote! {on_add}, attrs.on_add);
|
||||
let on_insert = hook_register_function_call(quote! {on_insert}, attrs.on_insert);
|
||||
let on_remove = hook_register_function_call(quote! {on_remove}, attrs.on_remove);
|
||||
|
||||
ast.generics
|
||||
.make_where_clause()
|
||||
.predicates
|
||||
|
@ -65,15 +69,28 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||
TokenStream::from(quote! {
|
||||
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
|
||||
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) {
|
||||
#on_add
|
||||
#on_insert
|
||||
#on_remove
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub const COMPONENT: &str = "component";
|
||||
pub const STORAGE: &str = "storage";
|
||||
pub const ON_ADD: &str = "on_add";
|
||||
pub const ON_INSERT: &str = "on_insert";
|
||||
pub const ON_REMOVE: &str = "on_remove";
|
||||
|
||||
struct Attrs {
|
||||
storage: StorageTy,
|
||||
on_add: Option<ExprPath>,
|
||||
on_insert: Option<ExprPath>,
|
||||
on_remove: Option<ExprPath>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -89,6 +106,9 @@ const SPARSE_SET: &str = "SparseSet";
|
|||
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
||||
let mut attrs = Attrs {
|
||||
storage: StorageTy::Table,
|
||||
on_add: None,
|
||||
on_insert: None,
|
||||
on_remove: None,
|
||||
};
|
||||
|
||||
for meta in ast.attrs.iter().filter(|a| a.path().is_ident(COMPONENT)) {
|
||||
|
@ -104,6 +124,15 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
|||
}
|
||||
};
|
||||
Ok(())
|
||||
} else if nested.path.is_ident(ON_ADD) {
|
||||
attrs.on_add = Some(nested.value()?.parse::<ExprPath>()?);
|
||||
Ok(())
|
||||
} else if nested.path.is_ident(ON_INSERT) {
|
||||
attrs.on_insert = Some(nested.value()?.parse::<ExprPath>()?);
|
||||
Ok(())
|
||||
} else if nested.path.is_ident(ON_REMOVE) {
|
||||
attrs.on_remove = Some(nested.value()?.parse::<ExprPath>()?);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(nested.error("Unsupported attribute"))
|
||||
}
|
||||
|
@ -121,3 +150,10 @@ fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
|
|||
|
||||
quote! { #bevy_ecs_path::component::StorageType::#storage_type }
|
||||
}
|
||||
|
||||
fn hook_register_function_call(
|
||||
hook: TokenStream2,
|
||||
function: Option<ExprPath>,
|
||||
) -> Option<TokenStream2> {
|
||||
function.map(|meta| quote! { hooks. #hook (#meta); })
|
||||
}
|
||||
|
|
|
@ -1124,11 +1124,29 @@ fn initialize_dynamic_bundle(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate as bevy_ecs;
|
||||
use crate::component::ComponentId;
|
||||
use crate::prelude::*;
|
||||
use crate::world::DeferredWorld;
|
||||
|
||||
#[derive(Component)]
|
||||
struct A;
|
||||
|
||||
#[derive(Component)]
|
||||
#[component(on_add = a_on_add, on_insert = a_on_insert, on_remove = a_on_remove)]
|
||||
struct AMacroHooks;
|
||||
|
||||
fn a_on_add(mut world: DeferredWorld, _: Entity, _: ComponentId) {
|
||||
world.resource_mut::<R>().assert_order(0);
|
||||
}
|
||||
|
||||
fn a_on_insert<T1, T2>(mut world: DeferredWorld, _: T1, _: T2) {
|
||||
world.resource_mut::<R>().assert_order(1);
|
||||
}
|
||||
|
||||
fn a_on_remove<T1, T2>(mut world: DeferredWorld, _: T1, _: T2) {
|
||||
world.resource_mut::<R>().assert_order(2);
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct B;
|
||||
|
||||
|
@ -1166,6 +1184,17 @@ mod tests {
|
|||
assert_eq!(3, world.resource::<R>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn component_hook_order_spawn_despawn_with_macro_hooks() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
|
||||
let entity = world.spawn(AMacroHooks).id();
|
||||
world.despawn(entity);
|
||||
|
||||
assert_eq!(3, world.resource::<R>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn component_hook_order_insert_remove() {
|
||||
let mut world = World::new();
|
||||
|
|
|
@ -93,6 +93,42 @@ use std::{
|
|||
/// [`Table`]: crate::storage::Table
|
||||
/// [`SparseSet`]: crate::storage::SparseSet
|
||||
///
|
||||
/// # Adding component's hooks
|
||||
///
|
||||
/// See [`ComponentHooks`] for a detailed explanation of component's hooks.
|
||||
///
|
||||
/// Alternatively to the example shown in [`ComponentHooks`]' documentation, hooks can be configured using following attributes:
|
||||
/// - `#[component(on_add = on_add_function)]`
|
||||
/// - `#[component(on_insert = on_insert_function)]`
|
||||
/// - `#[component(on_remove = on_remove_function)]`
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::world::DeferredWorld;
|
||||
/// # use bevy_ecs::entity::Entity;
|
||||
/// # use bevy_ecs::component::ComponentId;
|
||||
/// #
|
||||
/// #[derive(Component)]
|
||||
/// #[component(on_add = my_on_add_hook)]
|
||||
/// #[component(on_insert = my_on_insert_hook)]
|
||||
/// // Another possible way of configuring hooks:
|
||||
/// // #[component(on_add = my_on_add_hook, on_insert = my_on_insert_hook)]
|
||||
/// //
|
||||
/// // We don't have a remove hook, so we can leave it out:
|
||||
/// // #[component(on_remove = my_on_remove_hook)]
|
||||
/// struct ComponentA;
|
||||
///
|
||||
/// fn my_on_add_hook(world: DeferredWorld, entity: Entity, id: ComponentId) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// // You can also omit writing some types using generics.
|
||||
/// fn my_on_insert_hook<T1, T2>(world: DeferredWorld, _: T1, _: T2) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// # Implementing the trait for foreign types
|
||||
///
|
||||
/// As a consequence of the [orphan rule], it is not possible to separate into two different crates the implementation of `Component` from the definition of a type.
|
||||
|
@ -199,7 +235,11 @@ pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, Entity, ComponentId);
|
|||
///
|
||||
/// This information is stored in the [`ComponentInfo`] of the associated component.
|
||||
///
|
||||
/// # Example
|
||||
/// There is two ways of configuring hooks for a component:
|
||||
/// 1. Defining the [`Component::register_component_hooks`] method (see [`Component`])
|
||||
/// 2. Using the [`World::register_component_hooks`] method
|
||||
///
|
||||
/// # Example 2
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
|
|
|
@ -18,6 +18,12 @@ use bevy::prelude::*;
|
|||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Hooks can also be registered during component initialisation by
|
||||
/// using [`Component`] derive macro:
|
||||
/// ```no_run
|
||||
/// #[derive(Component)]
|
||||
/// #[component(on_add = ..., on_insert = ..., on_remove = ...)]
|
||||
/// ```
|
||||
struct MyComponent(KeyCode);
|
||||
|
||||
impl Component for MyComponent {
|
||||
|
|
Loading…
Reference in a new issue