Skip to content

Commit cdecd39

Browse files
author
Fpgu
authored
Remove InstanceId when Scene Despawn (#12778)
# Objective - Fix #12746 - When users despawn a scene, the `InstanceId` within `spawned_scenes` and `spawned_dynamic_scenes` is not removed, causing a potential memory leak ## Solution - `spawned_scenes` field was never used, and I removed it - Add a component remove hook for `Handle<DynamicScene>`, and when the `Handle<DynamicScene>` component is removed, delete the corresponding `InstanceId` from `spawned_dynamic_scenes`
1 parent 97f0555 commit cdecd39

File tree

3 files changed

+83
-15
lines changed

3 files changed

+83
-15
lines changed

crates/bevy_scene/src/bundle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{DynamicScene, InstanceId, Scene, SceneSpawner};
1616
/// [`InstanceId`] of a spawned scene. It can be used with the [`SceneSpawner`] to
1717
/// interact with the spawned scene.
1818
#[derive(Component, Deref, DerefMut)]
19-
pub struct SceneInstance(InstanceId);
19+
pub struct SceneInstance(pub(crate) InstanceId);
2020

2121
/// A component bundle for a [`Scene`] root.
2222
///

crates/bevy_scene/src/lib.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub mod prelude {
4444
}
4545

4646
use bevy_app::prelude::*;
47-
use bevy_asset::AssetApp;
47+
use bevy_asset::{AssetApp, Handle};
4848

4949
/// Plugin that provides scene functionality to an [`App`].
5050
#[derive(Default)]
@@ -59,6 +59,37 @@ impl Plugin for ScenePlugin {
5959
.add_event::<SceneInstanceReady>()
6060
.init_resource::<SceneSpawner>()
6161
.add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain());
62+
63+
// Register component hooks for DynamicScene
64+
app.world
65+
.register_component_hooks::<Handle<DynamicScene>>()
66+
.on_remove(|mut world, entity, _| {
67+
let Some(handle) = world.get::<Handle<DynamicScene>>(entity) else {
68+
return;
69+
};
70+
let id = handle.id();
71+
if let Some(&SceneInstance(scene_instance)) = world.get::<SceneInstance>(entity) {
72+
let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else {
73+
return;
74+
};
75+
if let Some(instance_ids) = scene_spawner.spawned_dynamic_scenes.get_mut(&id) {
76+
instance_ids.remove(&scene_instance);
77+
}
78+
scene_spawner.despawn_instance(scene_instance);
79+
}
80+
});
81+
82+
// Register component hooks for Scene
83+
app.world
84+
.register_component_hooks::<Handle<Scene>>()
85+
.on_remove(|mut world, entity, _| {
86+
if let Some(&SceneInstance(scene_instance)) = world.get::<SceneInstance>(entity) {
87+
let Some(mut scene_spawner) = world.get_resource_mut::<SceneSpawner>() else {
88+
return;
89+
};
90+
scene_spawner.despawn_instance(scene_instance);
91+
}
92+
});
6293
}
6394
}
6495

crates/bevy_scene/src/scene_spawner.rs

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ impl InstanceId {
6060
/// - [`despawn_instance`](Self::despawn_instance)
6161
#[derive(Default, Resource)]
6262
pub struct SceneSpawner {
63-
spawned_scenes: HashMap<AssetId<Scene>, Vec<InstanceId>>,
64-
spawned_dynamic_scenes: HashMap<AssetId<DynamicScene>, Vec<InstanceId>>,
65-
spawned_instances: HashMap<InstanceId, InstanceInfo>,
63+
pub(crate) spawned_dynamic_scenes: HashMap<AssetId<DynamicScene>, HashSet<InstanceId>>,
64+
pub(crate) spawned_instances: HashMap<InstanceId, InstanceInfo>,
6665
scene_asset_event_reader: ManualEventReader<AssetEvent<DynamicScene>>,
6766
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId)>,
6867
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId)>,
@@ -210,7 +209,7 @@ impl SceneSpawner {
210209
self.spawned_instances
211210
.insert(instance_id, InstanceInfo { entity_map });
212211
let spawned = self.spawned_dynamic_scenes.entry(id).or_default();
213-
spawned.push(instance_id);
212+
spawned.insert(instance_id);
214213
Ok(instance_id)
215214
}
216215

