From 171ec7490aa747b244a358fdd2a722763a493ce4 Mon Sep 17 00:00:00 2001 From: "kaosat.dev" Date: Sat, 3 Aug 2024 01:28:46 +0200 Subject: [PATCH] feat(Blenvy:Bevy): slowly adding back save/load (wip) * moved out old code * added very basics of saving (HEAVY WIP) --- crates/blenvy/src/lib.rs | 6 + crates/blenvy/src/save_load/mod.rs | 115 +++------- .../blenvy/src/save_load/{ => old}/loading.rs | 0 crates/blenvy/src/save_load/old/mod_old.rs | 76 +++++++ crates/blenvy/src/save_load/old/saving.rs | 196 ++++++++++++++++++ crates/blenvy/src/save_load/saveable.rs | 7 - crates/blenvy/src/save_load/saving.rs | 105 +++++----- 7 files changed, 359 insertions(+), 146 deletions(-) rename crates/blenvy/src/save_load/{ => old}/loading.rs (100%) create mode 100644 crates/blenvy/src/save_load/old/mod_old.rs create mode 100644 crates/blenvy/src/save_load/old/saving.rs delete mode 100644 crates/blenvy/src/save_load/saveable.rs diff --git a/crates/blenvy/src/lib.rs b/crates/blenvy/src/lib.rs index 9df591f..58cb3f9 100644 --- a/crates/blenvy/src/lib.rs +++ b/crates/blenvy/src/lib.rs @@ -10,6 +10,9 @@ pub use registry::*; pub mod blueprints; pub use blueprints::*; +pub mod save_load; +pub use save_load::*; + #[derive(Clone, Resource)] pub struct BlenvyConfig { // registry @@ -26,6 +29,8 @@ pub struct BlenvyConfig { // save & load pub(crate) save_component_filter: SceneFilter, pub(crate) save_resource_filter: SceneFilter, + //pub(crate) save_path: PathBuf, + // save_path: PathBuf::from("saves"), } #[derive(Debug, Clone)] @@ -63,6 +68,7 @@ impl Plugin for BlenvyPlugin { #[cfg(not(target_arch = "wasm32"))] ExportRegistryPlugin::default(), BlueprintsPlugin::default(), + SaveLoadPlugin::default() )) .insert_resource(BlenvyConfig { export_registry: self.export_registry, diff --git a/crates/blenvy/src/save_load/mod.rs b/crates/blenvy/src/save_load/mod.rs index b981b13..e88b968 100644 --- a/crates/blenvy/src/save_load/mod.rs +++ b/crates/blenvy/src/save_load/mod.rs @@ -1,108 +1,59 @@ -pub mod saveable; -use std::path::PathBuf; - -pub use saveable::*; - -pub mod saving; -pub use saving::*; - -pub mod loading; -pub use loading::*; - -use bevy::core_pipeline::core_3d::{Camera3dDepthTextureUsage, ScreenSpaceTransmissionQuality}; use bevy::prelude::*; -use bevy::prelude::{App, IntoSystemConfigs, Plugin}; -use blenvy::GltfBlueprintsSet; -#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] -pub enum SavingSet { - Save, -} +#[derive(Component, Reflect, Debug, Default)] +#[reflect(Component)] +/// component used to mark any entity as Dynamic: aka add this to make sure your entity is going to be saved +pub struct Dynamic; -#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] -pub enum LoadingSet { - Load, -} +#[derive(Component, Reflect, Debug, Default)] +#[reflect(Component)] +/// marker component for entities that do not have parents, or whose parents should be ignored when serializing +pub(crate) struct RootEntity; -// Plugin configuration +#[derive(Component, Debug)] +/// internal helper component to store parents before resetting them +pub(crate) struct OriginalParent(pub(crate) Entity); -#[derive(Clone, Resource)] -pub struct SaveLoadConfig { - pub(crate) save_path: PathBuf, - pub(crate) component_filter: SceneFilter, - pub(crate) resource_filter: SceneFilter, -} - -// define the plugin - -pub struct SaveLoadPlugin { - pub component_filter: SceneFilter, - pub resource_filter: SceneFilter, - pub save_path: PathBuf, -} - -impl Default for SaveLoadPlugin { - fn default() -> Self { - Self { - component_filter: SceneFilter::default(), - resource_filter: SceneFilter::default(), - save_path: PathBuf::from("scenes"), - } - } -} +/// Marker component to Flag the root entity of all static entities (immutables) #[derive(Component, Reflect, Debug, Default)] #[reflect(Component)] pub struct StaticEntitiesRoot; +/// Marker component to Flag the root entity of all dynamic entities (mutables) #[derive(Component, Reflect, Debug, Default)] #[reflect(Component)] pub struct DynamicEntitiesRoot; + +#[derive(Resource, Clone, Debug, Default, Reflect)] +#[reflect(Resource)] +pub struct StaticEntitiesBlueprintInfo { + //pub blueprint_info: BlueprintInfo, + pub path: String, +} + + +pub mod saving; +pub use saving::*; + +#[derive(Debug, Clone, Default)] +/// Plugin for saving & loading +pub struct SaveLoadPlugin {} + impl Plugin for SaveLoadPlugin { fn build(&self, app: &mut App) { app.register_type::() .register_type::() - // TODO: remove these in bevy 0.13, as these are now registered by default - .register_type::() - .register_type::() - .register_type::() - .add_event::() - .add_event::() - .add_event::() - .add_event::() - .insert_resource(SaveLoadConfig { - save_path: self.save_path.clone(), - component_filter: self.component_filter.clone(), - resource_filter: self.resource_filter.clone(), - }) - .configure_sets( - Update, - (LoadingSet::Load).chain().before(GltfBlueprintsSet::Spawn), //.before(GltfComponentsSet::Injection) - ) + .add_event::() + .add_event::() .add_systems( - PreUpdate, + Update, (prepare_save_game, apply_deferred, save_game, cleanup_save) .chain() .run_if(should_save), ) - .add_systems(Update, mark_load_requested) - .add_systems( - Update, - (unload_world, apply_deferred, load_game) - .chain() - .run_if(resource_exists::) - .run_if(not(resource_exists::)) - .in_set(LoadingSet::Load), - ) - .add_systems( - Update, - (load_static, apply_deferred, cleanup_loaded_scene) - .chain() - .run_if(resource_exists::) - // .run_if(in_state(AppState::LoadingGame)) - .in_set(LoadingSet::Load), - ); + ; } } diff --git a/crates/blenvy/src/save_load/loading.rs b/crates/blenvy/src/save_load/old/loading.rs similarity index 100% rename from crates/blenvy/src/save_load/loading.rs rename to crates/blenvy/src/save_load/old/loading.rs diff --git a/crates/blenvy/src/save_load/old/mod_old.rs b/crates/blenvy/src/save_load/old/mod_old.rs new file mode 100644 index 0000000..fb9e2a0 --- /dev/null +++ b/crates/blenvy/src/save_load/old/mod_old.rs @@ -0,0 +1,76 @@ +pub mod saveable; +use std::path::PathBuf; + +pub use saveable::*; + +pub mod saving; +pub use saving::*; + +pub mod loading; +pub use loading::*; + +use bevy::core_pipeline::core_3d::{Camera3dDepthTextureUsage, ScreenSpaceTransmissionQuality}; +use bevy::prelude::*; +use bevy::prelude::{App, IntoSystemConfigs, Plugin}; +use blenvy::GltfBlueprintsSet; + +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +pub enum SavingSet { + Save, +} + +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +pub enum LoadingSet { + Load, +} + + +#[derive(Component, Reflect, Debug, Default)] +#[reflect(Component)] +pub struct StaticEntitiesRoot; + +#[derive(Component, Reflect, Debug, Default)] +#[reflect(Component)] +pub struct DynamicEntitiesRoot; + +impl Plugin for SaveLoadPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .register_type::() + // TODO: remove these in bevy 0.13, as these are now registered by default + .register_type::() + .register_type::() + .register_type::() + .add_event::() + .add_event::() + .add_event::() + .add_event::() + .configure_sets( + Update, + (LoadingSet::Load).chain().before(GltfBlueprintsSet::Spawn), //.before(GltfComponentsSet::Injection) + ) + .add_systems( + PreUpdate, + (prepare_save_game, apply_deferred, save_game, cleanup_save) + .chain() + .run_if(should_save), + ) + .add_systems(Update, mark_load_requested) + .add_systems( + Update, + (unload_world, apply_deferred, load_game) + .chain() + .run_if(resource_exists::) + .run_if(not(resource_exists::)) + .in_set(LoadingSet::Load), + ) + .add_systems( + Update, + (load_static, apply_deferred, cleanup_loaded_scene) + .chain() + .run_if(resource_exists::) + // .run_if(in_state(AppState::LoadingGame)) + .in_set(LoadingSet::Load), + ); + } +} diff --git a/crates/blenvy/src/save_load/old/saving.rs b/crates/blenvy/src/save_load/old/saving.rs new file mode 100644 index 0000000..2c2812d --- /dev/null +++ b/crates/blenvy/src/save_load/old/saving.rs @@ -0,0 +1,196 @@ +use bevy::prelude::*; +use bevy::tasks::IoTaskPool; +use blenvy::{BlueprintName, InBlueprint, Library, SpawnHere}; + +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use crate::{DynamicEntitiesRoot, SaveLoadConfig, StaticEntitiesRoot}; + +#[derive(Event, Debug)] +pub struct SaveRequest { + pub path: String, +} + +#[derive(Event)] +pub struct SavingFinished; + +pub fn should_save(save_requests: EventReader) -> bool { + !save_requests.is_empty() +} + +#[derive(Resource, Clone, Debug, Default, Reflect)] +#[reflect(Resource)] +pub struct StaticEntitiesStorage { + pub name: String, + pub library_path: String, +} + +#[derive(Component, Reflect, Debug, Default)] +#[reflect(Component)] +/// marker component for entities that do not have parents, or whose parents should be ignored when serializing +pub(crate) struct RootEntity; + +#[derive(Component, Debug)] +/// internal helper component to store parents before resetting them +pub(crate) struct OriginalParent(pub(crate) Entity); + +// any child of dynamic/ saveable entities that is not saveable itself should be removed from the list of children +pub(crate) fn prepare_save_game( + saveables: Query, With)>, + root_entities: Query, Without)>>, // With + dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With>, + static_entities: Query<(Entity, &BlueprintName, Option<&Library>), With>, + + mut commands: Commands, +) { + for entity in saveables.iter() { + commands.entity(entity).insert(SpawnHere); + } + + for (entity, parent, children) in dynamic_entities.iter() { + let parent = parent.get(); + if root_entities.contains(parent) { + commands.entity(entity).insert(RootEntity); + } + + if let Some(children) = children { + for sub_child in children.iter() { + if !dynamic_entities.contains(*sub_child) { + commands.entity(*sub_child).insert(OriginalParent(entity)); + commands.entity(entity).remove_children(&[*sub_child]); + } + } + } + } + for (_, blueprint_name, library) in static_entities.iter() { + let library_path: String = library.map_or_else(|| "", |l| l.0.to_str().unwrap()).into(); + commands.insert_resource(StaticEntitiesStorage { + name: blueprint_name.0.clone(), + library_path, + }); + } +} + +pub(crate) fn save_game(world: &mut World) { + info!("saving"); + + let mut save_path: String = "".into(); + let mut events = world.resource_mut::>(); + + for event in events.get_reader().read(&events) { + info!("SAVE EVENT !! {:?}", event); + save_path.clone_from(&event.path); + } + events.clear(); + + let saveable_entities: Vec = world + .query_filtered::, Without, Without)>() + .iter(world) + .collect(); + + let saveable_root_entities: Vec = world + .query_filtered::, Without, With)>() + .iter(world) + .collect(); + + info!("saveable entities {}", saveable_entities.len()); + info!("saveable root entities {}", saveable_root_entities.len()); + + let save_load_config = world + .get_resource::() + .expect("SaveLoadConfig should exist at this stage"); + + // we hardcode some of the always allowed types + let filter = save_load_config + .component_filter + .clone() + .allow::() + .allow::() + .allow::() + .allow::() + .allow::() + + + ; + + // for root entities, it is the same EXCEPT we make sure parents are not included + let filter_root = filter.clone().deny::(); + + let filter_resources = save_load_config + .resource_filter + .clone() + .allow::() + ; + + // for default stuff + let scene_builder = DynamicSceneBuilder::from_world(world) + .with_filter(filter.clone()) + .with_resource_filter(filter_resources.clone()); + + let mut dyn_scene = scene_builder + .extract_resources() + .extract_entities(saveable_entities.clone().into_iter()) + .remove_empty_entities() + .build(); + + // for root entities + let scene_builder_root = DynamicSceneBuilder::from_world(world) + .with_filter(filter_root.clone()) + .with_resource_filter(filter_resources.clone()); + + // FIXME : add back + let mut dyn_scene_root = scene_builder_root + .extract_resources() + .extract_entities( + saveable_root_entities.clone().into_iter(), // .chain(static_world_markers.into_iter()), + ) + .remove_empty_entities() + .build(); + + dyn_scene.entities.append(&mut dyn_scene_root.entities); + // dyn_scene.resources.append(&mut dyn_scene_root.resources); + + let serialized_scene = dyn_scene + .serialize(&world.resource::().read()) + .unwrap(); + + let save_path = Path::new("assets") + .join(&save_load_config.save_path) + .join(Path::new(save_path.as_str())); // Path::new(&save_load_config.save_path).join(Path::new(save_path.as_str())); + info!("saving game to {:?}", save_path); + + // world.send_event(SavingFinished); + + #[cfg(not(target_arch = "wasm32"))] + IoTaskPool::get() + .spawn(async move { + // Write the scene RON data to file + File::create(save_path) + .and_then(|mut file| file.write(serialized_scene.as_bytes())) + .expect("Error while writing save to file"); + }) + .detach(); +} + +pub(crate) fn cleanup_save( + needs_parent_reset: Query<(Entity, &OriginalParent)>, + mut saving_finished: EventWriter, + mut commands: Commands, +) { + for (entity, original_parent) in needs_parent_reset.iter() { + commands.entity(original_parent.0).add_child(entity); + } + commands.remove_resource::(); + saving_finished.send(SavingFinished); +} +/* +pub(crate) fn cleanup_save(mut world: &mut World) { + + let mut query = world.query::<(Entity, &OriginalParent)>(); + for (mut entity, original_parent) in query.iter_mut(&mut world) { + let e = world.entity_mut(original_parent.0); + // .add_child(entity); + } +}*/ diff --git a/crates/blenvy/src/save_load/saveable.rs b/crates/blenvy/src/save_load/saveable.rs deleted file mode 100644 index c305594..0000000 --- a/crates/blenvy/src/save_load/saveable.rs +++ /dev/null @@ -1,7 +0,0 @@ -use bevy::prelude::*; - -#[derive(Component, Reflect, Debug, Default)] -#[reflect(Component)] -/// component used to mark any entity as Dynamic: aka add this to make sure your entity is going to be saved -pub struct Dynamic(pub bool); - diff --git a/crates/blenvy/src/save_load/saving.rs b/crates/blenvy/src/save_load/saving.rs index 5385f9b..b94ba43 100644 --- a/crates/blenvy/src/save_load/saving.rs +++ b/crates/blenvy/src/save_load/saving.rs @@ -1,55 +1,42 @@ -use bevy::prelude::*; -use bevy::tasks::IoTaskPool; -use blenvy::{BlueprintName, InBlueprint, Library, SpawnHere}; - use std::fs::File; use std::io::Write; use std::path::Path; -use crate::{Dynamic, DynamicEntitiesRoot, SaveLoadConfig, StaticEntitiesRoot}; +use bevy::render::camera::{CameraMainTextureUsages, CameraRenderGraph}; +use bevy::{prelude::*, tasks::IoTaskPool}; +use bevy::prelude::World; + +use crate::{BlenvyConfig, BlueprintInfo, Dynamic, FromBlueprint, RootEntity, SpawnBlueprint}; + +use super::{DynamicEntitiesRoot, OriginalParent, StaticEntitiesRoot}; #[derive(Event, Debug)] pub struct SaveRequest { pub path: String, } - #[derive(Event)] -pub struct SavingFinished; +pub struct SaveFinished; // TODO: merge the the events above pub fn should_save(save_requests: EventReader) -> bool { !save_requests.is_empty() } -#[derive(Resource, Clone, Debug, Default, Reflect)] -#[reflect(Resource)] -pub struct StaticEntitiesStorage { - pub name: String, - pub library_path: String, -} - -#[derive(Component, Reflect, Debug, Default)] -#[reflect(Component)] -/// marker component for entities that do not have parents, or whose parents should be ignored when serializing -pub(crate) struct RootEntity; - -#[derive(Component, Debug)] -/// internal helper component to store parents before resetting them -pub(crate) struct OriginalParent(pub(crate) Entity); // any child of dynamic/ saveable entities that is not saveable itself should be removed from the list of children pub(crate) fn prepare_save_game( - saveables: Query, With)>, + saveables: Query, With)>, root_entities: Query, Without)>>, // With dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With>, - static_entities: Query<(Entity, &BlueprintName, Option<&Library>), With>, + static_entities: Query<(Entity, &BlueprintInfo), With>, mut commands: Commands, ) { for entity in saveables.iter() { - commands.entity(entity).insert(SpawnHere); + commands.entity(entity).insert(SpawnBlueprint); } for (entity, parent, children) in dynamic_entities.iter() { + println!("prepare save game"); let parent = parent.get(); if root_entities.contains(parent) { commands.entity(entity).insert(RootEntity); @@ -64,14 +51,17 @@ pub(crate) fn prepare_save_game( } } } - for (_, blueprint_name, library) in static_entities.iter() { + /*for (_, blueprint_name) in static_entities.iter() { let library_path: String = library.map_or_else(|| "", |l| l.0.to_str().unwrap()).into(); commands.insert_resource(StaticEntitiesStorage { name: blueprint_name.0.clone(), library_path, }); - } + }*/ } + + + pub(crate) fn save_game(world: &mut World) { info!("saving"); @@ -85,39 +75,49 @@ pub(crate) fn save_game(world: &mut World) { events.clear(); let saveable_entities: Vec = world - .query_filtered::, Without, Without)>() + // .query_filtered::, Without, Without)>() + .query_filtered::, Without)>() .iter(world) .collect(); let saveable_root_entities: Vec = world - .query_filtered::, Without, With)>() + .query_filtered::, With)>() + //.query_filtered::, Without, With)>() .iter(world) .collect(); info!("saveable entities {}", saveable_entities.len()); info!("saveable root entities {}", saveable_root_entities.len()); - let save_load_config = world - .get_resource::() - .expect("SaveLoadConfig should exist at this stage"); + let config = world + .get_resource::() + .expect("Blenvy configuration should exist at this stage"); // we hardcode some of the always allowed types - let filter = save_load_config - .component_filter + let filter = config + .save_component_filter .clone() .allow::() .allow::() - .allow::() - .allow::() - .allow::(); + .allow::() + .allow::() + .allow::() + + /*.deny::() + .deny::() + .deny::>() + .deny::>() */ + ; // for root entities, it is the same EXCEPT we make sure parents are not included let filter_root = filter.clone().deny::(); - let filter_resources = save_load_config - .resource_filter - .clone() - .allow::(); + let filter_resources = config.clone() + .save_resource_filter + .deny::>() + + .clone(); + //.allow::(); // for default stuff let scene_builder = DynamicSceneBuilder::from_world(world) @@ -143,15 +143,15 @@ pub(crate) fn save_game(world: &mut World) { .remove_empty_entities() .build(); - dyn_scene.entities.append(&mut dyn_scene_root.entities); + // dyn_scene.entities.append(&mut dyn_scene_root.entities); // dyn_scene.resources.append(&mut dyn_scene_root.resources); let serialized_scene = dyn_scene .serialize(&world.resource::().read()) - .unwrap(); + .expect("filtered scene should serialize correctly"); let save_path = Path::new("assets") - .join(&save_load_config.save_path) + //.join(&config.save_path) .join(Path::new(save_path.as_str())); // Path::new(&save_load_config.save_path).join(Path::new(save_path.as_str())); info!("saving game to {:?}", save_path); @@ -170,21 +170,12 @@ pub(crate) fn save_game(world: &mut World) { pub(crate) fn cleanup_save( needs_parent_reset: Query<(Entity, &OriginalParent)>, - mut saving_finished: EventWriter, + mut saving_finished: EventWriter, mut commands: Commands, ) { for (entity, original_parent) in needs_parent_reset.iter() { commands.entity(original_parent.0).add_child(entity); } - commands.remove_resource::(); - saving_finished.send(SavingFinished); -} -/* -pub(crate) fn cleanup_save(mut world: &mut World) { - - let mut query = world.query::<(Entity, &OriginalParent)>(); - for (mut entity, original_parent) in query.iter_mut(&mut world) { - let e = world.entity_mut(original_parent.0); - // .add_child(entity); - } -}*/ + // commands.remove_resource::(); + saving_finished.send(SaveFinished); +} \ No newline at end of file