feat(Blenvy:Bevy): slowly adding back save/load (wip)

* moved out old code
 * added very basics of saving (HEAVY WIP)
This commit is contained in:
kaosat.dev 2024-08-03 01:28:46 +02:00
parent ae9f07f549
commit 171ec7490a
7 changed files with 359 additions and 146 deletions

View File

@ -10,6 +10,9 @@ pub use registry::*;
pub mod blueprints; pub mod blueprints;
pub use blueprints::*; pub use blueprints::*;
pub mod save_load;
pub use save_load::*;
#[derive(Clone, Resource)] #[derive(Clone, Resource)]
pub struct BlenvyConfig { pub struct BlenvyConfig {
// registry // registry
@ -26,6 +29,8 @@ pub struct BlenvyConfig {
// save & load // save & load
pub(crate) save_component_filter: SceneFilter, pub(crate) save_component_filter: SceneFilter,
pub(crate) save_resource_filter: SceneFilter, pub(crate) save_resource_filter: SceneFilter,
//pub(crate) save_path: PathBuf,
// save_path: PathBuf::from("saves"),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -63,6 +68,7 @@ impl Plugin for BlenvyPlugin {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
ExportRegistryPlugin::default(), ExportRegistryPlugin::default(),
BlueprintsPlugin::default(), BlueprintsPlugin::default(),
SaveLoadPlugin::default()
)) ))
.insert_resource(BlenvyConfig { .insert_resource(BlenvyConfig {
export_registry: self.export_registry, export_registry: self.export_registry,

View File

@ -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::*;
use bevy::prelude::{App, IntoSystemConfigs, Plugin};
use blenvy::GltfBlueprintsSet;
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] #[derive(Component, Reflect, Debug, Default)]
pub enum SavingSet { #[reflect(Component)]
Save, /// 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)] #[derive(Component, Reflect, Debug, Default)]
pub enum LoadingSet { #[reflect(Component)]
Load, /// 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)] #[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)] #[reflect(Component)]
pub struct StaticEntitiesRoot; pub struct StaticEntitiesRoot;
/// Marker component to Flag the root entity of all dynamic entities (mutables)
#[derive(Component, Reflect, Debug, Default)] #[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)] #[reflect(Component)]
pub struct DynamicEntitiesRoot; 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 { impl Plugin for SaveLoadPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.register_type::<Dynamic>() app.register_type::<Dynamic>()
.register_type::<StaticEntitiesRoot>() .register_type::<StaticEntitiesRoot>()
// TODO: remove these in bevy 0.13, as these are now registered by default
.register_type::<Camera3dDepthTextureUsage>()
.register_type::<ScreenSpaceTransmissionQuality>()
.register_type::<StaticEntitiesStorage>()
.add_event::<SaveRequest>()
.add_event::<LoadRequest>()
.add_event::<LoadingFinished>()
.add_event::<SavingFinished>()
.insert_resource(SaveLoadConfig {
save_path: self.save_path.clone(),
component_filter: self.component_filter.clone(), .add_event::<SaveRequest>()
resource_filter: self.resource_filter.clone(), .add_event::<SaveFinished>()
})
.configure_sets(
Update,
(LoadingSet::Load).chain().before(GltfBlueprintsSet::Spawn), //.before(GltfComponentsSet::Injection)
)
.add_systems( .add_systems(
PreUpdate, Update,
(prepare_save_game, apply_deferred, save_game, cleanup_save) (prepare_save_game, apply_deferred, save_game, cleanup_save)
.chain() .chain()
.run_if(should_save), .run_if(should_save),
) )
.add_systems(Update, mark_load_requested) ;
.add_systems(
Update,
(unload_world, apply_deferred, load_game)
.chain()
.run_if(resource_exists::<LoadRequested>)
.run_if(not(resource_exists::<LoadFirstStageDone>))
.in_set(LoadingSet::Load),
)
.add_systems(
Update,
(load_static, apply_deferred, cleanup_loaded_scene)
.chain()
.run_if(resource_exists::<LoadFirstStageDone>)
// .run_if(in_state(AppState::LoadingGame))
.in_set(LoadingSet::Load),
);
} }
} }

View File

@ -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::<Dynamic>()
.register_type::<StaticEntitiesRoot>()
// TODO: remove these in bevy 0.13, as these are now registered by default
.register_type::<Camera3dDepthTextureUsage>()
.register_type::<ScreenSpaceTransmissionQuality>()
.register_type::<StaticEntitiesStorage>()
.add_event::<SaveRequest>()
.add_event::<LoadRequest>()
.add_event::<LoadingFinished>()
.add_event::<SavingFinished>()
.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::<LoadRequested>)
.run_if(not(resource_exists::<LoadFirstStageDone>))
.in_set(LoadingSet::Load),
)
.add_systems(
Update,
(load_static, apply_deferred, cleanup_loaded_scene)
.chain()
.run_if(resource_exists::<LoadFirstStageDone>)
// .run_if(in_state(AppState::LoadingGame))
.in_set(LoadingSet::Load),
);
}
}

