enable_state_scoped_entities() as a derive attribute (#16180)
# Objective - I got tired of calling `enable_state_scoped_entities`, and though it would make more sense to define that at the place where the state is defined ## Solution - add a derive attribute `#[states(scoped_entities)]` when derive `States` or `SubStates` that enables it automatically when adding the state ## Testing - Ran the examples using it, they still work
This commit is contained in:
		
							parent
							
								
									2a1064ec5e
								
							
						
					
					
						commit
						fcfb685821
					
				@ -9,12 +9,12 @@ mod states;
 | 
				
			|||||||
use bevy_macro_utils::BevyManifest;
 | 
					use bevy_macro_utils::BevyManifest;
 | 
				
			||||||
use proc_macro::TokenStream;
 | 
					use proc_macro::TokenStream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[proc_macro_derive(States)]
 | 
					#[proc_macro_derive(States, attributes(states))]
 | 
				
			||||||
pub fn derive_states(input: TokenStream) -> TokenStream {
 | 
					pub fn derive_states(input: TokenStream) -> TokenStream {
 | 
				
			||||||
    states::derive_states(input)
 | 
					    states::derive_states(input)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[proc_macro_derive(SubStates, attributes(source))]
 | 
					#[proc_macro_derive(SubStates, attributes(states, source))]
 | 
				
			||||||
pub fn derive_substates(input: TokenStream) -> TokenStream {
 | 
					pub fn derive_substates(input: TokenStream) -> TokenStream {
 | 
				
			||||||
    states::derive_substates(input)
 | 
					    states::derive_substates(input)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,9 +4,42 @@ use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Pat, Path, Result};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use crate::bevy_state_path;
 | 
					use crate::bevy_state_path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const STATES: &str = "states";
 | 
				
			||||||
 | 
					pub const SCOPED_ENTITIES: &str = "scoped_entities";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct StatesAttrs {
 | 
				
			||||||
 | 
					    scoped_entities_enabled: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_states_attr(ast: &DeriveInput) -> Result<StatesAttrs> {
 | 
				
			||||||
 | 
					    let mut attrs = StatesAttrs {
 | 
				
			||||||
 | 
					        scoped_entities_enabled: false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for attr in ast.attrs.iter() {
 | 
				
			||||||
 | 
					        if attr.path().is_ident(STATES) {
 | 
				
			||||||
 | 
					            attr.parse_nested_meta(|nested| {
 | 
				
			||||||
 | 
					                if nested.path.is_ident(SCOPED_ENTITIES) {
 | 
				
			||||||
 | 
					                    attrs.scoped_entities_enabled = true;
 | 
				
			||||||
 | 
					                    Ok(())
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Err(nested.error("Unsupported attribute"))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(attrs)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn derive_states(input: TokenStream) -> TokenStream {
 | 
					pub fn derive_states(input: TokenStream) -> TokenStream {
 | 
				
			||||||
    let ast = parse_macro_input!(input as DeriveInput);
 | 
					    let ast = parse_macro_input!(input as DeriveInput);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let attrs = match parse_states_attr(&ast) {
 | 
				
			||||||
 | 
					        Ok(attrs) => attrs,
 | 
				
			||||||
 | 
					        Err(e) => return e.into_compile_error().into(),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let generics = ast.generics;
 | 
					    let generics = ast.generics;
 | 
				
			||||||
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 | 
					    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -23,8 +56,12 @@ pub fn derive_states(input: TokenStream) -> TokenStream {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let struct_name = &ast.ident;
 | 
					    let struct_name = &ast.ident;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let scoped_entities_enabled = attrs.scoped_entities_enabled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    quote! {
 | 
					    quote! {
 | 
				
			||||||
        impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {}
 | 
					        impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {
 | 
				
			||||||
 | 
					            const SCOPED_ENTITIES_ENABLED: bool = #scoped_entities_enabled;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause {
 | 
					        impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -37,7 +74,7 @@ struct Source {
 | 
				
			|||||||
    source_value: Pat,
 | 
					    source_value: Pat,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn parse_sources_attr(ast: &DeriveInput) -> Result<Source> {
 | 
					fn parse_sources_attr(ast: &DeriveInput) -> Result<(StatesAttrs, Source)> {
 | 
				
			||||||
    let mut result = ast
 | 
					    let mut result = ast
 | 
				
			||||||
        .attrs
 | 
					        .attrs
 | 
				
			||||||
        .iter()
 | 
					        .iter()
 | 
				
			||||||
@ -73,16 +110,19 @@ fn parse_sources_attr(ast: &DeriveInput) -> Result<Source> {
 | 
				
			|||||||
        ));
 | 
					        ));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let states_attrs = parse_states_attr(ast)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let Some(result) = result.pop() else {
 | 
					    let Some(result) = result.pop() else {
 | 
				
			||||||
        return Err(syn::Error::new(ast.span(), "SubStates require a source"));
 | 
					        return Err(syn::Error::new(ast.span(), "SubStates require a source"));
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(result)
 | 
					    Ok((states_attrs, result))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn derive_substates(input: TokenStream) -> TokenStream {
 | 
					pub fn derive_substates(input: TokenStream) -> TokenStream {
 | 
				
			||||||
    let ast = parse_macro_input!(input as DeriveInput);
 | 
					    let ast = parse_macro_input!(input as DeriveInput);
 | 
				
			||||||
    let sources = parse_sources_attr(&ast).expect("Failed to parse substate sources");
 | 
					    let (states_attrs, sources) =
 | 
				
			||||||
 | 
					        parse_sources_attr(&ast).expect("Failed to parse substate sources");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let generics = ast.generics;
 | 
					    let generics = ast.generics;
 | 
				
			||||||
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 | 
					    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 | 
				
			||||||
@ -113,6 +153,8 @@ pub fn derive_substates(input: TokenStream) -> TokenStream {
 | 
				
			|||||||
    let source_state_type = sources.source_type;
 | 
					    let source_state_type = sources.source_type;
 | 
				
			||||||
    let source_state_value = sources.source_value;
 | 
					    let source_state_value = sources.source_value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let scoped_entities_enabled = states_attrs.scoped_entities_enabled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let result = quote! {
 | 
					    let result = quote! {
 | 
				
			||||||
        impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {
 | 
					        impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {
 | 
				
			||||||
            type SourceStates = #source_state_type;
 | 
					            type SourceStates = #source_state_type;
 | 
				
			||||||
@ -124,6 +166,8 @@ pub fn derive_substates(input: TokenStream) -> TokenStream {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        impl #impl_generics #state_trait_path for #struct_name #ty_generics #where_clause {
 | 
					        impl #impl_generics #state_trait_path for #struct_name #ty_generics #where_clause {
 | 
				
			||||||
            const DEPENDENCY_DEPTH : usize = <Self as #trait_path>::SourceStates::SET_DEPENDENCY_DEPTH + 1;
 | 
					            const DEPENDENCY_DEPTH : usize = <Self as #trait_path>::SourceStates::SET_DEPENDENCY_DEPTH + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const SCOPED_ENTITIES_ENABLED: bool = #scoped_entities_enabled;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause {
 | 
					        impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause {
 | 
				
			||||||
 | 
				
			|||||||
@ -58,6 +58,9 @@ pub trait AppExtStates {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Enable state-scoped entity clearing for state `S`.
 | 
					    /// Enable state-scoped entity clearing for state `S`.
 | 
				
			||||||
    ///
 | 
					    ///
 | 
				
			||||||
 | 
					    /// If the [`States`] trait was derived with the `#[states(scoped_entities)]` attribute, it
 | 
				
			||||||
 | 
					    /// will be called automatically.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
    /// For more information refer to [`StateScoped`](crate::state_scoped::StateScoped).
 | 
					    /// For more information refer to [`StateScoped`](crate::state_scoped::StateScoped).
 | 
				
			||||||
    fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self;
 | 
					    fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -104,6 +107,9 @@ impl AppExtStates for SubApp {
 | 
				
			|||||||
                exited: None,
 | 
					                exited: None,
 | 
				
			||||||
                entered: Some(state),
 | 
					                entered: Some(state),
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					            if S::SCOPED_ENTITIES_ENABLED {
 | 
				
			||||||
 | 
					                self.enable_state_scoped_entities::<S>();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            let name = core::any::type_name::<S>();
 | 
					            let name = core::any::type_name::<S>();
 | 
				
			||||||
            warn!("State {} is already initialized.", name);
 | 
					            warn!("State {} is already initialized.", name);
 | 
				
			||||||
@ -126,6 +132,9 @@ impl AppExtStates for SubApp {
 | 
				
			|||||||
                exited: None,
 | 
					                exited: None,
 | 
				
			||||||
                entered: Some(state),
 | 
					                entered: Some(state),
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					            if S::SCOPED_ENTITIES_ENABLED {
 | 
				
			||||||
 | 
					                self.enable_state_scoped_entities::<S>();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // Overwrite previous state and initial event
 | 
					            // Overwrite previous state and initial event
 | 
				
			||||||
            self.insert_resource::<State<S>>(State::new(state.clone()));
 | 
					            self.insert_resource::<State<S>>(State::new(state.clone()));
 | 
				
			||||||
@ -160,6 +169,9 @@ impl AppExtStates for SubApp {
 | 
				
			|||||||
                exited: None,
 | 
					                exited: None,
 | 
				
			||||||
                entered: state,
 | 
					                entered: state,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					            if S::SCOPED_ENTITIES_ENABLED {
 | 
				
			||||||
 | 
					                self.enable_state_scoped_entities::<S>();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            let name = core::any::type_name::<S>();
 | 
					            let name = core::any::type_name::<S>();
 | 
				
			||||||
            warn!("Computed state {} is already initialized.", name);
 | 
					            warn!("Computed state {} is already initialized.", name);
 | 
				
			||||||
@ -188,6 +200,9 @@ impl AppExtStates for SubApp {
 | 
				
			|||||||
                exited: None,
 | 
					                exited: None,
 | 
				
			||||||
                entered: state,
 | 
					                entered: state,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					            if S::SCOPED_ENTITIES_ENABLED {
 | 
				
			||||||
 | 
					                self.enable_state_scoped_entities::<S>();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            let name = core::any::type_name::<S>();
 | 
					            let name = core::any::type_name::<S>();
 | 
				
			||||||
            warn!("Sub state {} is already initialized.", name);
 | 
					            warn!("Sub state {} is already initialized.", name);
 | 
				
			||||||
 | 
				
			|||||||
@ -64,4 +64,8 @@ pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug
 | 
				
			|||||||
    /// Used to help order transitions and de-duplicate [`ComputedStates`](crate::state::ComputedStates), as well as prevent cyclical
 | 
					    /// Used to help order transitions and de-duplicate [`ComputedStates`](crate::state::ComputedStates), as well as prevent cyclical
 | 
				
			||||||
    /// `ComputedState` dependencies.
 | 
					    /// `ComputedState` dependencies.
 | 
				
			||||||
    const DEPENDENCY_DEPTH: usize = 1;
 | 
					    const DEPENDENCY_DEPTH: usize = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Should [`StateScoped`](crate::state_scoped::StateScoped) be enabled for this state? If set to `true`,
 | 
				
			||||||
 | 
					    /// the `StateScoped` component will be used to remove entities when changing state.
 | 
				
			||||||
 | 
					    const SCOPED_ENTITIES_ENABLED: bool = false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,8 +16,8 @@ use crate::state::{StateTransitionEvent, States};
 | 
				
			|||||||
/// Entities marked with this component will be removed
 | 
					/// Entities marked with this component will be removed
 | 
				
			||||||
/// when the world's state of the matching type no longer matches the supplied value.
 | 
					/// when the world's state of the matching type no longer matches the supplied value.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// To enable this feature remember to configure your application
 | 
					/// To enable this feature remember to add the attribute `#[states(scoped_entities)]` when deriving [`States`].
 | 
				
			||||||
/// with [`enable_state_scoped_entities`](crate::app::AppExtStates::enable_state_scoped_entities) on your state(s) of choice.
 | 
					/// It's also possible to enable it when adding the state to an app with [`enable_state_scoped_entities`](crate::app::AppExtStates::enable_state_scoped_entities).
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// If `bevy_hierarchy` feature is enabled, which it is by default, the despawn will be recursive.
 | 
					/// If `bevy_hierarchy` feature is enabled, which it is by default, the despawn will be recursive.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
@ -26,6 +26,7 @@ use crate::state::{StateTransitionEvent, States};
 | 
				
			|||||||
/// use bevy_ecs::prelude::*;
 | 
					/// use bevy_ecs::prelude::*;
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
 | 
					/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
 | 
				
			||||||
 | 
					/// #[states(scoped_entities)]
 | 
				
			||||||
/// enum GameState {
 | 
					/// enum GameState {
 | 
				
			||||||
///     #[default]
 | 
					///     #[default]
 | 
				
			||||||
///     MainMenu,
 | 
					///     MainMenu,
 | 
				
			||||||
@ -53,7 +54,6 @@ use crate::state::{StateTransitionEvent, States};
 | 
				
			|||||||
/// # let mut app = AppMock;
 | 
					/// # let mut app = AppMock;
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// app.init_state::<GameState>();
 | 
					/// app.init_state::<GameState>();
 | 
				
			||||||
/// app.enable_state_scoped_entities::<GameState>();
 | 
					 | 
				
			||||||
/// app.add_systems(OnEnter(GameState::InGame), spawn_player);
 | 
					/// app.add_systems(OnEnter(GameState::InGame), spawn_player);
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
#[derive(Component, Clone)]
 | 
					#[derive(Component, Clone)]
 | 
				
			||||||
 | 
				
			|||||||
@ -25,6 +25,7 @@ enum AppState {
 | 
				
			|||||||
// in [`AppState::InGame`], the [`IsPaused`] state resource
 | 
					// in [`AppState::InGame`], the [`IsPaused`] state resource
 | 
				
			||||||
// will not exist.
 | 
					// will not exist.
 | 
				
			||||||
#[source(AppState = AppState::InGame)]
 | 
					#[source(AppState = AppState::InGame)]
 | 
				
			||||||
 | 
					#[states(scoped_entities)]
 | 
				
			||||||
enum IsPaused {
 | 
					enum IsPaused {
 | 
				
			||||||
    #[default]
 | 
					    #[default]
 | 
				
			||||||
    Running,
 | 
					    Running,
 | 
				
			||||||
@ -43,7 +44,6 @@ fn main() {
 | 
				
			|||||||
        .add_systems(OnExit(AppState::Menu), cleanup_menu)
 | 
					        .add_systems(OnExit(AppState::Menu), cleanup_menu)
 | 
				
			||||||
        .add_systems(OnEnter(AppState::InGame), setup_game)
 | 
					        .add_systems(OnEnter(AppState::InGame), setup_game)
 | 
				
			||||||
        .add_systems(OnEnter(IsPaused::Paused), setup_paused_screen)
 | 
					        .add_systems(OnEnter(IsPaused::Paused), setup_paused_screen)
 | 
				
			||||||
        .enable_state_scoped_entities::<IsPaused>()
 | 
					 | 
				
			||||||
        .add_systems(
 | 
					        .add_systems(
 | 
				
			||||||
            Update,
 | 
					            Update,
 | 
				
			||||||
            (
 | 
					            (
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,6 @@ fn main() {
 | 
				
			|||||||
    let mut app = App::new();
 | 
					    let mut app = App::new();
 | 
				
			||||||
    app.add_plugins((DefaultPlugins,))
 | 
					    app.add_plugins((DefaultPlugins,))
 | 
				
			||||||
        .init_state::<Scene>()
 | 
					        .init_state::<Scene>()
 | 
				
			||||||
        .enable_state_scoped_entities::<Scene>()
 | 
					 | 
				
			||||||
        .add_systems(OnEnter(Scene::Shapes), shapes::setup)
 | 
					        .add_systems(OnEnter(Scene::Shapes), shapes::setup)
 | 
				
			||||||
        .add_systems(OnEnter(Scene::Bloom), bloom::setup)
 | 
					        .add_systems(OnEnter(Scene::Bloom), bloom::setup)
 | 
				
			||||||
        .add_systems(OnEnter(Scene::Text), text::setup)
 | 
					        .add_systems(OnEnter(Scene::Text), text::setup)
 | 
				
			||||||
@ -20,6 +19,7 @@ fn main() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
 | 
					#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
 | 
				
			||||||
 | 
					#[states(scoped_entities)]
 | 
				
			||||||
enum Scene {
 | 
					enum Scene {
 | 
				
			||||||
    #[default]
 | 
					    #[default]
 | 
				
			||||||
    Shapes,
 | 
					    Shapes,
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,6 @@ fn main() {
 | 
				
			|||||||
    let mut app = App::new();
 | 
					    let mut app = App::new();
 | 
				
			||||||
    app.add_plugins((DefaultPlugins,))
 | 
					    app.add_plugins((DefaultPlugins,))
 | 
				
			||||||
        .init_state::<Scene>()
 | 
					        .init_state::<Scene>()
 | 
				
			||||||
        .enable_state_scoped_entities::<Scene>()
 | 
					 | 
				
			||||||
        .add_systems(OnEnter(Scene::Light), light::setup)
 | 
					        .add_systems(OnEnter(Scene::Light), light::setup)
 | 
				
			||||||
        .add_systems(OnEnter(Scene::Animation), animation::setup)
 | 
					        .add_systems(OnEnter(Scene::Animation), animation::setup)
 | 
				
			||||||
        .add_systems(Update, switch_scene);
 | 
					        .add_systems(Update, switch_scene);
 | 
				
			||||||
@ -24,6 +23,7 @@ fn main() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
 | 
					#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
 | 
				
			||||||
 | 
					#[states(scoped_entities)]
 | 
				
			||||||
enum Scene {
 | 
					enum Scene {
 | 
				
			||||||
    #[default]
 | 
					    #[default]
 | 
				
			||||||
    Light,
 | 
					    Light,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user