Skip to content

Commit e5508b4

Browse files
VasanthakumarVcart
andauthored
Improve Component Hook Ergonomics (#21800)
# Objective Fixes: #21794 ## Solution - If a hook-path is not provided by the user we insert the default path. ## Testing Added a doctest (Edit). Ran and looked at the expanded macro of the following example, ```rust use bevy_ecs::lifecycle::HookContext; use bevy_ecs::prelude::*; use bevy_ecs::world::DeferredWorld; #[derive(Component)] #[component(on_add, on_insert, on_despawn, on_remove=on_remove)] struct Tile; impl Tile { fn on_add(_world: DeferredWorld, _context: HookContext) { println!("added"); } fn on_insert(_world: DeferredWorld, _context: HookContext) { println!("inserted"); } fn on_despawn(_world: DeferredWorld, _context: HookContext) { println!("despawned"); } } fn on_remove(_world: DeferredWorld, _context: HookContext) { println!("removed"); } fn main() { let mut world = World::new(); let entity = world.spawn(Tile); entity.despawn(); } ``` --------- Co-authored-by: Carter Anderson <[email protected]>
1 parent 8d93f10 commit e5508b4

File tree

3 files changed

+51
-11
lines changed

3 files changed

+51
-11
lines changed

crates/bevy_ecs/macros/src/component.rs

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,18 @@ enum HookAttributeKind {
407407
}
408408

409409
impl HookAttributeKind {
410+
fn parse(
411+
input: syn::parse::ParseStream,
412+
default_hook_path: impl FnOnce() -> ExprPath,
413+
) -> Result<Self> {
414+
if input.peek(Token![=]) {
415+
input.parse::<Token![=]>()?;
416+
input.parse::<Expr>().and_then(Self::from_expr)
417+
} else {
418+
Ok(Self::Path(default_hook_path()))
419+
}
420+
}
421+
410422
fn from_expr(value: Expr) -> Result<Self> {
411423
match value {
412424
Expr::Path(path) => Ok(HookAttributeKind::Path(path)),
@@ -439,12 +451,6 @@ impl HookAttributeKind {
439451
}
440452
}
441453

442-
impl Parse for HookAttributeKind {
443-
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
444-
input.parse::<Expr>().and_then(Self::from_expr)
445-
}
446-
}
447-
448454
#[derive(Debug)]
449455
pub(super) enum MapEntitiesAttributeKind {
450456
/// expressions like function or struct names
@@ -566,19 +572,29 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
566572
};
567573
Ok(())
568574
} else if nested.path.is_ident(ON_ADD) {
569-
attrs.on_add = Some(nested.value()?.parse::<HookAttributeKind>()?);
575+
attrs.on_add = Some(HookAttributeKind::parse(nested.input, || {
576+
parse_quote! { Self::on_add }
577+
})?);
570578
Ok(())
571579
} else if nested.path.is_ident(ON_INSERT) {
572-
attrs.on_insert = Some(nested.value()?.parse::<HookAttributeKind>()?);
580+
attrs.on_insert = Some(HookAttributeKind::parse(nested.input, || {
581+
parse_quote! { Self::on_insert }
582+
})?);
573583
Ok(())
574584
} else if nested.path.is_ident(ON_REPLACE) {
575-
attrs.on_replace = Some(nested.value()?.parse::<HookAttributeKind>()?);
585+
attrs.on_replace = Some(HookAttributeKind::parse(nested.input, || {
586+
parse_quote! { Self::on_replace }
587+
})?);
576588
Ok(())
577589
} else if nested.path.is_ident(ON_REMOVE) {
578-
attrs.on_remove = Some(nested.value()?.parse::<HookAttributeKind>()?);
590+
attrs.on_remove = Some(HookAttributeKind::parse(nested.input, || {
591+
parse_quote! { Self::on_remove }
592+
})?);
579593
Ok(())
580594
} else if nested.path.is_ident(ON_DESPAWN) {
581-
attrs.on_despawn = Some(nested.value()?.parse::<HookAttributeKind>()?);
595+
attrs.on_despawn = Some(HookAttributeKind::parse(nested.input, || {
596+
parse_quote! { Self::on_despawn }
597+
})?);
582598
Ok(())
583599
} else if nested.path.is_ident(IMMUTABLE) {
584600
attrs.immutable = true;

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
646646
/// `function` can be either a path, e.g. `some_function::<Self>`,
647647
/// or a function call that returns a function that can be turned into
648648
/// a `ComponentHook`, e.g. `get_closure("Hi!")`.
649+
/// `function` can be elided if the path is `Self::on_add`, `Self::on_insert` etc.
649650
///
650651
/// ## Ignore this component when cloning an entity
651652
/// ```ignore

crates/bevy_ecs/src/component/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,29 @@ use core::{fmt::Debug, marker::PhantomData, ops::Deref};
405405
/// }
406406
///
407407
/// ```
408+
///
409+
/// A hook's function path can be elided if it is `Self::on_add`, `Self::on_insert` etc.
410+
/// ```
411+
/// # use bevy_ecs::lifecycle::HookContext;
412+
/// # use bevy_ecs::prelude::*;
413+
/// # use bevy_ecs::world::DeferredWorld;
414+
/// #
415+
/// #[derive(Component, Debug)]
416+
/// #[component(on_add)]
417+
/// struct DoubleOnSpawn(usize);
418+
///
419+
/// impl DoubleOnSpawn {
420+
/// fn on_add(mut world: DeferredWorld, context: HookContext) {
421+
/// let mut entity = world.get_mut::<Self>(context.entity).unwrap();
422+
/// entity.0 *= 2;
423+
/// }
424+
/// }
425+
/// #
426+
/// # let mut world = World::new();
427+
/// # let entity = world.spawn(DoubleOnSpawn(2));
428+
/// # assert_eq!(entity.get::<DoubleOnSpawn>().unwrap().0, 4);
429+
/// ```
430+
///
408431
/// # Setting the clone behavior
409432
///
410433
/// You can specify how the [`Component`] is cloned when deriving it.

0 commit comments

Comments
 (0)