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

Added App::with_component_hooks #16977

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

Conversation

pin3-free
Copy link
Contributor

Objective

  • Fixes make register_component_hooks method for App #16921
  • Right now, to add component hooks, you either have to implement them in the Component trait implementation, or use app.world_mut().register_component_hooks(), which is somewhat clunky. This PR aims to introduce a more concise way to add component hooks directly from the app interface

Solution

  • I could not find a good way to implement the interface proposed in the solution directly, since the user would need to not only pass the hook itself, but also specify which hook method (on_add, on_insert, etc.) they want to add the hook to. Plus there's the issue of fallible hook add methods.
  • The proposed solution is similar to methods such as with_children on the EntityCommands, passing a closure that will mutate the target (in this case -- &mut ComponentHooks) and return the parent, allowing it to chain nicely into the multitude of calls usually associated with App.

Testing

  • I tested this by creating a new example called with_component_hooks.rs in the ecs section of examples. It works identically to the component_hooks example, but uses the new API

Showcase

Here's a snipped from the component_hooks.rs example and the comparison to the with_component_hooks.rs example

While a showcase should aim to be brief and digestible, you can use a toggleable section to save space on longer showcases:

// === component_hooks.rs ===

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, trigger_hooks)
        .init_resource::<MyComponentIndex>()
        .add_event::<MyEvent>()
        .run();
}

fn setup(world: &mut World) {
    world
        .register_component_hooks::<MyComponent>()
        .on_add(|mut world, entity, component_id| {
            let value = world.get::<MyComponent>(entity).unwrap().0;
            println!("Component: {component_id:?} added to: {entity:?} with value {value:?}");
            world
                .resource_mut::<MyComponentIndex>()
                .insert(value, entity);
            world.send_event(MyEvent);
        })
        .on_insert(|world, _, _| {
            println!("Current Index: {:?}", world.resource::<MyComponentIndex>());
        })
        .on_replace(|mut world, entity, _| {
            let value = world.get::<MyComponent>(entity).unwrap().0;
            world.resource_mut::<MyComponentIndex>().remove(&value);
        })
        .on_remove(|mut world, entity, component_id| {
            let value = world.get::<MyComponent>(entity).unwrap().0;
            println!("Component: {component_id:?} removed from: {entity:?} with value {value:?}");
            world.commands().entity(entity).despawn();
        });
}
// === with_component_hooks.rs ===

// The setup function is no longer needed in this instance
fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Update, trigger_hooks)
        .with_component_hooks::<MyComponent>(|hooks| {
            hooks
                .on_add(|mut world, entity, component_id| {
                    let value = world.get::<MyComponent>(entity).unwrap().0;
                    println!(
                        "Component: {component_id:?} added to: {entity:?} with value {value:?}"
                    );
                    world
                        .resource_mut::<MyComponentIndex>()
                        .insert(value, entity);
                    world.send_event(MyEvent);
                })
                .on_insert(|world, _, _| {
                    println!("Current Index: {:?}", world.resource::<MyComponentIndex>());
                })
                .on_replace(|mut world, entity, _| {
                    let value = world.get::<MyComponent>(entity).unwrap().0;
                    world.resource_mut::<MyComponentIndex>().remove(&value);
                })
                .on_remove(|mut world, entity, component_id| {
                    let value = world.get::<MyComponent>(entity).unwrap().0;
                    println!(
                        "Component: {component_id:?} removed from: {entity:?} with value {value:?}"
                    );
                    world.commands().entity(entity).despawn();
                });
        })
        .init_resource::<MyComponentIndex>()
        .add_event::<MyEvent>()
        .run();
}

Copy link
Contributor

You added a new example but didn't add metadata for it. Please update the root Cargo.toml file.

Copy link
Contributor

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

@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 S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Dec 26, 2024
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 S-Needs-Review Needs reviewer attention (from anyone!) to move forward
Projects
None yet
Development

Successfully merging this pull request may close these issues.

make register_component_hooks method for App
3 participants