feat(Blenvy:Bevy): fixed & upgraded hot reloading

* added mapping of assets to blueprint instance ids so we know which blueprint instances
need to be de/respawned when a given asset changes
 * simplified & streamlined internals of hot reloading using the above
 * related cleanups & minor improvements
This commit is contained in:
kaosat.dev 2024-07-10 14:15:34 +02:00
parent 8602383445
commit f0cca65128
3 changed files with 68 additions and 25 deletions

View File

@ -1,34 +1,51 @@
use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintInfo, SpawnBlueprint}; use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintChildrenReady, BlueprintInfo, BlueprintInstanceReady, InBlueprint, SpawnBlueprint, SubBlueprintsSpawnTracker};
use bevy::asset::AssetEvent; use bevy::asset::{AssetEvent, UntypedAssetId};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::scene::SceneInstance; use bevy::scene::SceneInstance;
use bevy::utils::hashbrown::HashMap;
/// Resource mapping asset paths (ideally untyped ids, but more complex) to a list of blueprint instance entity ids
#[derive(Debug, Clone, Resource, Default)]
pub(crate) struct AssetToBlueprintInstancesMapper{
// pub(crate) untyped_id_to_blueprint_entity_ids: HashMap<UntypedAssetId, Vec<Entity>>
pub(crate) untyped_id_to_blueprint_entity_ids: HashMap<String, Vec<Entity>>
}
pub(crate) fn react_to_asset_changes( pub(crate) fn react_to_asset_changes(
mut gltf_events: EventReader<AssetEvent<Gltf>>, mut gltf_events: EventReader<AssetEvent<Gltf>>, // FIXME: Problem: we need to react to any asset change, not just gltf files !
// mut untyped_events: EventReader<AssetEvent<LoadedUntypedAsset>>, // mut untyped_events: EventReader<AssetEvent<LoadedUntypedAsset>>,
mut blueprint_assets: Query<( blueprint_assets: Query<(
Entity, Entity,
Option<&Name>, Option<&Name>,
&BlueprintInfo, &BlueprintInfo,
&mut BlueprintAssetsLoadState,
Option<&Children>, Option<&Children>,
)>, )>,
// 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>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut commands: Commands, mut commands: Commands,
) { ) {
for event in gltf_events.read() { for event in gltf_events.read() {
// LoadedUntypedAsset // LoadedUntypedAsset
match event { match event {
AssetEvent::Modified { id } => { AssetEvent::Modified { id } => {
// React to the image being modified // React to the gltf file being modified
// println!("Modified gltf {:?}", asset_server.get_path(*id)); // println!("Modified gltf {:?}", asset_server.get_path(*id));
for (entity, entity_name, _blueprint_info, mut assets_to_load, children) in if let Some(asset_path) = asset_server.get_path(*id) {
blueprint_assets.iter_mut() // let untyped = asset_server.get_handle_untyped(asset_path.clone());
{ // println!("matching untyped handle {:?}", untyped);
for tracker in assets_to_load.asset_infos.iter_mut() { // let bla = untyped.unwrap().id();
if asset_server.get_path(*id).is_some() { // asset_server.get
if tracker.path == asset_server.get_path(*id).unwrap().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() {
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); println!("HOLY MOLY IT DETECTS !!, now respawn {:?}", entity_name);
// TODO: only remove those that are "in blueprint"
if children.is_some() { if children.is_some() {
for child in children.unwrap().iter() { for child in children.unwrap().iter() {
commands.entity(*child).despawn_recursive(); commands.entity(*child).despawn_recursive();
@ -36,12 +53,12 @@ pub(crate) fn react_to_asset_changes(
} }
commands commands
.entity(entity) .entity(entity)
.remove::<BlueprintInstanceReady>()
.remove::<BlueprintAssetsLoaded>() .remove::<BlueprintAssetsLoaded>()
.remove::<SceneInstance>() .remove::<SceneInstance>()
.remove::<BlueprintAssetsLoadState>() .remove::<BlueprintAssetsLoadState>()
.remove::<SubBlueprintsSpawnTracker>()
.insert(SpawnBlueprint); .insert(SpawnBlueprint);
break;
} }
} }
} }

View File

@ -19,7 +19,7 @@ pub use copy_components::*;
pub(crate) mod hot_reload; pub(crate) mod hot_reload;
pub(crate) use hot_reload::*; pub(crate) use hot_reload::*;
use bevy::{prelude::*, utils::HashMap}; use bevy::{prelude::*, utils::hashbrown::HashMap};
use crate::{BlenvyConfig, GltfComponentsSet}; use crate::{BlenvyConfig, GltfComponentsSet};
@ -65,7 +65,8 @@ fn aabbs_enabled(blenvy_config: Res<BlenvyConfig>) -> bool {
} }
fn hot_reload(watching_for_changes: Res<WatchingForChanges>) -> bool { fn hot_reload(watching_for_changes: Res<WatchingForChanges>) -> bool {
watching_for_changes.0 // println!("hot reload ? {}", watching_for_changes.0);
return watching_for_changes.0
} }
trait BlenvyApp { trait BlenvyApp {
@ -91,6 +92,9 @@ const ASSET_ERROR: &str = ""; // TODO
impl Plugin for BlueprintsPlugin { impl Plugin for BlueprintsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.register_watching_for_changes() app.register_watching_for_changes()
.insert_resource(AssetToBlueprintInstancesMapper {
untyped_id_to_blueprint_entity_ids: HashMap::new(),
})
.add_event::<BlueprintEvent>() .add_event::<BlueprintEvent>()
.register_type::<BlueprintInfo>() .register_type::<BlueprintInfo>()
.register_type::<MaterialInfo>() .register_type::<MaterialInfo>()

View File

@ -4,9 +4,7 @@ use bevy::{gltf::Gltf, prelude::*, scene::SceneInstance, utils::hashbrown::HashM
use serde_json::Value; use serde_json::Value;
use crate::{ use crate::{
AnimationInfos, AssetLoadTracker, BlenvyConfig, BlueprintAnimationPlayerLink, AnimationInfos, AssetLoadTracker, AssetToBlueprintInstancesMapper, BlenvyConfig, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded, SceneAnimationPlayerLink, SceneAnimations
BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded,
BlueprintAssetsNotLoaded, SceneAnimationPlayerLink, SceneAnimations,
}; };
/// this is a flag component for our levels/game world /// this is a flag component for our levels/game world
@ -39,11 +37,7 @@ impl BlueprintInfo {
#[reflect(Component)] #[reflect(Component)]
pub struct SpawnBlueprint; pub struct SpawnBlueprint;
#[derive(Component, Debug)]
/// flag component added when a Blueprint instance ist Ready : ie :
/// - its assets have loaded
/// - it has finished spawning
pub struct BlueprintInstanceReady;
#[derive(Component, Reflect, Default, Debug)] #[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)] #[reflect(Component)]
@ -112,6 +106,9 @@ pub struct BlueprintSpawning;
use gltf::Gltf as RawGltf; use gltf::Gltf as RawGltf;
/* /*
Overview of the Blueprint Spawning process Overview of the Blueprint Spawning process
- Blueprint Load Assets - Blueprint Load Assets
@ -135,6 +132,8 @@ pub(crate) fn blueprints_prepare_spawn(
>, >,
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
// for hot reload
mut assets_to_blueprint_instances: ResMut<AssetToBlueprintInstancesMapper>
) { ) {
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!(
@ -186,6 +185,18 @@ pub(crate) fn blueprints_prepare_spawn(
handle: untyped_handle.clone(), handle: untyped_handle.clone(),
}); });
} }
// FIXME: dang, too early, asset server has not yet started loading yet
// let path_id = asset_server.get_path_id(&asset.path).expect("we should have alread checked for this asset");
let path_id = asset.path.clone();
// TODO: make this dependant on if hot reload is enabled or not
if !assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.contains_key(&path_id) {
assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.insert(path_id.clone(), vec![]);
}
// only insert if not already present in mapping
if !assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids[&path_id].contains(&entity) {
assets_to_blueprint_instances.untyped_id_to_blueprint_entity_ids.get_mut(&path_id).unwrap().push(entity);
}
} }
} }
} }
@ -659,6 +670,7 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
commands commands
.entity(original) .entity(original)
.remove::<BlueprintChildrenReady>() // we are done with this step, we can remove the `BlueprintChildrenReady` tag component
.insert(BlueprintReadyForPostProcess); // Tag the entity so any systems dealing with post processing can know it is now their "turn" .insert(BlueprintReadyForPostProcess); // Tag the entity so any systems dealing with post processing can know it is now their "turn"
commands.entity(blueprint_root_entity).despawn_recursive(); // Remove the root entity that comes from the spawned-in scene commands.entity(blueprint_root_entity).despawn_recursive(); // Remove the root entity that comes from the spawned-in scene
@ -669,6 +681,12 @@ pub(crate) fn blueprints_cleanup_spawned_scene(
#[reflect(Component)] #[reflect(Component)]
pub struct BlueprintReadyForFinalizing; pub struct BlueprintReadyForFinalizing;
#[derive(Component, Debug)]
/// flag component added when a Blueprint instance ist Ready : ie :
/// - its assets have loaded
/// - it has finished spawning
pub struct BlueprintInstanceReady;
pub(crate) fn blueprints_finalize_instances( pub(crate) fn blueprints_finalize_instances(
blueprint_instances: Query< blueprint_instances: Query<
( (
@ -684,6 +702,8 @@ pub(crate) fn blueprints_finalize_instances(
all_children: Query<&Children>, all_children: Query<&Children>,
mut blueprint_events: EventWriter<BlueprintEvent>, mut blueprint_events: EventWriter<BlueprintEvent>,
mut commands: Commands, mut commands: Commands,
all_names: Query<&Name>
) { ) {
for (entity, name, blueprint_info, parent_blueprint, hide_until_ready) in for (entity, name, blueprint_info, parent_blueprint, hide_until_ready) in
blueprint_instances.iter() blueprint_instances.iter()
@ -691,6 +711,7 @@ pub(crate) fn blueprints_finalize_instances(
info!("Finalizing blueprint instance {:?}", name); info!("Finalizing blueprint instance {:?}", name);
commands commands
.entity(entity) .entity(entity)
.remove::<BlueprintReadyForFinalizing>()
.remove::<BlueprintReadyForPostProcess>() .remove::<BlueprintReadyForPostProcess>()
.remove::<BlueprintSpawning>() .remove::<BlueprintSpawning>()
.remove::<SpawnBlueprint>() .remove::<SpawnBlueprint>()
@ -720,7 +741,8 @@ pub(crate) fn blueprints_finalize_instances(
} }
} }
if all_spawned { if all_spawned {
// println!("ALLLLL SPAAAAWNED for {} named {:?}", track_root.0, root_name); 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); commands.entity(track_root.0).insert(BlueprintChildrenReady);
} }
} }