Skip to content

Commit a22d288

Browse files
debounce asset events for scene reloading (#20430)
# Objective - examples `many_foxes` and `morph_targets` have stopped working since #18358 - any case that load several parts of a complex asset and a scene, and do setup on asset load will fail - Fixes #20383 ## Solution - Debounce scene asset events so that we ignore modified event happening too quickly after each others ## Testing - run examples `many_foxes` or `morph_targets` ## Alternative I *don't* think this is a good long term fix, but this should be fixed in the 0.17 and I think alternatives are worse / too complex. Alternatives are: - Revert #18358. Normal scene loading is more important than hot reloading - Implement partial asset loading in the asset server and the gltf loader so that when we load `file.gltf#Animation0`, only the animation data is loaded. This would be a very good change, but too big to do for the 0.17 - Have the asset server load parent assets only once, even when loading subassets. This would be a good change and less complex, but I think worse than the previous idea and kinda not compatible with it --------- Co-authored-by: Alice Cecile <[email protected]>
1 parent cbe1e02 commit a22d288

File tree

2 files changed

+85
-8
lines changed

2 files changed

+85
-8
lines changed

crates/bevy_scene/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ mod tests {
196196
.unwrap();
197197

198198
app.update();
199+
// TODO: multiple updates to avoid debounced asset events. See comment on SceneSpawner::debounced_scene_asset_events
200+
app.update();
201+
app.update();
202+
app.update();
199203

200204
let child_root = app
201205
.world()
@@ -336,6 +340,10 @@ mod tests {
336340
.unwrap();
337341

338342
app.update();
343+
// TODO: multiple updates to avoid debounced asset events. See comment on SceneSpawner::debounced_scene_asset_events
344+
app.update();
345+
app.update();
346+
app.update();
339347

340348
let child_root = app
341349
.world()

crates/bevy_scene/src/scene_spawner.rs

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,18 @@ pub struct SceneSpawner {
8282
pub(crate) spawned_dynamic_scenes: HashMap<AssetId<DynamicScene>, HashSet<InstanceId>>,
8383
spawned_instances: HashMap<InstanceId, InstanceInfo>,
8484
scene_asset_event_reader: EventCursor<AssetEvent<Scene>>,
85+
// TODO: temp fix for https://github.com/bevyengine/bevy/issues/12756 effect on scenes
86+
// To handle scene hot reloading, they are unloaded/reloaded on asset modifications.
87+
// When loading several subassets of a scene as is common with gltf, they each trigger a complete asset load,
88+
// and each will trigger either a created or modified event for the parent asset. This causes the scene to be
89+
// unloaded, losing its initial setup, and reloaded without it.
90+
// Debouncing scene asset events let us ignore events that happen less than SCENE_ASSET_AGE_THRESHOLD frames
91+
// apart and not reload the scene in those cases as it's unlikely to be an actual asset change.
92+
debounced_scene_asset_events: HashMap<AssetId<Scene>, u32>,
8593
dynamic_scene_asset_event_reader: EventCursor<AssetEvent<DynamicScene>>,
94+
// TODO: temp fix for https://github.com/bevyengine/bevy/issues/12756 effect on scenes
95+
// See debounced_scene_asset_events
96+
debounced_dynamic_scene_asset_events: HashMap<AssetId<DynamicScene>, u32>,
8697
scenes_to_spawn: Vec<(Handle<Scene>, InstanceId, Option<Entity>)>,
8798
dynamic_scenes_to_spawn: Vec<(Handle<DynamicScene>, InstanceId, Option<Entity>)>,
8899
scenes_to_despawn: Vec<AssetId<Scene>>,
@@ -552,21 +563,45 @@ pub fn scene_spawner_system(world: &mut World) {
552563
.scene_asset_event_reader
553564
.read(scene_asset_events)
554565
{
555-
if let AssetEvent::Modified { id } = event
556-
&& scene_spawner.spawned_scenes.contains_key(id)
557-
{
558-
updated_spawned_scenes.push(*id);
566+
match event {
567+
AssetEvent::Added { id } => {
568+
scene_spawner.debounced_scene_asset_events.insert(*id, 0);
569+
}
570+
AssetEvent::Modified { id } => {
571+
if scene_spawner
572+
.debounced_scene_asset_events
573+
.insert(*id, 0)
574+
.is_none()
575+
&& scene_spawner.spawned_scenes.contains_key(id)
576+
{
577+
updated_spawned_scenes.push(*id);
578+
}
579+
}
580+
_ => {}
559581
}
560582
}
561583
let mut updated_spawned_dynamic_scenes = Vec::new();
562584
for event in scene_spawner
563585
.dynamic_scene_asset_event_reader
564586
.read(dynamic_scene_asset_events)
565587
{
566-
if let AssetEvent::Modified { id } = event
567-
&& scene_spawner.spawned_dynamic_scenes.contains_key(id)
568-
{
569-
updated_spawned_dynamic_scenes.push(*id);
588+
match event {
589+
AssetEvent::Added { id } => {
590+
scene_spawner
591+
.debounced_dynamic_scene_asset_events
592+
.insert(*id, 0);
593+
}
594+
AssetEvent::Modified { id } => {
595+
if scene_spawner
596+
.debounced_dynamic_scene_asset_events
597+
.insert(*id, 0)
598+
.is_none()
599+
&& scene_spawner.spawned_dynamic_scenes.contains_key(id)
600+
{
601+
updated_spawned_dynamic_scenes.push(*id);
602+
}
603+
}
604+
_ => {}
570605
}
571606
}
572607

@@ -582,6 +617,40 @@ pub fn scene_spawner_system(world: &mut World) {
582617
.update_spawned_dynamic_scenes(world, &updated_spawned_dynamic_scenes)
583618
.unwrap();
584619
scene_spawner.trigger_scene_ready_events(world);
620+
621+
const SCENE_ASSET_AGE_THRESHOLD: u32 = 2;
622+
for asset_id in scene_spawner.debounced_scene_asset_events.clone().keys() {
623+
let age = scene_spawner
624+
.debounced_scene_asset_events
625+
.get(asset_id)
626+
.unwrap();
627+
if *age > SCENE_ASSET_AGE_THRESHOLD {
628+
scene_spawner.debounced_scene_asset_events.remove(asset_id);
629+
} else {
630+
scene_spawner
631+
.debounced_scene_asset_events
632+
.insert(*asset_id, *age + 1);
633+
}
634+
}
635+
for asset_id in scene_spawner
636+
.debounced_dynamic_scene_asset_events
637+
.clone()
638+
.keys()
639+
{
640+
let age = scene_spawner
641+
.debounced_dynamic_scene_asset_events
642+
.get(asset_id)
643+
.unwrap();
644+
if *age > SCENE_ASSET_AGE_THRESHOLD {
645+
scene_spawner
646+
.debounced_dynamic_scene_asset_events
647+
.remove(asset_id);
648+
} else {
649+
scene_spawner
650+
.debounced_dynamic_scene_asset_events
651+
.insert(*asset_id, *age + 1);
652+
}
653+
}
585654
});
586655
}
587656

0 commit comments

Comments
 (0)