Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enable_state_scoped_entities() as a derive attribute #16180

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions crates/bevy_state/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ mod states;
use bevy_macro_utils::BevyManifest;
use proc_macro::TokenStream;

#[proc_macro_derive(States)]
#[proc_macro_derive(States, attributes(states))]
pub fn derive_states(input: TokenStream) -> TokenStream {
states::derive_states(input)
}

#[proc_macro_derive(SubStates, attributes(source))]
#[proc_macro_derive(SubStates, attributes(states, source))]
pub fn derive_substates(input: TokenStream) -> TokenStream {
states::derive_substates(input)
}
Expand Down
52 changes: 48 additions & 4 deletions crates/bevy_state/macros/src/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,42 @@ use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Pat, Path, Result};

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 {
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 (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

Expand All @@ -23,8 +56,12 @@ pub fn derive_states(input: TokenStream) -> TokenStream {

let struct_name = &ast.ident;

let scoped_entities_enabled = attrs.scoped_entities_enabled;

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 {
}
Expand All @@ -37,7 +74,7 @@ struct Source {
source_value: Pat,
}

fn parse_sources_attr(ast: &DeriveInput) -> Result<Source> {
fn parse_sources_attr(ast: &DeriveInput) -> Result<(StatesAttrs, Source)> {
let mut result = ast
.attrs
.iter()
Expand Down Expand Up @@ -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 {
return Err(syn::Error::new(ast.span(), "SubStates require a source"));
};

Ok(result)
Ok((states_attrs, result))
}

pub fn derive_substates(input: TokenStream) -> TokenStream {
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 (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
Expand Down Expand Up @@ -113,6 +153,8 @@ pub fn derive_substates(input: TokenStream) -> TokenStream {
let source_state_type = sources.source_type;
let source_state_value = sources.source_value;

let scoped_entities_enabled = states_attrs.scoped_entities_enabled;

let result = quote! {
impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {
type SourceStates = #source_state_type;
Expand All @@ -124,6 +166,8 @@ pub fn derive_substates(input: TokenStream) -> TokenStream {

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 SCOPED_ENTITIES_ENABLED: bool = #scoped_entities_enabled;
}

impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause {
Expand Down
15 changes: 15 additions & 0 deletions crates/bevy_state/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ pub trait AppExtStates {

/// 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).
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self;

Expand Down Expand Up @@ -104,6 +107,9 @@ impl AppExtStates for SubApp {
exited: None,
entered: Some(state),
});
if S::SCOPED_ENTITIES_ENABLED {
self.enable_state_scoped_entities::<S>();
mockersf marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
let name = core::any::type_name::<S>();
warn!("State {} is already initialized.", name);
Expand All @@ -126,6 +132,9 @@ impl AppExtStates for SubApp {
exited: None,
entered: Some(state),
});
if S::SCOPED_ENTITIES_ENABLED {
self.enable_state_scoped_entities::<S>();
}
} else {
// Overwrite previous state and initial event
self.insert_resource::<State<S>>(State::new(state.clone()));
Expand Down Expand Up @@ -160,6 +169,9 @@ impl AppExtStates for SubApp {
exited: None,
entered: state,
});
if S::SCOPED_ENTITIES_ENABLED {
self.enable_state_scoped_entities::<S>();
}
} else {
let name = core::any::type_name::<S>();
warn!("Computed state {} is already initialized.", name);
Expand Down Expand Up @@ -188,6 +200,9 @@ impl AppExtStates for SubApp {
exited: None,
entered: state,
});
if S::SCOPED_ENTITIES_ENABLED {
self.enable_state_scoped_entities::<S>();
}
} else {
let name = core::any::type_name::<S>();
warn!("Sub state {} is already initialized.", name);
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_state/src/state/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// `ComputedState` dependencies.
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;
}
6 changes: 3 additions & 3 deletions crates/bevy_state/src/state_scoped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use crate::state::{StateTransitionEvent, States};
/// Entities marked with this component will be removed
/// when the world's state of the matching type no longer matches the supplied value.
///
/// To enable this feature remember to configure your application
/// with [`enable_state_scoped_entities`](crate::app::AppExtStates::enable_state_scoped_entities) on your state(s) of choice.
/// To enable this feature remember to add the attribute `#[states(scoped_entities)]` when deriving [`States`].
mockersf marked this conversation as resolved.
Show resolved Hide resolved
/// 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.
///
Expand All @@ -26,6 +26,7 @@ use crate::state::{StateTransitionEvent, States};
/// use bevy_ecs::prelude::*;
///
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
/// #[states(scoped_entities)]
/// enum GameState {
/// #[default]
/// MainMenu,
Expand Down Expand Up @@ -53,7 +54,6 @@ use crate::state::{StateTransitionEvent, States};
/// # let mut app = AppMock;
///
/// app.init_state::<GameState>();
/// app.enable_state_scoped_entities::<GameState>();
/// app.add_systems(OnEnter(GameState::InGame), spawn_player);
/// ```
#[derive(Component, Clone)]
Expand Down
2 changes: 1 addition & 1 deletion examples/state/sub_states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum AppState {
// in [`AppState::InGame`], the [`IsPaused`] state resource
// will not exist.
#[source(AppState = AppState::InGame)]
#[states(scoped_entities)]
enum IsPaused {
#[default]
Running,
Expand All @@ -43,7 +44,6 @@ fn main() {
.add_systems(OnExit(AppState::Menu), cleanup_menu)
.add_systems(OnEnter(AppState::InGame), setup_game)
.add_systems(OnEnter(IsPaused::Paused), setup_paused_screen)
.enable_state_scoped_entities::<IsPaused>()
.add_systems(
Update,
(
Expand Down
2 changes: 1 addition & 1 deletion examples/testbed/2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ fn main() {
let mut app = App::new();
app.add_plugins((DefaultPlugins,))
.init_state::<Scene>()
.enable_state_scoped_entities::<Scene>()
.add_systems(OnEnter(Scene::Shapes), shapes::setup)
.add_systems(OnEnter(Scene::Bloom), bloom::setup)
.add_systems(OnEnter(Scene::Text), text::setup)
Expand All @@ -20,6 +19,7 @@ fn main() {
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
#[states(scoped_entities)]
enum Scene {
#[default]
Shapes,
Expand Down
2 changes: 1 addition & 1 deletion examples/testbed/3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ fn main() {
let mut app = App::new();
app.add_plugins((DefaultPlugins,))
.init_state::<Scene>()
.enable_state_scoped_entities::<Scene>()
.add_systems(OnEnter(Scene::Light), light::setup)
.add_systems(OnEnter(Scene::Animation), animation::setup)
.add_systems(Update, switch_scene);
Expand All @@ -24,6 +23,7 @@ fn main() {
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, States, Default)]
#[states(scoped_entities)]
enum Scene {
#[default]
Light,
Expand Down