@@ -251,8 +250,6 @@ impl SceneSpawner {
251250
scene.write_to_world_with(world, &world.resource::<AppTypeRegistry>().clone())?;
252251

253252
self.spawned_instances.insert(instance_id, instance_info);
254-
let spawned = self.spawned_scenes.entry(id).or_default();
255-
spawned.push(instance_id);
256253
Ok(instance_id)
257254
})
258255
}
@@ -310,8 +307,8 @@ impl SceneSpawner {
310307
let spawned = self
311308
.spawned_dynamic_scenes
312309
.entry(handle.id())
313-
.or_insert_with(Vec::new);
314-
spawned.push(instance_id);
310+
.or_insert_with(HashSet::new);
311+
spawned.insert(instance_id);
315312
}
316313
Err(SceneSpawnError::NonExistentScene { .. }) => {
317314
self.dynamic_scenes_to_spawn.push((handle, instance_id));
@@ -443,17 +440,14 @@ pub fn scene_spawner_system(world: &mut World) {
443440
mod tests {
444441
use bevy_app::App;
445442
use bevy_asset::{AssetPlugin, AssetServer};
446-
use bevy_ecs::component::Component;
447-
use bevy_ecs::entity::Entity;
448443
use bevy_ecs::event::EventReader;
449444
use bevy_ecs::prelude::ReflectComponent;
450445
use bevy_ecs::query::With;
451-
use bevy_ecs::reflect::AppTypeRegistry;
452446
use bevy_ecs::system::{Commands, Res, ResMut, RunSystemOnce};
453-
use bevy_ecs::world::World;
447+
use bevy_ecs::{component::Component, system::Query};
454448
use bevy_reflect::Reflect;
455449

456-
use crate::{DynamicScene, DynamicSceneBuilder, SceneInstanceReady, ScenePlugin, SceneSpawner};
450+
use crate::{DynamicSceneBuilder, ScenePlugin};
457451

458452
use super::*;
459453

@@ -554,4 +548,47 @@ mod tests {
554548
},
555549
);
556550
}
551+
552+
#[test]
553+
fn despawn_scene() {
554+
let mut app = App::new();
555+
app.add_plugins((AssetPlugin::default(), ScenePlugin));
556+
app.register_type::<ComponentA>();
557+
558+
let asset_server = app.world.resource::<AssetServer>();
559+
560+
// Build scene.
561+
let scene = asset_server.add(DynamicScene::default());
562+
let count = 10;
563+
564+
// Checks the number of scene instances stored in `SceneSpawner`.
565+
let check = |world: &mut World, expected_count: usize| {
566+
let scene_spawner = world.resource::<SceneSpawner>();
567+
assert_eq!(
568+
scene_spawner.spawned_dynamic_scenes[&scene.id()].len(),
569+
expected_count
570+
);
571+
assert_eq!(scene_spawner.spawned_instances.len(), expected_count);
572+
};
573+
574+
// Spawn scene.
575+
for _ in 0..count {
576+
app.world.spawn((ComponentA, scene.clone()));
577+
}
578+
579+
app.update();
580+
check(&mut app.world, count);
581+
582+
// Despawn scene.
583+
app.world.run_system_once(
584+
|mut commands: Commands, query: Query<Entity, With<ComponentA>>| {
585+
for entity in query.iter() {
586+
commands.entity(entity).despawn_recursive();
587+
}
588+
},
589+
);
590+
591+
app.update();
592+
check(&mut app.world, 0);
593+
}
557594
}

0 commit comments

Comments
 (0)