1use core::any::TypeId;
7use core::time::Duration;
8use std::collections::HashMap;
9
10use bevy_app::{App, Plugin, PostUpdate};
11use bevy_ecs::{
12 change_detection::Tick,
13 reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
14 resource::Resource,
15 system::{Command, Commands, Res, ResMut},
16 world::World,
17};
18pub use bevy_ecs_macros::SettingsGroup;
19use bevy_log::warn;
20use bevy_reflect::{
21 prelude::ReflectDefault,
22 serde::{TypedReflectDeserializer, TypedReflectSerializer},
23 FromReflect, FromType, PartialReflect, ReflectMut, TypeInfo, TypePath, TypeRegistration,
24 TypeRegistry,
25};
26
27#[cfg(not(target_arch = "wasm32"))]
28mod store_fs;
29
30#[cfg(target_arch = "wasm32")]
31mod store_wasm;
32
33use bevy_time::{Time, Timer, TimerMode};
34use serde::de::DeserializeSeed;
35#[cfg(not(target_arch = "wasm32"))]
36use store_fs::SettingsStore;
37
38#[cfg(target_arch = "wasm32")]
39use store_wasm::SettingsStore;
40
41pub struct SettingsPlugin {
83 pub app_name: String,
85}
86
87impl SettingsPlugin {
88 pub fn new(app_name: &str) -> Self {
90 Self {
91 app_name: app_name.to_string(),
92 }
93 }
94}
95
96impl Plugin for SettingsPlugin {
97 fn build(&self, app: &mut App) {
98 let app_name = self.app_name.clone();
99 let world = app.world();
100 let last_save = world.read_change_tick();
101
102 let Some(app_types) = world.get_resource::<AppTypeRegistry>() else {
104 return;
105 };
106 let app_types = app_types.clone();
107 let types = app_types.read();
108
109 let world = app.world_mut();
110 let file_index = build_settings_registry(&app_name, &types, last_save);
111
112 for (filename, manifest) in file_index.files.iter() {
115 load_settings_file(world, &app_name, filename, manifest, &types);
116 }
117
118 drop(types);
121 world.insert_resource::<SettingsFileRegistry>(file_index);
122
123 app.add_systems(PostUpdate, handle_delayed_save);
124 }
125}
126
127pub trait SettingsGroup: Resource {
140 fn settings_group_name() -> &'static str;
142
143 fn settings_key_name() -> Option<&'static str>;
148
149 fn settings_source() -> Option<&'static str>;
152}
153
154#[derive(Clone)]
156pub struct ReflectSettingsGroup {
157 settings_group_name: &'static str,
159 settings_key_name: Option<&'static str>,
161 settings_source: Option<&'static str>,
163}
164
165impl<T: SettingsGroup + FromReflect + TypePath> FromType<T> for ReflectSettingsGroup {
166 fn from_type() -> Self {
167 ReflectSettingsGroup {
168 settings_group_name: T::settings_group_name(),
169 settings_key_name: T::settings_key_name(),
170 settings_source: T::settings_source(),
171 }
172 }
173
174 fn insert_dependencies(type_registration: &mut TypeRegistration) {
175 type_registration.register_type_data::<ReflectResource, T>();
176 }
177}
178
179#[derive(Default)]
182struct SettingsFileManifest {
183 last_save: Tick,
184 resource_types: Vec<TypeId>,
185}
186
187#[derive(Resource)]
191struct SettingsFileRegistry {
192 app_name: String,
194
195 files: HashMap<&'static str, SettingsFileManifest>,
197
198 save_timer: Timer,
200}
201
202#[derive(Default, PartialEq)]
205pub enum SaveSettingsSync {
206 #[default]
208 IfChanged,
209 Always,
211}
212
213impl Command for SaveSettingsSync {
214 type Out = ();
215
216 fn apply(self, world: &mut World) {
217 save_settings(world, false, self == SaveSettingsSync::Always);
218 }
219}
220
221#[derive(Default, PartialEq)]
223pub enum SaveSettings {
224 #[default]
226 IfChanged,
227 Always,
229}
230
231impl Command for SaveSettings {
232 type Out = ();
233
234 fn apply(self, world: &mut World) {
235 save_settings(world, true, self == SaveSettings::Always);
236 }
237}
238
239pub struct SaveSettingsDeferred(pub Duration);
244
245impl Default for SaveSettingsDeferred {
246 fn default() -> Self {
247 Self(Duration::from_secs(1))
248 }
249}
250
251impl Command for SaveSettingsDeferred {
252 type Out = ();
253
254 fn apply(self, world: &mut World) {
255 let Some(mut registry) = world.get_resource_mut::<SettingsFileRegistry>() else {
256 return;
257 };
258
259 registry.save_timer.set_duration(self.0);
260 registry.save_timer.reset();
261 registry.save_timer.unpause();
262 }
263}
264
265fn save_settings(world: &mut World, use_async: bool, force: bool) {
266 let this_run = world.change_tick();
267 let Some(registry) = world.get_resource::<SettingsFileRegistry>() else {
268 warn!("Settings registry not found - did you forget to install the SettingsPlugin?");
269 return;
270 };
271 let Some(app_types) = world.get_resource::<AppTypeRegistry>() else {
272 return;
273 };
274 let app_types = app_types.clone();
275 let types = app_types.read();
276
277 for (filename, manifest) in registry.files.iter() {
278 if force || has_settings_changed(world, manifest) {
279 let table = resources_to_toml(world, &types, manifest);
280 let store = SettingsStore::new(®istry.app_name);
281 if use_async {
282 store.save_async(filename, table);
283 } else {
284 store.save(filename, table);
285 }
286 }
287 }
288
289 let mut registry = world.get_resource_mut::<SettingsFileRegistry>().unwrap();
291 for manifest in registry.files.values_mut() {
292 manifest.last_save = this_run;
293 }
294}
295
296fn has_settings_changed(world: &World, manifest: &SettingsFileManifest) -> bool {
297 let this_run = world.read_change_tick();
298 manifest.resource_types.iter().any(|r| {
299 let Some(component_id) = world.components().get_id(*r) else {
300 return false;
301 };
302 if let Some(resource_change) = world.get_resource_change_ticks_by_id(component_id) {
303 return resource_change.is_changed(manifest.last_save, this_run);
304 }
305 false
306 })
307}
308
309fn resources_to_toml(
310 world: &World,
311 types: &TypeRegistry,
312 manifest: &SettingsFileManifest,
313) -> toml::map::Map<String, toml::Value> {
314 let mut table = toml::Table::new();
315
316 for tid in manifest.resource_types.iter() {
317 let ty = types.get(*tid).unwrap();
318
319 let Some(cmp) = ty.data::<ReflectComponent>() else {
320 continue;
321 };
322
323 let Some(reflect_settings_group) = ty.data::<ReflectSettingsGroup>() else {
324 continue;
325 };
326
327 let settings_group = reflect_settings_group.settings_group_name;
328 let settings_key = reflect_settings_group.settings_key_name;
329
330 let Some(component_id) = world.components().get_id(*tid) else {
331 continue;
332 };
333
334 let Some(res_entity) = world.resource_entities().get(component_id) else {
335 continue;
336 };
337 let res_entity_ref = world.entity(res_entity);
338 let Some(reflect) = cmp.reflect(res_entity_ref) else {
339 continue;
340 };
341
342 let serializer = TypedReflectSerializer::new(reflect.as_partial_reflect(), types);
343
344 let toml_value = if let Some(settings_key) = settings_key {
345 toml::Value::Table(toml::Table::from_iter([(
347 settings_key.to_string(),
348 toml::Value::try_from(serializer).unwrap(),
349 )]))
350 } else {
351 toml::Value::try_from(serializer).unwrap()
353 };
354
355 match (
356 toml_value.as_table(),
357 table
358 .get_mut(settings_group)
359 .and_then(|value| value.as_table_mut()),
360 ) {
361 (Some(from), Some(to)) => {
362 for (key, value) in from.iter() {
364 to.insert(key.clone(), value.clone());
365 }
366 }
367 _ => {
368 table.insert(settings_group.to_string(), toml_value);
369 }
370 };
371 }
372
373 table
374}
375
376fn build_settings_registry(
382 app_name: &str,
383 types: &TypeRegistry,
384 last_save: Tick,
385) -> SettingsFileRegistry {
386 let mut file_index = SettingsFileRegistry {
389 app_name: app_name.to_string(),
390 files: HashMap::new(),
391 save_timer: Timer::new(Duration::from_secs(1), TimerMode::Once),
392 };
393 file_index.save_timer.pause(); for ty in types.iter() {
398 if !ty.contains::<ReflectDefault>() {
399 continue;
400 };
401
402 let Some(reflect_group) = ty.data::<ReflectSettingsGroup>() else {
403 continue;
404 };
405
406 let filename = reflect_group.settings_source.unwrap_or("settings");
408 let pending_file = file_index
409 .files
410 .entry(filename)
411 .or_insert(SettingsFileManifest {
412 last_save,
413 resource_types: Vec::new(),
414 });
415 pending_file.last_save = last_save;
416 pending_file.resource_types.push(ty.type_id());
417 }
418
419 file_index
420}
421
422fn load_settings_file(
424 world: &mut World,
425 app_name: &str,
426 filename: &str,
427 manifest: &SettingsFileManifest,
428 types: &TypeRegistry,
429) {
430 let store = SettingsStore::new(app_name);
432 let toml = store.load(filename);
433 if toml.is_none() {
434 warn!("Filename {filename}.toml not found");
435 }
436
437 apply_settings_to_world(world, toml.as_ref(), manifest, types);
438}
439
440fn apply_settings_to_world(
447 world: &mut World,
448 toml: Option<&toml::Table>,
449 manifest: &SettingsFileManifest,
450 types: &TypeRegistry,
451) {
452 for tid in manifest.resource_types.iter() {
453 let ty = types.get(*tid).unwrap();
454 let Some(reflect_settings_group) = ty.data::<ReflectSettingsGroup>() else {
455 continue;
456 };
457
458 let settings_group = reflect_settings_group.settings_group_name;
459 let settings_key = reflect_settings_group.settings_key_name;
460
461 let reflect_component = ty.data::<ReflectComponent>().unwrap();
462 let component_id = world.components().get_id(*tid);
463 let res_entity = component_id.and_then(|cid| world.resource_entities().get(cid));
464
465 if let Some(res_entity) = res_entity {
466 let res_entity_mut = world.entity_mut(res_entity);
468 let Some(mut reflect) = reflect_component.reflect_mut(res_entity_mut) else {
469 continue;
470 };
471
472 if let Some(toml) = toml
473 && let Some(value) = toml.get(settings_group)
474 {
475 let value = if let Some(settings_key) = settings_key {
476 value.get(settings_key).unwrap_or(value)
479 } else {
480 value
482 };
483
484 load_properties(value, &mut *reflect, types);
485 }
486 } else {
487 let reflect_default = ty.data::<ReflectDefault>().unwrap();
489 let mut default_value = reflect_default.default();
490 let mut res_entity = world.spawn_empty();
491
492 if let Some(toml) = toml
493 && let Some(value) = toml.get(settings_group)
494 {
495 let value = if let Some(settings_key) = settings_key {
496 value.get(settings_key).unwrap_or(value)
499 } else {
500 value
502 };
503
504 load_properties(value, &mut *default_value, types);
505 }
506
507 reflect_component.insert(&mut res_entity, default_value.as_partial_reflect(), types);
509 }
510 }
511}
512
513fn load_properties(value: &toml::Value, resource: &mut dyn PartialReflect, types: &TypeRegistry) {
514 let Some(tinfo) = resource.get_represented_type_info() else {
515 return;
516 };
517
518 match tinfo {
519 TypeInfo::Struct(stinfo) => {
520 if let Some(table) = value.as_table()
521 && let ReflectMut::Struct(st_reflect) = resource.reflect_mut()
522 {
523 for (idx, field) in stinfo.field_names().iter().enumerate() {
525 if let Some(toml_field_value) = table.get(*field)
526 && let Some(field_info) = stinfo.field_at(idx)
527 && let Some(field_type) = types.get(field_info.type_id())
528 {
529 let deserializer = TypedReflectDeserializer::new(field_type, types);
530 if let Ok(field_value) = deserializer.deserialize(toml_field_value.clone())
531 {
532 st_reflect.field_at_mut(idx).unwrap().apply(&*field_value);
534 }
535 }
536 }
537 }
538 }
539 TypeInfo::TupleStruct(tstinfo) => {
540 if let ReflectMut::TupleStruct(tst_reflect) = resource.reflect_mut() {
541 if tst_reflect.field_len() > 1
543 && let Some(array) = value.as_array()
544 {
545 for (idx, toml_field_value) in array.iter().enumerate() {
546 if let Some(field_info) = tstinfo.field_at(idx)
547 && let Some(field_type) = types.get(field_info.type_id())
548 {
549 let deserializer = TypedReflectDeserializer::new(field_type, types);
550 if let Ok(field_value) =
551 deserializer.deserialize(toml_field_value.clone())
552 {
553 tst_reflect.field_mut(idx).unwrap().apply(&*field_value);
555 }
556 }
557 }
558 } else if tst_reflect.field_len() == 1
559 && let Some(field_info) = tstinfo.field_at(0)
560 && let Some(field_type) = types.get(field_info.type_id())
561 {
562 let deserializer = TypedReflectDeserializer::new(field_type, types);
563 if let Ok(field_value) = deserializer.deserialize(value.clone()) {
564 tst_reflect.field_mut(0).unwrap().apply(&*field_value);
566 }
567 }
568 }
569 }
570 TypeInfo::Enum(einfo) => {
571 if let ReflectMut::Enum(en_reflect) = resource.reflect_mut()
572 && let Some(variant_type) = types.get(einfo.type_id())
573 {
574 let deserializer = TypedReflectDeserializer::new(variant_type, types);
575
576 if let Ok(variant_value) = deserializer.deserialize(value.clone()) {
577 en_reflect.apply(&*variant_value);
578 }
579 }
580 }
581 _ => {}
582 }
583}
584
585fn handle_delayed_save(
586 mut settings: ResMut<SettingsFileRegistry>,
587 time: Res<Time>,
588 mut commands: Commands,
589) {
590 settings.save_timer.tick(time.delta());
591 if settings.save_timer.just_finished() {
592 commands.queue(SaveSettings::IfChanged);
593 }
594}
595
596#[cfg(test)]
597mod tests {
598 use super::*;
599 use bevy_ecs::change_detection::Tick;
600 use bevy_reflect::Reflect;
601 extern crate self as bevy_settings;
603
604 #[derive(Resource, SettingsGroup, Reflect, Default)]
606 #[reflect(Resource, SettingsGroup, Default)]
607 struct CounterSettings {
608 count: i32,
609 }
610
611 #[derive(Resource, SettingsGroup, Reflect, Default)]
613 #[reflect(Resource, SettingsGroup, Default)]
614 #[settings_group(group = "counter_settings")]
615 struct ExtraCounterSettings {
616 enabled: bool,
617 }
618
619 #[derive(Resource, SettingsGroup, Reflect, Debug, Default, PartialEq)]
620 #[reflect(Resource, SettingsGroup, Default)]
621 #[settings_group(group = "counter_settings", key = "refresh_rate")]
622 enum CounterRefreshRateSettings {
623 #[default]
624 Slow,
625 Fast,
626 }
627
628 #[derive(Resource, SettingsGroup, Reflect, Default)]
630 #[reflect(Resource, SettingsGroup, Default)]
631 #[settings_group(file = "audio")]
632 struct AudioSettings {
633 volume: f32,
634 }
635
636 #[test]
637 fn test_build_registry_single_struct_resource() {
638 let mut types = TypeRegistry::default();
639 types.register::<CounterSettings>();
640
641 let registry = build_settings_registry("test_app", &types, Tick::new(0));
642
643 assert_eq!(registry.app_name, "test_app");
644 assert_eq!(registry.files.len(), 1);
645 assert!(registry.files.contains_key("settings"));
646
647 let manifest = registry.files.get("settings").unwrap();
648 assert_eq!(manifest.resource_types.len(), 1);
649 }
650
651 #[test]
652 fn test_build_registry_single_enum_resource() {
653 let mut types = TypeRegistry::default();
654 types.register::<CounterRefreshRateSettings>();
655
656 let registry = build_settings_registry("test_app", &types, Tick::new(0));
657
658 assert_eq!(registry.app_name, "test_app");
659 assert_eq!(registry.files.len(), 1);
660 assert!(registry.files.contains_key("settings"));
661
662 let manifest = registry.files.get("settings").unwrap();
663 assert_eq!(manifest.resource_types.len(), 1);
664 }
665
666 #[test]
667 fn test_build_registry_merged_groups() {
668 let mut types = TypeRegistry::default();
669 types.register::<CounterSettings>();
670 types.register::<ExtraCounterSettings>();
671
672 let registry = build_settings_registry("test_app", &types, Tick::new(0));
673
674 assert_eq!(registry.files.len(), 1);
676 assert!(registry.files.contains_key("settings"));
677
678 let manifest = registry.files.get("settings").unwrap();
679 assert_eq!(manifest.resource_types.len(), 2);
681 }
682
683 #[test]
684 fn test_build_registry_separate_files() {
685 let mut types = TypeRegistry::default();
686 types.register::<CounterSettings>();
687 types.register::<AudioSettings>();
688
689 let registry = build_settings_registry("test_app", &types, Tick::new(0));
690
691 assert_eq!(registry.files.len(), 2);
693 assert!(registry.files.contains_key("settings"));
694 assert!(registry.files.contains_key("audio"));
695
696 let settings_manifest = registry.files.get("settings").unwrap();
697 assert_eq!(settings_manifest.resource_types.len(), 1);
698
699 let audio_manifest = registry.files.get("audio").unwrap();
700 assert_eq!(audio_manifest.resource_types.len(), 1);
701 }
702
703 #[test]
704 fn test_resources_to_toml_merges_same_group() {
705 let mut world = World::new();
706 let mut types = TypeRegistry::default();
707 types.register::<CounterSettings>();
708 types.register::<ExtraCounterSettings>();
709 types.register::<CounterRefreshRateSettings>();
710
711 world.insert_resource(CounterSettings { count: 42 });
713 world.insert_resource(ExtraCounterSettings { enabled: true });
714 world.insert_resource(CounterRefreshRateSettings::Fast);
715
716 let manifest = SettingsFileManifest {
718 last_save: Tick::new(0),
719 resource_types: vec![
720 TypeId::of::<CounterSettings>(),
721 TypeId::of::<ExtraCounterSettings>(),
722 TypeId::of::<CounterRefreshRateSettings>(),
723 ],
724 };
725
726 let table = resources_to_toml(&world, &types, &manifest);
727
728 assert!(table.contains_key("counter_settings"));
730 let counter_section = table.get("counter_settings").unwrap().as_table().unwrap();
731
732 assert_eq!(
734 counter_section.get("count").unwrap().as_integer().unwrap(),
735 42
736 );
737 assert!(counter_section.get("enabled").unwrap().as_bool().unwrap());
738 assert_eq!(
739 counter_section
740 .get("refresh_rate")
741 .unwrap()
742 .as_str()
743 .unwrap(),
744 "Fast"
745 );
746 }
747
748 #[test]
749 fn test_round_trip_serialization() {
750 #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)]
751 #[reflect(Resource, SettingsGroup, Default)]
752 struct SingleFieldTupleStruct(u8);
753
754 #[derive(Reflect, PartialEq, Debug, Default)]
755 #[reflect(Default)]
756 struct NestedStruct {
757 a: u8,
758 b: u16,
759 }
760
761 #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)]
762 #[reflect(Resource, SettingsGroup, Default)]
763 struct MultiFieldTupleStruct(u8, NestedStruct);
764
765 #[derive(Resource, SettingsGroup, Reflect, Default)]
766 #[reflect(Resource, SettingsGroup, Default)]
767 struct NewTypeSingleTupleStruct(SingleFieldTupleStruct);
768
769 #[derive(Resource, SettingsGroup, Reflect, Default)]
770 #[reflect(Resource, SettingsGroup, Default)]
771 struct NewTypeMultiTupleStruct(SingleFieldTupleStruct, MultiFieldTupleStruct);
772
773 #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)]
774 #[reflect(Resource, SettingsGroup, Default)]
775 enum EnumUnitVariant {
776 #[default]
777 A,
778 }
779
780 #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
781 #[reflect(Resource, SettingsGroup, Default)]
782 enum EnumSingleTupleVariant {
783 A(u8),
784 }
785
786 impl Default for EnumSingleTupleVariant {
787 fn default() -> Self {
788 EnumSingleTupleVariant::A(0)
789 }
790 }
791
792 #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
793 #[reflect(Resource, SettingsGroup, Default)]
794 enum EnumMultiTupleVariant {
795 A(u16, u32),
796 }
797
798 impl Default for EnumMultiTupleVariant {
799 fn default() -> Self {
800 EnumMultiTupleVariant::A(0, 0)
801 }
802 }
803
804 #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
805 #[reflect(Resource, SettingsGroup, Default)]
806 enum EnumStructVariant {
807 A { x: u8, y: u16 },
808 }
809
810 impl Default for EnumStructVariant {
811 fn default() -> Self {
812 EnumStructVariant::A { x: 0, y: 0 }
813 }
814 }
815
816 #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
817 #[reflect(Resource, SettingsGroup, Default)]
818 enum EnumSingleNewTypeVariant {
819 A(SingleFieldTupleStruct),
820 }
821
822 impl Default for EnumSingleNewTypeVariant {
823 fn default() -> Self {
824 EnumSingleNewTypeVariant::A(SingleFieldTupleStruct(0))
825 }
826 }
827
828 #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
829 #[reflect(Resource, SettingsGroup, Default)]
830 enum EnumMultiNewTypeVariant {
831 A(SingleFieldTupleStruct, MultiFieldTupleStruct),
832 }
833
834 impl Default for EnumMultiNewTypeVariant {
835 fn default() -> Self {
836 EnumMultiNewTypeVariant::A(
837 SingleFieldTupleStruct(0),
838 MultiFieldTupleStruct(0, NestedStruct { a: 0, b: 0 }),
839 )
840 }
841 }
842
843 let mut world = World::new();
844 let mut types = TypeRegistry::default();
845
846 types.register::<CounterSettings>();
847 types.register::<ExtraCounterSettings>();
848 types.register::<CounterRefreshRateSettings>();
849 types.register::<SingleFieldTupleStruct>();
850 types.register::<MultiFieldTupleStruct>();
851 types.register::<NewTypeSingleTupleStruct>();
852 types.register::<NewTypeMultiTupleStruct>();
853 types.register::<EnumUnitVariant>();
854 types.register::<EnumSingleTupleVariant>();
855 types.register::<EnumMultiTupleVariant>();
856 types.register::<EnumStructVariant>();
857 types.register::<EnumSingleNewTypeVariant>();
858 types.register::<EnumMultiNewTypeVariant>();
859
860 world.insert_resource(CounterSettings { count: 123 });
862 world.insert_resource(ExtraCounterSettings { enabled: false });
863 world.insert_resource(CounterRefreshRateSettings::Fast);
864 world.insert_resource(SingleFieldTupleStruct(1));
865 world.insert_resource(MultiFieldTupleStruct(2, NestedStruct { a: 1, b: 2 }));
866 world.insert_resource(NewTypeSingleTupleStruct(SingleFieldTupleStruct(1)));
867 world.insert_resource(NewTypeMultiTupleStruct(
868 SingleFieldTupleStruct(1),
869 MultiFieldTupleStruct(2, NestedStruct { a: 1, b: 2 }),
870 ));
871 world.insert_resource(EnumUnitVariant::A);
872 world.insert_resource(EnumSingleTupleVariant::A(1));
873 world.insert_resource(EnumMultiTupleVariant::A(1, 2));
874 world.insert_resource(EnumStructVariant::A { x: 1, y: 2 });
875 world.insert_resource(EnumSingleNewTypeVariant::A(SingleFieldTupleStruct(1)));
876 world.insert_resource(EnumMultiNewTypeVariant::A(
877 SingleFieldTupleStruct(1),
878 MultiFieldTupleStruct(2, NestedStruct { a: 1, b: 2 }),
879 ));
880
881 let manifest = SettingsFileManifest {
883 last_save: Tick::new(0),
884 resource_types: vec![
885 TypeId::of::<CounterSettings>(),
886 TypeId::of::<ExtraCounterSettings>(),
887 TypeId::of::<CounterRefreshRateSettings>(),
888 TypeId::of::<SingleFieldTupleStruct>(),
889 TypeId::of::<MultiFieldTupleStruct>(),
890 TypeId::of::<NewTypeSingleTupleStruct>(),
891 TypeId::of::<NewTypeMultiTupleStruct>(),
892 TypeId::of::<EnumUnitVariant>(),
893 TypeId::of::<EnumSingleTupleVariant>(),
894 TypeId::of::<EnumMultiTupleVariant>(),
895 TypeId::of::<EnumStructVariant>(),
896 TypeId::of::<EnumSingleNewTypeVariant>(),
897 TypeId::of::<EnumMultiNewTypeVariant>(),
898 ],
899 };
900
901 let table = resources_to_toml(&world, &types, &manifest);
903
904 let mut new_world = World::new();
906 apply_settings_to_world(&mut new_world, Some(&table), &manifest, &types);
907
908 let counter = new_world.get_resource::<CounterSettings>().unwrap();
910 assert_eq!(counter.count, 123);
911
912 let extra = new_world.get_resource::<ExtraCounterSettings>().unwrap();
913 assert!(!extra.enabled);
914
915 let refresh_rate = new_world
916 .get_resource::<CounterRefreshRateSettings>()
917 .unwrap();
918 assert_eq!(*refresh_rate, CounterRefreshRateSettings::Fast);
919
920 let single_field_tuple_struct = new_world.get_resource::<SingleFieldTupleStruct>().unwrap();
921 assert_eq!(single_field_tuple_struct.0, 1);
922
923 let multi_field_tuple_struct = new_world.get_resource::<MultiFieldTupleStruct>().unwrap();
924 assert_eq!(multi_field_tuple_struct.0, 2);
925 assert_eq!(multi_field_tuple_struct.1.a, 1);
926 assert_eq!(multi_field_tuple_struct.1.b, 2);
927
928 let new_type_single_tuple_struct = new_world
929 .get_resource::<NewTypeSingleTupleStruct>()
930 .unwrap();
931 assert_eq!(new_type_single_tuple_struct.0 .0, 1);
932
933 let new_type_multi_tuple_struct =
934 new_world.get_resource::<NewTypeMultiTupleStruct>().unwrap();
935 assert_eq!(new_type_multi_tuple_struct.0 .0, 1);
936 assert_eq!(new_type_multi_tuple_struct.1 .0, 2);
937 assert_eq!(new_type_multi_tuple_struct.1 .1.a, 1);
938 assert_eq!(new_type_multi_tuple_struct.1 .1.b, 2);
939
940 let enum_unit_variant = new_world.get_resource::<EnumUnitVariant>().unwrap();
941 assert_eq!(*enum_unit_variant, EnumUnitVariant::A);
942
943 let enum_single_tuple_variant = new_world.get_resource::<EnumSingleTupleVariant>().unwrap();
944 assert_eq!(*enum_single_tuple_variant, EnumSingleTupleVariant::A(1));
945
946 let enum_multi_tuple_variant = new_world.get_resource::<EnumMultiTupleVariant>().unwrap();
947 assert_eq!(*enum_multi_tuple_variant, EnumMultiTupleVariant::A(1, 2));
948
949 let enum_struct_variant = new_world.get_resource::<EnumStructVariant>().unwrap();
950 assert_eq!(*enum_struct_variant, EnumStructVariant::A { x: 1, y: 2 });
951
952 let enum_single_new_type_variant = new_world
953 .get_resource::<EnumSingleNewTypeVariant>()
954 .unwrap();
955 assert_eq!(
956 *enum_single_new_type_variant,
957 EnumSingleNewTypeVariant::A(SingleFieldTupleStruct(1))
958 );
959
960 let enum_multi_new_type_variant =
961 new_world.get_resource::<EnumMultiNewTypeVariant>().unwrap();
962 assert_eq!(
963 *enum_multi_new_type_variant,
964 EnumMultiNewTypeVariant::A(
965 SingleFieldTupleStruct(1),
966 MultiFieldTupleStruct(2, NestedStruct { a: 1, b: 2 })
967 )
968 );
969 }
970
971 #[test]
972 fn test_round_trip_with_existing_resources() {
973 let mut world = World::new();
974 let mut types = TypeRegistry::default();
975 types.register::<CounterSettings>();
976 types.register::<CounterRefreshRateSettings>();
977
978 world.insert_resource(CounterSettings { count: 100 });
980 world.insert_resource(CounterRefreshRateSettings::Fast);
981
982 let manifest = SettingsFileManifest {
983 last_save: Tick::new(0),
984 resource_types: vec![
985 TypeId::of::<CounterSettings>(),
986 TypeId::of::<CounterRefreshRateSettings>(),
987 ],
988 };
989
990 let table = resources_to_toml(&world, &types, &manifest);
992
993 world.resource_mut::<CounterSettings>().count = 999;
995 *world.resource_mut::<CounterRefreshRateSettings>() = CounterRefreshRateSettings::Slow;
996
997 apply_settings_to_world(&mut world, Some(&table), &manifest, &types);
999
1000 let counter = world.get_resource::<CounterSettings>().unwrap();
1001 assert_eq!(counter.count, 100);
1002 let refresh_rate = world.get_resource::<CounterRefreshRateSettings>().unwrap();
1003 assert_eq!(*refresh_rate, CounterRefreshRateSettings::Fast);
1004 }
1005
1006 #[test]
1007 fn test_partial_toml_preserves_missing_fields() {
1008 let mut world = World::new();
1009 let mut types = TypeRegistry::default();
1010 types.register::<CounterSettings>();
1011 types.register::<ExtraCounterSettings>();
1012 types.register::<CounterRefreshRateSettings>();
1013
1014 world.insert_resource(CounterSettings { count: 50 });
1016 world.insert_resource(ExtraCounterSettings { enabled: true });
1017 world.insert_resource(CounterRefreshRateSettings::Fast);
1018
1019 let mut table = toml::Table::new();
1021 let mut counter_section = toml::Table::new();
1022 counter_section.insert("count".to_string(), toml::Value::Integer(999));
1023 table.insert(
1024 "counter_settings".to_string(),
1025 toml::Value::Table(counter_section),
1026 );
1027 let manifest = SettingsFileManifest {
1030 last_save: Tick::new(0),
1031 resource_types: vec![
1032 TypeId::of::<CounterSettings>(),
1033 TypeId::of::<ExtraCounterSettings>(),
1034 TypeId::of::<CounterRefreshRateSettings>(),
1035 ],
1036 };
1037
1038 apply_settings_to_world(&mut world, Some(&table), &manifest, &types);
1040
1041 let counter = world.get_resource::<CounterSettings>().unwrap();
1043 assert_eq!(counter.count, 999);
1044
1045 let extra = world.get_resource::<ExtraCounterSettings>().unwrap();
1047 assert!(extra.enabled);
1048
1049 let refresh_rate = world.get_resource::<CounterRefreshRateSettings>().unwrap();
1051 assert_eq!(*refresh_rate, CounterRefreshRateSettings::Fast);
1052 }
1053}