bevy/crates
RobWalt a85a3a2a15
allow Call and Closure expressions in hook macro attributes (#18017)
# Objective

This PR adds:

- function call hook attributes `#[component(on_add = func(42))]`
  - main feature of this commit
- closure hook attributes `#[component(on_add = |w, ctx| { /* ... */
})]`
  - maybe too verbose
  - but was easy to add
  - was suggested on discord

This allows to reuse common functionality without replicating a lot of
boilerplate. A small example is a hook which just adds different default
sprites. The sprite loading code would be the same for every component.
Unfortunately we can't use the required components feature, since we
need at least an `AssetServer` or other `Resource`s or `Component`s to
load the sprite.

```rs
fn load_sprite(path: &str) -> impl Fn(DeferredWorld, HookContext) {
  |mut world, ctx| {
    // ... use world to load sprite
  }
}

#[derive(Component)]
#[component(on_add = load_sprite("knight.png"))]
struct Knight;

#[derive(Component)]
#[component(on_add = load_sprite("monster.png"))]
struct Monster;
```

---

The commit also reorders the logic of the derive macro a bit. It's
probably a bit less lazy now, but the functionality shouldn't be
performance critical and is executed at compile time anyways.

## Solution

- Introduce `HookKind` enum in the component proc macro module
- extend parsing to allow more cases of expressions

## Testing

I have some code laying around. I'm not sure where to put it yet though.
Also is there a way to check compilation failures? Anyways, here it is:

```rs
use bevy::prelude::*;

#[derive(Component)]
#[component(
    on_add = fooing_and_baring,
    on_insert = fooing_and_baring,
    on_replace = fooing_and_baring,
    on_despawn = fooing_and_baring,
    on_remove = fooing_and_baring
)]
pub struct FooPath;

fn fooing_and_baring(
    world: bevy::ecs::world::DeferredWorld,
    ctx: bevy::ecs::component::HookContext,
) {
}

#[derive(Component)]
#[component(
    on_add = baring_and_bazzing("foo"),
    on_insert = baring_and_bazzing("foo"),
    on_replace = baring_and_bazzing("foo"),
    on_despawn = baring_and_bazzing("foo"),
    on_remove = baring_and_bazzing("foo")
)]
pub struct FooCall;

fn baring_and_bazzing(
    path: &str,
) -> impl Fn(bevy::ecs::world::DeferredWorld, bevy::ecs::component::HookContext) {
    |world, ctx| {}
}

#[derive(Component)]
#[component(
    on_add = |w,ctx| {},
    on_insert = |w,ctx| {},
    on_replace = |w,ctx| {},
    on_despawn = |w,ctx| {},
    on_remove = |w,ctx| {}
)]
pub struct FooClosure;

#[derive(Component, Debug)]
#[relationship(relationship_target = FooTargets)]
#[component(
    on_add = baring_and_bazzing("foo"),
    // on_insert = baring_and_bazzing("foo"),
    // on_replace = baring_and_bazzing("foo"),
    on_despawn = baring_and_bazzing("foo"),
    on_remove = baring_and_bazzing("foo")
)]
pub struct FooTargetOf(Entity);

#[derive(Component, Debug)]
#[relationship_target(relationship = FooTargetOf)]
#[component(
    on_add = |w,ctx| {},
    on_insert = |w,ctx| {},
    // on_replace = |w,ctx| {},
    // on_despawn = |w,ctx| {},
    on_remove = |w,ctx| {}
)]
pub struct FooTargets(Vec<Entity>);

// MSG:  mismatched types  expected fn pointer `for<'w> fn(bevy::bevy_ecs::world::DeferredWorld<'w>, bevy::bevy_ecs::component::HookContext)`    found struct `Bar`
//
// pub struct Bar;
// #[derive(Component)]
// #[component(
//     on_add = Bar,
// )]
// pub struct FooWrongPath;

// MSG: this function takes 1 argument but 2 arguements were supplied
//
// #[derive(Component)]
// #[component(
//     on_add = wrong_bazzing("foo"),
// )]
// pub struct FooWrongCall;
//
// fn wrong_bazzing(path: &str) -> impl Fn(bevy::ecs::world::DeferredWorld) {
//     |world| {}
// }

// MSG: expected 1 argument, found 2
//
// #[derive(Component)]
// #[component(
//     on_add = |w| {},
// )]
// pub struct FooWrongCall;
```

