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
This commit is contained in:
kaosat.dev 2024-07-11 01:03:43 +02:00
parent f0cca65128
commit 33cddda7a5
4 changed files with 93 additions and 37 deletions

View File

@ -37,7 +37,7 @@ pub fn compute_scene_aabbs(
} }
} }
for entity in other_entities.iter() { for entity in other_entities.iter() {
println!("stuff with AABB"); println!("already got AABB");
commands.entity(entity).insert(BlueprintReadyForFinalizing); // FIXME ! Yikes !! commands.entity(entity).insert(BlueprintReadyForFinalizing); // FIXME ! Yikes !!
} }
} }

View File

@ -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 /// 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)] #[derive(Component, Reflect, Default, Debug, Deserialize)]
#[reflect(Component)] #[reflect(Component)]
pub struct BlueprintAssets { pub struct BlueprintAssets {
@ -29,6 +30,14 @@ pub struct BlueprintAssets {
} }
//(pub Vec<BlueprintAsset>); //(pub Vec<BlueprintAsset>);
/// 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<BlueprintAsset>,
}
//////////////////////// ////////////////////////
/// ///
/// flag component, usually added when a blueprint is loaded /// flag component, usually added when a blueprint is loaded

View File

@ -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::asset::{AssetEvent, UntypedAssetId};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::scene::SceneInstance; 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 // 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<AssetToBlueprintInstancesMapper>, assets_to_blueprint_instances: Res<AssetToBlueprintInstancesMapper>,
all_parents: Query<&Parent>,
spawning_blueprints: Query<&BlueprintSpawning>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut commands: Commands, mut commands: Commands,
) { ) {
let mut respawn_candidates: Vec<&Entity> = vec![];
for event in gltf_events.read() { for event in gltf_events.read() {
// LoadedUntypedAsset // LoadedUntypedAsset
match event { match event {
@ -39,10 +43,47 @@ pub(crate) fn react_to_asset_changes(
// println!("matching untyped handle {:?}", untyped); // println!("matching untyped handle {:?}", untyped);
// let bla = untyped.unwrap().id(); // let bla = untyped.unwrap().id();
// asset_server.get // 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()) { if let Some(entities) = assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.get(&asset_path.to_string()) {
for entity in entities.iter() { for entity in entities.iter() {
println!("matching blueprint instance {}", entity); println!("matching blueprint instance {}", entity);
if let Ok((entity, entity_name, _blueprint_info, children)) = blueprint_assets.get(*entity) { // 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);
}
}
}
}
}
_ => {}
}
}
// 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<Entity> = 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); println!("HOLY MOLY IT DETECTS !!, now respawn {:?}", entity_name);
// TODO: only remove those that are "in blueprint" // TODO: only remove those that are "in blueprint"
@ -61,10 +102,6 @@ pub(crate) fn react_to_asset_changes(
.insert(SpawnBlueprint); .insert(SpawnBlueprint);
} }
} }
}
} // println!("done with asset updates");
}
_ => {}
}
}
} }

View File

@ -133,7 +133,9 @@ pub(crate) fn blueprints_prepare_spawn(
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
// for hot reload // for hot reload
mut assets_to_blueprint_instances: ResMut<AssetToBlueprintInstancesMapper> mut assets_to_blueprint_instances: ResMut<AssetToBlueprintInstancesMapper>,
// for debug
all_names: Query<&Name>
) { ) {
for (entity, blueprint_info, entity_name) in blueprint_instances_to_spawn.iter() { for (entity, blueprint_info, entity_name) in blueprint_instances_to_spawn.iter() {
info!( info!(
@ -173,6 +175,7 @@ pub(crate) fn blueprints_prepare_spawn(
// println!("all_assets {:?}", all_assets); // println!("all_assets {:?}", all_assets);
for asset in all_assets.assets.iter() { for asset in all_assets.assets.iter() {
println!("ASSET {}",asset.path);
let untyped_handle = asset_server.load_untyped(&asset.path); let untyped_handle = asset_server.load_untyped(&asset.path);
let asset_id = untyped_handle.id(); let asset_id = untyped_handle.id();
let loaded = asset_server.is_loaded_with_dependencies(asset_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 // only insert if not already present in mapping
if !assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids[&path_id].contains(&entity) { 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); 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<BlueprintSpawning>, With<BlueprintReadyForFinalizing>), (With<BlueprintSpawning>, With<BlueprintReadyForFinalizing>),
>, >,
mut sub_blueprint_trackers: Query<&mut SubBlueprintsSpawnTracker, With<BlueprintInfo>>, mut sub_blueprint_trackers: Query<&mut SubBlueprintsSpawnTracker, With<BlueprintInfo>>,
spawning_blueprints: Query<&BlueprintSpawning>,
all_children: Query<&Children>, all_children: Query<&Children>,
mut blueprint_events: EventWriter<BlueprintEvent>, mut blueprint_events: EventWriter<BlueprintEvent>,
mut commands: Commands, mut commands: Commands,
@ -725,6 +730,8 @@ pub(crate) fn blueprints_finalize_instances(
// this should always be done last, as children should be finished before the parent can be processed correctly // this should always be done last, as children should be finished before the parent can be processed correctly
// TODO: perhaps use observers for these // TODO: perhaps use observers for these
if let Some(track_root) = parent_blueprint { if let Some(track_root) = parent_blueprint {
// 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) { if let Ok(mut tracker) = sub_blueprint_trackers.get_mut(track_root.0) {
tracker tracker
.sub_blueprint_instances .sub_blueprint_instances
@ -741,9 +748,12 @@ pub(crate) fn blueprints_finalize_instances(
} }
} }
if all_spawned { if all_spawned {
let root_name = all_names.get(track_root.0); let root_name = all_names.get(track_root.0);
println!("ALLLLL SPAAAAWNED for {} named {:?}", track_root.0, root_name); println!("ALLLLL SPAAAAWNED for {} named {:?}", track_root.0, root_name);
commands.entity(track_root.0).insert(BlueprintChildrenReady); commands.entity(track_root.0).insert(BlueprintChildrenReady);
}
} }
} }
} }