View File

@ -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<SaveRequest>) -> 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<Entity, (With<Dynamic>, With<BlueprintName>)>,
root_entities: Query<Entity, Or<(With<DynamicEntitiesRoot>, Without<Parent>)>>, // With<DynamicEntitiesRoot>
dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With<Dynamic>>,
static_entities: Query<(Entity, &BlueprintName, Option<&Library>), With<StaticEntitiesRoot>>,
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::<Events<SaveRequest>>();
for event in events.get_reader().read(&events) {
info!("SAVE EVENT !! {:?}", event);
save_path.clone_from(&event.path);
}
events.clear();
let saveable_entities: Vec<Entity> = world
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, Without<RootEntity>)>()
.iter(world)
.collect();
let saveable_root_entities: Vec<Entity> = world
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, With<RootEntity>)>()
.iter(world)
.collect();
info!("saveable entities {}", saveable_entities.len());
info!("saveable root entities {}", saveable_root_entities.len());
let save_load_config = world
.get_resource::<SaveLoadConfig>()
.expect("SaveLoadConfig should exist at this stage");
// we hardcode some of the always allowed types
let filter = save_load_config
.component_filter
.clone()
.allow::<Parent>()
.allow::<Children>()
.allow::<BlueprintName>()
.allow::<SpawnHere>()
.allow::<Dynamic>()
;
// for root entities, it is the same EXCEPT we make sure parents are not included
let filter_root = filter.clone().deny::<Parent>();
let filter_resources = save_load_config
.resource_filter
.clone()
.allow::<StaticEntitiesStorage>()
;
// 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::<AppTypeRegistry>().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<SavingFinished>,
mut commands: Commands,
) {
for (entity, original_parent) in needs_parent_reset.iter() {
commands.entity(original_parent.0).add_child(entity);
}
commands.remove_resource::<StaticEntitiesStorage>();
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);
}
}*/

View File

@ -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);

View File

