Skip to content

Conversation

ElliottjPierce
Copy link
Contributor

@ElliottjPierce ElliottjPierce commented Feb 25, 2025

Objective

I found a bug while working on #17871. When required components are registered, ones that are more specific (smaller inheritance depth) are preferred to others. So, if a ComponentA is already required, but it is registered as required again, it will be updated if and only if the new requirement has a smaller inheritance depth (is more specific). However, this logic was not reflected in merging RequriedComponentss together. Hence, for complicated requirement trees, the wrong initializer could be used.

Solution

Re-write merging to work by extending the collection via require_dynamic instead of blindly combining the inner storage.

Testing

I created this test to ensure this bug doesn't creep back in. This test fails on main, but passes on this branch.

    #[test]
    fn required_components_inheritance_depth_bias() {
        #[derive(Component, PartialEq, Eq, Clone, Copy, Debug)]
        struct MyRequired(bool);

        #[derive(Component, Default)]
        #[require(MyRequired(|| MyRequired(false)))]
        struct MiddleMan;

        #[derive(Component, Default)]
        #[require(MiddleMan)]
        struct ConflictingRequire;

        #[derive(Component, Default)]
        #[require(MyRequired(|| MyRequired(true)))]
        struct MyComponent;

        let mut world = World::new();
        let order_a = world
            .spawn((ConflictingRequire, MyComponent))
            .get::<MyRequired>()
            .cloned();
        let order_b = world
            .spawn((MyComponent, ConflictingRequire))
            .get::<MyRequired>()
            .cloned();

        assert_eq!(order_a, Some(MyRequired(true)));
        assert_eq!(order_b, Some(MyRequired(true)));
    }

Note that when the inheritance depth is 0 (Like if there were no middle man above), the order of the components in the bundle still matters.

Migration Guide

RequiredComponents::register_dynamic has been changed to RequiredComponents::register_dynamic_with.

Old:

required_components.register_dynamic(
      component_id,
      component_constructor.clone(),
      requirement_inheritance_depth,
);

New:

required_components.register_dynamic_with(
      component_id,
      requirement_inheritance_depth,
      || component_constructor.clone(),
);

This can prevent unnecessary cloning.

@alice-i-cecile alice-i-cecile added this to the 0.16 milestone Feb 25, 2025
@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-ECS Entities, components, systems, and events D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Feb 25, 2025
@alice-i-cecile
Copy link
Member

No migration guide for bug fixes in general :)

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.

Good test: that clearly demonstrated the problem to me.

@ElliottjPierce
Copy link
Contributor Author

@alice-i-cecile The additional cloning during merging was annoying us. I was going to have to fix it anyway for #17871, but since we aren't going in that direction anymore, I went ahead and fixed it here. I added register_dynamic_with and changed to using that. That saved I think 4 cloning paths, which we now only do if the registration actually goes through.

I kept register_dynamic even though it's not used anymore. It's public, and I didn't want this to be a breaking change. Happy to remove it though, if you say the word.

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.

Let's axe register_dynamic (just have a little migration guide showing the snippet you currently have). It really doesn't seem to add much value here. Once that's done, this LGTM.

@ElliottjPierce
Copy link
Contributor Author

@alice-i-cecile

Let's axe register_dynamic

Done! Also needs M-Needs-Migration-Guide if I understand correctly. Thanks!

@alice-i-cecile alice-i-cecile added the M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide label Mar 6, 2025
Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
Copy link
Member

@cart cart left a comment

Choose a reason for hiding this comment

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

This makes sense to me. Nice catch!

Copy link
Contributor

@Jondolf Jondolf left a comment

Choose a reason for hiding this comment

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

Changes make sense, looks good!

@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-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Mar 9, 2025
Co-Authored-By: Joona Aalto <jondolf.dev@gmail.com>
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Mar 10, 2025
Merged via the queue into bevyengine:main with commit 79e7f8a Mar 10, 2025
31 checks passed
github-merge-queue bot pushed a commit that referenced this pull request Jul 29, 2025
# Objective

- Fixes #19863 by removing `inheritance_depth`
- Fixed #19333 by properly using depth-first order priority 
- Helps with #19300 (not sure if it could be closed with this PR)
- Fixes #20173
- Overall, fix the weird situation that required components are in,
where some operations respect a depth-first order priority while other
respect a breadth-first order priority.

## Solution

- Switch everything to a depth-first order priority as @cart originally
intended it.
- Effectively revert #18028, which tried to give priority to components
with higher inheritance depth (i.e. with a breadth-first order
priority).

## Testing

Most existing tests pass, except a couple that were removed due to
testing inheritance depth or the breadth-first order priority that this
PR will remove.

Some tests were also added, some of which as regression tests and others
to add more coverage.
tychedelia pushed a commit to tychedelia/bevy that referenced this pull request Jul 31, 2025
…0110)

# Objective

- Fixes bevyengine#19863 by removing `inheritance_depth`
- Fixed bevyengine#19333 by properly using depth-first order priority 
- Helps with bevyengine#19300 (not sure if it could be closed with this PR)
- Fixes bevyengine#20173
- Overall, fix the weird situation that required components are in,
where some operations respect a depth-first order priority while other
respect a breadth-first order priority.

## Solution

- Switch everything to a depth-first order priority as @cart originally
intended it.
- Effectively revert bevyengine#18028, which tried to give priority to components
with higher inheritance depth (i.e. with a breadth-first order
priority).

## Testing

Most existing tests pass, except a couple that were removed due to
testing inheritance depth or the breadth-first order priority that this
PR will remove.

Some tests were also added, some of which as regression tests and others
to add more coverage.
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-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants