diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_init_sub_state.md b/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_init_sub_state.md new file mode 100644 index 00000000..db20af74 --- /dev/null +++ b/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_init_sub_state.md @@ -0,0 +1,34 @@ +Automatically initializes a sub state in the app. + +# Parameters +- `plugin = PluginType` - Required. Specifies which plugin should initialize this sub state. + +# Example +```rust +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct MyPlugin; + +#[derive(States, Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Reflect)] +#[auto_init_state(plugin = MyPlugin)] +#[auto_register_state_type(plugin = MyPlugin)] +enum AppState { + #[default] + Menu, + InGame +} + +#[derive(SubStates, Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Reflect)] +#[source(AppState = AppState::InGame)] +#[auto_init_sub_state(plugin = MyPlugin)] +#[auto_register_state_type(plugin = MyPlugin)] +enum GamePhase { + #[default] + Setup, + Battle, + Conclusion +} +``` \ No newline at end of file diff --git a/crates/bevy_auto_plugin_proc_macros/src/lib.rs b/crates/bevy_auto_plugin_proc_macros/src/lib.rs index 0c29a664..4380ba1b 100644 --- a/crates/bevy_auto_plugin_proc_macros/src/lib.rs +++ b/crates/bevy_auto_plugin_proc_macros/src/lib.rs @@ -62,6 +62,13 @@ pub fn auto_init_state(attr: CompilerStream, input: CompilerStream) -> CompilerS handle_attribute(expand::attr::auto_init_state, attr, input) } +/// Automatically initializes a SubState in the Bevy `App`. +#[doc = include_str!("../docs/proc_attributes/auto_init_sub_state.md")] +#[proc_macro_attribute] +pub fn auto_init_sub_state(attr: CompilerStream, input: CompilerStream) -> CompilerStream { + handle_attribute(expand::attr::auto_init_sub_state, attr, input) +} + /// Automatically registers a required component `Name` with a value using the concrete name of the item. #[doc = include_str!("../docs/proc_attributes/auto_name.md")] #[proc_macro_attribute] diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs index 307dfd2a..5dcf8f58 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs @@ -1,6 +1,6 @@ use crate::__private::attribute::RewriteAttribute; use crate::__private::auto_plugin_registry::_plugin_entry_block; -use crate::codegen::with_target_path::WithTargetPath; +use crate::codegen::with_target_path::{ToTokensIterItem, WithTargetPath}; use crate::macro_api::attributes::ItemAttributeArgs; use crate::macro_api::attributes::prelude::*; use crate::macro_api::with_plugin::{PluginBound, WithPlugin}; @@ -22,16 +22,27 @@ fn body( let plugin = params.plugin().clone(); let with_target_path = WithTargetPath::from((ident.into(), params)); let output = with_target_path - .to_tokens_iter() + .to_tokens_iter_items() .enumerate() - .map(|(ix, input)| { - let body = body(input); - let expr: syn::ExprClosure = syn::parse_quote!(|app| { #body }); - // required for generics - let unique_ident = format_ident!("{unique_ident}_{ix}"); - let output = _plugin_entry_block(&unique_ident, &plugin, &expr); - Ok(output) - }) + .map( + |( + ix, + ToTokensIterItem { + required_uses, + main_tokens: tokens, + }, + )| { + let body = body(tokens); + let expr: syn::ExprClosure = syn::parse_quote!(|app| { + #(#required_uses)* + #body + }); + // required for generics + let unique_ident = format_ident!("{unique_ident}_{ix}"); + let output = _plugin_entry_block(&unique_ident, &plugin, &expr); + Ok(output) + }, + ) .collect::>()?; assert!( !output.is_empty(), @@ -240,6 +251,7 @@ gen_auto_attribute_outers! { auto_init_resource => InitResourceArgs, auto_insert_resource => InsertResourceArgs, auto_init_state => InitStateArgs, + auto_init_sub_state => InitSubStateArgs, auto_name => NameArgs, auto_register_state_type => RegisterStateTypeArgs, auto_add_system => AddSystemArgs, diff --git a/crates/bevy_auto_plugin_shared/src/codegen/tokens.rs b/crates/bevy_auto_plugin_shared/src/codegen/tokens.rs index b52fb918..2bbae76c 100644 --- a/crates/bevy_auto_plugin_shared/src/codegen/tokens.rs +++ b/crates/bevy_auto_plugin_shared/src/codegen/tokens.rs @@ -200,6 +200,12 @@ pub fn derive_reflect() -> TokenStream { let derive_reflect_path = derive_reflect_path(); quote! { #[derive(#derive_reflect_path)] } } + +pub fn use_bevy_state_app_ext_states() -> syn::ItemUse { + let root = crate::__private::paths::state::root_path(); + parse_quote! { use #root::app::AppExtStates as _; } +} + pub fn auto_register_type(plugin: NonEmptyPath, args: RegisterTypeArgs) -> TokenStream { ArgsWithPlugin::new(plugin, args).to_token_stream() } diff --git a/crates/bevy_auto_plugin_shared/src/codegen/with_target_path.rs b/crates/bevy_auto_plugin_shared/src/codegen/with_target_path.rs index 72db43e7..4251e1e3 100644 --- a/crates/bevy_auto_plugin_shared/src/codegen/with_target_path.rs +++ b/crates/bevy_auto_plugin_shared/src/codegen/with_target_path.rs @@ -22,6 +22,22 @@ pub trait ToTokensWithConcreteTargetPath: GenericsArgs { self.to_tokens_with_concrete_target_path(&mut tokens, target); tokens } + fn required_use_statements(&self) -> Vec { + vec![] + } +} + +#[derive(Debug, Clone)] +pub struct ToTokensIterItem { + pub required_uses: Vec, + pub main_tokens: TokenStream, +} + +impl ToTokensIterItem { + #[cfg(test)] + pub fn into_main_tokens(self) -> TokenStream { + self.main_tokens + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -46,14 +62,23 @@ impl WithTargetPath { } impl WithTargetPath { - pub fn to_tokens_iter(&self) -> impl Iterator { + /// used to reconstruct `|app| { #(#required_uses)* #main_tokens }` + pub fn to_tokens_iter_items(&self) -> impl Iterator { self.concrete_target_paths() .into_iter() - .map(|concrete_target_path| { - self.inner - .to_token_stream_with_concrete_target_path(&concrete_target_path) + .map(|concrete_target_path| ToTokensIterItem { + required_uses: self.inner.required_use_statements(), + main_tokens: self + .inner + .to_token_stream_with_concrete_target_path(&concrete_target_path), }) } + #[cfg(test)] + /// used in tests checking the output of `ToTokensIterItem::main_tokens` + pub fn to_tokens_iter(&self) -> impl Iterator { + self.to_tokens_iter_items() + .map(ToTokensIterItem::into_main_tokens) + } } impl From<(PathWithoutGenerics, T)> for WithTargetPath { diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_state.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_state.rs index b85978e3..e8c8b995 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_state.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_state.rs @@ -1,3 +1,4 @@ +use crate::codegen::tokens; use crate::codegen::tokens::ArgsBackToTokens; use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; use crate::macro_api::attributes::prelude::GenericsArgs; @@ -12,10 +13,7 @@ use syn::Item; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] -pub struct InitStateArgs { - #[darling(multiple)] - pub generics: Vec, -} +pub struct InitStateArgs {} impl AttributeIdent for InitStateArgs { const IDENT: &'static str = "auto_init_state"; @@ -29,7 +27,7 @@ impl ItemAttributeArgs for InitStateArgs { impl GenericsArgs for InitStateArgs { fn type_lists(&self) -> &[TypeList] { - &self.generics + &[] } } @@ -40,10 +38,12 @@ impl ToTokensWithConcreteTargetPath for InitStateArgs { target: &ConcreteTargetPath, ) { tokens.extend(quote! { - // TODO: requires bevy_state::app::AppExtStates; .init_state::< #target >() }) } + fn required_use_statements(&self) -> Vec { + vec![tokens::use_bevy_state_app_ext_states()] + } } impl ArgsBackToTokens for InitStateArgs { @@ -75,45 +75,4 @@ mod tests { assert!(token_iter.next().is_none()); Ok(()) } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .init_state :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool), generics(bool, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .init_state :: < FooTarget > () - } - .to_string() - ); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .init_state :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_sub_state.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_sub_state.rs new file mode 100644 index 00000000..a6be47be --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_sub_state.rs @@ -0,0 +1,78 @@ +use crate::codegen::tokens; +use crate::codegen::tokens::ArgsBackToTokens; +use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; +use crate::macro_api::attributes::prelude::GenericsArgs; +use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; +use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; +use crate::syntax::ast::type_list::TypeList; +use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::quote; +use syn::Item; + +#[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] +#[darling(derive_syn_parse, default)] +pub struct InitSubStateArgs {} + +impl AttributeIdent for InitSubStateArgs { + const IDENT: &'static str = "auto_init_sub_state"; +} + +impl ItemAttributeArgs for InitSubStateArgs { + fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { + resolve_ident_from_struct_or_enum(item) + } +} + +impl GenericsArgs for InitSubStateArgs { + fn type_lists(&self) -> &[TypeList] { + &[] + } +} + +impl ToTokensWithConcreteTargetPath for InitSubStateArgs { + fn to_tokens_with_concrete_target_path( + &self, + tokens: &mut TokenStream, + target: &ConcreteTargetPath, + ) { + tokens.extend(quote! { + .add_sub_state::< #target >() + }) + } + fn required_use_statements(&self) -> Vec { + vec![tokens::use_bevy_state_app_ext_states()] + } +} + +impl ArgsBackToTokens for InitSubStateArgs { + fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(self.generics().to_attribute_arg_tokens()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::codegen::with_target_path::WithTargetPath; + use internal_test_proc_macro::xtest; + use syn::{Path, parse_quote, parse2}; + + #[xtest] + fn test_to_tokens_no_generics() -> syn::Result<()> { + let args = parse2::(quote!())?; + let path: Path = parse_quote!(FooTarget); + let args_with_target = WithTargetPath::try_from((path, args))?; + let mut token_iter = args_with_target.to_tokens_iter(); + assert_eq!( + token_iter.next().expect("token_iter").to_string(), + quote! { + .add_sub_state :: < FooTarget > () + } + .to_string() + ); + assert!(token_iter.next().is_none()); + Ok(()) + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs index 6c6dfdce..98d27a9b 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs @@ -4,6 +4,7 @@ pub mod auto_add_plugin; pub mod auto_add_system; pub mod auto_init_resource; pub mod auto_init_state; +pub mod auto_init_sub_state; pub mod auto_insert_resource; pub mod auto_name; pub mod auto_register_state_type; diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/mod.rs index fc5b9a0f..906c391d 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/mod.rs @@ -23,6 +23,7 @@ pub mod prelude { pub use crate::macro_api::attributes::actions::auto_add_system::AddSystemArgs; pub use crate::macro_api::attributes::actions::auto_init_resource::InitResourceArgs; pub use crate::macro_api::attributes::actions::auto_init_state::InitStateArgs; + pub use crate::macro_api::attributes::actions::auto_init_sub_state::InitSubStateArgs; pub use crate::macro_api::attributes::actions::auto_insert_resource::InsertResourceArgs; pub use crate::macro_api::attributes::actions::auto_name::NameArgs; pub use crate::macro_api::attributes::actions::auto_register_state_type::RegisterStateTypeArgs; diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_states.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_states.rs index 00975df6..999c9853 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_states.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_states.rs @@ -14,8 +14,6 @@ use quote::quote; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] pub struct StatesArgs { - #[darling(multiple)] - pub generics: Vec, pub derive: FlagOrList, pub reflect: FlagOrList, pub register: bool, @@ -24,7 +22,7 @@ pub struct StatesArgs { impl GenericsArgs for StatesArgs { fn type_lists(&self) -> &[TypeList] { - &self.generics + &[] } } @@ -33,18 +31,14 @@ impl AttributeIdent for StatesArgs { } impl<'a> From<&'a StatesArgs> for RegisterTypeArgs { - fn from(value: &'a StatesArgs) -> Self { - Self { - generics: value.generics.clone(), - } + fn from(_value: &'a StatesArgs) -> Self { + Self::default() } } impl<'a> From<&'a StatesArgs> for InitStateArgs { - fn from(value: &'a StatesArgs) -> Self { - Self { - generics: value.generics.clone(), - } + fn from(_value: &'a StatesArgs) -> Self { + Self::default() } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/with_plugin.rs b/crates/bevy_auto_plugin_shared/src/macro_api/with_plugin.rs index 13125ea6..87a2df4c 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/with_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/with_plugin.rs @@ -77,6 +77,10 @@ where self.inner .to_tokens_with_concrete_target_path(tokens, target) } + + fn required_use_statements(&self) -> Vec { + self.inner.required_use_statements() + } } impl PluginBound for WithPlugin diff --git a/src/lib.rs b/src/lib.rs index adce4298..2332492d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,9 @@ pub mod prelude { #[doc(inline)] pub use bevy_auto_plugin_proc_macros::auto_init_state; + #[doc(inline)] + pub use bevy_auto_plugin_proc_macros::auto_init_sub_state; + #[doc(inline)] pub use bevy_auto_plugin_proc_macros::auto_insert_resource; diff --git a/tests/e2e/auto_init_state.rs b/tests/e2e/auto_init_state.rs index 78499595..0902a3cd 100644 --- a/tests/e2e/auto_init_state.rs +++ b/tests/e2e/auto_init_state.rs @@ -17,14 +17,6 @@ enum Test { B, } -#[auto_init_state(plugin = TestPlugin)] -#[derive(SubStates, Debug, Copy, Clone, Default, PartialEq, Eq, Hash)] -#[source(Test = Test::B)] -enum InnerTest { - #[default] - A, -} - fn app() -> App { let mut app = internal_test_util::create_minimal_app(); app.add_plugins(StatesPlugin); @@ -43,12 +35,4 @@ fn test_auto_init_state() { app.world().get_resource::>().is_some(), "did not auto init state" ); - assert!( - app.world().get_resource::>().is_some(), - "did not auto init state" - ); - assert!( - app.world().get_resource::>().is_some(), - "did not auto init state" - ); } diff --git a/tests/e2e/auto_init_sub_state.rs b/tests/e2e/auto_init_sub_state.rs new file mode 100644 index 00000000..d4ba5f18 --- /dev/null +++ b/tests/e2e/auto_init_sub_state.rs @@ -0,0 +1,106 @@ +use bevy_app::prelude::*; +use bevy_auto_plugin::prelude::*; +use bevy_state::app::StatesPlugin; +use bevy_state::prelude::*; +use internal_test_proc_macro::xtest; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct TestPlugin; + +#[auto_init_state(plugin = TestPlugin)] +#[derive(States, Debug, Copy, Clone, Default, PartialEq, Eq, Hash)] +enum AppState { + #[default] + Menu, + InGame, +} + +#[auto_init_sub_state(plugin = TestPlugin)] +#[derive(SubStates, Debug, Copy, Clone, Default, PartialEq, Eq, Hash)] +#[source(AppState = AppState::InGame)] +enum IsPaused { + #[default] + Running, + Paused, +} + +fn app() -> App { + let mut app = internal_test_util::create_minimal_app(); + app.add_plugins(StatesPlugin); + app.add_plugins(TestPlugin); + app +} + +#[xtest] +fn test_auto_init_state() { + let app = app(); + assert!( + app.world().get_resource::>().is_some(), + "did not auto init State" + ); + assert!( + app.world().get_resource::>().is_some(), + "did not auto init NextState" + ); + assert!( + app.world().get_resource::>().is_none(), + "State shouldn't be set" + ); + assert!( + app.world().get_resource::>().is_some(), + "did not auto init NextState" + ); +} + +#[xtest] +fn test_update_state() { + let mut app = app(); + + app.update(); + + assert_eq!( + app.world() + .get_resource::>() + .map(|state| state.get()), + None, + ); + + app.world_mut() + .resource_mut::>() + .into_inner() + .set(AppState::InGame); + + app.update(); + + assert_eq!( + app.world().resource::>().get(), + &IsPaused::Running, + ); + + app.world_mut() + .resource_mut::>() + .into_inner() + .set(IsPaused::Paused); + + app.update(); + + assert_eq!( + app.world().resource::>().get(), + &IsPaused::Paused, + ); + + app.world_mut() + .resource_mut::>() + .into_inner() + .set(AppState::Menu); + + app.update(); + + assert_eq!( + app.world() + .get_resource::>() + .map(|state| state.get()), + None, + ); +} diff --git a/tests/e2e/auto_plugin_with_generics.rs b/tests/e2e/auto_plugin_with_generics.rs index 712fa151..1060d1ae 100644 --- a/tests/e2e/auto_plugin_with_generics.rs +++ b/tests/e2e/auto_plugin_with_generics.rs @@ -6,7 +6,7 @@ use internal_test_proc_macro::xtest; use internal_test_util::{create_minimal_app, type_id_of, vec_spread}; use std::fmt::Debug; use std::hash::Hash; -use std::ops::{AddAssign, Deref}; +use std::ops::AddAssign; #[derive(AutoPlugin, Default)] #[auto_plugin(impl_plugin_trait)] @@ -65,28 +65,6 @@ where value2: T2, } -#[derive(States, Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect)] -#[auto_init_state(plugin = Test::, generics(u8, bool))] -#[auto_register_state_type(plugin = Test::, generics(u8, bool))] -enum FooState -where - T1: Default + Debug + Default + Copy + Clone + PartialEq + Eq + Hash + Send + Sync + 'static, - T2: Default + Debug + Default + Copy + Clone + PartialEq + Eq + Hash + Send + Sync + 'static, -{ - Start(T1, T2), - End(T1, T2), -} - -impl Default for FooState -where - T1: Debug + Default + Copy + Clone + PartialEq + Eq + Hash + Send + Sync + 'static, - T2: Debug + Default + Copy + Clone + PartialEq + Eq + Hash + Send + Sync + 'static, -{ - fn default() -> Self { - Self::Start(T1::default(), T2::default()) - } -} - #[derive(Resource, Debug, Default, PartialEq, Reflect)] #[reflect(Resource)] #[auto_register_type(plugin = Test::)] @@ -307,33 +285,6 @@ fn test_auto_event_foo_entity_event() { } } -#[xtest] -fn test_auto_register_state_type_foo_state() { - let app = app(); - let type_registry = app.world().resource::().0.clone(); - let type_registry = type_registry.read(); - assert!( - type_registry.contains(type_id_of::>>()), - "did not auto register type" - ); - assert!( - type_registry.contains(type_id_of::>>()), - "did not auto register type" - ); -} - -#[xtest] -fn test_auto_init_state_type_foo_state() { - let app = app(); - assert_eq!( - app.world() - .get_resource::>>() - .map(Deref::deref), - Some(&FooState::::default()), - "did not auto init state" - ); -} - #[xtest] fn test_auto_add_observer_foo_observer() { let mut app = app(); diff --git a/tests/e2e/mod.rs b/tests/e2e/mod.rs index 8708039f..4968fab0 100644 --- a/tests/e2e/mod.rs +++ b/tests/e2e/mod.rs @@ -14,6 +14,7 @@ mod auto_bind_plugin; mod auto_init_resource; mod auto_init_resource_generic; mod auto_init_state; +mod auto_init_sub_state; mod auto_insert_resource; mod auto_insert_resource_with_generics; mod auto_name;