Skip to content

Commit 695c8e0

Browse files
authored
Bevy settings support tuple structs and non-unit like enums (#23812)
# Objective Implements part of #23302 For background on bevy settings see the initial PR: #23034 ## Solution As we invoke the serde toml serializer: Single field tuple structs are serialized as single values: ```rust #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)] #[reflect(Resource, SettingsGroup, Default)] struct SingleFieldTupleStruct(u8); ``` Results in: ```toml [single_field_tuple_struct] single_field_tuple_struct = 0 ``` Multi-field tuple structs are serialized as arrays of values, with nested structs serialized as inline tables: ```rust #[derive(Reflect, PartialEq, Debug, Default)] #[reflect(Default)] struct NestedStruct { a: u8, b: u16, } #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)] #[reflect(Resource, SettingsGroup, Default)] struct MultiFieldTupleStruct(u8, NestedStruct); ``` Results in: ```toml [multi_field_tuple_struct] multi_field_tuple_struct = [0, { a = 0, b = 0 }] ``` Non-unit enums retain the "key" field with it defaulting the resources name, with it being serialized into the group table: ```rust #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)] #[reflect(Resource, SettingsGroup, Default)] enum EnumSingleTupleVariant { A(u8), } impl Default for EnumSingleTupleVariant { fn default() -> Self { EnumSingleTupleVariant::A(0) } } ``` Results in: ```toml [enum_single_tuple_variant.enum_single_tuple_variant] A = 0 ``` The same multi-field tuple serialization occurs in enums: ```rust #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)] #[reflect(Resource, SettingsGroup, Default)] enum EnumMultiNewTypeVariant { A(SingleFieldTupleStruct, MultiFieldTupleStruct), } impl Default for EnumMultiNewTypeVariant { fn default() -> Self { EnumMultiNewTypeVariant::A( SingleFieldTupleStruct(0), MultiFieldTupleStruct(0, NestedStruct { a: 0, b: 0 }), ) } } ``` Results in: ```toml [enum_multi_new_type_variant.enum_multi_new_type_variant] A = [0, [0, { a = 0, b = 0 }]] ``` Enums variants with fields are serialised with field entries: ```rust #[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)] #[reflect(Resource, SettingsGroup, Default)] enum EnumStructVariant { A { x: u8, y: u16 }, } impl Default for EnumStructVariant { fn default() -> Self { EnumStructVariant::A { x: 0, y: 0 } } } ``` Results in: ```toml [enum_struct_variant.enum_struct_variant.A] x = 0 y = 0 ``` ## Notes - I'm not 100% sold on the `[group_name.key_name]` for non-unit like enums, its easy enough to not include the key for these types but i'll keep in for the sake of discussion. - Still not supporting Unions. #23302 did not mention them but I can add support for these in this PR or we can just not support them at all. Open to discussion. ## Testing - I've added to the above examples to the bevy settings test suite. - I've ran the `persisting_preferences` example and cross referenced the toml output in the file.
1 parent 574e935 commit 695c8e0

2 files changed

Lines changed: 225 additions & 27 deletions

File tree

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -649,45 +649,33 @@ pub fn derive_settings_group(input: TokenStream) -> TokenStream {
649649
(override_group_name, override_key_name, override_file)
650650
};
651651

652-
let (group_name, key_name) = match &input.data {
653-
Data::Struct(_) => {
654-
if override_key_name.is_some() {
652+
let key_name = match &input.data {
653+
Data::Struct(data) => match data.fields {
654+
Fields::Named(_) if override_key_name.is_some() => {
655655
return syn::Error::new(
656656
Span::call_site(),
657-
"The `key` attribute is not supported for structs",
657+
"The `key` attribute is not supported for structs with named fields",
658658
)
659659
.into_compile_error()
660660
.into();
661661
}
662-
let group_name = override_group_name.unwrap_or(pascal_to_snake_case(&name.to_string()));
663-
664-
(group_name, override_key_name)
665-
}
666-
Data::Enum(data) => {
667-
if data.variants.iter().any(|v| v.fields != Fields::Unit) {
668-
return syn::Error::new(
669-
Span::call_site(),
670-
"SettingsGroup can only be derived for enums with unit variants",
671-
)
672-
.into_compile_error()
673-
.into();
662+
Fields::Named(_) => None,
663+
Fields::Unnamed(_) | Fields::Unit => {
664+
override_key_name.or_else(|| Some(pascal_to_snake_case(&name.to_string())))
674665
}
675-
676-
let group_name = override_group_name.unwrap_or(pascal_to_snake_case(&name.to_string()));
677-
let key_name = override_key_name.or(Some(pascal_to_snake_case(&name.to_string())));
678-
679-
(group_name, key_name)
680-
}
681-
_ => {
666+
},
667+
Data::Enum(_) => override_key_name.or(Some(pascal_to_snake_case(&name.to_string()))),
668+
Data::Union(_) => {
682669
return syn::Error::new(
683670
Span::call_site(),
684-
"SettingsGroup can only be derived for structs and enums",
671+
"SettingsGroup cannot be derived for unions",
685672
)
686673
.into_compile_error()
687674
.into();
688675
}
689676
};
690677

678+
let group_name = override_group_name.unwrap_or(pascal_to_snake_case(&name.to_string()));
691679
let key_name = key_name
692680
.map(|f| quote! { ::core::option::Option::Some(#f) })
693681
.unwrap_or(quote! { ::core::option::Option::None });

crates/bevy_settings/src/lib.rs

Lines changed: 213 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ fn resources_to_toml(
322322
manifest: &PreferenceFileManifest,
323323
) -> toml::map::Map<String, toml::Value> {
324324
let mut table = toml::Table::new();
325+
325326
for tid in manifest.resource_types.iter() {
326327
let ty = types.get(*tid).unwrap();
327328

@@ -509,6 +510,7 @@ fn apply_settings_to_world(
509510
// No settings key, so we can apply the whole section to the resource
510511
value
511512
};
513+
512514
load_properties(value, &mut *default_value, types);
513515
}
514516

@@ -544,6 +546,37 @@ fn load_properties(value: &toml::Value, resource: &mut dyn PartialReflect, types
544546
}
545547
}
546548
}
549+
TypeInfo::TupleStruct(tstinfo) => {
550+
if let ReflectMut::TupleStruct(tst_reflect) = resource.reflect_mut() {
551+
// tuple structs with length > 1 are always serialized as arrays
552+
if tst_reflect.field_len() > 1
553+
&& let Some(array) = value.as_array()
554+
{
555+
for (idx, toml_field_value) in array.iter().enumerate() {
556+
if let Some(field_info) = tstinfo.field_at(idx)
557+
&& let Some(field_type) = types.get(field_info.type_id())
558+
{
559+
let deserializer = TypedReflectDeserializer::new(field_type, types);
560+
if let Ok(field_value) =
561+
deserializer.deserialize(toml_field_value.clone())
562+
{
563+
// Should be safe to unwrap here since we know the field exists (above).
564+
tst_reflect.field_mut(idx).unwrap().apply(&*field_value);
565+
}
566+
}
567+
}
568+
} else if tst_reflect.field_len() == 1
569+
&& let Some(field_info) = tstinfo.field_at(0)
570+
&& let Some(field_type) = types.get(field_info.type_id())
571+
{
572+
let deserializer = TypedReflectDeserializer::new(field_type, types);
573+
if let Ok(field_value) = deserializer.deserialize(value.clone()) {
574+
// Should be safe to unwrap here since we know the field exists (above).
575+
tst_reflect.field_mut(0).unwrap().apply(&*field_value);
576+
}
577+
}
578+
}
579+
}
547580
TypeInfo::Enum(einfo) => {
548581
if let ReflectMut::Enum(en_reflect) = resource.reflect_mut()
549582
&& let Some(variant_type) = types.get(einfo.type_id())
@@ -705,9 +738,6 @@ mod tests {
705738
let counter_section = table.get("counter_settings").unwrap().as_table().unwrap();
706739

707740
// Check that fields are present in the merged section
708-
assert!(counter_section.contains_key("count"));
709-
assert!(counter_section.contains_key("enabled"));
710-
assert!(counter_section.contains_key("refresh_rate"));
711741
assert_eq!(
712742
counter_section.get("count").unwrap().as_integer().unwrap(),
713743
42
@@ -725,16 +755,136 @@ mod tests {
725755

726756
#[test]
727757
fn test_round_trip_serialization() {
758+
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)]
759+
#[reflect(Resource, SettingsGroup, Default)]
760+
struct SingleFieldTupleStruct(u8);
761+
762+
#[derive(Reflect, PartialEq, Debug, Default)]
763+
#[reflect(Default)]
764+
struct NestedStruct {
765+
a: u8,
766+
b: u16,
767+
}
768+
769+
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)]
770+
#[reflect(Resource, SettingsGroup, Default)]
771+
struct MultiFieldTupleStruct(u8, NestedStruct);
772+
773+
#[derive(Resource, SettingsGroup, Reflect, Default)]
774+
#[reflect(Resource, SettingsGroup, Default)]
775+
struct NewTypeSingleTupleStruct(SingleFieldTupleStruct);
776+
777+
#[derive(Resource, SettingsGroup, Reflect, Default)]
778+
#[reflect(Resource, SettingsGroup, Default)]
779+
struct NewTypeMultiTupleStruct(SingleFieldTupleStruct, MultiFieldTupleStruct);
780+
781+
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)]
782+
#[reflect(Resource, SettingsGroup, Default)]
783+
enum EnumUnitVariant {
784+
#[default]
785+
A,
786+
}
787+
788+
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
789+
#[reflect(Resource, SettingsGroup, Default)]
790+
enum EnumSingleTupleVariant {
791+
A(u8),
792+
}
793+
794+
impl Default for EnumSingleTupleVariant {
795+
fn default() -> Self {
796+
EnumSingleTupleVariant::A(0)
797+
}
798+
}
799+
800+
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
801+
#[reflect(Resource, SettingsGroup, Default)]
802+
enum EnumMultiTupleVariant {
803+
A(u16, u32),
804+
}
805+
806+
impl Default for EnumMultiTupleVariant {
807+
fn default() -> Self {
808+
EnumMultiTupleVariant::A(0, 0)
809+
}
810+
}
811+
812+
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
813+
#[reflect(Resource, SettingsGroup, Default)]
814+
enum EnumStructVariant {
815+
A { x: u8, y: u16 },
816+
}
817+
818+
impl Default for EnumStructVariant {
819+
fn default() -> Self {
820+
EnumStructVariant::A { x: 0, y: 0 }
821+
}
822+
}
823+
824+
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
825+
#[reflect(Resource, SettingsGroup, Default)]
826+
enum EnumSingleNewTypeVariant {
827+
A(SingleFieldTupleStruct),
828+
}
829+
830+
impl Default for EnumSingleNewTypeVariant {
831+
fn default() -> Self {
832+
EnumSingleNewTypeVariant::A(SingleFieldTupleStruct(0))
833+
}
834+
}
835+
836+
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
837+
#[reflect(Resource, SettingsGroup, Default)]
838+
enum EnumMultiNewTypeVariant {
839+
A(SingleFieldTupleStruct, MultiFieldTupleStruct),
840+
}
841+
842+
impl Default for EnumMultiNewTypeVariant {
843+
fn default() -> Self {
844+
EnumMultiNewTypeVariant::A(
845+
SingleFieldTupleStruct(0),
846+
MultiFieldTupleStruct(0, NestedStruct { a: 0, b: 0 }),
847+
)
848+
}
849+
}
850+
728851
let mut world = World::new();
729852
let mut types = TypeRegistry::default();
853+
730854
types.register::<CounterSettings>();
731855
types.register::<ExtraCounterSettings>();
732856
types.register::<CounterRefreshRateSettings>();
857+
types.register::<SingleFieldTupleStruct>();
858+
types.register::<MultiFieldTupleStruct>();
859+
types.register::<NewTypeSingleTupleStruct>();
860+
types.register::<NewTypeMultiTupleStruct>();
861+
types.register::<EnumUnitVariant>();
862+
types.register::<EnumSingleTupleVariant>();
863+
types.register::<EnumMultiTupleVariant>();
864+
types.register::<EnumStructVariant>();
865+
types.register::<EnumSingleNewTypeVariant>();
866+
types.register::<EnumMultiNewTypeVariant>();
733867

734868
// Insert resources with specific values
735869
world.insert_resource(CounterSettings { count: 123 });
736870
world.insert_resource(ExtraCounterSettings { enabled: false });
737871
world.insert_resource(CounterRefreshRateSettings::Fast);
872+
world.insert_resource(SingleFieldTupleStruct(1));
873+
world.insert_resource(MultiFieldTupleStruct(2, NestedStruct { a: 1, b: 2 }));
874+
world.insert_resource(NewTypeSingleTupleStruct(SingleFieldTupleStruct(1)));
875+
world.insert_resource(NewTypeMultiTupleStruct(
876+
SingleFieldTupleStruct(1),
877+
MultiFieldTupleStruct(2, NestedStruct { a: 1, b: 2 }),
878+
));
879+
world.insert_resource(EnumUnitVariant::A);
880+
world.insert_resource(EnumSingleTupleVariant::A(1));
881+
world.insert_resource(EnumMultiTupleVariant::A(1, 2));
882+
world.insert_resource(EnumStructVariant::A { x: 1, y: 2 });
883+
world.insert_resource(EnumSingleNewTypeVariant::A(SingleFieldTupleStruct(1)));
884+
world.insert_resource(EnumMultiNewTypeVariant::A(
885+
SingleFieldTupleStruct(1),
886+
MultiFieldTupleStruct(2, NestedStruct { a: 1, b: 2 }),
887+
));
738888

739889
// Build a manifest with both resource types
740890
let manifest = PreferenceFileManifest {
@@ -743,6 +893,16 @@ mod tests {
743893
TypeId::of::<CounterSettings>(),
744894
TypeId::of::<ExtraCounterSettings>(),
745895
TypeId::of::<CounterRefreshRateSettings>(),
896+
TypeId::of::<SingleFieldTupleStruct>(),
897+
TypeId::of::<MultiFieldTupleStruct>(),
898+
TypeId::of::<NewTypeSingleTupleStruct>(),
899+
TypeId::of::<NewTypeMultiTupleStruct>(),
900+
TypeId::of::<EnumUnitVariant>(),
901+
TypeId::of::<EnumSingleTupleVariant>(),
902+
TypeId::of::<EnumMultiTupleVariant>(),
903+
TypeId::of::<EnumStructVariant>(),
904+
TypeId::of::<EnumSingleNewTypeVariant>(),
905+
TypeId::of::<EnumMultiNewTypeVariant>(),
746906
],
747907
};
748908

@@ -764,6 +924,56 @@ mod tests {
764924
.get_resource::<CounterRefreshRateSettings>()
765925
.unwrap();
766926
assert_eq!(*refresh_rate, CounterRefreshRateSettings::Fast);
927+
928+
let single_field_tuple_struct = new_world.get_resource::<SingleFieldTupleStruct>().unwrap();
929+
assert_eq!(single_field_tuple_struct.0, 1);
930+
931+
let multi_field_tuple_struct = new_world.get_resource::<MultiFieldTupleStruct>().unwrap();
932+
assert_eq!(multi_field_tuple_struct.0, 2);
933+
assert_eq!(multi_field_tuple_struct.1.a, 1);
934+
assert_eq!(multi_field_tuple_struct.1.b, 2);
935+
936+
let new_type_single_tuple_struct = new_world
937+
.get_resource::<NewTypeSingleTupleStruct>()
938+
.unwrap();
939+
assert_eq!(new_type_single_tuple_struct.0 .0, 1);
940+
941+
let new_type_multi_tuple_struct =
942+
new_world.get_resource::<NewTypeMultiTupleStruct>().unwrap();
943+
assert_eq!(new_type_multi_tuple_struct.0 .0, 1);
944+
assert_eq!(new_type_multi_tuple_struct.1 .0, 2);
945+
assert_eq!(new_type_multi_tuple_struct.1 .1.a, 1);
946+
assert_eq!(new_type_multi_tuple_struct.1 .1.b, 2);
947+
948+
let enum_unit_variant = new_world.get_resource::<EnumUnitVariant>().unwrap();
949+
assert_eq!(*enum_unit_variant, EnumUnitVariant::A);
950+
951+
let enum_single_tuple_variant = new_world.get_resource::<EnumSingleTupleVariant>().unwrap();
952+
assert_eq!(*enum_single_tuple_variant, EnumSingleTupleVariant::A(1));
953+
954+
let enum_multi_tuple_variant = new_world.get_resource::<EnumMultiTupleVariant>().unwrap();
955+
assert_eq!(*enum_multi_tuple_variant, EnumMultiTupleVariant::A(1, 2));
956+
957+
let enum_struct_variant = new_world.get_resource::<EnumStructVariant>().unwrap();
958+
assert_eq!(*enum_struct_variant, EnumStructVariant::A { x: 1, y: 2 });
959+
960+
let enum_single_new_type_variant = new_world
961+
.get_resource::<EnumSingleNewTypeVariant>()
962+
.unwrap();
963+
assert_eq!(
964+
*enum_single_new_type_variant,
965+
EnumSingleNewTypeVariant::A(SingleFieldTupleStruct(1))
966+
);
967+
968+
let enum_multi_new_type_variant =
969+
new_world.get_resource::<EnumMultiNewTypeVariant>().unwrap();
970+
assert_eq!(
971+
*enum_multi_new_type_variant,
972+
EnumMultiNewTypeVariant::A(
973+
SingleFieldTupleStruct(1),
974+
MultiFieldTupleStruct(2, NestedStruct { a: 1, b: 2 })
975+
)
976+
);
767977
}
768978

769979
#[test]

0 commit comments

Comments
 (0)