diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index a0ac81646d..0f72efee24 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -69,8 +69,8 @@ impl Component for OrderIndependentTransparencySettings { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_add(|world, context| { + fn on_add() -> Option { + Some(|world, context| { if let Some(value) = world.get::(context.entity) { if value.layer_count > 32 { warn!("{}OrderIndependentTransparencySettings layer_count set to {} might be too high.", @@ -79,7 +79,7 @@ impl Component for OrderIndependentTransparencySettings { ); } } - }); + }) } } diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index ba695bef49..826a72c8ec 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -71,15 +71,11 @@ 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 mut on_insert = hook_register_function_call(quote! {on_insert}, attrs.on_insert); - let mut on_replace = hook_register_function_call(quote! {on_replace}, attrs.on_replace); - let on_remove: Option = - hook_register_function_call(quote! {on_remove}, attrs.on_remove); - let mut on_despawn = hook_register_function_call(quote! {on_despawn}, attrs.on_despawn); + let on_add_path = attrs.on_add.map(|path| path.to_token_stream()); + let on_remove_path = attrs.on_remove.map(|path| path.to_token_stream()); - if relationship.is_some() { - if on_insert.is_some() { + let on_insert_path = if relationship.is_some() { + if attrs.on_insert.is_some() { return syn::Error::new( ast.span(), "Custom on_insert hooks are not supported as relationships already define an on_insert hook", @@ -88,11 +84,13 @@ pub fn derive_component(input: TokenStream) -> TokenStream { .into(); } - on_insert = Some( - quote!(hooks.on_insert(::on_insert);), - ); + Some(quote!(::on_insert)) + } else { + attrs.on_insert.map(|path| path.to_token_stream()) + }; - if on_replace.is_some() { + let on_replace_path = if relationship.is_some() { + if attrs.on_replace.is_some() { return syn::Error::new( ast.span(), "Custom on_replace hooks are not supported as Relationships already define an on_replace hook", @@ -101,13 +99,9 @@ pub fn derive_component(input: TokenStream) -> TokenStream { .into(); } - on_replace = Some( - quote!(hooks.on_replace(::on_replace);), - ); - } - - if let Some(relationship_target) = &attrs.relationship_target { - if on_replace.is_some() { + Some(quote!(::on_replace)) + } else if attrs.relationship_target.is_some() { + if attrs.on_replace.is_some() { return syn::Error::new( ast.span(), "Custom on_replace hooks are not supported as RelationshipTarget already defines an on_replace hook", @@ -116,25 +110,36 @@ pub fn derive_component(input: TokenStream) -> TokenStream { .into(); } - on_replace = Some( - quote!(hooks.on_replace(::on_replace);), - ); + Some(quote!(::on_replace)) + } else { + attrs.on_replace.map(|path| path.to_token_stream()) + }; - if relationship_target.despawn_descendants { - if on_despawn.is_some() { - return syn::Error::new( - ast.span(), - "Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the despawn_descendants attribute", - ) - .into_compile_error() - .into(); - } - - on_despawn = Some( - quote!(hooks.on_despawn(::on_despawn);), - ); + let on_despawn_path = if attrs + .relationship_target + .is_some_and(|target| target.despawn_descendants) + { + if attrs.on_despawn.is_some() { + return syn::Error::new( + ast.span(), + "Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the despawn_descendants attribute", + ) + .into_compile_error() + .into(); } - } + + Some(quote!(::on_despawn)) + } else { + attrs.on_despawn.map(|path| path.to_token_stream()) + }; + + let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path); + let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path); + let on_replace = + hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path); + let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path); + let on_despawn = + hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path); ast.generics .make_where_clause() @@ -232,14 +237,11 @@ pub fn derive_component(input: TokenStream) -> TokenStream { recursion_check_stack.pop(); } - #[allow(unused_variables)] - fn register_component_hooks(hooks: &mut #bevy_ecs_path::component::ComponentHooks) { - #on_add - #on_insert - #on_replace - #on_remove - #on_despawn - } + #on_add + #on_insert + #on_replace + #on_remove + #on_despawn fn get_component_clone_handler() -> #bevy_ecs_path::component::ComponentCloneHandler { #clone_handler @@ -444,10 +446,17 @@ fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 { } fn hook_register_function_call( + bevy_ecs_path: &Path, hook: TokenStream2, - function: Option, + function: Option, ) -> Option { - function.map(|meta| quote! { hooks. #hook (#meta); }) + function.map(|meta| { + quote! { + fn #hook() -> ::core::option::Option<#bevy_ecs_path::component::ComponentHook> { + ::core::option::Option::Some(#meta) + } + } + }) } impl Parse for Relationship { diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 190910d7d0..dfcb20bf3e 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -401,7 +401,38 @@ pub trait Component: Send + Sync + 'static { type Mutability: ComponentMutability; /// Called when registering this component, allowing mutable access to its [`ComponentHooks`]. - fn register_component_hooks(_hooks: &mut ComponentHooks) {} + #[deprecated( + since = "0.16.0", + note = "Use the individual hook methods instead (e.g., `Component::on_add`, etc.)" + )] + fn register_component_hooks(hooks: &mut ComponentHooks) { + hooks.update_from_component::(); + } + + /// Gets the `on_add` [`ComponentHook`] for this [`Component`] if one is defined. + fn on_add() -> Option { + None + } + + /// Gets the `on_insert` [`ComponentHook`] for this [`Component`] if one is defined. + fn on_insert() -> Option { + None + } + + /// Gets the `on_replace` [`ComponentHook`] for this [`Component`] if one is defined. + fn on_replace() -> Option { + None + } + + /// Gets the `on_remove` [`ComponentHook`] for this [`Component`] if one is defined. + fn on_remove() -> Option { + None + } + + /// Gets the `on_despawn` [`ComponentHook`] for this [`Component`] if one is defined. + fn on_despawn() -> Option { + None + } /// Registers required components. fn register_required_components( @@ -575,6 +606,26 @@ pub struct ComponentHooks { } impl ComponentHooks { + pub(crate) fn update_from_component(&mut self) -> &mut Self { + if let Some(hook) = C::on_add() { + self.on_add(hook); + } + if let Some(hook) = C::on_insert() { + self.on_insert(hook); + } + if let Some(hook) = C::on_replace() { + self.on_replace(hook); + } + if let Some(hook) = C::on_remove() { + self.on_remove(hook); + } + if let Some(hook) = C::on_despawn() { + self.on_despawn(hook); + } + + self + } + /// Register a [`ComponentHook`] that will be run when this component is added to an entity. /// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as /// adding all of its components. @@ -1181,7 +1232,14 @@ impl Components { recursion_check_stack, ); let info = &mut self.components[id.index()]; + + #[expect( + deprecated, + reason = "need to use this method until it is removed to ensure user defined components register hooks correctly" + )] + // TODO: Replace with `info.hooks.update_from_component::();` once `Component::register_component_hooks` is removed T::register_component_hooks(&mut info.hooks); + info.required_components = required_components; let clone_handler = T::get_component_clone_handler(); self.component_clone_handlers diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 1ad30890e4..fe7a9fb3c9 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,6 +1,6 @@ use crate::{ component::{ - Component, ComponentCloneHandler, ComponentHooks, HookContext, Mutable, StorageType, + Component, ComponentCloneHandler, ComponentHook, HookContext, Mutable, StorageType, }, entity::{ComponentCloneCtx, Entity, EntityCloneBuilder}, observer::ObserverState, @@ -16,8 +16,8 @@ impl Component for ObservedBy { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_remove(|mut world, HookContext { entity, .. }| { + fn on_remove() -> Option { + Some(|mut world, HookContext { entity, .. }| { let observed_by = { let mut component = world.get_mut::(entity).unwrap(); core::mem::take(&mut component.0) @@ -42,7 +42,7 @@ impl Component for ObservedBy { world.commands().entity(e).despawn(); } } - }); + }) } fn get_component_clone_handler() -> ComponentCloneHandler { diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 11ff5eabbc..7dab9e28f9 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -2,7 +2,7 @@ use alloc::{boxed::Box, vec, vec::Vec}; use core::any::Any; use crate::{ - component::{ComponentHook, ComponentHooks, ComponentId, HookContext, Mutable, StorageType}, + component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType}, observer::{ObserverDescriptor, ObserverTrigger}, prelude::*, query::DebugCheckedUnwrap, @@ -65,13 +65,16 @@ impl Component for ObserverState { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_add(|mut world, HookContext { entity, .. }| { + fn on_add() -> Option { + Some(|mut world, HookContext { entity, .. }| { world.commands().queue(move |world: &mut World| { world.register_observer(entity); }); - }); - hooks.on_remove(|mut world, HookContext { entity, .. }| { + }) + } + + fn on_remove() -> Option { + Some(|mut world, HookContext { entity, .. }| { let descriptor = core::mem::take( &mut world .entity_mut(entity) @@ -83,7 +86,7 @@ impl Component for ObserverState { world.commands().queue(move |world: &mut World| { world.unregister_observer(entity, descriptor); }); - }); + }) } } @@ -322,14 +325,14 @@ impl Observer { impl Component for Observer { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; - fn register_component_hooks(hooks: &mut ComponentHooks) { - hooks.on_add(|world, context| { + fn on_add() -> Option { + Some(|world, context| { let Some(observe) = world.get::(context.entity) else { return; }; let hook = observe.hook_on_add; hook(world, context); - }); + }) } } @@ -389,7 +392,7 @@ fn observer_system_runner>( } } -/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`ComponentHooks::on_add`). +/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::component::ComponentHooks::on_add`). /// /// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters /// erased. diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index aeb2a1f284..836779c4e6 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -14,7 +14,7 @@ //! between components (like hierarchies or parent-child links) and need to maintain correctness. use bevy::{ - ecs::component::{ComponentHooks, HookContext, Mutable, StorageType}, + ecs::component::{ComponentHook, HookContext, Mutable, StorageType}, prelude::*, }; use std::collections::HashMap; @@ -33,9 +33,11 @@ impl Component for MyComponent { type Mutability = Mutable; /// Hooks can also be registered during component initialization by - /// implementing `register_component_hooks` - fn register_component_hooks(_hooks: &mut ComponentHooks) { - // Register hooks... + /// implementing the associated method + fn on_add() -> Option { + // We don't have an `on_add` hook so we'll just return None. + // Note that this is the default behavior when not implementing a hook. + None } }