From 33cddda7a5fee90dc7b38f544309f910e61a9bbd Mon Sep 17 00:00:00 2001 From: "kaosat.dev" Date: Thu, 11 Jul 2024 01:03:43 +0200 Subject: [PATCH] feat(Blenvy:Bevy): big improvements to hot reload: * does not try to respawn parents & their children at the same time anymore * does not try to respawn already spawning blueprint instances anymore * "batches" respawning per set of simultaneous changes to assets to avoid chaotic respawning * minor other tweaks & improvements --- crates/blenvy/src/blueprints/aabb.rs | 2 +- crates/blenvy/src/blueprints/assets.rs | 9 +++ crates/blenvy/src/blueprints/hot_reload.rs | 73 ++++++++++++++----- .../src/blueprints/spawn_from_blueprints.rs | 46 +++++++----- 4 files changed, 93 insertions(+), 37 deletions(-) diff --git a/crates/blenvy/src/blueprints/aabb.rs b/crates/blenvy/src/blueprints/aabb.rs index 308a2ca..8e46aef 100644 --- a/crates/blenvy/src/blueprints/aabb.rs +++ b/crates/blenvy/src/blueprints/aabb.rs @@ -37,7 +37,7 @@ pub fn compute_scene_aabbs( } } for entity in other_entities.iter() { - println!("stuff with AABB"); + println!("already got AABB"); commands.entity(entity).insert(BlueprintReadyForFinalizing); // FIXME ! Yikes !! } } diff --git a/crates/blenvy/src/blueprints/assets.rs b/crates/blenvy/src/blueprints/assets.rs index aa84f40..7e3a67c 100644 --- a/crates/blenvy/src/blueprints/assets.rs +++ b/crates/blenvy/src/blueprints/assets.rs @@ -10,6 +10,7 @@ pub struct BlueprintAsset { } /// helper component, is used to store the list of sub blueprints to enable automatic loading of dependend blueprints +/// these are only the DIRECT dependencies of a blueprint, does not contain the indirect assets (ie assets of sub blueprints, etc) #[derive(Component, Reflect, Default, Debug, Deserialize)] #[reflect(Component)] pub struct BlueprintAssets { @@ -29,6 +30,14 @@ pub struct BlueprintAssets { } //(pub Vec); +/// helper component, is used to store the list of sub blueprints to enable automatic loading of dependend blueprints +#[derive(Component, Reflect, Default, Debug, Deserialize)] +pub struct BlueprintAllAssets { + /// only this field should get filled in from the Blender side + pub assets: Vec, +} + + //////////////////////// /// /// flag component, usually added when a blueprint is loaded diff --git a/crates/blenvy/src/blueprints/hot_reload.rs b/crates/blenvy/src/blueprints/hot_reload.rs index 8c84d20..1ba0397 100644 --- a/crates/blenvy/src/blueprints/hot_reload.rs +++ b/crates/blenvy/src/blueprints/hot_reload.rs @@ -1,4 +1,4 @@ -use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintChildrenReady, BlueprintInfo, BlueprintInstanceReady, InBlueprint, SpawnBlueprint, SubBlueprintsSpawnTracker}; +use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintChildrenReady, BlueprintInfo, BlueprintInstanceReady, BlueprintSpawning, InBlueprint, SpawnBlueprint, SubBlueprintsSpawnTracker}; use bevy::asset::{AssetEvent, UntypedAssetId}; use bevy::prelude::*; use bevy::scene::SceneInstance; @@ -23,11 +23,15 @@ pub(crate) fn react_to_asset_changes( )>, // blueprint_children_entities: Query<&InBlueprint>, => can only be used if the entites are tagged, right now that is optional...perhaps do not make it optional assets_to_blueprint_instances: Res, + all_parents: Query<&Parent>, + spawning_blueprints: Query<&BlueprintSpawning>, asset_server: Res, mut commands: Commands, ) { + let mut respawn_candidates: Vec<&Entity> = vec![]; + for event in gltf_events.read() { // LoadedUntypedAsset match event { @@ -39,27 +43,16 @@ pub(crate) fn react_to_asset_changes( // println!("matching untyped handle {:?}", untyped); // let bla = untyped.unwrap().id(); // asset_server.get + // in order to avoid respawn both a parent & a child , which would crash Bevy, we do things in two steps if let Some(entities) = assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.get(&asset_path.to_string()) { for entity in entities.iter() { println!("matching blueprint instance {}", entity); - if let Ok((entity, entity_name, _blueprint_info, children)) = blueprint_assets.get(*entity) { - println!("HOLY MOLY IT DETECTS !!, now respawn {:?}", entity_name); - - // TODO: only remove those that are "in blueprint" - if children.is_some() { - for child in children.unwrap().iter() { - commands.entity(*child).despawn_recursive(); - } - } - commands - .entity(entity) - .remove::() - .remove::() - .remove::() - .remove::() - .remove::() - .insert(SpawnBlueprint); + // disregard entities that are already (re) spawning + if !respawn_candidates.contains(&entity) && blueprint_assets.get(*entity).is_ok() && spawning_blueprints.get(*entity).is_err() + { + respawn_candidates.push(entity); } + } } } @@ -67,4 +60,48 @@ pub(crate) fn react_to_asset_changes( _ => {} } } + // we process all candidates here to deal with the case where multiple assets have changed in a single frame, which could cause respawn chaos + // now find hierarchy of changes and only set the uppermost parent up for respawning + // TODO: improve this, very inneficient + let mut retained_candidates: Vec = vec![]; + 'outer: for entity in respawn_candidates.iter() { + for parent in all_parents.iter_ancestors(**entity){ + for ent in respawn_candidates.iter() { + if **ent == parent { + if ! retained_candidates.contains(&parent) { + retained_candidates.push(parent); + } + continue 'outer; + } + } + } + if ! retained_candidates.contains(&entity) { + retained_candidates.push(**entity); + } + } + // println!("respawn candidates {:?}", respawn_candidates); + for retained in retained_candidates.iter() { + println!("retained {}", retained); + + if let Ok((entity, entity_name, _blueprint_info, children)) = blueprint_assets.get(*retained) { + println!("HOLY MOLY IT DETECTS !!, now respawn {:?}", entity_name); + + // TODO: only remove those that are "in blueprint" + if children.is_some() { + for child in children.unwrap().iter() { + commands.entity(*child).despawn_recursive(); + } + } + commands + .entity(entity) + .remove::() + .remove::() + .remove::() + .remove::() + .remove::() + .insert(SpawnBlueprint); + } + } + + // println!("done with asset updates"); } diff --git a/crates/blenvy/src/blueprints/spawn_from_blueprints.rs b/crates/blenvy/src/blueprints/spawn_from_blueprints.rs index efdf7c3..3534c85 100644 --- a/crates/blenvy/src/blueprints/spawn_from_blueprints.rs +++ b/crates/blenvy/src/blueprints/spawn_from_blueprints.rs @@ -133,7 +133,9 @@ pub(crate) fn blueprints_prepare_spawn( mut commands: Commands, asset_server: Res, // for hot reload - mut assets_to_blueprint_instances: ResMut + mut assets_to_blueprint_instances: ResMut, + // for debug + all_names: Query<&Name> ) { for (entity, blueprint_info, entity_name) in blueprint_instances_to_spawn.iter() { info!( @@ -173,6 +175,7 @@ pub(crate) fn blueprints_prepare_spawn( // println!("all_assets {:?}", all_assets); for asset in all_assets.assets.iter() { + println!("ASSET {}",asset.path); let untyped_handle = asset_server.load_untyped(&asset.path); let asset_id = untyped_handle.id(); let loaded = asset_server.is_loaded_with_dependencies(asset_id); @@ -195,6 +198,7 @@ pub(crate) fn blueprints_prepare_spawn( } // only insert if not already present in mapping if !assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids[&path_id].contains(&entity) { + println!("adding mapping between {} and entity {:?}", path_id, all_names.get(entity)); assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.get_mut(&path_id).unwrap().push(entity); } } @@ -699,6 +703,7 @@ pub(crate) fn blueprints_finalize_instances( (With, With), >, mut sub_blueprint_trackers: Query<&mut SubBlueprintsSpawnTracker, With>, + spawning_blueprints: Query<&BlueprintSpawning>, all_children: Query<&Children>, mut blueprint_events: EventWriter, mut commands: Commands, @@ -725,25 +730,30 @@ pub(crate) fn blueprints_finalize_instances( // this should always be done last, as children should be finished before the parent can be processed correctly // TODO: perhaps use observers for these if let Some(track_root) = parent_blueprint { - if let Ok(mut tracker) = sub_blueprint_trackers.get_mut(track_root.0) { - tracker - .sub_blueprint_instances - .entry(entity) - .or_insert(true); - tracker.sub_blueprint_instances.insert(entity, true); + // only propagate sub_blueprint spawning if the parent blueprint instance ist actually in spawning mode + if spawning_blueprints.get(track_root.0).is_ok() { + if let Ok(mut tracker) = sub_blueprint_trackers.get_mut(track_root.0) { + tracker + .sub_blueprint_instances + .entry(entity) + .or_insert(true); + tracker.sub_blueprint_instances.insert(entity, true); + + // TODO: ugh, my limited rust knowledge, this is bad code + let mut all_spawned = true; + for val in tracker.sub_blueprint_instances.values() { + if !val { + all_spawned = false; + break; + } + } + if all_spawned { + + let root_name = all_names.get(track_root.0); + println!("ALLLLL SPAAAAWNED for {} named {:?}", track_root.0, root_name); + commands.entity(track_root.0).insert(BlueprintChildrenReady); - // TODO: ugh, my limited rust knowledge, this is bad code - let mut all_spawned = true; - for val in tracker.sub_blueprint_instances.values() { - if !val { - all_spawned = false; - break; } - } - if all_spawned { - let root_name = all_names.get(track_root.0); - println!("ALLLLL SPAAAAWNED for {} named {:?}", track_root.0, root_name); - commands.entity(track_root.0).insert(BlueprintChildrenReady); } } }