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 bevy::asset::AssetEvent;
use crate::{BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintChildrenReady, BlueprintInfo, BlueprintInstanceReady, InBlueprint, SpawnBlueprint, SubBlueprintsSpawnTracker};
use bevy::asset::{AssetEvent, UntypedAssetId};
use bevy::prelude::*;
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(
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 blueprint_assets: Query<(
blueprint_assets: Query<(
Entity,
Option<&Name>,
&BlueprintInfo,
&mut BlueprintAssetsLoadState,
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>,
mut commands: Commands,
) {
for event in gltf_events.read() {
// LoadedUntypedAsset
match event {
AssetEvent::Modified { id } => {
// React to the image being modified
// React to the gltf file being modified
// println!("Modified gltf {:?}", asset_server.get_path(*id));
for (entity, entity_name, _blueprint_info, mut assets_to_load, children) in
blueprint_assets.iter_mut()
{
for tracker in assets_to_load.asset_infos.iter_mut() {
if asset_server.get_path(*id).is_some() {
if tracker.path == asset_server.get_path(*id).unwrap().to_string() {
if let Some(asset_path) = asset_server.get_path(*id) {
// let untyped = asset_server.get_handle_untyped(asset_path.clone());
// println!("matching untyped handle {:?}", untyped);
// let bla = untyped.unwrap().id();
// asset_server.get
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();
@ -36,12 +53,12 @@ pub(crate) fn react_to_asset_changes(
}
commands
.entity(entity)
.remove::<BlueprintInstanceReady>()
.remove::<BlueprintAssetsLoaded>()
.remove::<SceneInstance>()
.remove::<BlueprintAssetsLoadState>()
.remove::<SubBlueprintsSpawnTracker>()
.insert(SpawnBlueprint);
break;
}
}
}

View File

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

View File

@ -4,9 +4,7 @@ use bevy::{gltf::Gltf, prelude::*, scene::SceneInstance, utils::hashbrown::HashM
use serde_json::Value;
use crate::{
AnimationInfos, AssetLoadTracker, BlenvyConfig, BlueprintAnimationPlayerLink,
BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded,
BlueprintAssetsNotLoaded, SceneAnimationPlayerLink, SceneAnimations,
AnimationInfos, AssetLoadTracker, AssetToBlueprintInstancesMapper, BlenvyConfig, BlueprintAnimationPlayerLink, BlueprintAnimations, BlueprintAssets, BlueprintAssetsLoadState, BlueprintAssetsLoaded, BlueprintAssetsNotLoaded, SceneAnimationPlayerLink, SceneAnimations
};
/// this is a flag component for our levels/game world
@ -39,11 +37,7 @@ impl BlueprintInfo {
#[reflect(Component)]
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)]
#[reflect(Component)]
@ -112,6 +106,9 @@ pub struct BlueprintSpawning;
use gltf::Gltf as RawGltf;
/*
Overview of the Blueprint Spawning process
- Blueprint Load Assets
@ -135,6 +132,8 @@ pub(crate) fn blueprints_prepare_spawn(
>,
mut commands: Commands,
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() {
info!(
@ -186,6 +185,18 @@ pub(crate) fn blueprints_prepare_spawn(
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
.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"
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)]
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(
blueprint_instances: Query<
(
@ -684,6 +702,8 @@ pub(crate) fn blueprints_finalize_instances(
all_children: Query<&Children>,
mut blueprint_events: EventWriter<BlueprintEvent>,
mut commands: Commands,
all_names: Query<&Name>
) {
for (entity, name, blueprint_info, parent_blueprint, hide_until_ready) in
blueprint_instances.iter()
@ -691,6 +711,7 @@ pub(crate) fn blueprints_finalize_instances(
info!("Finalizing blueprint instance {:?}", name);
commands
.entity(entity)
.remove::<BlueprintReadyForFinalizing>()
.remove::<BlueprintReadyForPostProcess>()
.remove::<BlueprintSpawning>()
.remove::<SpawnBlueprint>()
@ -720,7 +741,8 @@ pub(crate) fn blueprints_finalize_instances(
}
}
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);
}
}