1use std::io::{self, Read};
2use std::marker::PhantomData;
3use std::path::PathBuf;
4
5use bevy_asset::AssetServer;
6use bevy_world_serialization::DynamicWorld;
7use moonshine_util::expect::{expect_deferred, ExpectDeferredWorld};
8use moonshine_util::Static;
9use serde::de::DeserializeSeed;
10
11use bevy_ecs::entity::EntityHashMap;
12use bevy_ecs::prelude::*;
13use bevy_ecs::query::QueryFilter;
14use bevy_log::prelude::*;
15use bevy_world_serialization::{serde::WorldDeserializer, WorldInstanceSpawnError};
16
17use moonshine_util::event::{OnSingle, SingleEvent, TriggerSingle};
18use thiserror::Error;
19
20use crate::save::Save;
21use crate::{MapComponent, SceneMapper};
22
23#[derive(Component, Default, Clone)]
65pub struct Unload;
66
67pub trait TriggerLoad {
69 #[doc(alias = "trigger_single")]
71 fn trigger_load(self, event: impl LoadEvent);
72}
73
74impl TriggerLoad for &mut Commands<'_, '_> {
75 fn trigger_load(self, event: impl LoadEvent) {
76 self.trigger_single(event);
77 }
78}
79
80impl TriggerLoad for &mut World {
81 fn trigger_load(self, event: impl LoadEvent) {
82 self.trigger_single(event);
83 }
84}
85
86pub type DefaultUnloadFilter = Or<(With<Save>, With<Unload>)>;
88
89pub trait LoadEvent: SingleEvent {
96 type UnloadFilter: QueryFilter;
98
99 fn input(&mut self) -> LoadInput;
101
102 fn before_load(&mut self, _world: &mut World) {}
106
107 fn before_unload(&mut self, _world: &mut World, _entities: &[Entity]) {}
112
113 fn after_load(&mut self, _world: &mut World, _result: &LoadResult) {}
118}
119
120pub struct LoadWorld<U: QueryFilter = DefaultUnloadFilter> {
122 pub input: LoadInput,
124 pub mapper: SceneMapper,
126 #[doc(hidden)]
127 pub unload: PhantomData<U>,
128}
129
130impl<U: QueryFilter> LoadWorld<U> {
131 pub fn new(input: LoadInput, mapper: SceneMapper) -> Self {
133 LoadWorld {
134 input,
135 mapper,
136 unload: PhantomData,
137 }
138 }
139
140 pub fn from_file(path: impl Into<PathBuf>) -> Self {
143 LoadWorld {
144 input: LoadInput::file(path),
145 mapper: SceneMapper::default(),
146 unload: PhantomData,
147 }
148 }
149
150 pub fn from_stream(stream: impl LoadStream) -> Self {
153 LoadWorld {
154 input: LoadInput::stream(stream),
155 mapper: SceneMapper::default(),
156 unload: PhantomData,
157 }
158 }
159
160 pub fn map_component<T: Component>(self, m: impl MapComponent<T>) -> Self {
162 LoadWorld {
163 mapper: self.mapper.map(m),
164 ..self
165 }
166 }
167}
168
169impl LoadWorld {
170 pub fn default_from_file(path: impl Into<PathBuf>) -> Self {
173 Self::from_file(path)
174 }
175
176 pub fn default_from_stream(stream: impl LoadStream) -> Self {
179 Self::from_stream(stream)
180 }
181}
182
183impl<U: QueryFilter> SingleEvent for LoadWorld<U> where U: Static {}
184
185impl<U: QueryFilter> LoadEvent for LoadWorld<U>
186where
187 U: Static,
188{
189 type UnloadFilter = U;
190
191 fn input(&mut self) -> LoadInput {
192 self.input.consume().unwrap()
193 }
194
195 fn before_load(&mut self, world: &mut World) {
196 world.insert_resource(ExpectDeferredWorld);
197 }
198
199 fn after_load(&mut self, world: &mut World, result: &LoadResult) {
200 if let Ok(loaded) = result {
201 for entity in loaded.entities() {
202 let Ok(entity) = world.get_entity_mut(entity) else {
203 continue;
205 };
206 self.mapper.replace(entity);
207 }
208 }
209
210 expect_deferred(world);
211 }
212}
213
214pub enum LoadInput {
216 File(PathBuf),
218 Stream(Box<dyn LoadStream>),
220 World(DynamicWorld),
224 #[deprecated(note = "use `LoadInput::World` instead")]
225 Scene(DynamicWorld),
227 #[doc(hidden)]
228 Invalid,
229}
230
231impl LoadInput {
232 pub fn file(path: impl Into<PathBuf>) -> Self {
234 Self::File(path.into())
235 }
236
237 pub fn stream<S: LoadStream + 'static>(stream: S) -> Self {
239 Self::Stream(Box::new(stream))
240 }
241
242 pub fn consume(&mut self) -> Option<LoadInput> {
244 let input = std::mem::replace(self, LoadInput::Invalid);
245 if let LoadInput::Invalid = input {
246 return None;
247 }
248 Some(input)
249 }
250}
251
252pub trait LoadStream: Read
254where
255 Self: Static,
256{
257}
258
259impl<S: Read> LoadStream for S where S: Static {}
260
261#[derive(Event)]
265pub struct Loaded {
266 pub entity_map: EntityHashMap<Entity>,
268}
269
270impl Loaded {
271 pub fn entities(&self) -> impl Iterator<Item = Entity> + '_ {
276 self.entity_map.values().copied()
277 }
278}
279
280#[doc(hidden)]
281#[deprecated(since = "0.5.2", note = "use `Loaded` instead")]
282pub type OnLoad = Loaded;
283
284#[derive(Error, Debug)]
286pub enum LoadError {
287 #[error("Failed to read world: {0}")]
289 Io(io::Error),
290 #[error("Failed to deserialize world: {0}")]
292 Ron(ron::Error),
293 #[error("Failed to spawn scene: {0}")]
295 Scene(WorldInstanceSpawnError),
296}
297
298impl From<io::Error> for LoadError {
299 fn from(e: io::Error) -> Self {
300 Self::Io(e)
301 }
302}
303
304impl From<ron::de::SpannedError> for LoadError {
305 fn from(e: ron::de::SpannedError) -> Self {
306 Self::Ron(e.into())
307 }
308}
309
310impl From<ron::Error> for LoadError {
311 fn from(e: ron::Error) -> Self {
312 Self::Ron(e)
313 }
314}
315
316impl From<WorldInstanceSpawnError> for LoadError {
317 fn from(e: WorldInstanceSpawnError) -> Self {
318 Self::Scene(e)
319 }
320}
321
322pub type LoadResult = Result<Loaded, LoadError>;
324
325pub fn load_on_default_event(event: OnSingle<LoadWorld>, commands: Commands) {
327 load_on(event, commands);
328}
329
330pub fn load_on<E: LoadEvent>(event: OnSingle<E>, mut commands: Commands) {
332 commands.queue_handled(LoadCommand(event.consume().unwrap()), |err, ctx| {
333 error!("load failed: {err:?} ({ctx})");
334 });
335}
336
337fn load_world<E: LoadEvent>(mut event: E, world: &mut World) -> LoadResult {
338 event.before_load(world);
340
341 let mut asset_server = world.resource::<AssetServer>().clone();
342
343 let loaded_world = match event.input() {
345 LoadInput::File(path) => {
346 let bytes = std::fs::read(&path)?;
347 let mut deserializer = ron::Deserializer::from_bytes(&bytes)?;
348 let type_registry = &world.resource::<AppTypeRegistry>().read();
349 let world_deserializer = WorldDeserializer {
350 type_registry,
351 load_from_path: &mut asset_server,
352 };
353 world_deserializer.deserialize(&mut deserializer)?
354 }
355 LoadInput::Stream(mut data) => {
356 let mut bytes = Vec::new();
357 data.read_to_end(&mut bytes)?;
358 let mut deserializer = ron::Deserializer::from_bytes(&bytes)?;
359 let type_registry = &world.resource::<AppTypeRegistry>().read();
360 let world_deserializer = WorldDeserializer {
361 type_registry,
362 load_from_path: &mut asset_server,
363 };
364 world_deserializer.deserialize(&mut deserializer)?
365 }
366 LoadInput::World(input_world) => input_world,
367 #[allow(deprecated)] LoadInput::Scene(scene) => scene,
369 LoadInput::Invalid => {
370 panic!("LoadInput is invalid");
371 }
372 };
373
374 let entities: Vec<_> = world
376 .query_filtered::<Entity, E::UnloadFilter>()
377 .iter(world)
378 .collect();
379 event.before_unload(world, &entities);
380 for entity in entities {
381 if let Ok(entity) = world.get_entity_mut(entity) {
382 entity.despawn();
383 }
384 }
385
386 let mut entity_map = EntityHashMap::default();
388 loaded_world.write_to_world(world, &mut entity_map)?;
389 debug!("loaded {} entities", entity_map.len());
390
391 let result = Ok(Loaded { entity_map });
392 event.after_load(world, &result);
393 result
394}
395
396#[doc(hidden)]
398pub struct LoadCommand<E>(pub E);
399
400impl<E: LoadEvent> Command for LoadCommand<E> {
401 type Out = Result<(), LoadError>;
402
403 fn apply(self, world: &mut World) -> Result<(), LoadError> {
404 let loaded = load_world(self.0, world)?;
405 world.trigger(loaded);
406 Ok(())
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use std::fs::*;
413
414 use bevy::prelude::*;
415 use bevy_ecs::system::RunSystemOnce;
416
417 use super::*;
418
419 pub const DATA: &str = "(
420 resources: {},
421 entities: {
422 4294967293: (
423 components: {
424 \"moonshine_save::load::tests::Foo\": (),
425 },
426 ),
427 },
428 )";
429
430 #[derive(Component, Default, Reflect)]
431 #[reflect(Component)]
432 #[require(Save)]
433 struct Foo;
434
435 fn app() -> App {
436 let mut app = App::new();
437 app.add_plugins(MinimalPlugins)
438 .add_plugins(AssetPlugin::default())
439 .register_type::<Foo>();
440 app
441 }
442
443 #[test]
444 fn test_load_file() {
445 #[derive(Resource)]
446 struct EventTriggered;
447
448 pub const PATH: &str = "test_load_file.ron";
449
450 write(PATH, DATA).unwrap();
451
452 let mut app = app();
453 app.add_observer(load_on_default_event);
454
455 app.add_observer(|_: On<Loaded>, mut commands: Commands| {
456 commands.insert_resource(EventTriggered);
457 });
458
459 let _ = app.world_mut().run_system_once(|mut commands: Commands| {
460 commands.trigger_load(LoadWorld::default_from_file(PATH));
461 });
462
463 let world = app.world_mut();
464 assert!(world.contains_resource::<EventTriggered>());
465 assert!(world
466 .query_filtered::<(), With<Foo>>()
467 .single(world)
468 .is_ok());
469
470 remove_file(PATH).unwrap();
471 }
472
473 #[test]
474 fn test_load_stream() {
475 pub const PATH: &str = "test_load_stream.ron";
476
477 write(PATH, DATA).unwrap();
478
479 let mut app = app();
480 app.add_observer(load_on_default_event);
481
482 let _ = app.world_mut().run_system_once(|mut commands: Commands| {
483 commands.spawn((Foo, Save));
484 commands.trigger_load(LoadWorld::default_from_stream(File::open(PATH).unwrap()));
485 });
486
487 let data = read_to_string(PATH).unwrap();
488 assert!(data.contains("Foo"));
489
490 remove_file(PATH).unwrap();
491 }
492
493 #[test]
494 fn test_load_map_component() {
495 pub const PATH: &str = "test_load_map_component.ron";
496
497 write(PATH, DATA).unwrap();
498
499 #[derive(Component)]
500 struct Bar; let mut app = app();
503 app.add_observer(load_on_default_event);
504
505 let _ = app.world_mut().run_system_once(|mut commands: Commands| {
506 commands.trigger_load(LoadWorld::default_from_file(PATH).map_component(|_: &Foo| Bar));
507 });
508
509 let world = app.world_mut();
510 assert!(world
511 .query_filtered::<(), With<Bar>>()
512 .single(world)
513 .is_ok());
514 assert!(world.query_filtered::<(), With<Foo>>().iter(world).count() == 0);
515
516 remove_file(PATH).unwrap();
517 }
518}