Skip to content

Registered systems that unregister themselves panic #22380

@CorvusPrudens

Description

@CorvusPrudens

Bevy version and features

[email protected]

What you did

Reproduction repo

use bevy_ecs::{lifecycle::HookContext, prelude::*, system::SystemId, world::DeferredWorld};

fn main() {
    let mut world = World::new();

    let system_cleanup = world.spawn_empty().id();
    let system_id = world.register_system(move |mut commands: Commands| {
        commands.entity(system_cleanup).despawn();
    });
    world
        .entity_mut(system_cleanup)
        .insert(SystemCleanup(system_id));

    world.run_system(system_id).unwrap();
}

#[derive(Component)]
#[component(on_replace = on_replace)]
struct SystemCleanup(SystemId);

fn on_replace(mut world: DeferredWorld, context: HookContext) {
    let system = world.get_mut::<SystemCleanup>(context.entity).unwrap().0;
    world.commands().unregister_system(system);
}

While this may seem contrived at first glance, it's a pretty useful pattern in my code base for ephemeral, self-cleaning event handlers.

What went wrong

thread 'main' (22524110) panicked at /Users/corvus/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_ecs-0.18.0-rc.2/src/world/entity_access/world_mut.rs:1806:14:
Attempted to update the location of a despawned entity, which is impossible. This was the result of performing an operation on this EntityWorldMut that queued a despawn command: InvalidEntityError { entity: 1v0, current_generation: EntityGeneration(1) }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Discussion

As far as I can tell, the main contributing factor to this bug was introduced in #19451, where update_location can now panic. I tried a trivial fix for the offending insert:

        if let Ok(mut entity) = self.get_entity_mut(id.entity)
            && !entity.is_despawned()
        {
            entity.insert::<RegisteredSystem<I, O>>(RegisteredSystem {
                initialized,
                system,
            });
        }

But this can't actually determine whether the queued commands will despawn it. I believe the despawning commands are flushed here, so there's no opportunity to check before updating the location.

A proper fix isn't obvious to me. Without larger changes to update_location such as making it fallible, the only solution I can see is simply not panicking (assuming it's not preserving any safety invariants). Naturally, not panicking poses its own issues.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ECSEntities, components, systems, and eventsC-BugAn unexpected or incorrect behaviorP-RegressionFunctionality that used to work but no longer does. Add a test for this!S-Needs-DesignThis issue requires design work to think about how it would best be accomplished

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions