Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow Call and Closure expressions in hook macro attributes #18017

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

RobWalt
Copy link
Contributor

@RobWalt RobWalt commented Feb 24, 2025

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 Resources or Components to load the sprite.

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:

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.

Copy link
Contributor

@Bleachfuel Bleachfuel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just reviewed macro code!

@RobWalt
Copy link
Contributor Author

RobWalt commented Feb 25, 2025

@Bleachfuel Thanks a lot for the review, I addressed your comments :)

@RobWalt RobWalt marked this pull request as ready for review February 25, 2025 11:23
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use M-Needs-Release-Note Work that should be called out in the blog due to impact labels Feb 25, 2025
@alice-i-cecile alice-i-cecile requested a review from cart February 25, 2025 23:13
@alice-i-cecile alice-i-cecile added X-Uncontroversial This work is generally agreed upon S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Feb 25, 2025
.into_compile_error()
.into();
ast.span(),
"Custom on_insert hooks are not supported as relationships already define an on_insert hook",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future work: we can probably automatically concatenate these function calls 🤔

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs doc tests, both to ensure that this works and teach users that it does. Code looks great, and the feature is 🔥 though.

For things that should fail to compile, there's a bevy_ecs_compile_fail crate that you can add stuff to :)

@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Feb 25, 2025
@alice-i-cecile
Copy link
Member

@cart, this steps on the toes of your Construct plans a bit, so I wanted to pull in your review to help you form a broader picture. Regardless of what you decide to do there, I think that this is a really nice intuitive addition to hooks.

@alice-i-cecile alice-i-cecile added this to the 0.16 milestone Feb 25, 2025
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! I have a small wording suggestion but those doc tests should be sufficient to validate that this keeps working.

I think you could probably be a bit more clear in your compile fail tests about why the code shouldn't compile but I won't block on it.

@RobWalt
Copy link
Contributor Author

RobWalt commented Feb 26, 2025

@alice-i-cecile note that the compile tests weren't evaluated nor updated by me so please don't merge yet. I don't have rustup installed and this kind of blocks me here. I'm looking into it though.

@RobWalt
Copy link
Contributor Author

RobWalt commented Feb 26, 2025

The compile tests are fixed and I was able to generate the new stderr files

@alice-i-cecile alice-i-cecile added S-Needs-Review Needs reviewer attention (from anyone!) to move forward S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Feb 27, 2025
@alice-i-cecile alice-i-cecile removed this from the 0.16 milestone Feb 27, 2025
@alice-i-cecile
Copy link
Member

alice-i-cecile commented Feb 27, 2025

@RobWalt I've removed the 0.16 milestone from this since I don't think it's pressing enough to block the release, but this work is free to proceed. Do bother folks for more reviews once you've removed the in-line function syntax.

Copy link
Contributor

Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke!
You can review it at https://pixel-eagle.com/project/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D?filter=PR-18017

If it's expected, please add the S-Deliberate-Rendering-Change label.

github-merge-queue bot pushed a commit that referenced this pull request Mar 1, 2025
I noticed this while working on #18017 . Some of the `stderr`
compile_fail tests were updated while I generated the output for the new
tests I introduced in the mentioned PR.

I'm on rust 1.85.0
This commit adds:

- function call hook attributes
  - main feature of this commit

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;
```
@RobWalt RobWalt force-pushed the more-permissive-hook-attributes branch from 55a0c4f to 79df196 Compare March 1, 2025 09:16
@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Mar 4, 2025
@alice-i-cecile
Copy link
Member

@RobWalt I think the recent macro cleanup stuff broke the imports here (or maybe my merge conflict resolution was bad?). Either way, ping me when this is green and I'll press the button :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use M-Needs-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Uncontroversial This work is generally agreed upon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants