Skip to content

Inconsistent Behavior with Required Components #19333

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

Open
Zeenobit opened this issue May 22, 2025 · 0 comments
Open

Inconsistent Behavior with Required Components #19333

Zeenobit opened this issue May 22, 2025 · 0 comments
Labels
C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled

Comments

@Zeenobit
Copy link
Contributor

Zeenobit commented May 22, 2025

Bevy version

Bevy 0.16

What you did

This issue is somewhat related to: #19300

While I do believe that Required Components with higher specificity should precede those with lower specificity, I also believe there has been a regression from Bevy 0.15 to 0.16 in component requirements. (I haven't tested for a regression specifically, but I don't recall having issues like this in Bevy 0.15 as I use such patterns heavily in my project. It could just be that the upgrade exposes the issue).

Consider the following example, using Avian 0.3.0 and Bevy 0.16.0:

use avian3d::prelude::*;
use bevy_ecs::prelude::*;

#[derive(Default, Component)]
#[require(CollisionLayers::NONE, Collider::default())]
struct A;

#[derive(Default, Component)]
#[require(A, Collider::default())] // ((CollisionLayers::NONE, Collider::default())
struct B;

#[derive(Default, Component)]
#[require(B, Collider::default())] // (CollisionLayers::NONE, Collider::default())
struct C;

#[test]
fn case_b() {
    let mut w = World::new();
    let a = w.spawn(B);
    // Passes!
    assert_eq!(
        a.get::<CollisionLayers>().unwrap().memberships,
        LayerMask::NONE
    );
}

#[test]
fn case_c() {
    let mut w = World::new();
    let a = w.spawn(C);
    // Fails?!
    assert_eq!(
        a.get::<CollisionLayers>().unwrap().memberships,
        LayerMask::NONE
    );
}

In this example, case_b PASSES, while case_c FAILS.

Both C and B should have the same exact components in the same exact order.
Neither B, nor C, nor Collider explicitly declare CollisionLayers::default() as a requirement, yet that's what we end up with in case_c but not in case_b.

I don't see how this behavior matches the documented behavior:

From a user perspective, just think about this as the following:

  1. Specifying a required component constructor for Foo directly on a spawned component Bar will result in that constructor being used (and overriding existing constructors lower in the inheritance tree). This is the classic “inheritance override” behavior people expect.

  2. For cases where “multiple inheritance” results in constructor clashes, Components should be listed in “importance order”. List a component earlier in the requirement list to initialize its inheritance tree earlier.

In all cases, A and B are required before Collider, which implies CollisionLayers::NONE should be required before Collider, and we're never explicitly inserting a Collider on spawn either.

What went wrong

There are inconsistencies between the expected and actual override behavior of required components.

Additional information

I believe the solution proposed in #19300 would automatically solve this problem in a more generalized way. However, the issue raised here seems like a bug to me.

@Zeenobit Zeenobit added C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled labels May 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled
Projects
None yet
Development

No branches or pull requests

1 participant