@ -1,55 +1,42 @@
use bevy::prelude::*;
use bevy::tasks::IoTaskPool;
use blenvy::{BlueprintName, InBlueprint, Library, SpawnHere};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::Path; 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)] #[derive(Event, Debug)]
pub struct SaveRequest { pub struct SaveRequest {
pub path: String, pub path: String,
} }
#[derive(Event)] #[derive(Event)]
pub struct SavingFinished; pub struct SaveFinished; // TODO: merge the the events above
pub fn should_save(save_requests: EventReader<SaveRequest>) -> bool { pub fn should_save(save_requests: EventReader<SaveRequest>) -> bool {
!save_requests.is_empty() !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 // 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( pub(crate) fn prepare_save_game(
saveables: Query<Entity, (With<Dynamic>, With<BlueprintName>)>, saveables: Query<Entity, (With<Dynamic>, With<BlueprintInfo>)>,
root_entities: Query<Entity, Or<(With<DynamicEntitiesRoot>, Without<Parent>)>>, // With<DynamicEntitiesRoot> root_entities: Query<Entity, Or<(With<DynamicEntitiesRoot>, Without<Parent>)>>, // With<DynamicEntitiesRoot>
dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With<Dynamic>>, dynamic_entities: Query<(Entity, &Parent, Option<&Children>), With<Dynamic>>,
static_entities: Query<(Entity, &BlueprintName, Option<&Library>), With<StaticEntitiesRoot>>, static_entities: Query<(Entity, &BlueprintInfo), With<StaticEntitiesRoot>>,
mut commands: Commands, mut commands: Commands,
) { ) {
for entity in saveables.iter() { for entity in saveables.iter() {
commands.entity(entity).insert(SpawnHere); commands.entity(entity).insert(SpawnBlueprint);
} }
for (entity, parent, children) in dynamic_entities.iter() { for (entity, parent, children) in dynamic_entities.iter() {
println!("prepare save game");
let parent = parent.get(); let parent = parent.get();
if root_entities.contains(parent) { if root_entities.contains(parent) {
commands.entity(entity).insert(RootEntity); 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(); let library_path: String = library.map_or_else(|| "", |l| l.0.to_str().unwrap()).into();
commands.insert_resource(StaticEntitiesStorage { commands.insert_resource(StaticEntitiesStorage {
name: blueprint_name.0.clone(), name: blueprint_name.0.clone(),
library_path, library_path,
}); });
}*/
} }
}
pub(crate) fn save_game(world: &mut World) { pub(crate) fn save_game(world: &mut World) {
info!("saving"); info!("saving");
@ -85,39 +75,49 @@ pub(crate) fn save_game(world: &mut World) {
events.clear(); events.clear();
let saveable_entities: Vec<Entity> = world let saveable_entities: Vec<Entity> = world
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, Without<RootEntity>)>() // .query_filtered::<Entity, (With<Dynamic>, Without<FromBlueprint>, Without<RootEntity>)>()
.query_filtered::<Entity, (With<Dynamic>, Without<RootEntity>)>()
.iter(world) .iter(world)
.collect(); .collect();
let saveable_root_entities: Vec<Entity> = world let saveable_root_entities: Vec<Entity> = world
.query_filtered::<Entity, (With<Dynamic>, Without<InBlueprint>, With<RootEntity>)>() .query_filtered::<Entity, (With<Dynamic>, With<RootEntity>)>()
//.query_filtered::<Entity, (With<Dynamic>, Without<FromBlueprint>, With<RootEntity>)>()
.iter(world) .iter(world)
.collect(); .collect();
info!("saveable entities {}", saveable_entities.len()); info!("saveable entities {}", saveable_entities.len());
info!("saveable root entities {}", saveable_root_entities.len()); info!("saveable root entities {}", saveable_root_entities.len());
let save_load_config = world let config = world
.get_resource::<SaveLoadConfig>() .get_resource::<BlenvyConfig>()
.expect("SaveLoadConfig should exist at this stage"); .expect("Blenvy configuration should exist at this stage");
// we hardcode some of the always allowed types // we hardcode some of the always allowed types
let filter = save_load_config let filter = config
.component_filter .save_component_filter
.clone() .clone()
.allow::<Parent>() .allow::<Parent>()
.allow::<Children>() .allow::<Children>()
.allow::<BlueprintName>() .allow::<BlueprintInfo>()
.allow::<SpawnHere>() .allow::<SpawnBlueprint>()
.allow::<Dynamic>(); .allow::<Dynamic>()
/*.deny::<CameraRenderGraph>()
.deny::<CameraMainTextureUsages>()
.deny::<Handle<Mesh>>()
.deny::<Handle<StandardMaterial>>() */
;
// for root entities, it is the same EXCEPT we make sure parents are not included // for root entities, it is the same EXCEPT we make sure parents are not included
let filter_root = filter.clone().deny::<Parent>(); let filter_root = filter.clone().deny::<Parent>();
let filter_resources = save_load_config let filter_resources = config.clone()
.resource_filter .save_resource_filter
.clone() .deny::<Time<Real>>()
.allow::<StaticEntitiesStorage>();
.clone();
//.allow::<StaticEntitiesStorage>();
// for default stuff // for default stuff
let scene_builder = DynamicSceneBuilder::from_world(world) let scene_builder = DynamicSceneBuilder::from_world(world)
@ -143,15 +143,15 @@ pub(crate) fn save_game(world: &mut World) {
.remove_empty_entities() .remove_empty_entities()
.build(); .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); // dyn_scene.resources.append(&mut dyn_scene_root.resources);
let serialized_scene = dyn_scene let serialized_scene = dyn_scene
.serialize(&world.resource::<AppTypeRegistry>().read()) .serialize(&world.resource::<AppTypeRegistry>().read())
.unwrap(); .expect("filtered scene should serialize correctly");
let save_path = Path::new("assets") 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())); .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); info!("saving game to {:?}", save_path);
@ -170,21 +170,12 @@ pub(crate) fn save_game(world: &mut World) {
pub(crate) fn cleanup_save( pub(crate) fn cleanup_save(
needs_parent_reset: Query<(Entity, &OriginalParent)>, needs_parent_reset: Query<(Entity, &OriginalParent)>,
mut saving_finished: EventWriter<SavingFinished>, mut saving_finished: EventWriter<SaveFinished>,
mut commands: Commands, mut commands: Commands,
) { ) {
for (entity, original_parent) in needs_parent_reset.iter() { for (entity, original_parent) in needs_parent_reset.iter() {
commands.entity(original_parent.0).add_child(entity); commands.entity(original_parent.0).add_child(entity);
} }
commands.remove_resource::<StaticEntitiesStorage>(); // commands.remove_resource::<StaticEntitiesStorage>();
saving_finished.send(SavingFinished); saving_finished.send(SaveFinished);
} }
/*
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);
}
}*/