---

## Showcase

I'll try to continue to work on this to have a small section in the
release notes.
2025-03-06 16:39:11 +00:00
..
bevy_a11y Automatically enable portable-atomic when required (#17570) 2025-02-24 20:52:46 +00:00
bevy_animation Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_app Automatically enable portable-atomic when required (#17570) 2025-02-24 20:52:46 +00:00
bevy_asset Make asset watcher work when path contains "../" (#18023) 2025-03-02 18:15:27 +00:00
bevy_audio Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_color Allow bevy_reflect and wgpu-types features in no_std for bevy_color (#18061) 2025-03-01 00:31:35 +00:00
bevy_core_pipeline Fix Component require() IDE integration (#18165) 2025-03-06 02:44:47 +00:00
bevy_derive allow Call and Closure expressions in hook macro attributes (#18017) 2025-03-06 16:39:11 +00:00
bevy_dev_tools Make Query::single (and friends) return a Result (#18082) 2025-03-02 19:51:56 +00:00
bevy_diagnostic Automatically enable portable-atomic when required (#17570) 2025-02-24 20:52:46 +00:00
bevy_dylib Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_ecs allow Call and Closure expressions in hook macro attributes (#18017) 2025-03-06 16:39:11 +00:00
bevy_encase_derive Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_gilrs Replace some !Send resources with thread_local! (#17730) 2025-03-04 07:48:02 +00:00
bevy_gizmos Fix Component require() IDE integration (#18165) 2025-03-06 02:44:47 +00:00
bevy_gltf Update itertools requirement from 0.13 to 0.14 (#18128) 2025-03-03 19:36:29 +00:00
bevy_image Update ruzstd requirement from 0.7.0 to 0.8.0 (#18145) 2025-03-03 21:44:51 +00:00
bevy_input Fix Component require() IDE integration (#18165) 2025-03-06 02:44:47 +00:00
bevy_input_focus Make Query::single (and friends) return a Result (#18082) 2025-03-02 19:51:56 +00:00
bevy_internal Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_log Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_macro_utils Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_math Update itertools requirement from 0.13 to 0.14 (#18128) 2025-03-03 19:36:29 +00:00
bevy_mesh Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_mikktspace Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_pbr Replace internal uses of insert_or_spawn_batch (#18035) 2025-03-06 16:16:36 +00:00
bevy_picking Make Query::single (and friends) return a Result (#18082) 2025-03-02 19:51:56 +00:00
bevy_platform_support Automatically enable portable-atomic when required (#17570) 2025-02-24 20:52:46 +00:00
bevy_ptr moved Debug from derive to impl_ptr in bevy_ptr (#18042) 2025-02-28 02:54:46 +00:00
bevy_reflect Improve bevy_reflect no_std support (#18060) 2025-02-27 06:16:10 +00:00
bevy_remote BRP resource methods (#17423) 2025-02-26 20:29:47 +00:00
bevy_render Replace internal uses of insert_or_spawn_batch (#18035) 2025-03-06 16:16:36 +00:00
bevy_scene Fix Component require() IDE integration (#18165) 2025-03-06 02:44:47 +00:00
bevy_sprite Replace internal uses of insert_or_spawn_batch (#18035) 2025-03-06 16:16:36 +00:00
bevy_state Automatically enable portable-atomic when required (#17570) 2025-02-24 20:52:46 +00:00
bevy_tasks TaskPool: Prefer task completion over executing new tasks (#18009) 2025-02-26 00:08:36 +00:00
bevy_text Fix Component require() IDE integration (#18165) 2025-03-06 02:44:47 +00:00
bevy_time Automatically enable portable-atomic when required (#17570) 2025-02-24 20:52:46 +00:00
bevy_transform Fix Component require() IDE integration (#18165) 2025-03-06 02:44:47 +00:00
bevy_ui Replace internal uses of insert_or_spawn_batch (#18035) 2025-03-06 16:16:36 +00:00
bevy_utils Automatically enable portable-atomic when required (#17570) 2025-02-24 20:52:46 +00:00
bevy_window Upgrade to Rust Edition 2024 (#17967) 2025-02-24 03:54:47 +00:00
bevy_winit Replace some !Send resources with thread_local! (#17730) 2025-03-04 07:48:02 +00:00