Skip to content

Commit c040d76

Browse files
mpowell90kfc35alice-i-cecile
authored
Enum support in SettingsGroup derive macro, implemented key attribute (#23719)
# Objective Implements part of #23302 Fixes: #23722 For background on bevy settings see the initial PR: #23034 ## Solution Supports `unit-like` Enum resources: ```rust #[derive(Resource, SettingsGroup, Reflect, Debug, Default)] #[reflect(Resource, SettingsGroup, Default)] enum CounterRefreshRateSettings { #[default] Slow, Fast, } ``` Assuming the above is initialised as `CounterRefreshRateSettings::Slow`, results in: ```toml [counter_refresh_rate_settings] counter_refresh_rate_settings = "Slow" ``` Setting the `group` attribute works the same as structs: ```rust #[settings_group(group = "counter_settings")] ``` Results in: ```toml [counter_settings] counter_refresh_rate_settings = "Slow" ``` This PR adds a `key` attribute which can only be used on enums (for now), otherwise a compile-time error is thrown: ```rust #[settings_group(key = "refresh_rate")] ``` Results in: ```toml [counter_refresh_rate_settings] refresh_rate = "Slow" ``` ## Testing - Added tests that checks merging enums into groups and round trip serialisation. - Added a "cheat sheet" for the derive syntax, immitating other derive macros in the file. --------- Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent d15ada7 commit c040d76

3 files changed

Lines changed: 256 additions & 32 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,7 @@ bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.19.0-dev", default-fea
736736
bevy_image = { path = "crates/bevy_image", version = "0.19.0-dev", default-features = false }
737737
bevy_reflect = { path = "crates/bevy_reflect", version = "0.19.0-dev", default-features = false }
738738
bevy_render = { path = "crates/bevy_render", version = "0.19.0-dev", default-features = false }
739+
bevy_settings = { path = "crates/bevy_settings", version = "0.19.0-dev", default-features = false }
739740
bevy_state = { path = "crates/bevy_state", version = "0.19.0-dev", default-features = false }
740741
bevy_scene = { path = "crates/bevy_scene", version = "0.19.0-dev", default-features = false }
741742
# Needed to poll Task examples

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use proc_macro2::{Ident, Span};
2525
use quote::{format_ident, quote, ToTokens};
2626
use syn::{
2727
parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, ConstParam, Data,
28-
DeriveInput, GenericParam, TypeParam,
28+
DeriveInput, Fields, GenericParam, TypeParam,
2929
};
3030

3131
enum BundleFieldKind {
@@ -524,6 +524,10 @@ pub(crate) fn bevy_ecs_path() -> syn::Path {
524524
BevyManifest::shared(|manifest| manifest.get_path("bevy_ecs"))
525525
}
526526

527+
pub(crate) fn bevy_settings_path() -> syn::Path {
528+
BevyManifest::shared(|manifest| manifest.get_path("bevy_settings"))
529+
}
530+
527531
/// Implement the `Event` trait.
528532
#[proc_macro_derive(Event, attributes(event))]
529533
pub fn derive_event(input: TokenStream) -> TokenStream {
@@ -560,15 +564,58 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
560564
component::derive_resource(input)
561565
}
562566

563-
/// Implement the `SettingsGroup` trait.
567+
/// Cheat sheet for derive syntax,
568+
///
569+
/// ## Group Override
570+
/// ```ignore
571+
/// #[derive(SettingsGroup)]
572+
/// #[settings_group(group = "my_group")]
573+
/// struct MySettings {
574+
/// test: true
575+
/// }
576+
/// ```
577+
/// results in:
578+
/// ```ignore
579+
/// [my_group]
580+
/// test = true
581+
/// ```
582+
///
583+
/// ## File Override
584+
/// ```ignore
585+
/// #[derive(SettingsGroup)]
586+
/// #[settings_group(file = "my_file")]
587+
/// struct MySettings {
588+
/// test: true
589+
/// }
590+
/// ```
591+
/// results in a different file being used as the source of the settings.
592+
///
593+
/// ## Key Override
594+
/// Only valid for enums, as struct keys are always derived from the field name.
595+
/// ```ignore
596+
/// #[derive(SettingsGroup)]
597+
/// #[settings_group(key = "my_key")]
598+
/// enum MySettingsEnum {
599+
/// Variant1,
600+
/// Variant2
601+
/// };
602+
/// ```
603+
/// results in:
604+
/// ```ignore
605+
/// [my_settings_enum]
606+
/// my_key = "variant1"
607+
/// ```
564608
#[proc_macro_derive(SettingsGroup, attributes(settings_group))]
565609
pub fn derive_settings_group(input: TokenStream) -> TokenStream {
566610
let input = parse_macro_input!(input as DeriveInput);
567611

568612
let name = &input.ident;
569613

570-
let (override_name, override_file) = {
571-
let mut override_name: Option<String> = None;
614+
let path = bevy_settings_path();
615+
616+
let (override_group_name, override_key_name, override_file) = {
617+
let mut override_group_name: Option<String> = None;
618+
let mut override_key_name: Option<String> = None;
572619
let mut override_file: Option<String> = None;
573620

574621
input
@@ -580,7 +627,12 @@ pub fn derive_settings_group(input: TokenStream) -> TokenStream {
580627
if meta.path.is_ident("group") {
581628
let value = meta.value()?;
582629
let s: syn::LitStr = value.parse()?;
583-
override_name = Some(s.value());
630+
override_group_name = Some(s.value());
631+
Ok(())
632+
} else if meta.path.is_ident("key") {
633+
let value = meta.value()?;
634+
let s: syn::LitStr = value.parse()?;
635+
override_key_name = Some(s.value());
584636
Ok(())
585637
} else if meta.path.is_ident("file") {
586638
let value = meta.value()?;
@@ -594,19 +646,65 @@ pub fn derive_settings_group(input: TokenStream) -> TokenStream {
594646
.ok()
595647
});
596648

597-
(override_name, override_file)
649+
(override_group_name, override_key_name, override_file)
598650
};
599651

600-
let group_name = override_name.unwrap_or(pascal_to_snake_case(&name.to_string()));
652+
let (group_name, key_name) = match &input.data {
653+
Data::Struct(_) => {
654+
if override_key_name.is_some() {
655+
return syn::Error::new(
656+
Span::call_site(),
657+
"The `key` attribute is not supported for structs",
658+
)
659+
.into_compile_error()
660+
.into();
661+
}
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();
674+
}
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+
_ => {
682+
return syn::Error::new(
683+
Span::call_site(),
684+
"SettingsGroup can only be derived for structs and enums",
685+
)
686+
.into_compile_error()
687+
.into();
688+
}
689+
};
690+
691+
let key_name = key_name
692+
.map(|f| quote! { ::core::option::Option::Some(#f) })
693+
.unwrap_or(quote! { ::core::option::Option::None });
601694
let file_name = override_file
602695
.map(|f| quote! { ::core::option::Option::Some(#f) })
603696
.unwrap_or(quote! { ::core::option::Option::None });
604697

605698
let expanded = quote! {
606-
impl SettingsGroup for #name {
699+
impl #path::SettingsGroup for #name {
607700
fn settings_group_name() -> &'static str {
608701
#group_name
609702
}
703+
704+
fn settings_key_name() -> ::core::option::Option<&'static str> {
705+
#key_name
706+
}
707+
610708
fn settings_source() -> ::core::option::Option<&'static str> {
611709
#file_name
612710
}

0 commit comments

Comments
 (0)