moonshine_save/
load.rs

1//! Elements related to loading saved world state.
2//!
3//! # Example
4//! ```
5//! use bevy::prelude::*;
6//! use moonshine_save::prelude::*;
7//!
8//! #[derive(Component, Default, Reflect)]
9//! #[reflect(Component)]
10//! struct Data(u32);
11//!
12//! # fn generate_data() {
13//! #   let mut app = App::new();
14//! #   app.add_plugins((MinimalPlugins, SavePlugin))
15//! #       .register_type::<Data>()
16//! #       .add_systems(PreUpdate, save_default().into(static_file("example.ron")));
17//! #   app.world_mut().spawn((Data(12), Save));
18//! #   app.update();
19//! # }
20//! #
21//! # generate_data();
22//! #
23//! let mut app = App::new();
24//! app.add_plugins((MinimalPlugins, LoadPlugin))
25//!     .register_type::<Data>()
26//!     .add_systems(PreUpdate, load(static_file("example.ron")));
27//!
28//! app.update();
29//!
30//! let data = std::fs::read_to_string("example.ron").unwrap();
31//! # assert!(data.contains("(12)"));
32//! # std::fs::remove_file("example.ron");
33//! ```
34
35use std::io::{self, Read};
36use std::path::PathBuf;
37
38use bevy_app::{App, Plugin, PreUpdate};
39use bevy_ecs::entity::EntityHashMap;
40use bevy_ecs::{prelude::*, query::QueryFilter, schedule::SystemConfigs};
41use bevy_hierarchy::DespawnRecursiveExt;
42use bevy_scene::{serde::SceneDeserializer, SceneSpawnError};
43use bevy_utils::tracing::{error, info, warn};
44use moonshine_util::system::*;
45use serde::de::DeserializeSeed;
46
47use crate::{
48    file_from_event, file_from_resource,
49    save::{Save, SaveSystem, Saved},
50    static_file, FileFromEvent, FileFromResource, GetFilePath, MapComponent, Pipeline, SceneMapper,
51    StaticFile,
52};
53use crate::{GetStaticStream, GetStream, StaticStream, StreamFromEvent, StreamFromResource};
54
55/// A [`Plugin`] which configures [`LoadSystem`] in [`PreUpdate`] schedule.
56pub struct LoadPlugin;
57
58impl Plugin for LoadPlugin {
59    fn build(&self, app: &mut App) {
60        app.configure_sets(
61            PreUpdate,
62            (
63                LoadSystem::Load,
64                LoadSystem::PostLoad.run_if(has_resource::<Loaded>),
65            )
66                .chain()
67                .before(SaveSystem::Save),
68        )
69        .add_systems(
70            PreUpdate,
71            remove_resource::<Loaded>.in_set(LoadSystem::PostLoad),
72        );
73    }
74}
75
76/// A [`SystemSet`] for systems that process loading [`Saved`] data.
77#[derive(Clone, Debug, Hash, PartialEq, Eq, SystemSet)]
78pub enum LoadSystem {
79    /// Reserved for systems which deserialize [`Saved`] data and process the output.
80    Load,
81    /// Runs after [`LoadSystem::Load`].
82    PostLoad,
83}
84
85/// A [`Component`] which marks its [`Entity`] to be despawned prior to load.
86///
87/// # Usage
88/// When saving game state, it is often undesirable to save visual and aesthetic elements of the game.
89/// Elements such as transforms, camera settings, scene hierarchy, or UI elements are typically either
90/// spawned at game start, or added during initialization of the game data they represent.
91///
92/// This component may be used on such entities to despawn them prior to loading.
93///
94/// # Example
95/// ```
96/// use bevy::prelude::*;
97/// use moonshine_save::prelude::*;
98///
99/// #[derive(Bundle)]
100/// struct PlayerBundle {
101///     player: Player,
102///     /* Saved Player Data */
103///     save: Save,
104/// }
105///
106/// #[derive(Component, Default, Reflect)]
107/// #[reflect(Component)]
108/// struct Player;
109///
110/// #[derive(Component)] // <-- Not serialized!
111/// struct PlayerSprite(Entity);
112///
113/// #[derive(Bundle, Default)]
114/// struct PlayerSpriteBundle {
115///     /* Player Visuals/Aesthetics */
116///     unload: Unload,
117/// }
118///
119/// fn spawn_player_sprite(query: Query<Entity, Added<Player>>, mut commands: Commands) {
120///     for entity in &query {
121///         let sprite = PlayerSprite(commands.spawn(PlayerSpriteBundle::default()).id());
122///         commands.entity(entity).insert(sprite);
123///     }
124/// }
125/// ```
126#[derive(Component, Default, Clone)]
127pub struct Unload;
128
129/// A [`Resource`] which contains the loaded entity map. See [`FromLoaded`] for usage.
130#[derive(Resource)]
131pub struct Loaded {
132    pub entity_map: EntityHashMap<Entity>,
133}
134
135#[derive(Debug)]
136pub enum LoadError {
137    Io(io::Error),
138    De(ron::de::SpannedError),
139    Ron(ron::Error),
140    Scene(SceneSpawnError),
141}
142
143impl From<io::Error> for LoadError {
144    fn from(e: io::Error) -> Self {
145        Self::Io(e)
146    }
147}
148
149impl From<ron::de::SpannedError> for LoadError {
150    fn from(e: ron::de::SpannedError) -> Self {
151        Self::De(e)
152    }
153}
154
155impl From<ron::Error> for LoadError {
156    fn from(e: ron::Error) -> Self {
157        Self::Ron(e)
158    }
159}
160
161impl From<SceneSpawnError> for LoadError {
162    fn from(e: SceneSpawnError) -> Self {
163        Self::Scene(e)
164    }
165}
166
167/// Default [`LoadPipeline`].
168///
169/// # Usage
170///
171/// This pipeline tries to load all saved entities from a file at given `path`. If successful, it
172/// despawns all entities marked with [`Unload`] (recursively) and spawns the loaded entities.
173///
174/// Typically, it should be used with [`run_if`](bevy_ecs::schedule::SystemSet::run_if).
175///
176/// # Example
177/// ```
178/// use bevy::prelude::*;
179/// use moonshine_save::prelude::*;
180///
181/// let mut app = App::new();
182/// app.add_plugins(LoadPlugin)
183///     .add_systems(PreUpdate, load(static_file("example.ron")).run_if(should_load));
184///
185/// fn should_load() -> bool {
186///     todo!()
187/// }
188/// ```
189#[deprecated]
190pub fn load_from_file(path: impl Into<PathBuf>) -> SystemConfigs {
191    load(static_file(path))
192}
193
194#[deprecated]
195pub fn load_from_file_with_mapper(path: impl Into<PathBuf>, mapper: SceneMapper) -> SystemConfigs {
196    load(LoadPipelineBuilder {
197        pipeline: static_file(path),
198        mapper,
199    })
200}
201
202/// A [`LoadPipeline`] like [`load_from_file`] which is only triggered if a [`LoadFromFileRequest`] [`Resource`] is present.
203///
204/// # Example
205/// ```
206/// # use std::path::{Path, PathBuf};
207/// # use bevy::prelude::*;
208/// # use moonshine_save::prelude::*;
209///
210/// #[derive(Resource)]
211/// struct LoadRequest {
212///     pub path: PathBuf,
213/// }
214///
215/// impl GetFilePath for LoadRequest {
216///     fn path(&self) -> &Path {
217///         self.path.as_ref()
218///     }
219/// }
220///
221/// let mut app = App::new();
222/// app.add_plugins((MinimalPlugins, LoadPlugin))
223///     .add_systems(Update, load(file_from_resource::<LoadRequest>()));
224/// ```
225#[deprecated]
226pub fn load_from_file_on_request<R>() -> SystemConfigs
227where
228    R: GetFilePath + Resource,
229{
230    load(file_from_resource::<R>())
231}
232
233#[deprecated]
234pub fn load_from_file_on_request_with_mapper<R>(mapper: SceneMapper) -> SystemConfigs
235where
236    R: GetFilePath + Resource,
237{
238    load(LoadPipelineBuilder {
239        pipeline: file_from_resource::<R>(),
240        mapper,
241    })
242}
243
244/// A [`LoadPipeline`] like [`load_from_file`] which is only triggered if a [`LoadFromFileRequest`] [`Event`] is sent.
245///
246/// Note: If multiple events are sent in a single update cycle, only the first one is processed.
247#[deprecated]
248pub fn load_from_file_on_event<R>() -> SystemConfigs
249where
250    R: GetFilePath + Event,
251{
252    load(file_from_event::<R>())
253}
254
255// TODO: LoadPipelineBuilder
256#[deprecated]
257pub fn load_from_file_on_event_with_mapper<R>(mapper: SceneMapper) -> SystemConfigs
258where
259    R: GetFilePath + Event,
260{
261    load(LoadPipelineBuilder::<FileFromEvent<R>> {
262        pipeline: file_from_event::<R>(),
263        mapper,
264    })
265}
266
267pub trait LoadPipeline: Pipeline {
268    fn load(&self) -> impl System<In = (), Out = Result<Saved, LoadError>>;
269}
270
271impl LoadPipeline for StaticFile {
272    fn load(&self) -> impl System<In = (), Out = Result<Saved, LoadError>> {
273        IntoSystem::into_system(read_static_file(self.0.clone(), Default::default()))
274    }
275}
276
277impl<S: GetStaticStream> LoadPipeline for StaticStream<S>
278where
279    S::Stream: Read,
280{
281    fn load(&self) -> impl System<In = (), Out = Result<Saved, LoadError>> {
282        IntoSystem::into_system((|| S::stream()).pipe(read_stream))
283    }
284}
285
286impl<R> LoadPipeline for FileFromResource<R>
287where
288    R: Resource + GetFilePath,
289{
290    fn load(&self) -> impl System<In = (), Out = Result<Saved, LoadError>> {
291        IntoSystem::into_system(get_file_from_resource::<R>.pipe(read_file))
292    }
293}
294
295impl<R: GetStream + Resource> LoadPipeline for StreamFromResource<R>
296where
297    R::Stream: Read,
298{
299    fn load(&self) -> impl System<In = (), Out = Result<Saved, LoadError>> {
300        IntoSystem::into_system((|resource: Res<R>| resource.stream()).pipe(read_stream))
301    }
302}
303
304impl<E> LoadPipeline for FileFromEvent<E>
305where
306    E: Event + GetFilePath,
307{
308    fn load(&self) -> impl System<In = (), Out = Result<Saved, LoadError>> {
309        IntoSystem::into_system(get_file_from_event::<E>.pipe(read_file))
310    }
311}
312
313impl<E: GetStream + Event> LoadPipeline for StreamFromEvent<E>
314where
315    E::Stream: Read,
316{
317    fn load(&self) -> impl System<In = (), Out = Result<Saved, LoadError>> {
318        IntoSystem::into_system(get_stream_from_event::<E>.pipe(read_stream))
319    }
320}
321
322pub fn load(p: impl LoadPipeline) -> SystemConfigs {
323    let system = p
324        .load()
325        .pipe(unload::<DefaultUnloadFilter>)
326        .pipe(write_to_world)
327        .pipe(insert_into_loaded(Save))
328        .pipe(insert_loaded);
329    p.finish(IntoSystem::into_system(system))
330        .in_set(LoadSystem::Load)
331}
332
333pub trait LoadMapComponent: Sized {
334    fn map_component<U: Component>(self, m: impl MapComponent<U>) -> LoadPipelineBuilder<Self>;
335}
336
337impl<P: Pipeline> LoadMapComponent for P {
338    fn map_component<U: Component>(self, m: impl MapComponent<U>) -> LoadPipelineBuilder<Self> {
339        LoadPipelineBuilder {
340            pipeline: self,
341            mapper: SceneMapper::default().map(m),
342        }
343    }
344}
345
346pub struct LoadPipelineBuilder<P> {
347    pipeline: P,
348    mapper: SceneMapper,
349}
350
351impl<P> LoadPipelineBuilder<P> {
352    pub fn map_component<U: Component>(self, m: impl MapComponent<U>) -> Self {
353        Self {
354            mapper: self.mapper.map(m),
355            ..self
356        }
357    }
358}
359
360impl<P: Pipeline> Pipeline for LoadPipelineBuilder<P> {
361    fn finish(&self, pipeline: impl System<In = (), Out = ()>) -> SystemConfigs {
362        self.pipeline.finish(pipeline)
363    }
364}
365
366impl<P: LoadPipeline> LoadPipeline for LoadPipelineBuilder<P> {
367    fn load(&self) -> impl System<In = (), Out = Result<Saved, LoadError>> {
368        let mapper = self.mapper.clone();
369        IntoSystem::into_system(self.pipeline.load().pipe(
370            move |In(saved): In<Result<Saved, LoadError>>| {
371                saved.map(|saved| Saved {
372                    mapper: mapper.clone(),
373                    ..saved
374                })
375            },
376        ))
377    }
378}
379
380/// A [`System`] which reads [`Saved`] data from a file at given `path`.
381pub fn read_static_file(
382    path: impl Into<PathBuf>,
383    mapper: SceneMapper,
384) -> impl Fn(Res<AppTypeRegistry>) -> Result<Saved, LoadError> {
385    let path = path.into();
386    move |type_registry| {
387        let input = std::fs::read(&path)?;
388        let mut deserializer = ron::Deserializer::from_bytes(&input)?;
389        let scene = {
390            let type_registry = &type_registry.read();
391            let scene_deserializer = SceneDeserializer { type_registry };
392            scene_deserializer.deserialize(&mut deserializer)?
393        };
394        info!("loaded from file: {path:?}");
395        Ok(Saved {
396            scene,
397            mapper: mapper.clone(),
398        })
399    }
400}
401
402/// A [`System`] which reads [`Saved`] data from a file with its path defined at runtime.
403pub fn read_file(
404    In(path): In<PathBuf>,
405    type_registry: Res<AppTypeRegistry>,
406) -> Result<Saved, LoadError> {
407    let input = std::fs::read(&path)?;
408    let mut deserializer = ron::Deserializer::from_bytes(&input)?;
409    let scene = {
410        let type_registry = &type_registry.read();
411        let scene_deserializer = SceneDeserializer { type_registry };
412        scene_deserializer.deserialize(&mut deserializer)?
413    };
414    info!("loaded from file: {path:?}");
415    Ok(Saved {
416        scene,
417        mapper: Default::default(),
418    })
419}
420
421pub fn read_stream<S: Read>(
422    In(mut stream): In<S>,
423    type_registry: Res<AppTypeRegistry>,
424) -> Result<Saved, LoadError> {
425    let mut input = Vec::new();
426    stream.read_to_end(&mut input)?;
427    let mut deserializer = ron::Deserializer::from_bytes(&input)?;
428    let scene = {
429        let type_registry = &type_registry.read();
430        let scene_deserializer = SceneDeserializer { type_registry };
431        scene_deserializer.deserialize(&mut deserializer)?
432    };
433    info!("loaded from stream");
434    Ok(Saved {
435        scene,
436        mapper: Default::default(),
437    })
438}
439
440pub type DefaultUnloadFilter = Or<(With<Save>, With<Unload>)>;
441
442/// A [`System`] which recursively despawns all entities that match the given `Filter`.
443pub fn unload<Filter: QueryFilter>(
444    In(result): In<Result<Saved, LoadError>>,
445    world: &mut World,
446) -> Result<Saved, LoadError> {
447    let saved = result?;
448    let entities: Vec<Entity> = world
449        .query_filtered::<Entity, Filter>()
450        .iter(world)
451        .collect();
452    for entity in entities {
453        if let Ok(entity) = world.get_entity_mut(entity) {
454            entity.despawn_recursive();
455        }
456    }
457    Ok(saved)
458}
459
460/// A [`System`] which writes [`Saved`] data into current [`World`].
461pub fn write_to_world(
462    In(result): In<Result<Saved, LoadError>>,
463    world: &mut World,
464) -> Result<Loaded, LoadError> {
465    let Saved { scene, mut mapper } = result?;
466    let mut entity_map = EntityHashMap::default();
467    scene.write_to_world(world, &mut entity_map)?;
468    if !mapper.is_empty() {
469        for entity in entity_map.values() {
470            if let Ok(entity) = world.get_entity_mut(*entity) {
471                mapper.replace(entity);
472            }
473        }
474    }
475    Ok(Loaded { entity_map })
476}
477
478/// A [`System`] which inserts a clone of the given [`Bundle`] into all loaded entities.
479pub fn insert_into_loaded(
480    bundle: impl Bundle + Clone,
481) -> impl Fn(In<Result<Loaded, LoadError>>, &mut World) -> Result<Loaded, LoadError> {
482    move |In(result), world| {
483        if let Ok(loaded) = &result {
484            for (saved_entity, entity) in loaded.entity_map.iter() {
485                if let Ok(mut entity) = world.get_entity_mut(*entity) {
486                    entity.insert(bundle.clone());
487                } else {
488                    error!(
489                        "entity {saved_entity} is referenced in saved data but was never saved (raw bits = {})",
490                        saved_entity.to_bits()
491                    );
492                }
493            }
494        }
495        result
496    }
497}
498
499/// A [`System`] which finishes the load process.
500///
501/// # Usage
502///
503/// All load pipelines should end with this system.
504pub fn insert_loaded(In(result): In<Result<Loaded, LoadError>>, world: &mut World) {
505    match result {
506        Ok(loaded) => world.insert_resource(loaded),
507        Err(why) => error!("load failed: {why:?}"),
508    }
509}
510
511/// A [`System`] which extracts the path from a [`LoadFromFileRequest`] [`Resource`].
512pub fn get_file_from_resource<R>(request: Res<R>) -> PathBuf
513where
514    R: GetFilePath + Resource,
515{
516    request.path().to_owned()
517}
518
519/// A [`System`] which extracts the path from a [`LoadFromFileRequest`] [`Event`].
520///
521/// # Warning
522///
523/// If multiple events are sent in a single update cycle, only the first one is processed.
524///
525/// This system assumes that at least one event has been sent. It must be used in conjunction with [`has_event`].
526pub fn get_file_from_event<E>(mut events: EventReader<E>) -> PathBuf
527where
528    E: GetFilePath + Event,
529{
530    let mut iter = events.read();
531    let event = iter.next().unwrap();
532    if iter.next().is_some() {
533        warn!("multiple load request events received; only the first one is processed.");
534    }
535    event.path().to_owned()
536}
537
538pub fn get_stream_from_event<E>(mut events: EventReader<E>) -> E::Stream
539where
540    E: GetStream + Event,
541{
542    let mut iter = events.read();
543    let event = iter.next().unwrap();
544    if iter.next().is_some() {
545        warn!("multiple load request events received; only the first one is processed.");
546    }
547    event.stream()
548}
549
550#[cfg(test)]
551mod tests {
552    use std::{fs::*, path::Path};
553
554    use bevy::prelude::*;
555
556    use super::*;
557    use crate::*;
558
559    pub const DATA: &str = "(
560        resources: {},
561        entities: {
562            4294967296: (
563                components: {
564                    \"moonshine_save::load::tests::Dummy\": (),
565                },
566            ),
567        },
568    )";
569
570    #[derive(Component, Default, Reflect)]
571    #[reflect(Component)]
572    struct Dummy;
573
574    fn app() -> App {
575        let mut app = App::new();
576        app.add_plugins((MinimalPlugins, LoadPlugin))
577            .register_type::<Dummy>();
578        app
579    }
580
581    #[test]
582    fn test_load_file() {
583        pub const PATH: &str = "test_load_file.ron";
584
585        write(PATH, DATA).unwrap();
586
587        let mut app = app();
588        app.add_systems(PreUpdate, load(static_file(PATH)));
589
590        app.update();
591
592        let world = app.world_mut();
593        assert!(!world.contains_resource::<Loaded>());
594        assert!(world
595            .query_filtered::<(), With<Dummy>>()
596            .get_single(world)
597            .is_ok());
598
599        remove_file(PATH).unwrap();
600    }
601
602    #[test]
603    fn test_load_stream() {
604        pub const PATH: &str = "test_load_stream.ron";
605
606        struct LoadStream;
607
608        impl GetStaticStream for LoadStream {
609            type Stream = File;
610
611            fn stream() -> Self::Stream {
612                File::open(PATH).unwrap()
613            }
614        }
615
616        write(PATH, DATA).unwrap();
617
618        let mut app = app();
619        app.add_systems(PreUpdate, load(static_stream(LoadStream)));
620
621        app.update();
622
623        let world = app.world_mut();
624        assert!(!world.contains_resource::<Loaded>());
625        assert!(world
626            .query_filtered::<(), With<Dummy>>()
627            .get_single(world)
628            .is_ok());
629
630        remove_file(PATH).unwrap();
631    }
632
633    #[test]
634    fn test_load_file_from_resource() {
635        pub const PATH: &str = "test_load_file_from_resource.ron";
636
637        write(PATH, DATA).unwrap();
638
639        #[derive(Resource)]
640        struct LoadRequest;
641
642        impl GetFilePath for LoadRequest {
643            fn path(&self) -> &Path {
644                Path::new(PATH)
645            }
646        }
647
648        let mut app = app();
649        app.add_systems(PreUpdate, load(file_from_resource::<LoadRequest>()));
650
651        app.world_mut().insert_resource(LoadRequest);
652        app.update();
653
654        let world = app.world_mut();
655        assert!(world
656            .query_filtered::<(), With<Dummy>>()
657            .get_single(world)
658            .is_ok());
659
660        remove_file(PATH).unwrap();
661    }
662
663    #[test]
664    fn test_load_stream_from_resource() {
665        pub const PATH: &str = "test_load_stream_from_resource.ron";
666
667        write(PATH, DATA).unwrap();
668
669        #[derive(Resource)]
670        struct LoadRequest(&'static str);
671
672        impl GetStream for LoadRequest {
673            type Stream = File;
674
675            fn stream(&self) -> Self::Stream {
676                File::open(self.0).unwrap()
677            }
678        }
679
680        let mut app = app();
681        app.add_systems(PreUpdate, load(stream_from_resource::<LoadRequest>()));
682
683        app.world_mut().insert_resource(LoadRequest(PATH));
684        app.update();
685
686        let world = app.world_mut();
687        assert!(world
688            .query_filtered::<(), With<Dummy>>()
689            .get_single(world)
690            .is_ok());
691
692        remove_file(PATH).unwrap();
693    }
694
695    #[test]
696    fn test_load_file_from_event() {
697        pub const PATH: &str = "test_load_file_from_event.ron";
698
699        write(PATH, DATA).unwrap();
700
701        #[derive(Event)]
702        struct LoadRequest;
703
704        impl GetFilePath for LoadRequest {
705            fn path(&self) -> &Path {
706                Path::new(PATH)
707            }
708        }
709
710        let mut app = app();
711        app.add_event::<LoadRequest>()
712            .add_systems(PreUpdate, load(file_from_event::<LoadRequest>()));
713
714        app.world_mut().send_event(LoadRequest);
715        app.update();
716
717        let world = app.world_mut();
718        assert!(world
719            .query_filtered::<(), With<Dummy>>()
720            .get_single(world)
721            .is_ok());
722
723        remove_file(PATH).unwrap();
724    }
725
726    #[test]
727    fn test_load_stream_from_event() {
728        pub const PATH: &str = "test_load_stream_from_event.ron";
729
730        write(PATH, DATA).unwrap();
731
732        #[derive(Event)]
733        struct LoadRequest(&'static str);
734
735        impl GetStream for LoadRequest {
736            type Stream = File;
737
738            fn stream(&self) -> Self::Stream {
739                File::open(self.0).unwrap()
740            }
741        }
742
743        let mut app = app();
744        app.add_event::<LoadRequest>()
745            .add_systems(PreUpdate, load(stream_from_event::<LoadRequest>()));
746
747        app.world_mut().send_event(LoadRequest(PATH));
748        app.update();
749
750        let world = app.world_mut();
751        assert!(world
752            .query_filtered::<(), With<Dummy>>()
753            .get_single(world)
754            .is_ok());
755
756        remove_file(PATH).unwrap();
757    }
758
759    #[test]
760    fn test_load_map_component() {
761        pub const PATH: &str = "test_load_map_component.ron";
762
763        write(PATH, DATA).unwrap();
764
765        let mut app = app();
766
767        #[derive(Component)]
768        struct Foo; // Not serializable
769
770        app.add_systems(
771            PreUpdate,
772            load(static_file(PATH).map_component(|_: &Dummy| Foo)),
773        );
774
775        app.update();
776
777        let world = app.world_mut();
778        assert!(world
779            .query_filtered::<(), With<Foo>>()
780            .get_single(world)
781            .is_ok());
782        assert!(world
783            .query_filtered::<(), With<Dummy>>()
784            .get_single(world)
785            .is_err());
786
787        remove_file(PATH).unwrap();
788    }
789}