Skip to content

Bevy crashes when simultaneously setting camera.is_active = false and inserting Disabled to it #20301

@M4tsuri

Description

@M4tsuri

Bevy version

0.16.1

What you did

Here is a mini repro:

use bevy::{ecs::entity_disabling::Disabled, prelude::*};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, input)
        .run();
}

#[derive(Component)]
struct SingleCamera(Entity);

fn setup(mut commands: Commands) {
    let camera = commands.spawn((
        Camera3d::default(),
        Camera {
            is_active: true,
            ..Default::default()
        },
        // Disabled
    )).id();

    commands.spawn(SingleCamera(camera));
}


fn input(
    mut commands: Commands,
    input: Res<ButtonInput<KeyCode>>,
    camera: Query<&SingleCamera>,
    mut cameras: Query<(&mut Camera, Has<Disabled>)>
) {
    let entity = camera.single().unwrap().0;
    let mut camera = cameras.single_mut().unwrap();
    if input.just_released(KeyCode::Space) {
        // now set is_active to true and insert the "Disabled" component
        camera.0.is_active = false;
        commands.entity(entity).insert(Disabled);
    }
}

Run this App and press "Space", there will be a crash with message

thread 'Compute Task Pool (4)' panicked at /bevy/crates/bevy_core_pipeline/src/core_3d/mod.rs:833:22:
The depth texture usage should already exist for this target

Additional information

I tried to dig into the source code to figure out what exactly causes this. Here are my findings.

It seems that the components like ExtractedView are not automatically cleaned up from render world after each run of render pipeline. This the reason why we need to manually remove them once the corresponding camera becomes inactive.

if !camera.is_active {
commands.entity(render_entity).remove::<(
ExtractedCamera,
ExtractedView,
RenderVisibleEntities,
TemporalJitter,
RenderLayers,
Projection,
NoIndirectDrawing,
ViewUniformOffset,
)>();
continue;

However, when the camera is disabled by a Disabled component, it is filtered out from the query, thus makes the extracted components remain in the render world, even we set camera.is_active to false. However, there are serval tasks like prepare_core_3d_depth_textures highly relies on the existence of these extracted components to determine currently active cameras.

pub fn prepare_core_3d_depth_textures(

So this function will panic as the camera simply does not exist.

I added a Has<Disabled> filter to query of extract_cameras and changed the condition here

if !camera.is_active {

to

if !camera.is_active || disabled {

Then the mini repro works.

P.S. I'm not familiar when bevy's codebase, so my analysis could be wrong. I would appreciate it if someone can correct me.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-RenderingDrawing game state to the screenC-BugAn unexpected or incorrect behaviorD-Domain-AgnosticCan be tackled by anyone with generic programming or Rust skillsD-StraightforwardSimple bug fixes and API improvements, docs, test and examplesS-Ready-For-ImplementationThis issue is ready for an implementation PR. Go for it!

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions