Skip to content

commands.insert_if_new inserts Required Components also if not new #24554

@lucidBrot

Description

@lucidBrot

Of the two ways of thinking about Required Components as discussed here, I am using Required Components to "include" other components at insertion, not to constantly require them. This seems to be the intent from the docs as well:

Components can specify Required Components. If some Component A requires Component B, then when A is inserted, B will also be initialized and inserted (if it was not manually specified).

But I've found that the required Component B is also inserted when A is not being inserted. What I did is this:

  • Insert (A,B) as my_entity
  • Remove B from my_entity
  • Perform my_entity_commands.insert_if_new(A)
  • Observe that, surprisingly, there is now a B, although the A was not inserted.

Bevy version and features

Bevy version: 0.18.1, default features

# rustup -V
info: The currently active `rustc` version is `rustc 1.94.0-nightly (31010ca61 2025-12-16)`

What you did

I'll attach the repro project files with a minimal test. Run cargo test to see it fail.

Repro.zip

What went wrong

I was expecting insert_if_new to have no effect if the provided Component is not new. Something like "it checks whether the Component exists on that Entity. If yes, it does nothing. If no, it inserts it and triggers hooks and required components appropriately".

What it does instead is it enforces the required Components even if the Component itself was already present. This is a logic bug in my code, where I have a Marker Component that should initially be present (achieved by #[require(ing it) and eventually gets removed.

Possible Workaround

I suspect I could work around this bug by using Component Hooks to perform the check myself. I did not check, but I would be surprised if the insertion hook would also be called without any insertion happening. When/if I try this, I will update this post.

Reproduction Test Case

mod repro3 {
    use bevy::prelude::*;
    #[derive(Component, Reflect, Default)]
    #[reflect(Component)]
    pub struct RequiredMarkerComponent {}

    #[derive(Reflect, Component, Default)]
    #[reflect(Component)]
    #[require(RequiredMarkerComponent)]
    pub(crate) struct MainComponent {}

    #[test]
    fn simple_insert_if_new_test() {
        // Create an empty world
        let mut world = World::new();
        // Spawn a new entity, with a Component and another, required, Component.
        let mut my_entity = world.spawn(MainComponent::default());

        // Remove the required Component.
        my_entity.remove::<RequiredMarkerComponent>();
        // Be very certain that we did remove the Marker Component
        assert!(my_entity.get::<RequiredMarkerComponent>().is_none());
        // Be very certain that we did not remove the other Component.
        my_entity
            .get::<MainComponent>()
            .expect("This component should still exist...");
        // Add the Component only if new (it's not new. this should do nothing.)
        my_entity.insert_if_new(MainComponent::default());
        match my_entity.get::<RequiredMarkerComponent>() {
            None => {
                // All good.
            }
            Some(_) => panic!(
                "This RequiredMarkerComponent should not exist, because the MainComponent was not new."
            ),
        }
    }
}

Is It a Bug?

I am of the opinion that this behavior is very unexpected and not as documented, but I see the possibility for the viewpoint that it is working as-intended too... If the consensus is actually that this is fine, I think it should still be documented clearer in the docs of insert_if_new() and Required Components.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ECSEntities, components, systems, and eventsC-BugAn unexpected or incorrect behaviorD-ModestA "normal" level of difficulty; suitable for simple features or challenging fixesS-Ready-For-ImplementationThis issue is ready for an implementation PR. Go for it!X-UncontroversialThis work is generally agreed upon

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Needs SME Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions