Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
```
7 changes: 7 additions & 0 deletions crates/bevy_auto_plugin_proc_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
32 changes: 22 additions & 10 deletions crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -22,16 +22,27 @@ fn body<T: PluginBound>(
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::<syn::Result<MacroStream>>()?;
assert!(
!output.is_empty(),
Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_auto_plugin_shared/src/codegen/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
33 changes: 29 additions & 4 deletions crates/bevy_auto_plugin_shared/src/codegen/with_target_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<syn::ItemUse> {
vec![]
}
}

#[derive(Debug, Clone)]
pub struct ToTokensIterItem {
pub required_uses: Vec<syn::ItemUse>,
pub main_tokens: TokenStream,
}

impl ToTokensIterItem {
#[cfg(test)]
pub fn into_main_tokens(self) -> TokenStream {
self.main_tokens
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
Expand All @@ -46,14 +62,23 @@ impl<T: GenericsArgs + Clone> WithTargetPath<T> {
}

impl<T: ToTokensWithConcreteTargetPath + Clone> WithTargetPath<T> {
pub fn to_tokens_iter(&self) -> impl Iterator<Item = TokenStream> {
/// used to reconstruct `|app| { #(#required_uses)* #main_tokens }`
pub fn to_tokens_iter_items(&self) -> impl Iterator<Item = ToTokensIterItem> {
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<Item = TokenStream> {
self.to_tokens_iter_items()
.map(ToTokensIterItem::into_main_tokens)
}
}

impl<T> From<(PathWithoutGenerics, T)> for WithTargetPath<T> {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<TypeList>,
}
pub struct InitStateArgs {}

impl AttributeIdent for InitStateArgs {
const IDENT: &'static str = "auto_init_state";
Expand All @@ -29,7 +27,7 @@ impl ItemAttributeArgs for InitStateArgs {

impl GenericsArgs for InitStateArgs {
fn type_lists(&self) -> &[TypeList] {
&self.generics
&[]
}
}

Expand All @@ -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<syn::ItemUse> {
vec![tokens::use_bevy_state_app_ext_states()]
}
}

impl ArgsBackToTokens for InitStateArgs {
Expand Down Expand Up @@ -75,45 +75,4 @@ mod tests {
assert!(token_iter.next().is_none());
Ok(())
}

#[xtest]
fn test_to_tokens_single() -> syn::Result<()> {
let args = parse2::<InitStateArgs>(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<u8, bool> > ()
}
.to_string()
);
assert!(token_iter.next().is_none());
Ok(())
}

#[xtest]
fn test_to_tokens_multiple() -> syn::Result<()> {
let args = parse2::<InitStateArgs>(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<u8, bool> > ()
}
.to_string()
);
assert_eq!(
token_iter.next().expect("token_iter").to_string(),
quote! {
.init_state :: < FooTarget<bool, bool> > ()
}
.to_string()
);
assert!(token_iter.next().is_none());
Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -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<syn::ItemUse> {
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::<InitSubStateArgs>(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(())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypeList>,
pub derive: FlagOrList<NonEmptyPath>,
pub reflect: FlagOrList<Ident>,
pub register: bool,
Expand All @@ -24,7 +22,7 @@ pub struct StatesArgs {

impl GenericsArgs for StatesArgs {
fn type_lists(&self) -> &[TypeList] {
&self.generics
&[]
}
}

Expand All @@ -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()
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_auto_plugin_shared/src/macro_api/with_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ where
self.inner
.to_tokens_with_concrete_target_path(tokens, target)
}

fn required_use_statements(&self) -> Vec<syn::ItemUse> {
self.inner.required_use_statements()
}
}

impl<T> PluginBound for WithPlugin<T>
Expand Down
Loading