Enable state scoped entities by default (#19354)

# Objective

- Enable state scoped entities by default
- Provide a way to disable it when needed

---------

Co-authored-by: Ben Frankel <ben.frankel7@gmail.com>
This commit is contained in:
François Mockers 2025-05-26 22:26:41 +02:00 committed by GitHub
parent 8db7b6e122
commit 8a223be651
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 21 additions and 21 deletions

View File

@ -1,6 +1,6 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Pat, Path, Result}; use syn::{parse_macro_input, spanned::Spanned, DeriveInput, LitBool, Pat, Path, Result};
use crate::bevy_state_path; use crate::bevy_state_path;
@ -13,14 +13,16 @@ struct StatesAttrs {
fn parse_states_attr(ast: &DeriveInput) -> Result<StatesAttrs> { fn parse_states_attr(ast: &DeriveInput) -> Result<StatesAttrs> {
let mut attrs = StatesAttrs { let mut attrs = StatesAttrs {
scoped_entities_enabled: false, scoped_entities_enabled: true,
}; };
for attr in ast.attrs.iter() { for attr in ast.attrs.iter() {
if attr.path().is_ident(STATES) { if attr.path().is_ident(STATES) {
attr.parse_nested_meta(|nested| { attr.parse_nested_meta(|nested| {
if nested.path.is_ident(SCOPED_ENTITIES) { if nested.path.is_ident(SCOPED_ENTITIES) {
attrs.scoped_entities_enabled = true; if let Ok(value) = nested.value() {
attrs.scoped_entities_enabled = value.parse::<LitBool>()?.value();
}
Ok(()) Ok(())
} else { } else {
Err(nested.error("Unsupported attribute")) Err(nested.error("Unsupported attribute"))

View File

@ -59,10 +59,11 @@ 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 /// This is enabled by default. If you don't want this behavior, add the `#[states(scoped_entities = false)]`
/// will be called automatically. /// attribute when deriving the [`States`] trait.
/// ///
/// For more information refer to [`crate::state_scoped`]. /// For more information refer to [`crate::state_scoped`].
#[doc(hidden)]
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self; fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self;
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
@ -214,6 +215,7 @@ impl AppExtStates for SubApp {
self self
} }
#[doc(hidden)]
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self { fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self {
if !self if !self
.world() .world()
@ -285,6 +287,7 @@ impl AppExtStates for App {
self self
} }
#[doc(hidden)]
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self { fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self {
self.main_mut().enable_state_scoped_entities::<S>(); self.main_mut().enable_state_scoped_entities::<S>();
self self

View File

@ -14,8 +14,7 @@ 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 add the attribute `#[states(scoped_entities)]` when deriving [`States`]. /// If you need to disable this behavior, add the attribute `#[states(scoped_entities = false)]` when deriving [`States`].
/// 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).
/// ///
/// ``` /// ```
/// use bevy_state::prelude::*; /// use bevy_state::prelude::*;
@ -23,7 +22,6 @@ use crate::state::{StateTransitionEvent, States};
/// use bevy_ecs::system::ScheduleSystem; /// use bevy_ecs::system::ScheduleSystem;
/// ///
/// #[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,
@ -44,7 +42,6 @@ use crate::state::{StateTransitionEvent, States};
/// # struct AppMock; /// # struct AppMock;
/// # impl AppMock { /// # impl AppMock {
/// # fn init_state<S>(&mut self) {} /// # fn init_state<S>(&mut self) {}
/// # fn enable_state_scoped_entities<S>(&mut self) {}
/// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {} /// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {}
/// # } /// # }
/// # struct Update; /// # struct Update;
@ -123,14 +120,12 @@ pub fn despawn_entities_on_exit_state<S: States>(
/// # struct AppMock; /// # struct AppMock;
/// # impl AppMock { /// # impl AppMock {
/// # fn init_state<S>(&mut self) {} /// # fn init_state<S>(&mut self) {}
/// # fn enable_state_scoped_entities<S>(&mut self) {}
/// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {} /// # fn add_systems<S, M>(&mut self, schedule: S, systems: impl IntoScheduleConfigs<ScheduleSystem, M>) {}
/// # } /// # }
/// # struct Update; /// # struct Update;
/// # 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)]

View File

@ -10,7 +10,6 @@ fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.init_state::<GameState>() .init_state::<GameState>()
.enable_state_scoped_entities::<GameState>()
.add_systems(Startup, setup_camera) .add_systems(Startup, setup_camera)
.add_systems(OnEnter(GameState::A), on_a_enter) .add_systems(OnEnter(GameState::A), on_a_enter)
.add_systems(OnEnter(GameState::B), on_b_enter) .add_systems(OnEnter(GameState::B), on_b_enter)

View File

@ -25,7 +25,6 @@ fn main() {
TimerMode::Repeating, TimerMode::Repeating,
))) )))
.init_state::<GameState>() .init_state::<GameState>()
.enable_state_scoped_entities::<GameState>()
.add_systems(Startup, setup_cameras) .add_systems(Startup, setup_cameras)
.add_systems(OnEnter(GameState::Playing), setup) .add_systems(OnEnter(GameState::Playing), setup)
.add_systems( .add_systems(

View File

@ -184,9 +184,6 @@ fn main() {
// We only want to run the [`setup_game`] function when we enter the [`AppState::InGame`] state, regardless // We only want to run the [`setup_game`] function when we enter the [`AppState::InGame`] state, regardless
// of whether the game is paused or not. // of whether the game is paused or not.
.add_systems(OnEnter(InGame), setup_game) .add_systems(OnEnter(InGame), setup_game)
// And we only want to run the [`clear_game`] function when we leave the [`AppState::InGame`] state, regardless
// of whether we're paused.
.enable_state_scoped_entities::<InGame>()
// We want the color change, toggle_pause and quit_to_menu systems to ignore the paused condition, so we can use the [`InGame`] derived // We want the color change, toggle_pause and quit_to_menu systems to ignore the paused condition, so we can use the [`InGame`] derived
// state here as well. // state here as well.
.add_systems( .add_systems(
@ -200,15 +197,12 @@ fn main() {
) )
// We can continue setting things up, following all the same patterns used above and in the `states` example. // We can continue setting things up, following all the same patterns used above and in the `states` example.
.add_systems(OnEnter(IsPaused::Paused), setup_paused_screen) .add_systems(OnEnter(IsPaused::Paused), setup_paused_screen)
.enable_state_scoped_entities::<IsPaused>()
.add_systems(OnEnter(TurboMode), setup_turbo_text) .add_systems(OnEnter(TurboMode), setup_turbo_text)
.enable_state_scoped_entities::<TurboMode>()
.add_systems( .add_systems(
OnEnter(Tutorial::MovementInstructions), OnEnter(Tutorial::MovementInstructions),
movement_instructions, movement_instructions,
) )
.add_systems(OnEnter(Tutorial::PauseInstructions), pause_instructions) .add_systems(OnEnter(Tutorial::PauseInstructions), pause_instructions)
.enable_state_scoped_entities::<Tutorial>()
.add_systems( .add_systems(
Update, Update,
( (

View File

@ -26,7 +26,6 @@ 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,

View File

@ -26,7 +26,6 @@ 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,

View File

@ -0,0 +1,10 @@
---
title: Entities are now state scoped by default
pull_requests: [19354]
---
State scoped entities is now enabled by default, and you don't need to call `app.enable_state_scoped_entities::<State>()` anymore.
If you were previously adding the `#[states(scoped_entities)]` attribute when deriving the `States` trait, you can remove it.
If you want to keep the previous behavior, you must add the attribute `#[states(scoped_entities = false)]`.