moonshine_save/
load.rs

1use std::io::{self, Read};
2use std::marker::PhantomData;
3use std::path::PathBuf;
4
5use bevy_scene::DynamicScene;
6use moonshine_util::expect::{expect_deferred, ExpectDeferred};
7use moonshine_util::Static;
8use serde::de::DeserializeSeed;
9
10use bevy_ecs::entity::EntityHashMap;
11use bevy_ecs::prelude::*;
12use bevy_ecs::query::QueryFilter;
13use bevy_log::prelude::*;
14use bevy_scene::{serde::SceneDeserializer, SceneSpawnError};
15
16use moonshine_util::event::{OnSingle, SingleEvent, TriggerSingle};
17use thiserror::Error;
18
19use crate::save::Save;
20use crate::{MapComponent, SceneMapper};
21
22/// A [`Component`] which marks its [`Entity`] to be despawned prior to load.
23///
24/// # Usage
25/// When saving game state, it is often undesirable to save visual and aesthetic elements of the game.
26/// Elements such as transforms, camera settings, scene hierarchy, or UI elements are typically either
27/// spawned at game start, or added during initialization of the game data they represent.
28///
29/// This component may be used on such entities to despawn them prior to loading.
30///
31/// # Example
32/// ```
33/// use bevy::prelude::*;
34/// use moonshine_save::prelude::*;
35///
36/// #[derive(Bundle)]
37/// struct PlayerBundle {
38///     player: Player,
39///     /* Saved Player Data */
40///     save: Save,
41/// }
42///
43/// #[derive(Component, Default, Reflect)]
44/// #[reflect(Component)]
45/// struct Player;
46///
47/// #[derive(Component)] // <-- Not serialized!
48/// struct PlayerSprite(Entity);
49///
50/// #[derive(Bundle, Default)]
51/// struct PlayerSpriteBundle {
52///     /* Player Visuals/Aesthetics */
53///     unload: Unload,
54/// }
55///
56/// fn spawn_player_sprite(query: Query<Entity, Added<Player>>, mut commands: Commands) {
57///     for entity in &query {
58///         let sprite = PlayerSprite(commands.spawn(PlayerSpriteBundle::default()).id());
59///         commands.entity(entity).insert(sprite);
60///     }
61/// }
62/// ```
63#[derive(Component, Default, Clone)]
64pub struct Unload;
65
66/// A trait used to trigger a [`LoadEvent`] via [`Commands`] or [`World`].
67pub trait TriggerLoad {
68    /// Triggers the given [`LoadEvent`].
69    #[doc(alias = "trigger_single")]
70    fn trigger_load(self, event: impl LoadEvent);
71}
72
73impl TriggerLoad for &mut Commands<'_, '_> {
74    fn trigger_load(self, event: impl LoadEvent) {
75        self.trigger_single(event);
76    }
77}
78
79impl TriggerLoad for &mut World {
80    fn trigger_load(self, event: impl LoadEvent) {
81        self.trigger_single(event);
82    }
83}
84
85/// A [`QueryFilter`] which determines which entities should be unloaded before the load process begins.
86pub type DefaultUnloadFilter = Or<(With<Save>, With<Unload>)>;
87
88/// A [`SingleEvent`] which starts the load process with the given parameters.
89///
90/// See also:
91/// - [`trigger_load`](TriggerLoad::trigger_load)
92/// - [`trigger_single`](TriggerSingle::trigger_single)
93/// - [`LoadWorld`]
94pub trait LoadEvent: SingleEvent {
95    /// A [`QueryFilter`] used as the initial filter for selecting entities to unload.
96    type UnloadFilter: QueryFilter;
97
98    /// Returns the [`LoadInput`] of the load process.
99    fn input(&mut self) -> LoadInput;
100
101    /// Called once before the load process starts.
102    ///
103    /// This is useful if you want to modify the world just before loading.
104    fn before_load(&mut self, _world: &mut World) {}
105
106    /// Called once before unloading entities.
107    ///
108    /// All given entities will be despawned after this call.
109    /// This is useful if you want to update the world state as a result of unloading these entities.
110    fn before_unload(&mut self, _world: &mut World, _entities: &[Entity]) {}
111
112    /// Called for all entities after they have been loaded.
113    ///
114    /// This is useful to undo any modifications done before loading.
115    /// You also have access to [`Loaded`] here for any additional post-processing before [`OnLoad`] is triggered.
116    fn after_load(&mut self, _world: &mut World, _result: &LoadResult) {}
117}
118
119/// A generic [`LoadEvent`] which loads the world from a file or stream.
120pub struct LoadWorld<U: QueryFilter = DefaultUnloadFilter> {
121    /// The input data used to load the world.
122    pub input: LoadInput,
123    /// A [`SceneMapper`] used to map components after the load process.
124    pub mapper: SceneMapper,
125    #[doc(hidden)]
126    pub unload: PhantomData<U>,
127}
128
129impl<U: QueryFilter> LoadWorld<U> {
130    /// Creates a new [`LoadWorld`] with the given input and mapper.
131    pub fn new(input: LoadInput, mapper: SceneMapper) -> Self {
132        LoadWorld {
133            input,
134            mapper,
135            unload: PhantomData,
136        }
137    }
138
139    /// Creates a new [`LoadWorld`] which unloads entities matching the given
140    /// [`QueryFilter`] before the file at given path.
141    pub fn from_file(path: impl Into<PathBuf>) -> Self {
142        LoadWorld {
143            input: LoadInput::File(path.into()),
144            mapper: SceneMapper::default(),
145            unload: PhantomData,
146        }
147    }
148
149    /// Creates a new [`LoadWorld`] which unloads entities matching the given
150    /// [`QueryFilter`] before loading from the given [`Read`] stream.
151    pub fn from_stream(stream: impl LoadStream) -> Self {
152        LoadWorld {
153            input: LoadInput::Stream(Box::new(stream)),
154            mapper: SceneMapper::default(),
155            unload: PhantomData,
156        }
157    }
158
159    /// Maps the given [`Component`] into another using a [component mapper](MapComponent) after loading.
160    pub fn map_component<T: Component>(self, m: impl MapComponent<T>) -> Self {
161        LoadWorld {
162            mapper: self.mapper.map(m),
163            ..self
164        }
165    }
166}
167
168impl LoadWorld {
169    /// Creates a new [`LoadWorld`] event which unloads default entities (with [`Unload`] or [`Save`])
170    /// before loading the file at the given path.
171    pub fn default_from_file(path: impl Into<PathBuf>) -> Self {
172        Self::from_file(path)
173    }
174
175    /// Creates a new [`LoadWorld`] event which unloads default entities (with [`Unload`] or [`Save`])
176    /// before loading from the given [`Read`] stream.
177    pub fn default_from_stream(stream: impl LoadStream) -> Self {
178        Self::from_stream(stream)
179    }
180}
181
182impl<U: QueryFilter> SingleEvent for LoadWorld<U> where U: Static {}
183
184impl<U: QueryFilter> LoadEvent for LoadWorld<U>
185where
186    U: Static,
187{
188    type UnloadFilter = U;
189
190    fn input(&mut self) -> LoadInput {
191        self.input.consume().unwrap()
192    }
193
194    fn before_load(&mut self, world: &mut World) {
195        world.insert_resource(ExpectDeferred);
196    }
197
198    fn after_load(&mut self, world: &mut World, result: &LoadResult) {
199        if let Ok(loaded) = result {
200            for entity in loaded.entities() {
201                let Ok(entity) = world.get_entity_mut(entity) else {
202                    // Some entities may be invalid during load. See `unsaved.rs` test.
203                    continue;
204                };
205                self.mapper.replace(entity);
206            }
207        }
208
209        expect_deferred(world);
210    }
211}
212
213/// Input of the load process.
214pub enum LoadInput {
215    /// Load from a file at the given path.
216    File(PathBuf),
217    /// Load from a [`Read`] stream.
218    Stream(Box<dyn LoadStream>),
219    /// Load from a [`DynamicScene`].
220    ///
221    /// This is useful if you would like to deserialize the scene manually from any data source.
222    Scene(DynamicScene),
223    #[doc(hidden)]
224    Invalid,
225}
226
227impl LoadInput {
228    /// Creates a new [`LoadInput`] which loads from a file at the given path.
229    pub fn file(path: impl Into<PathBuf>) -> Self {
230        Self::File(path.into())
231    }
232
233    /// Creates a new [`LoadInput`] which loads from a [`Read`] stream.
234    pub fn stream<S: LoadStream + 'static>(stream: S) -> Self {
235        Self::Stream(Box::new(stream))
236    }
237
238    /// Invalidates this [`LoadInput`] and returns it if it was valid.
239    pub fn consume(&mut self) -> Option<LoadInput> {
240        let input = std::mem::replace(self, LoadInput::Invalid);
241        if let LoadInput::Invalid = input {
242            return None;
243        }
244        Some(input)
245    }
246}
247
248/// Alias for a `'static` [`Read`] stream.
249pub trait LoadStream: Read
250where
251    Self: Static,
252{
253}
254
255impl<S: Read> LoadStream for S where S: Static {}
256
257/// An [`Event`] triggered at the end of a successful load process.
258///
259/// This event contains the loaded entity map.
260#[derive(Event)]
261pub struct Loaded {
262    /// The map of all loaded entities and their new entity IDs.
263    pub entity_map: EntityHashMap<Entity>,
264}
265
266impl Loaded {
267    /// Iterates over all loaded entities.
268    ///
269    /// Note that not all of these entities may be valid. This would indicate an error with save data.
270    /// See `unsaved.rs` test for an example of how this may happen.
271    pub fn entities(&self) -> impl Iterator<Item = Entity> + '_ {
272        self.entity_map.values().copied()
273    }
274}
275
276#[doc(hidden)]
277#[deprecated(since = "0.5.2", note = "use `Loaded` instead")]
278pub type OnLoad = Loaded;
279
280/// An error which indicates a failure during the load process.
281#[derive(Error, Debug)]
282pub enum LoadError {
283    /// Indicates a failure to access the saved data.
284    #[error("Failed to read world: {0}")]
285    Io(io::Error),
286    /// Indicates a deserialization error.
287    #[error("Failed to deserialize world: {0}")]
288    Ron(ron::Error),
289    /// Indicates a failure to reconstruct the world from the loaded data.
290    #[error("Failed to spawn scene: {0}")]
291    Scene(SceneSpawnError),
292}
293
294impl From<io::Error> for LoadError {
295    fn from(e: io::Error) -> Self {
296        Self::Io(e)
297    }
298}
299
300impl From<ron::de::SpannedError> for LoadError {
301    fn from(e: ron::de::SpannedError) -> Self {
302        Self::Ron(e.into())
303    }
304}
305
306impl From<ron::Error> for LoadError {
307    fn from(e: ron::Error) -> Self {
308        Self::Ron(e)
309    }
310}
311
312impl From<SceneSpawnError> for LoadError {
313    fn from(e: SceneSpawnError) -> Self {
314        Self::Scene(e)
315    }
316}
317
318/// [`Result`] of a [`LoadEvent`].
319pub type LoadResult = Result<Loaded, LoadError>;
320
321/// An [`Observer`] which loads the world when a [`LoadWorld`] event is triggered.
322pub fn load_on_default_event(event: OnSingle<LoadWorld>, commands: Commands) {
323    load_on(event, commands);
324}
325
326/// An [`Observer`] which loads the world when the given [`LoadEvent`] is triggered.
327pub fn load_on<E: LoadEvent>(event: OnSingle<E>, mut commands: Commands) {
328    commands.queue_handled(LoadCommand(event.consume().unwrap()), |err, ctx| {
329        error!("load failed: {err:?} ({ctx})");
330    });
331}
332
333fn load_world<E: LoadEvent>(mut event: E, world: &mut World) -> LoadResult {
334    // Notify
335    event.before_load(world);
336
337    // Deserialize
338    let scene = match event.input() {
339        LoadInput::File(path) => {
340            let bytes = std::fs::read(&path)?;
341            let mut deserializer = ron::Deserializer::from_bytes(&bytes)?;
342            let type_registry = &world.resource::<AppTypeRegistry>().read();
343            let scene_deserializer = SceneDeserializer { type_registry };
344            scene_deserializer.deserialize(&mut deserializer).unwrap()
345        }
346        LoadInput::Stream(mut stream) => {
347            let mut bytes = Vec::new();
348            stream.read_to_end(&mut bytes)?;
349            let mut deserializer = ron::Deserializer::from_bytes(&bytes)?;
350            let type_registry = &world.resource::<AppTypeRegistry>().read();
351            let scene_deserializer = SceneDeserializer { type_registry };
352            scene_deserializer.deserialize(&mut deserializer)?
353        }
354        LoadInput::Scene(scene) => scene,
355        LoadInput::Invalid => {
356            panic!("LoadInput is invalid");
357        }
358    };
359
360    // Unload
361    let entities: Vec<_> = world
362        .query_filtered::<Entity, E::UnloadFilter>()
363        .iter(world)
364        .collect();
365    event.before_unload(world, &entities);
366    for entity in entities {
367        if let Ok(entity) = world.get_entity_mut(entity) {
368            entity.despawn();
369        }
370    }
371
372    // Load
373    let mut entity_map = EntityHashMap::default();
374    scene.write_to_world(world, &mut entity_map)?;
375    debug!("loaded {} entities", entity_map.len());
376
377    let result = Ok(Loaded { entity_map });
378    event.after_load(world, &result);
379    result
380}
381
382struct LoadCommand<E>(E);
383
384impl<E: LoadEvent> Command<Result<(), LoadError>> for LoadCommand<E> {
385    fn apply(self, world: &mut World) -> Result<(), LoadError> {
386        let loaded = load_world(self.0, world)?;
387        world.trigger(loaded);
388        Ok(())
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use std::fs::*;
395
396    use bevy::prelude::*;
397    use bevy_ecs::system::RunSystemOnce;
398
399    use super::*;
400
401    pub const DATA: &str = "(
402        resources: {},
403        entities: {
404            4294967293: (
405                components: {
406                    \"moonshine_save::load::tests::Foo\": (),
407                },
408            ),
409        },
410    )";
411
412    #[derive(Component, Default, Reflect)]
413    #[reflect(Component)]
414    #[require(Save)]
415    struct Foo;
416
417    fn app() -> App {
418        let mut app = App::new();
419        app.add_plugins(MinimalPlugins).register_type::<Foo>();
420        app
421    }
422
423    #[test]
424    fn test_load_file() {
425        #[derive(Resource)]
426        struct EventTriggered;
427
428        pub const PATH: &str = "test_load_file.ron";
429
430        write(PATH, DATA).unwrap();
431
432        let mut app = app();
433        app.add_observer(load_on_default_event);
434
435        app.add_observer(|_: On<Loaded>, mut commands: Commands| {
436            commands.insert_resource(EventTriggered);
437        });
438
439        let _ = app.world_mut().run_system_once(|mut commands: Commands| {
440            commands.trigger_load(LoadWorld::default_from_file(PATH));
441        });
442
443        let world = app.world_mut();
444        assert!(world.contains_resource::<EventTriggered>());
445        assert!(world
446            .query_filtered::<(), With<Foo>>()
447            .single(world)
448            .is_ok());
449
450        remove_file(PATH).unwrap();
451    }
452
453    #[test]
454    fn test_load_stream() {
455        pub const PATH: &str = "test_load_stream.ron";
456
457        write(PATH, DATA).unwrap();
458
459        let mut app = app();
460        app.add_observer(load_on_default_event);
461
462        let _ = app.world_mut().run_system_once(|mut commands: Commands| {
463            commands.spawn((Foo, Save));
464            commands.trigger_load(LoadWorld::default_from_stream(File::open(PATH).unwrap()));
465        });
466
467        let data = read_to_string(PATH).unwrap();
468        assert!(data.contains("Foo"));
469
470        remove_file(PATH).unwrap();
471    }
472
473    #[test]
474    fn test_load_map_component() {
475        pub const PATH: &str = "test_load_map_component.ron";
476
477        write(PATH, DATA).unwrap();
478
479        #[derive(Component)]
480        struct Bar; // Not serializable
481
482        let mut app = app();
483        app.add_observer(load_on_default_event);
484
485        let _ = app.world_mut().run_system_once(|mut commands: Commands| {
486            commands.trigger_load(LoadWorld::default_from_file(PATH).map_component(|_: &Foo| Bar));
487        });
488
489        let world = app.world_mut();
490        assert!(world
491            .query_filtered::<(), With<Bar>>()
492            .single(world)
493            .is_ok());
494        assert!(world.query_filtered::<(), With<Foo>>().iter(world).count() == 0);
495
496        remove_file(PATH).unwrap();
497    }
498}