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() {
println!("stuff with AABB");
println!("already got AABB");
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
/// 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<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

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::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<AssetToBlueprintInstancesMapper>,
all_parents: Query<&Parent>,
spawning_blueprints: Query<&BlueprintSpawning>,
asset_server: Res<AssetServer>,
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::<BlueprintInstanceReady>()
.remove::<BlueprintAssetsLoaded>()
.remove::<SceneInstance>()
.remove::<BlueprintAssetsLoadState>()
.remove::<SubBlueprintsSpawnTracker>()
.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<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);
// 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::<BlueprintInstanceReady>()
.remove::<BlueprintAssetsLoaded>()
.remove::<SceneInstance>()
.remove::<BlueprintAssetsLoadState>()
.remove::<SubBlueprintsSpawnTracker>()
.insert(SpawnBlueprint);
}
}
// println!("done with asset updates");
}

View File

@ -133,7 +133,9 @@ pub(crate) fn blueprints_prepare_spawn(
mut commands: Commands,
asset_server: Res<AssetServer>,
// 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() {
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<BlueprintSpawning>, With<BlueprintReadyForFinalizing>),
>,
mut sub_blueprint_trackers: Query<&mut SubBlueprintsSpawnTracker, With<BlueprintInfo>>,
spawning_blueprints: Query<&BlueprintSpawning>,
all_children: Query<&Children>,
mut blueprint_events: EventWriter<BlueprintEvent>,
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);
}
}
}