use std::io::{self, Read};
use std::marker::PhantomData;
use std::path::PathBuf;
use bevy_scene::DynamicScene;
use moonshine_util::expect::{expect_deferred, ExpectDeferred};
use moonshine_util::Static;
use serde::de::DeserializeSeed;
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::prelude::*;
use bevy_ecs::query::QueryFilter;
use bevy_log::prelude::*;
use bevy_scene::{serde::SceneDeserializer, SceneSpawnError};
use moonshine_util::event::{OnSingle, SingleEvent, TriggerSingle};
use thiserror::Error;
use crate::save::Save;
use crate::{MapComponent, SceneMapper};
#[derive(Component, Default, Clone)]
pub struct Unload;
pub trait TriggerLoad {
#[doc(alias = "trigger_single")]
fn trigger_load(self, event: impl LoadEvent);
}
impl TriggerLoad for &mut Commands<'_, '_> {
fn trigger_load(self, event: impl LoadEvent) {
self.trigger_single(event);
}
}
impl TriggerLoad for &mut World {
fn trigger_load(self, event: impl LoadEvent) {
self.trigger_single(event);
}
}
pub type DefaultUnloadFilter = Or<(With<Save>, With<Unload>)>;
pub trait LoadEvent: SingleEvent {
type UnloadFilter: QueryFilter;
fn input(&mut self) -> LoadInput;
fn before_load(&mut self, _world: &mut World) {}
fn before_unload(&mut self, _world: &mut World, _entities: &[Entity]) {}
fn after_load(&mut self, _world: &mut World, _result: &LoadResult) {}
}
pub struct LoadWorld<U: QueryFilter = DefaultUnloadFilter> {
pub input: LoadInput,
pub mapper: SceneMapper,
#[doc(hidden)]
pub unload: PhantomData<U>,
}
impl<U: QueryFilter> LoadWorld<U> {
pub fn new(input: LoadInput, mapper: SceneMapper) -> Self {
LoadWorld {
input,
mapper,
unload: PhantomData,
}
}
pub fn from_file(path: impl Into<PathBuf>) -> Self {
LoadWorld {
input: LoadInput::File(path.into()),
mapper: SceneMapper::default(),
unload: PhantomData,
}
}
pub fn from_stream(stream: impl LoadStream) -> Self {
LoadWorld {
input: LoadInput::Stream(Box::new(stream)),
mapper: SceneMapper::default(),
unload: PhantomData,
}
}
pub fn map_component<T: Component>(self, m: impl MapComponent<T>) -> Self {
LoadWorld {
mapper: self.mapper.map(m),
..self
}
}
}
impl LoadWorld {
pub fn default_from_file(path: impl Into<PathBuf>) -> Self {
Self::from_file(path)
}
pub fn default_from_stream(stream: impl LoadStream) -> Self {
Self::from_stream(stream)
}
}
impl<U: QueryFilter> SingleEvent for LoadWorld<U> where U: Static {}
impl<U: QueryFilter> LoadEvent for LoadWorld<U>
where
U: Static,
{
type UnloadFilter = U;
fn input(&mut self) -> LoadInput {
self.input.consume().unwrap()
}
fn before_load(&mut self, world: &mut World) {
world.insert_resource(ExpectDeferred);
}
fn after_load(&mut self, world: &mut World, result: &LoadResult) {
if let Ok(loaded) = result {
for entity in loaded.entities() {
let Ok(entity) = world.get_entity_mut(entity) else {
continue;
};
self.mapper.replace(entity);
}
}
expect_deferred(world);
}
}
pub enum LoadInput {
File(PathBuf),
Stream(Box<dyn LoadStream>),
Scene(DynamicScene),
#[doc(hidden)]
Invalid,
}
impl LoadInput {
pub fn file(path: impl Into<PathBuf>) -> Self {
Self::File(path.into())
}
pub fn stream<S: LoadStream + 'static>(stream: S) -> Self {
Self::Stream(Box::new(stream))
}
pub fn consume(&mut self) -> Option<LoadInput> {
let input = std::mem::replace(self, LoadInput::Invalid);
if let LoadInput::Invalid = input {
return None;
}
Some(input)
}
}
pub trait LoadStream: Read
where
Self: Static,
{
}
impl<S: Read> LoadStream for S where S: Static {}
#[derive(Event)]
pub struct Loaded {
pub entity_map: EntityHashMap<Entity>,
}
impl Loaded {
pub fn entities(&self) -> impl Iterator<Item = Entity> + '_ {
self.entity_map.values().copied()
}
}
#[doc(hidden)]
#[deprecated(since = "0.5.2", note = "use `Loaded` instead")]
pub type OnLoad = Loaded;
#[derive(Error, Debug)]
pub enum LoadError {
#[error("Failed to read world: {0}")]
Io(io::Error),
#[error("Failed to deserialize world: {0}")]
Ron(ron::Error),
#[error("Failed to spawn scene: {0}")]
Scene(SceneSpawnError),
}
impl From<io::Error> for LoadError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
impl From<ron::de::SpannedError> for LoadError {
fn from(e: ron::de::SpannedError) -> Self {
Self::Ron(e.into())
}
}
impl From<ron::Error> for LoadError {
fn from(e: ron::Error) -> Self {
Self::Ron(e)
}
}
impl From<SceneSpawnError> for LoadError {
fn from(e: SceneSpawnError) -> Self {
Self::Scene(e)
}
}
pub type LoadResult = Result<Loaded, LoadError>;
pub fn load_on_default_event(event: OnSingle<LoadWorld>, commands: Commands) {
load_on(event, commands);
}
pub fn load_on<E: LoadEvent>(event: OnSingle<E>, mut commands: Commands) {
commands.queue_handled(LoadCommand(event.consume().unwrap()), |err, ctx| {
error!("load failed: {err:?} ({ctx})");
});
}
fn load_world<E: LoadEvent>(mut event: E, world: &mut World) -> LoadResult {
event.before_load(world);
let scene = match event.input() {
LoadInput::File(path) => {
let bytes = std::fs::read(&path)?;
let mut deserializer = ron::Deserializer::from_bytes(&bytes)?;
let type_registry = &world.resource::<AppTypeRegistry>().read();
let scene_deserializer = SceneDeserializer { type_registry };
scene_deserializer.deserialize(&mut deserializer).unwrap()
}
LoadInput::Stream(mut stream) => {
let mut bytes = Vec::new();
stream.read_to_end(&mut bytes)?;
let mut deserializer = ron::Deserializer::from_bytes(&bytes)?;
let type_registry = &world.resource::<AppTypeRegistry>().read();
let scene_deserializer = SceneDeserializer { type_registry };
scene_deserializer.deserialize(&mut deserializer)?
}
LoadInput::Scene(scene) => scene,
LoadInput::Invalid => {
panic!("LoadInput is invalid");
}
};
let entities: Vec<_> = world
.query_filtered::<Entity, E::UnloadFilter>()
.iter(world)
.collect();
event.before_unload(world, &entities);
for entity in entities {
if let Ok(entity) = world.get_entity_mut(entity) {
entity.despawn();
}
}
let mut entity_map = EntityHashMap::default();
scene.write_to_world(world, &mut entity_map)?;
debug!("loaded {} entities", entity_map.len());
let result = Ok(Loaded { entity_map });
event.after_load(world, &result);
result
}
struct LoadCommand<E>(E);
impl<E: LoadEvent> Command<Result<(), LoadError>> for LoadCommand<E> {
fn apply(self, world: &mut World) -> Result<(), LoadError> {
let loaded = load_world(self.0, world)?;
world.trigger(loaded);
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::fs::*;
use bevy::prelude::*;
use bevy_ecs::system::RunSystemOnce;
use super::*;
pub const DATA: &str = "(
resources: {},
entities: {
4294967293: (
components: {
\"moonshine_save::load::tests::Foo\": (),
},
),
},
)";
#[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_load_file() {
#[derive(Resource)]
struct EventTriggered;
pub const PATH: &str = "test_load_file.ron";
write(PATH, DATA).unwrap();
let mut app = app();
app.add_observer(load_on_default_event);
app.add_observer(|_: On<Loaded>, mut commands: Commands| {
commands.insert_resource(EventTriggered);
});
let _ = app.world_mut().run_system_once(|mut commands: Commands| {
commands.trigger_load(LoadWorld::default_from_file(PATH));
});
let world = app.world_mut();
assert!(world.contains_resource::<EventTriggered>());
assert!(world
.query_filtered::<(), With<Foo>>()
.single(world)
.is_ok());
remove_file(PATH).unwrap();
}
#[test]
fn test_load_stream() {
pub const PATH: &str = "test_load_stream.ron";
write(PATH, DATA).unwrap();
let mut app = app();
app.add_observer(load_on_default_event);
let _ = app.world_mut().run_system_once(|mut commands: Commands| {
commands.spawn((Foo, Save));
commands.trigger_load(LoadWorld::default_from_stream(File::open(PATH).unwrap()));
});
let data = read_to_string(PATH).unwrap();
assert!(data.contains("Foo"));
remove_file(PATH).unwrap();
}
#[test]
fn test_load_map_component() {
pub const PATH: &str = "test_load_map_component.ron";
write(PATH, DATA).unwrap();
#[derive(Component)]
struct Bar;
let mut app = app();
app.add_observer(load_on_default_event);
let _ = app.world_mut().run_system_once(|mut commands: Commands| {
commands.trigger_load(LoadWorld::default_from_file(PATH).map_component(|_: &Foo| Bar));
});
let world = app.world_mut();
assert!(world
.query_filtered::<(), With<Bar>>()
.single(world)
.is_ok());
assert!(world.query_filtered::<(), With<Foo>>().iter(world).count() == 0);
remove_file(PATH).unwrap();
}
}