moonshine_save/
save.rs

1use std::any::TypeId;
2use std::io::{self, Write};
3use std::marker::PhantomData;
4use std::path::PathBuf;
5
6use bevy_ecs::entity::EntityHashSet;
7use bevy_ecs::prelude::*;
8use bevy_ecs::query::QueryFilter;
9use bevy_log::prelude::*;
10use bevy_scene::{DynamicScene, DynamicSceneBuilder, SceneFilter};
11
12use moonshine_util::event::{OnSingle, SingleEvent, TriggerSingle};
13use moonshine_util::Static;
14use thiserror::Error;
15
16use crate::{MapComponent, SceneMapper};
17
18/// A [`Component`] which marks its [`Entity`] to be saved.
19#[derive(Component, Default, Debug, Clone)]
20pub struct Save;
21
22/// A trait used to trigger a [`SaveEvent`] via [`Commands`] or [`World`].
23pub trait TriggerSave {
24    /// Triggers the given [`SaveEvent`].
25    #[doc(alias = "trigger_single")]
26    fn trigger_save(self, event: impl SaveEvent);
27}
28
29impl TriggerSave for &mut Commands<'_, '_> {
30    fn trigger_save(self, event: impl SaveEvent) {
31        self.trigger_single(event);
32    }
33}
34
35impl TriggerSave for &mut World {
36    fn trigger_save(self, event: impl SaveEvent) {
37        self.trigger_single(event);
38    }
39}
40
41/// A [`SingleEvent`] which starts the save process with the given parameters.
42///
43/// See also:
44/// - [`trigger_save`](TriggerSave::trigger_save)
45/// - [`trigger_single`](TriggerSingle::trigger_single)
46/// - [`SaveWorld`]
47pub trait SaveEvent: SingleEvent {
48    /// A [`QueryFilter`] used as the initial filter for selecting saved entities.
49    type SaveFilter: QueryFilter;
50
51    /// Return `true` if the given [`Entity`] should be saved.
52    fn filter_entity(&self, _entity: EntityRef) -> bool {
53        true
54    }
55
56    /// Called once before the save process starts.
57    ///
58    /// This is useful if you want to modify the world just before saving.
59    fn before_save(&mut self, _world: &mut World) {}
60
61    /// Called once before serialization.
62    ///
63    /// This is useful to undo any modifications done before saving.
64    fn before_serialize(&mut self, _world: &mut World, _entities: &[Entity]) {}
65
66    /// Returns a [`SceneFilter`] for selecting which components should be saved.
67    fn component_filter(&mut self) -> SceneFilter {
68        SceneFilter::allow_all()
69    }
70
71    /// Returns a [`SceneFilter`] for selecting which resources should be saved.
72    fn resource_filter(&mut self) -> SceneFilter {
73        SceneFilter::deny_all()
74    }
75
76    /// Called once after serialization.
77    ///
78    /// This is useful if you would like to do any post-processing of the [`Saved`] data *before* [`OnSave`] is triggered.
79    fn after_save(&mut self, _world: &mut World, _result: &SaveResult) {}
80
81    /// Returns the [`SaveOutput`] of the save process.
82    fn output(&mut self) -> SaveOutput;
83}
84
85/// A generic [`SaveEvent`] which can be used to save the [`World`].
86pub struct SaveWorld<F: QueryFilter = DefaultSaveFilter> {
87    /// A filter for selecting which entities should be saved.
88    ///
89    /// By default, all entities are selected.
90    pub entities: EntityFilter,
91    /// A filter for selecting which resources should be saved.
92    ///
93    /// By default, no resources are selected. Most Bevy resources are not safely serializable.
94    pub resources: SceneFilter,
95    /// A filter for selecting which components should be saved.
96    ///
97    /// By default, all serializable components are selected.
98    pub components: SceneFilter,
99    /// A mapper for transforming components during the save process.
100    ///
101    /// See [`MapComponent`] for more information.
102    pub mapper: SceneMapper,
103    /// Output of the saved world.
104    pub output: SaveOutput,
105    #[doc(hidden)]
106    pub filter: PhantomData<F>,
107}
108
109impl<F: QueryFilter> SaveWorld<F> {
110    /// Creates a new [`SaveWorld`] event with the given [`SaveOutput`].
111    pub fn new(output: SaveOutput) -> Self {
112        Self {
113            entities: EntityFilter::allow_all(),
114            resources: SceneFilter::deny_all(),
115            components: SceneFilter::allow_all(),
116            mapper: SceneMapper::default(),
117            output,
118            filter: PhantomData,
119        }
120    }
121
122    /// Creates a new [`SaveWorld`] event which saves entities matching the
123    /// given [`QueryFilter`] into a file at the given path.
124    pub fn into_file(path: impl Into<PathBuf>) -> Self {
125        Self {
126            entities: EntityFilter::allow_all(),
127            resources: SceneFilter::deny_all(),
128            components: SceneFilter::allow_all(),
129            mapper: SceneMapper::default(),
130            output: SaveOutput::file(path),
131            filter: PhantomData,
132        }
133    }
134
135    /// Creates a new [`SaveWorld`] event which saves entities matching the
136    /// given [`QueryFilter`] into a [`Write`] stream.
137    pub fn into_stream(stream: impl SaveStream) -> Self {
138        Self {
139            entities: EntityFilter::allow_all(),
140            resources: SceneFilter::deny_all(),
141            components: SceneFilter::allow_all(),
142            mapper: SceneMapper::default(),
143            output: SaveOutput::stream(stream),
144            filter: PhantomData,
145        }
146    }
147
148    /// Includes the given [`Resource`] in the save data.
149    pub fn include_resource<R: Resource>(mut self) -> Self {
150        self.resources = self.resources.allow::<R>();
151        self
152    }
153
154    /// Includes the given [`Resource`] by its [`TypeId`] in the save data.
155    pub fn include_resource_by_id(mut self, type_id: TypeId) -> Self {
156        self.resources = self.resources.allow_by_id(type_id);
157        self
158    }
159
160    /// Excludes the given [`Component`] from the save data.
161    pub fn exclude_component<T: Component>(mut self) -> Self {
162        self.components = self.components.deny::<T>();
163        self
164    }
165
166    /// Excludes the given [`Component`] by its [`TypeId`] from the save data.
167    pub fn exclude_component_by_id(mut self, type_id: TypeId) -> Self {
168        self.components = self.components.deny_by_id(type_id);
169        self
170    }
171
172    /// Maps the given [`Component`] into another using a [component mapper](MapComponent) before saving.
173    pub fn map_component<T: Component>(mut self, m: impl MapComponent<T>) -> Self {
174        self.mapper = self.mapper.map(m);
175        self
176    }
177}
178
179impl SaveWorld {
180    /// Creates a new [`SaveWorld`] event which saves default entities (with [`Save`])
181    /// into a file at the given path.
182    pub fn default_into_file(path: impl Into<PathBuf>) -> Self {
183        Self::into_file(path)
184    }
185
186    /// Creates a new [`SaveWorld`] event which saves default entities (with [`Save`])
187    /// into a [`Write`] stream.
188    pub fn default_into_stream(stream: impl SaveStream) -> Self {
189        Self::into_stream(stream)
190    }
191}
192
193impl SaveWorld<()> {
194    /// Creates a new [`SaveWorld`] event which saves all entities into a file at the given path.
195    pub fn all_into_file(path: impl Into<PathBuf>) -> Self {
196        Self::into_file(path)
197    }
198
199    /// Creates a new [`SaveWorld`] event which saves all entities into a [`Write`] stream.
200    pub fn all_into_stream(stream: impl SaveStream) -> Self {
201        Self::into_stream(stream)
202    }
203}
204
205impl<F: QueryFilter> SingleEvent for SaveWorld<F> where F: Static {}
206
207impl<F: QueryFilter> SaveEvent for SaveWorld<F>
208where
209    F: Static,
210{
211    type SaveFilter = F;
212
213    fn filter_entity(&self, entity: EntityRef) -> bool {
214        match &self.entities {
215            EntityFilter::Allow(allow) => allow.contains(&entity.id()),
216            EntityFilter::Block(block) => !block.contains(&entity.id()),
217        }
218    }
219
220    fn before_serialize(&mut self, world: &mut World, entities: &[Entity]) {
221        for entity in entities {
222            self.mapper.apply(world.entity_mut(*entity));
223        }
224    }
225
226    fn after_save(&mut self, world: &mut World, result: &SaveResult) {
227        let Ok(saved) = result else {
228            return;
229        };
230
231        for entity in saved.entities() {
232            self.mapper.undo(world.entity_mut(entity));
233        }
234    }
235
236    fn component_filter(&mut self) -> SceneFilter {
237        std::mem::replace(&mut self.components, SceneFilter::Unset)
238    }
239
240    fn resource_filter(&mut self) -> SceneFilter {
241        std::mem::replace(&mut self.resources, SceneFilter::Unset)
242    }
243
244    fn output(&mut self) -> SaveOutput {
245        self.output.consume().unwrap()
246    }
247}
248
249/// Filter used for the default [`SaveWorld`] event.
250/// This includes all entities with the [`Save`] component.
251pub type DefaultSaveFilter = With<Save>;
252
253/// Output of the save process.
254pub enum SaveOutput {
255    /// Save into a file at the given path.
256    File(PathBuf),
257    /// Save into a [`Write`] stream.
258    Stream(Box<dyn SaveStream>),
259    /// Drops the save data.
260    ///
261    /// This is useful if you would like to process the [`Saved`] data manually.
262    /// You can observe the [`OnSave`] event for post-processing logic.
263    Drop,
264    #[doc(hidden)]
265    Invalid,
266}
267
268impl SaveOutput {
269    /// Creates a new [`SaveOutput`] which saves into a file at the given path.
270    pub fn file(path: impl Into<PathBuf>) -> Self {
271        Self::File(path.into())
272    }
273
274    /// Creates a new [`SaveOutput`] which saves into a [`Write`] stream.
275    pub fn stream<S: SaveStream + 'static>(stream: S) -> Self {
276        Self::Stream(Box::new(stream))
277    }
278
279    /// Invalidates this [`SaveOutput`] and returns it if it was valid.
280    pub fn consume(&mut self) -> Option<SaveOutput> {
281        let output = std::mem::replace(self, SaveOutput::Invalid);
282        if let SaveOutput::Invalid = output {
283            return None;
284        }
285        Some(output)
286    }
287}
288
289/// A filter for selecting which [`Entity`]s within a [`World`].
290#[derive(Clone, Debug)]
291pub enum EntityFilter {
292    /// Select only the specified entities.
293    Allow(EntityHashSet),
294    /// Select all entities except the specified ones.
295    Block(EntityHashSet),
296}
297
298impl EntityFilter {
299    /// Creates a new [`EntityFilter`] which allows all entities.
300    pub fn allow_all() -> Self {
301        Self::Block(EntityHashSet::new())
302    }
303
304    /// Creates a new [`EntityFilter`] which allows only the specified entities.
305    pub fn allow(entities: impl IntoIterator<Item = Entity>) -> Self {
306        Self::Allow(entities.into_iter().collect())
307    }
308
309    /// Creates a new [`EntityFilter`] which blocks the specified entities.
310    pub fn block(entities: impl IntoIterator<Item = Entity>) -> Self {
311        Self::Block(entities.into_iter().collect())
312    }
313}
314
315impl Default for EntityFilter {
316    fn default() -> Self {
317        Self::allow_all()
318    }
319}
320
321/// Alias for a `'static` [`Write`] stream.
322pub trait SaveStream: Write
323where
324    Self: Static,
325{
326}
327
328impl<S: Write> SaveStream for S where S: Static {}
329
330/// An [`Event`] triggered at the end of the save process.
331///
332/// This event contains the saved [`World`] data as a [`DynamicScene`].
333#[derive(Event)]
334pub struct Saved {
335    /// The saved [`DynamicScene`] to be serialized.
336    pub scene: DynamicScene,
337}
338
339impl Saved {
340    /// Iterates over all the saved entities.
341    pub fn entities(&self) -> impl Iterator<Item = Entity> + '_ {
342        self.scene.entities.iter().map(|de| de.entity)
343    }
344}
345
346#[doc(hidden)]
347#[deprecated(since = "0.5.2", note = "use `Saved` instead")]
348pub type OnSave = Saved;
349
350/// An error that may occur during the save process.
351#[derive(Error, Debug)]
352pub enum SaveError {
353    /// An error occurred while serializing the scene.
354    #[error("Failed to serialize world: {0}")]
355    Ron(ron::Error),
356    /// An error occurred while writing into [`SaveOutput`].
357    #[error("Failed to write world: {0}")]
358    Io(io::Error),
359}
360
361impl From<ron::Error> for SaveError {
362    fn from(e: ron::Error) -> Self {
363        Self::Ron(e)
364    }
365}
366
367impl From<io::Error> for SaveError {
368    fn from(e: io::Error) -> Self {
369        Self::Io(e)
370    }
371}
372
373/// [`Result`] of a [`SaveEvent`].
374pub type SaveResult = Result<Saved, SaveError>;
375
376/// An [`Observer`] which saved the world when a [`SaveWorld`] event is triggered.
377pub fn save_on_default_event(event: OnSingle<SaveWorld>, commands: Commands) {
378    save_on(event, commands);
379}
380
381/// An [`Observer`] which saved the world when the given [`SaveEvent`] is triggered.
382pub fn save_on<E: SaveEvent>(event: OnSingle<E>, mut commands: Commands) {
383    commands.queue_handled(SaveCommand(event.consume().unwrap()), |err, ctx| {
384        error!("save failed: {err:?} ({ctx})");
385    });
386}
387
388fn save_world<E: SaveEvent>(mut event: E, world: &mut World) -> SaveResult {
389    // Notify
390    event.before_save(world);
391
392    // Filter
393    let entities: Vec<_> = world
394        .query_filtered::<Entity, E::SaveFilter>()
395        .iter(world)
396        .filter(|entity| event.filter_entity(world.entity(*entity)))
397        .collect();
398
399    // Serialize
400    event.before_serialize(world, &entities);
401    let scene = DynamicSceneBuilder::from_world(world)
402        .with_component_filter(event.component_filter())
403        .with_resource_filter(event.resource_filter())
404        .extract_resources()
405        .extract_entities(entities.iter().copied())
406        .build();
407
408    // Write
409    let saved = match event.output() {
410        SaveOutput::File(path) => {
411            if let Some(parent) = path.parent() {
412                std::fs::create_dir_all(parent)?;
413            }
414
415            let type_registry = world.resource::<AppTypeRegistry>().read();
416            let data = scene.serialize(&type_registry)?;
417            std::fs::write(&path, data.as_bytes())?;
418            debug!("saved into file: {path:?}");
419            Saved { scene }
420        }
421        SaveOutput::Stream(mut stream) => {
422            let type_registry = world.resource::<AppTypeRegistry>().read();
423            let data = scene.serialize(&type_registry)?;
424            stream.write_all(data.as_bytes())?;
425            debug!("saved into stream");
426            Saved { scene }
427        }
428        SaveOutput::Drop => {
429            debug!("saved data dropped");
430            Saved { scene }
431        }
432        SaveOutput::Invalid => {
433            panic!("SaveOutput is invalid");
434        }
435    };
436
437    let result = Ok(saved);
438    event.after_save(world, &result);
439    result
440}
441
442struct SaveCommand<E>(E);
443
444impl<E: SaveEvent> Command<Result<(), SaveError>> for SaveCommand<E> {
445    fn apply(self, world: &mut World) -> Result<(), SaveError> {
446        let saved = save_world(self.0, world)?;
447        world.trigger(saved);
448        Ok(())
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use std::fs::*;
455
456    use bevy::prelude::*;
457    use bevy_ecs::system::RunSystemOnce;
458
459    use super::*;
460
461    #[derive(Component, Default, Reflect)]
462    #[reflect(Component)]
463    #[require(Save)]
464    struct Foo;
465
466    fn app() -> App {
467        let mut app = App::new();
468        app.add_plugins(MinimalPlugins).register_type::<Foo>();
469        app
470    }
471
472    #[test]
473    fn test_save_into_file() {
474        #[derive(Resource)]
475        struct EventTriggered;
476
477        pub const PATH: &str = "test_save_into_file.ron";
478        let mut app = app();
479        app.add_observer(save_on_default_event);
480
481        app.add_observer(|_: On<Saved>, mut commands: Commands| {
482            commands.insert_resource(EventTriggered);
483        });
484
485        let _ = app.world_mut().run_system_once(|mut commands: Commands| {
486            commands.spawn((Foo, Save));
487            commands.trigger_save(SaveWorld::default_into_file(PATH));
488        });
489
490        let data = read_to_string(PATH).unwrap();
491        let world = app.world();
492        assert!(data.contains("Foo"));
493        assert!(world.contains_resource::<EventTriggered>());
494
495        remove_file(PATH).unwrap();
496    }
497
498    #[test]
499    fn test_save_into_stream() {
500        pub const PATH: &str = "test_save_to_stream.ron";
501
502        let mut app = app();
503        app.add_observer(save_on_default_event);
504
505        let _ = app.world_mut().run_system_once(|mut commands: Commands| {
506            commands.spawn((Foo, Save));
507            commands.trigger_save(SaveWorld::default_into_stream(File::create(PATH).unwrap()));
508        });
509
510        let data = read_to_string(PATH).unwrap();
511        assert!(data.contains("Foo"));
512
513        remove_file(PATH).unwrap();
514    }
515
516    #[test]
517    fn test_save_resource() {
518        pub const PATH: &str = "test_save_resource.ron";
519
520        #[derive(Resource, Default, Reflect)]
521        #[reflect(Resource)]
522        struct Bar;
523
524        let mut app = app();
525        app.register_type::<Bar>()
526            .add_observer(save_on_default_event);
527
528        let _ = app.world_mut().run_system_once(|mut commands: Commands| {
529            commands.insert_resource(Bar);
530            commands.trigger_save(
531                SaveWorld::default_into_stream(File::create(PATH).unwrap())
532                    .include_resource::<Bar>(),
533            );
534        });
535
536        app.update();
537
538        let data = read_to_string(PATH).unwrap();
539        assert!(data.contains("Bar"));
540
541        remove_file(PATH).unwrap();
542    }
543
544    #[test]
545    fn test_save_without_component() {
546        pub const PATH: &str = "test_save_without_component.ron";
547
548        #[derive(Component, Default, Reflect)]
549        #[reflect(Component)]
550        #[require(Save)]
551        struct Baz;
552
553        let mut app = app();
554        app.add_observer(save_on_default_event);
555
556        let _ = app.world_mut().run_system_once(|mut commands: Commands| {
557            commands.spawn((Foo, Baz, Save));
558            commands.trigger_save(SaveWorld::default_into_file(PATH).exclude_component::<Baz>());
559        });
560
561        let data = read_to_string(PATH).unwrap();
562        assert!(data.contains("Foo"));
563        assert!(!data.contains("Baz"));
564
565        remove_file(PATH).unwrap();
566    }
567
568    #[test]
569    fn test_map_component() {
570        pub const PATH: &str = "test_map_component.ron";
571
572        #[derive(Component, Default)]
573        struct Bar(#[allow(dead_code)] u32); // Not serializable
574
575        #[derive(Component, Default, Reflect)]
576        #[reflect(Component)]
577        struct Baz(u32); // Serializable
578
579        let mut app = app();
580        app.register_type::<Baz>()
581            .add_observer(save_on_default_event);
582
583        let entity = app
584            .world_mut()
585            .run_system_once(|mut commands: Commands| {
586                let entity = commands.spawn((Bar(12), Save)).id();
587                commands.trigger_save(
588                    SaveWorld::default_into_file(PATH).map_component::<Bar>(|Bar(i): &Bar| Baz(*i)),
589                );
590                entity
591            })
592            .unwrap();
593
594        let data = read_to_string(PATH).unwrap();
595        assert!(data.contains("Baz"));
596        assert!(data.contains("(12)"));
597        assert!(!data.contains("Bar"));
598        assert!(app.world().entity(entity).contains::<Bar>());
599        assert!(!app.world().entity(entity).contains::<Baz>());
600
601        remove_file(PATH).unwrap();
602    }
603}