Skip to content

Inconsistent Behavior with Required Components #19333

Open
@Zeenobit

Description

@Zeenobit

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ECSEntities, components, systems, and eventsC-BugAn unexpected or incorrect behaviorS-Needs-DesignThis issue requires design work to think about how it would best be accomplished

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions