use std::any::TypeId;
use std::io::{self, Write};
use std::marker::PhantomData;
use std::path::PathBuf;
use bevy_ecs::entity::EntityHashSet;
use bevy_ecs::prelude::*;
use bevy_ecs::query::QueryFilter;
use bevy_log::prelude::*;
use bevy_scene::{DynamicScene, DynamicSceneBuilder, SceneFilter};
use moonshine_util::event::{OnSingle, SingleEvent, TriggerSingle};
use moonshine_util::Static;
use thiserror::Error;
use crate::{MapComponent, SceneMapper};
#[derive(Component, Default, Debug, Clone)]
pub struct Save;
pub trait TriggerSave {
#[doc(alias = "trigger_single")]
fn trigger_save(self, event: impl SaveEvent);
}
impl TriggerSave for &mut Commands<'_, '_> {
fn trigger_save(self, event: impl SaveEvent) {
self.trigger_single(event);
}
}
impl TriggerSave for &mut World {
fn trigger_save(self, event: impl SaveEvent) {
self.trigger_single(event);
}
}
pub trait SaveEvent: SingleEvent {
type SaveFilter: QueryFilter;
fn filter_entity(&self, _entity: EntityRef) -> bool {
true
}
fn before_save(&mut self, _world: &mut World) {}
fn before_serialize(&mut self, _world: &mut World, _entities: &[Entity]) {}
fn component_filter(&mut self) -> SceneFilter {
SceneFilter::allow_all()
}
fn resource_filter(&mut self) -> SceneFilter {
SceneFilter::deny_all()
}
fn after_save(&mut self, _world: &mut World, _result: &SaveResult) {}
fn output(&mut self) -> SaveOutput;
}
pub struct SaveWorld<F: QueryFilter = DefaultSaveFilter> {
pub entities: EntityFilter,
pub resources: SceneFilter,
pub components: SceneFilter,
pub mapper: SceneMapper,
pub output: SaveOutput,
#[doc(hidden)]
pub filter: PhantomData<F>,
}
impl<F: QueryFilter> SaveWorld<F> {
pub fn new(output: SaveOutput) -> Self {
Self {
entities: EntityFilter::allow_all(),
resources: SceneFilter::deny_all(),
components: SceneFilter::allow_all(),
mapper: SceneMapper::default(),
output,
filter: PhantomData,
}
}
pub fn into_file(path: impl Into<PathBuf>) -> Self {
Self {
entities: EntityFilter::allow_all(),
resources: SceneFilter::deny_all(),
components: SceneFilter::allow_all(),
mapper: SceneMapper::default(),
output: SaveOutput::file(path),
filter: PhantomData,
}
}
pub fn into_stream(stream: impl SaveStream) -> Self {
Self {
entities: EntityFilter::allow_all(),
resources: SceneFilter::deny_all(),
components: SceneFilter::allow_all(),
mapper: SceneMapper::default(),
output: SaveOutput::stream(stream),
filter: PhantomData,
}
}
pub fn include_resource<R: Resource>(mut self) -> Self {
self.resources = self.resources.allow::<R>();
self
}
pub fn include_resource_by_id(mut self, type_id: TypeId) -> Self {
self.resources = self.resources.allow_by_id(type_id);
self
}
pub fn exclude_component<T: Component>(mut self) -> Self {
self.components = self.components.deny::<T>();
self
}
pub fn exclude_component_by_id(mut self, type_id: TypeId) -> Self {
self.components = self.components.deny_by_id(type_id);
self
}
pub fn map_component<T: Component>(mut self, m: impl MapComponent<T>) -> Self {
self.mapper = self.mapper.map(m);
self
}
}
impl SaveWorld {
pub fn default_into_file(path: impl Into<PathBuf>) -> Self {
Self::into_file(path)
}
pub fn default_into_stream(stream: impl SaveStream) -> Self {
Self::into_stream(stream)
}
}
impl SaveWorld<()> {
pub fn all_into_file(path: impl Into<PathBuf>) -> Self {
Self::into_file(path)
}
pub fn all_into_stream(stream: impl SaveStream) -> Self {
Self::into_stream(stream)
}
}
impl<F: QueryFilter> SingleEvent for SaveWorld<F> where F: Static {}
impl<F: QueryFilter> SaveEvent for SaveWorld<F>
where
F: Static,
{
type SaveFilter = F;
fn filter_entity(&self, entity: EntityRef) -> bool {
match &self.entities {
EntityFilter::Allow(allow) => allow.contains(&entity.id()),
EntityFilter::Block(block) => !block.contains(&entity.id()),
}
}
fn before_serialize(&mut self, world: &mut World, entities: &[Entity]) {
for entity in entities {
self.mapper.apply(world.entity_mut(*entity));
}
}
fn after_save(&mut self, world: &mut World, result: &SaveResult) {
let Ok(saved) = result else {
return;
};
for entity in saved.entities() {
self.mapper.undo(world.entity_mut(entity));
}
}
fn component_filter(&mut self) -> SceneFilter {
std::mem::replace(&mut self.components, SceneFilter::Unset)
}
fn resource_filter(&mut self) -> SceneFilter {
std::mem::replace(&mut self.resources, SceneFilter::Unset)
}
fn output(&mut self) -> SaveOutput {
self.output.consume().unwrap()
}
}
pub type DefaultSaveFilter = With<Save>;
pub enum SaveOutput {
File(PathBuf),
Stream(Box<dyn SaveStream>),
Drop,
#[doc(hidden)]
Invalid,
}
impl SaveOutput {
pub fn file(path: impl Into<PathBuf>) -> Self {
Self::File(path.into())
}
pub fn stream<S: SaveStream + 'static>(stream: S) -> Self {
Self::Stream(Box::new(stream))
}
pub fn consume(&mut self) -> Option<SaveOutput> {
let output = std::mem::replace(self, SaveOutput::Invalid);
if let SaveOutput::Invalid = output {
return None;
}
Some(output)
}
}
#[derive(Clone, Debug)]
pub enum EntityFilter {
Allow(EntityHashSet),
Block(EntityHashSet),
}
impl EntityFilter {
pub fn allow_all() -> Self {
Self::Block(EntityHashSet::new())
}
pub fn allow(entities: impl IntoIterator<Item = Entity>) -> Self {
Self::Allow(entities.into_iter().collect())
}
pub fn block(entities: impl IntoIterator<Item = Entity>) -> Self {
Self::Block(entities.into_iter().collect())
}
}
impl Default for EntityFilter {
fn default() -> Self {
Self::allow_all()
}
}
pub trait SaveStream: Write
where
Self: Static,
{
}
impl<S: Write> SaveStream for S where S: Static {}
#[derive(Event)]
pub struct Saved {
pub scene: DynamicScene,
}
impl Saved {
pub fn entities(&self) -> impl Iterator<Item = Entity> + '_ {
self.scene.entities.iter().map(|de| de.entity)
}
}
#[doc(hidden)]
#[deprecated(since = "0.5.2", note = "use `Saved` instead")]
pub type OnSave = Saved;
#[derive(Error, Debug)]
pub enum SaveError {
#[error("Failed to serialize world: {0}")]
Ron(ron::Error),
#[error("Failed to write world: {0}")]
Io(io::Error),
}
impl From<ron::Error> for SaveError {
fn from(e: ron::Error) -> Self {
Self::Ron(e)
}
}
impl From<io::Error> for SaveError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
pub type SaveResult = Result<Saved, SaveError>;
pub fn save_on_default_event(event: OnSingle<SaveWorld>, commands: Commands) {
save_on(event, commands);
}
pub fn save_on<E: SaveEvent>(event: OnSingle<E>, mut commands: Commands) {
commands.queue_handled(SaveCommand(event.consume().unwrap()), |err, ctx| {
error!("save failed: {err:?} ({ctx})");
});
}
fn save_world<E: SaveEvent>(mut event: E, world: &mut World) -> SaveResult {
event.before_save(world);
let entities: Vec<_> = world
.query_filtered::<Entity, E::SaveFilter>()
.iter(world)
.filter(|entity| event.filter_entity(world.entity(*entity)))
.collect();
event.before_serialize(world, &entities);
let scene = DynamicSceneBuilder::from_world(world)
.with_component_filter(event.component_filter())
.with_resource_filter(event.resource_filter())
.extract_resources()
.extract_entities(entities.iter().copied())
.build();
let saved = match event.output() {
SaveOutput::File(path) => {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let type_registry = world.resource::<AppTypeRegistry>().read();
let data = scene.serialize(&type_registry)?;
std::fs::write(&path, data.as_bytes())?;
debug!("saved into file: {path:?}");
Saved { scene }
}
SaveOutput::Stream(mut stream) => {
let type_registry = world.resource::<AppTypeRegistry>().read();
let data = scene.serialize(&type_registry)?;
stream.write_all(data.as_bytes())?;
debug!("saved into stream");
Saved { scene }
}
SaveOutput::Drop => {
debug!("saved data dropped");
Saved { scene }
}
SaveOutput::Invalid => {
panic!("SaveOutput is invalid");
}
};
let result = Ok(saved);
event.after_save(world, &result);
result
}
struct SaveCommand<E>(E);
impl<E: SaveEvent> Command<Result<(), SaveError>> for SaveCommand<E> {
fn apply(self, world: &mut World) -> Result<(), SaveError> {
let saved = save_world(self.0, world)?;
world.trigger(saved);
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::fs::*;
use bevy::prelude::*;
use bevy_ecs::system::RunSystemOnce;
use super::*;
#[derive(Component, Default, Reflect)]
#[reflect(Component)]
#[require(Save)]
struct Foo;
fn app() -> App {
let mut app = App::new();
app.add_plugins(MinimalPlugins).register_type::<Foo>();
app
}
#[test]
fn test_save_into_file() {
#[derive(Resource)]
struct EventTriggered;
pub const PATH: &str = "test_save_into_file.ron";
let mut app = app();
app.add_observer(save_on_default_event);
app.add_observer(|_: On<Saved>, mut commands: Commands| {
commands.insert_resource(EventTriggered);
});
let _ = app.world_mut().run_system_once(|mut commands: Commands| {
commands.spawn((Foo, Save));
commands.trigger_save(SaveWorld::default_into_file(PATH));
});
let data = read_to_string(PATH).unwrap();
let world = app.world();
assert!(data.contains("Foo"));
assert!(world.contains_resource::<EventTriggered>());
remove_file(PATH).unwrap();
}
#[test]
fn test_save_into_stream() {
pub const PATH: &str = "test_save_to_stream.ron";
let mut app = app();
app.add_observer(save_on_default_event);
let _ = app.world_mut().run_system_once(|mut commands: Commands| {
commands.spawn((Foo, Save));
commands.trigger_save(SaveWorld::default_into_stream(File::create(PATH).unwrap()));
});
let data = read_to_string(PATH).unwrap();
assert!(data.contains("Foo"));
remove_file(PATH).unwrap();
}
#[test]
fn test_save_resource() {
pub const PATH: &str = "test_save_resource.ron";
#[derive(Resource, Default, Reflect)]
#[reflect(Resource)]
struct Bar;
let mut app = app();
app.register_type::<Bar>()
.add_observer(save_on_default_event);
let _ = app.world_mut().run_system_once(|mut commands: Commands| {
commands.insert_resource(Bar);
commands.trigger_save(
SaveWorld::default_into_stream(File::create(PATH).unwrap())
.include_resource::<Bar>(),
);
});
app.update();
let data = read_to_string(PATH).unwrap();
assert!(data.contains("Bar"));
remove_file(PATH).unwrap();
}
#[test]
fn test_save_without_component() {
pub const PATH: &str = "test_save_without_component.ron";
#[derive(Component, Default, Reflect)]
#[reflect(Component)]
#[require(Save)]
struct Baz;
let mut app = app();
app.add_observer(save_on_default_event);
let _ = app.world_mut().run_system_once(|mut commands: Commands| {
commands.spawn((Foo, Baz, Save));
commands.trigger_save(SaveWorld::default_into_file(PATH).exclude_component::<Baz>());
});
let data = read_to_string(PATH).unwrap();
assert!(data.contains("Foo"));
assert!(!data.contains("Baz"));
remove_file(PATH).unwrap();
}
#[test]
fn test_map_component() {
pub const PATH: &str = "test_map_component.ron";
#[derive(Component, Default)]
struct Bar(#[allow(dead_code)] u32);
#[derive(Component, Default, Reflect)]
#[reflect(Component)]
struct Baz(u32);
let mut app = app();
app.register_type::<Baz>()
.add_observer(save_on_default_event);
let entity = app
.world_mut()
.run_system_once(|mut commands: Commands| {
let entity = commands.spawn((Bar(12), Save)).id();
commands.trigger_save(
SaveWorld::default_into_file(PATH).map_component::<Bar>(|Bar(i): &Bar| Baz(*i)),
);
entity
})
.unwrap();
let data = read_to_string(PATH).unwrap();
assert!(data.contains("Baz"));
assert!(data.contains("(12)"));
assert!(!data.contains("Bar"));
assert!(app.world().entity(entity).contains::<Bar>());
assert!(!app.world().entity(entity).contains::<Baz>());
remove_file(PATH).unwrap();
}
}