Skip to content

Commit a536222

Browse files
Allow all hooks on relationship components (#24000)
# Objective Fixes #23990 ## Solution If both the user and the relationship define a hook, call both. ## Testing Added a test checking hooks don't interfere with relationships. --- ## Showcase ```rust #[derive(Component)] #[relationship(relationship_target = LikedBy)] #[component(on_add = on_add)] struct Likes(Entity); ```
1 parent 480ad98 commit a536222

4 files changed

Lines changed: 89 additions & 269 deletions

File tree

crates/bevy_ecs/compile_fail/tests/ui/component_hook_relationship.rs

Lines changed: 0 additions & 63 deletions
This file was deleted.

crates/bevy_ecs/compile_fail/tests/ui/component_hook_relationship.stderr

Lines changed: 0 additions & 111 deletions
This file was deleted.

crates/bevy_ecs/macros/src/component.rs

Lines changed: 61 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,6 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
4949

5050
let storage = storage_path(&bevy_ecs_path, StorageTy::Table);
5151

52-
let on_add_path = None;
53-
let on_remove_path = None;
54-
let on_insert_path = None;
55-
let on_replace_path = None;
56-
let on_despawn_path = None;
57-
58-
let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);
59-
let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);
60-
let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);
61-
let on_replace =
62-
hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path);
63-
let on_despawn =
64-
hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);
65-
6652
ast.generics
6753
.make_where_clause()
6854
.predicates
@@ -95,12 +81,6 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
9581
#(#register_required)*
9682
}
9783

98-
#on_add
99-
#on_insert
100-
#on_replace
101-
#on_remove
102-
#on_despawn
103-
10484
fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior {
10585
#bevy_ecs_path::component::ComponentCloneBehavior::Default
10686
}
@@ -160,85 +140,60 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
160140

161141
let storage = storage_path(&bevy_ecs_path, attrs.storage);
162142

163-
let on_add_path = attrs
164-
.on_add
165-
.map(|path| path.to_token_stream(&bevy_ecs_path));
166-
let on_remove_path = attrs
167-
.on_remove
168-
.map(|path| path.to_token_stream(&bevy_ecs_path));
169-
170-
let on_insert_path = if relationship.is_some() {
171-
if attrs.on_insert.is_some() {
172-
return syn::Error::new(
173-
ast.span(),
174-
"Custom on_insert hooks are not supported as relationships already define an on_insert hook",
175-
)
176-
.into_compile_error()
177-
.into();
178-
}
143+
let on_add_path = Vec::from_iter(
144+
attrs
145+
.on_add
146+
.map(|path| path.to_token_stream(&bevy_ecs_path)),
147+
);
148+
let on_remove_path = Vec::from_iter(
149+
attrs
150+
.on_remove
151+
.map(|path| path.to_token_stream(&bevy_ecs_path)),
152+
);
179153

180-
Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_insert))
181-
} else {
154+
let mut on_insert_path = Vec::from_iter(
182155
attrs
183156
.on_insert
184-
.map(|path| path.to_token_stream(&bevy_ecs_path))
185-
};
186-
187-
let on_discard_path = if relationship.is_some() {
188-
if attrs.on_discard.is_some() {
189-
return syn::Error::new(
190-
ast.span(),
191-
"Custom on_discard hooks are not supported as Relationships already define an on_discard hook",
192-
)
193-
.into_compile_error()
194-
.into();
195-
}
157+
.map(|path| path.to_token_stream(&bevy_ecs_path)),
158+
);
196159

197-
Some(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_discard))
198-
} else if attrs.relationship_target.is_some() {
199-
if attrs.on_discard.is_some() {
200-
return syn::Error::new(
201-
ast.span(),
202-
"Custom on_discard hooks are not supported as RelationshipTarget already defines an on_discard hook",
203-
)
204-
.into_compile_error()
205-
.into();
206-
}
207-
208-
Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_discard))
209-
} else {
160+
let mut on_discard_path = Vec::from_iter(
210161
attrs
211162
.on_discard
212-
.map(|path| path.to_token_stream(&bevy_ecs_path))
213-
};
163+
.map(|path| path.to_token_stream(&bevy_ecs_path)),
164+
);
214165

215-
let on_despawn_path = if attrs
216-
.relationship_target
217-
.is_some_and(|target| target.linked_spawn)
218-
{
219-
if attrs.on_despawn.is_some() {
220-
return syn::Error::new(
221-
ast.span(),
222-
"Custom on_despawn hooks are not supported as this RelationshipTarget already defines an on_despawn hook, via the 'linked_spawn' attribute",
223-
)
224-
.into_compile_error()
225-
.into();
226-
}
227-
228-
Some(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_despawn))
229-
} else {
166+
let mut on_despawn_path = Vec::from_iter(
230167
attrs
231168
.on_despawn
232-
.map(|path| path.to_token_stream(&bevy_ecs_path))
233-
};
169+
.map(|path| path.to_token_stream(&bevy_ecs_path)),
170+
);
171+
172+
if relationship.is_some() {
173+
on_insert_path
174+
.push(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_insert));
175+
on_discard_path
176+
.push(quote!(<Self as #bevy_ecs_path::relationship::Relationship>::on_discard));
177+
}
178+
if let Some(target) = attrs.relationship_target {
179+
on_discard_path
180+
.push(quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_discard));
181+
if target.linked_spawn {
182+
on_despawn_path.push(
183+
quote!(<Self as #bevy_ecs_path::relationship::RelationshipTarget>::on_despawn),
184+
);
185+
}
186+
}
234187

235-
let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path);
236-
let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path);
188+
let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, &on_add_path);
189+
let on_insert =
190+
hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, &on_insert_path);
237191
let on_discard =
238-
hook_register_function_call(&bevy_ecs_path, quote! {on_discard}, on_discard_path);
239-
let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path);
192+
hook_register_function_call(&bevy_ecs_path, quote! {on_discard}, &on_discard_path);
193+
let on_remove =
194+
hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, &on_remove_path);
240195
let on_despawn =
241-
hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path);
196+
hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, &on_despawn_path);
242197

243198
ast.generics
244199
.make_where_clause()
@@ -800,15 +755,27 @@ fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
800755
fn hook_register_function_call(
801756
bevy_ecs_path: &Path,
802757
hook: TokenStream2,
803-
function: Option<TokenStream2>,
804-
) -> Option<TokenStream2> {
805-
function.map(|meta| {
806-
quote! {
807-
fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {
808-
::core::option::Option::Some(#meta)
758+
functions: &[TokenStream2],
759+
) -> TokenStream2 {
760+
let hook_function = match functions {
761+
[] => return TokenStream2::new(),
762+
[single] => single.clone(),
763+
multiple => {
764+
quote! {
765+
{
766+
fn hook(world: #bevy_ecs_path::world::DeferredWorld, context: #bevy_ecs_path::lifecycle::HookContext) {
767+
quote! {#(#multiple(world.reborrow(), context.clone());)*}
768+
}
769+
hook
770+
}
809771
}
810772
}
811-
})
773+
};
774+
quote! {
775+
fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {
776+
::core::option::Option::Some(#hook_function)
777+
}
778+
}
812779
}
813780

814781
mod kw {

0 commit comments

Comments
 (0)