bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.14k stars 3.45k forks source link

`MeshAllocator` panics when spawning and despawning lots of meshes #14540

Open eero-lehtinen opened 1 month ago

eero-lehtinen commented 1 month ago

Bevy version

Main (ba09f3547)

MeshAllocator was added recently in #14257.

Relevant system information

Rust v1.80.0
SystemInfo { os: "Linux rolling EndeavourOS", kernel: "6.10.1-arch1-1", cpu: "AMD Ryzen 7 5800X3D 8-Core Processor", core_count: "8", memory: "31.3 GiB" }
AdapterInfo { name: "NVIDIA GeForce RTX 3070 Ti", vendor: 4318, device: 9346, device_type: DiscreteGpu, driver: "NVIDIA", driver_info: "555.58.02", backend: Vulkan }

What you did

Ran into this issue in my game. Was able to reproduce in a fairly minimal example. After running it for a couple of seconds, it panics.

Below are my logs when running the example code and the example code itself.

Panic Logs

2024-07-30T18:34:41.538405Z  INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Linux rolling EndeavourOS", kernel: "6.10.1-arch1-1", cpu: "AMD Ryzen 7 5800X3D 8-Core Processor", core_count: "8", memory: "31.3 GiB" }
2024-07-30T18:34:41.742583Z  INFO bevy_render::renderer: AdapterInfo { name: "NVIDIA GeForce RTX 3070 Ti", vendor: 4318, device: 9346, device_type: DiscreteGpu, driver: "NVIDIA", driver_info: "555.58.02", backend: Vulkan }
2024-07-30T18:34:42.278247Z  INFO gilrs_core::platform::platform::gamepad: Gamepad /dev/input/event9 (Generic X-Box pad) connected.    
2024-07-30T18:34:42.379764Z  INFO bevy_winit::system: Creating new window "App" (Entity { index: 0, generation: 1 })
2024-07-30T18:34:42.380248Z  INFO winit::platform_impl::linux::x11::window: Guessed window scale factor: 1.25
2024-07-30T18:34:42.385865Z  INFO bevy_input::gamepad: Gamepad { id: 0 } Connected
2024-07-30T18:34:42.686618Z  WARN bevy_render::view::window: Couldn't get swap chain texture. This often happens with the NVIDIA drivers on Linux. It can be safely ignored.
2024-07-30T18:34:44.077495Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:34:45.561625Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:34:47.125133Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:34:48.576740Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:34:50.082296Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:34:51.547897Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:34:52.980272Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:34:54.472600Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:34:56.042392Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:34:58.284618Z  INFO 3d_shapes: Freed entities
2024-07-30T18:35:00.384228Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:35:01.903551Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:35:03.328986Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:35:04.864563Z  INFO 3d_shapes: Spawned 100 entities
2024-07-30T18:35:06.239920Z  INFO 3d_shapes: Spawned 100 entities
thread 'Compute Task Pool (7)' panicked at crates/bevy_render/src/mesh/allocator.rs:674:21:
internal error: entered unreachable code: Slab not found
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_render::mesh::allocator::allocate_and_free_meshes`!
2024-07-30T18:35:07.681254Z  INFO 3d_shapes: Spawned 100 entities
thread '<unnamed>' panicked at /rustc/051478957371ee0084a7c0913941d2a8c4757bb9/library/std/src/thread/local.rs:260:26:
cannot access a Thread Local Storage value during or after destruction: AccessError
fatal runtime error: failed to initiate panic, error 5

Example Code

use bevy::prelude::*;

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

/// A marker component for our shapes so we can query them separately from the ground plane
#[derive(Component)]
struct Shape;

fn setup(mut commands: Commands) {
    commands.spawn(Camera3dBundle {
        transform: Transform::from_xyz(0.0, 7., 14.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
        ..default()
    });
}

fn update(
    query: Query<Entity, With<Shape>>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut commands: Commands,
    mut counter: Local<u32>,
) {
    *counter += 1;

    if *counter % 100 == 0 {
        for entity in query.iter() {
            commands.entity(entity).despawn();
        }
        info!("Freed entities");
    }

    let n = if *counter % 100 > 50 && *counter % 100 < 60 {
        100
    } else {
        0
    };

    let material = materials.add(StandardMaterial::default());
    for _ in 0..n {
        let mesh = meshes.add(Sphere::new(1.).mesh().ico(50 + n / 10).unwrap());

        commands.spawn((
            PbrBundle {
                mesh,
                material: material.clone(),
                transform: Transform::from_xyz(1000., 2., 2.),
                ..default()
            },
            Shape,
        ));
    }

    if n > 0 {
        info!("Spawned {} entities", n);
    }
}

Additional information

This change to the problem area seems to fix the issue, but I don't really understand the whole system that well so might be wrong.

diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs
index 218e19c47..1599f75da 100644
--- a/crates/bevy_render/src/mesh/allocator.rs
+++ b/crates/bevy_render/src/mesh/allocator.rs
@@ -671,7 +671,7 @@ impl MeshAllocator {
         'slab: for &slab_id in &*candidate_slabs {
             loop {
                 let Some(Slab::General(ref mut slab)) = self.slabs.get_mut(&slab_id) else {
-                    unreachable!("Slab not found")
+                    continue 'slab;
                 };

                 if let Some(allocation) = slab.allocator.allocate(data_slot_count) {
alice-i-cecile commented 1 month ago

With that fix, are all of the meshes ultimately spawned, or are some of them missing? We should be putting them back into a queue if we can't access the slab allocator there.

eero-lehtinen commented 1 month ago

With that fix, are all of the meshes ultimately spawned, or are some of them missing? We should be putting them back into a queue if we can't access the slab allocator there.

With the fix a new slab is allocated in the failure case later in the function. The whole function: https://github.com/bevyengine/bevy/blob/e579622a658e2e8f7c729e916c00c9142b08af0d/crates/bevy_render/src/mesh/allocator.rs#L657-L731

All meshes show up correctly. Though I'm not sure if it's somehow less efficient compared to what's intended.

The bug seems to be that in some cases the self.slab_layouts are not reset after freeing a slab, so we hit the unreachable.

alice-i-cecile commented 1 month ago

Got it. Can you open a PR with the draft fix? It'll be easier to examine in that form.