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#[derive(Component, Default, Debug, Clone)]
20pub struct Save;
21
22pub trait TriggerSave {
24 #[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
41pub trait SaveEvent: SingleEvent {
48 type SaveFilter: QueryFilter;
50
51 fn filter_entity(&self, _entity: EntityRef) -> bool {
53 true
54 }
55
56 fn before_save(&mut self, _world: &mut World) {}
60
61 fn before_serialize(&mut self, _world: &mut World, _entities: &[Entity]) {}
65
66 fn component_filter(&mut self) -> SceneFilter {
68 SceneFilter::allow_all()
69 }
70
71 fn resource_filter(&mut self) -> SceneFilter {
73 SceneFilter::deny_all()
74 }
75
76 fn after_save(&mut self, _world: &mut World, _result: &SaveResult) {}
80
81 fn output(&mut self) -> SaveOutput;
83}
84
85pub struct SaveWorld<F: QueryFilter = DefaultSaveFilter> {
87 pub entities: EntityFilter,
91 pub resources: SceneFilter,
95 pub components: SceneFilter,
99 pub mapper: SceneMapper,
103 pub output: SaveOutput,
105 #[doc(hidden)]
106 pub filter: PhantomData<F>,
107}
108
109impl<F: QueryFilter> SaveWorld<F> {
110 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 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 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 pub fn include_resource<R: Resource>(mut self) -> Self {
150 self.resources = self.resources.allow::<R>();
151 self
152 }
153
154 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 pub fn exclude_component<T: Component>(mut self) -> Self {
162 self.components = self.components.deny::<T>();
163 self
164 }
165
166 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 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 pub fn default_into_file(path: impl Into<PathBuf>) -> Self {
183 Self::into_file(path)
184 }
185
186 pub fn default_into_stream(stream: impl SaveStream) -> Self {
189 Self::into_stream(stream)
190 }
191}
192
193impl SaveWorld<()> {
194 pub fn all_into_file(path: impl Into<PathBuf>) -> Self {
196 Self::into_file(path)
197 }
198
199 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
249pub type DefaultSaveFilter = With<Save>;
252
253pub enum SaveOutput {
255 File(PathBuf),
257 Stream(Box<dyn SaveStream>),
259 Drop,
264 #[doc(hidden)]
265 Invalid,
266}
267
268impl SaveOutput {
269 pub fn file(path: impl Into<PathBuf>) -> Self {
271 Self::File(path.into())
272 }
273
274 pub fn stream<S: SaveStream + 'static>(stream: S) -> Self {
276 Self::Stream(Box::new(stream))
277 }
278
279 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#[derive(Clone, Debug)]
291pub enum EntityFilter {
292 Allow(EntityHashSet),
294 Block(EntityHashSet),
296}
297
298impl EntityFilter {
299 pub fn allow_all() -> Self {
301 Self::Block(EntityHashSet::new())
302 }
303
304 pub fn allow(entities: impl IntoIterator<Item = Entity>) -> Self {
306 Self::Allow(entities.into_iter().collect())
307 }
308
309 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
321pub trait SaveStream: Write
323where
324 Self: Static,
325{
326}
327
328impl<S: Write> SaveStream for S where S: Static {}
329
330#[derive(Event)]
334pub struct Saved {
335 pub scene: DynamicScene,
337}
338
339impl Saved {
340 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#[derive(Error, Debug)]
352pub enum SaveError {
353 #[error("Failed to serialize world: {0}")]
355 Ron(ron::Error),
356 #[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
373pub type SaveResult = Result<Saved, SaveError>;
375
376pub fn save_on_default_event(event: OnSingle<SaveWorld>, commands: Commands) {
378 save_on(event, commands);
379}
380
381pub 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 event.before_save(world);
391
392 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 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 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); #[derive(Component, Default, Reflect)]
576 #[reflect(Component)]
577 struct Baz(u32); 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}