bevy/crates/bevy_state/src/app.rs
Zachary Harrold d70595b667
Add core and alloc over std Lints (#15281)
# Objective

- Fixes #6370
- Closes #6581

## Solution

- Added the following lints to the workspace:
  - `std_instead_of_core`
  - `std_instead_of_alloc`
  - `alloc_instead_of_core`
- Used `cargo +nightly fmt` with [item level use
formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Item%5C%3A)
to split all `use` statements into single items.
- Used `cargo clippy --workspace --all-targets --all-features --fix
--allow-dirty` to _attempt_ to resolve the new linting issues, and
intervened where the lint was unable to resolve the issue automatically
(usually due to needing an `extern crate alloc;` statement in a crate
root).
- Manually removed certain uses of `std` where negative feature gating
prevented `--all-features` from finding the offending uses.
- Used `cargo +nightly fmt` with [crate level use
formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Crate%5C%3A)
to re-merge all `use` statements matching Bevy's previous styling.
- Manually fixed cases where the `fmt` tool could not re-merge `use`
statements due to conditional compilation attributes.

## Testing

- Ran CI locally

## Migration Guide

The MSRV is now 1.81. Please update to this version or higher.

## Notes

- This is a _massive_ change to try and push through, which is why I've
outlined the semi-automatic steps I used to create this PR, in case this
fails and someone else tries again in the future.
- Making this change has no impact on user code, but does mean Bevy
contributors will be warned to use `core` and `alloc` instead of `std`
where possible.
- This lint is a critical first step towards investigating `no_std`
options for Bevy.

---------

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2024-09-27 00:59:59 +00:00

359 lines
14 KiB
Rust

use bevy_app::{App, MainScheduleOrder, Plugin, PreStartup, PreUpdate, SubApp};
use bevy_ecs::{event::Events, schedule::IntoSystemConfigs, world::FromWorld};
use bevy_utils::{tracing::warn, warn_once};
use crate::{
state::{
setup_state_transitions_in_world, ComputedStates, FreelyMutableState, NextState, State,
StateTransition, StateTransitionEvent, StateTransitionSteps, States, SubStates,
},
state_scoped::clear_state_scoped_entities,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{FromReflect, GetTypeRegistration, Typed};
/// State installation methods for [`App`] and [`SubApp`].
pub trait AppExtStates {
/// Initializes a [`State`] with standard starting values.
///
/// This method is idempotent: it has no effect when called again using the same generic type.
///
/// Adds [`State<S>`] and [`NextState<S>`] resources, and enables use of the [`OnEnter`](crate::state::OnEnter),
/// [`OnTransition`](crate::state::OnTransition) and [`OnExit`](crate::state::OnExit) schedules.
/// These schedules are triggered before [`Update`](bevy_app::Update) and at startup.
///
/// If you would like to control how other systems run based on the current state, you can
/// emulate this behavior using the [`in_state`](crate::condition::in_state) [`Condition`](bevy_ecs::prelude::Condition).
///
/// Note that you can also apply state transitions at other points in the schedule
/// by triggering the [`StateTransition`](struct@StateTransition) schedule manually.
///
/// The use of any states requires the presence of [`StatesPlugin`] (which is included in `DefaultPlugins`).
fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self;
/// Inserts a specific [`State`] to the current [`App`] and overrides any [`State`] previously
/// added of the same type.
///
/// Adds [`State<S>`] and [`NextState<S>`] resources, and enables use of the [`OnEnter`](crate::state::OnEnter),
/// [`OnTransition`](crate::state::OnTransition) and [`OnExit`](crate::state::OnExit) schedules.
/// These schedules are triggered before [`Update`](bevy_app::Update) and at startup.
///
/// If you would like to control how other systems run based on the current state, you can
/// emulate this behavior using the [`in_state`](crate::condition::in_state) [`Condition`](bevy_ecs::prelude::Condition).
///
/// Note that you can also apply state transitions at other points in the schedule
/// by triggering the [`StateTransition`](struct@StateTransition) schedule manually.
fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self;
/// Sets up a type implementing [`ComputedStates`].
///
/// This method is idempotent: it has no effect when called again using the same generic type.
fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self;
/// Sets up a type implementing [`SubStates`].
///
/// This method is idempotent: it has no effect when called again using the same generic type.
fn add_sub_state<S: SubStates>(&mut self) -> &mut Self;
/// Enable state-scoped entity clearing for state `S`.
///
/// For more information refer to [`StateScoped`](crate::state_scoped::StateScoped).
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self;
#[cfg(feature = "bevy_reflect")]
/// Registers the state type `T` using [`App::register_type`],
/// and adds [`ReflectState`](crate::reflect::ReflectState) type data to `T` in the type registry.
///
/// This enables reflection code to access the state. For detailed information, see the docs on [`crate::reflect::ReflectState`] .
fn register_type_state<S>(&mut self) -> &mut Self
where
S: States + FromReflect + GetTypeRegistration + Typed;
#[cfg(feature = "bevy_reflect")]
/// Registers the state type `T` using [`App::register_type`],
/// and adds [`crate::reflect::ReflectState`] and [`crate::reflect::ReflectFreelyMutableState`] type data to `T` in the type registry.
///
/// This enables reflection code to access and modify the state.
/// For detailed information, see the docs on [`crate::reflect::ReflectState`] and [`crate::reflect::ReflectFreelyMutableState`].
fn register_type_mutable_state<S>(&mut self) -> &mut Self
where
S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed;
}
/// Separate function to only warn once for all state installation methods.
fn warn_if_no_states_plugin_installed(app: &SubApp) {
if !app.is_plugin_added::<StatesPlugin>() {
warn_once!("States were added to the app, but `StatesPlugin` is not installed.");
}
}
impl AppExtStates for SubApp {
fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self {
warn_if_no_states_plugin_installed(self);
if !self.world().contains_resource::<State<S>>() {
self.init_resource::<State<S>>()
.init_resource::<NextState<S>>()
.add_event::<StateTransitionEvent<S>>();
let schedule = self.get_schedule_mut(StateTransition).expect(
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling init_state?"
);
S::register_state(schedule);
let state = self.world().resource::<State<S>>().get().clone();
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: Some(state),
});
} else {
let name = core::any::type_name::<S>();
warn!("State {} is already initialized.", name);
}
self
}
fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self {
warn_if_no_states_plugin_installed(self);
if !self.world().contains_resource::<State<S>>() {
self.insert_resource::<State<S>>(State::new(state.clone()))
.init_resource::<NextState<S>>()
.add_event::<StateTransitionEvent<S>>();
let schedule = self.get_schedule_mut(StateTransition).expect(
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling insert_state?"
);
S::register_state(schedule);
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: Some(state),
});
} else {
// Overwrite previous state and initial event
self.insert_resource::<State<S>>(State::new(state.clone()));
self.world_mut()
.resource_mut::<Events<StateTransitionEvent<S>>>()
.clear();
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: Some(state),
});
}
self
}
fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self {
warn_if_no_states_plugin_installed(self);
if !self
.world()
.contains_resource::<Events<StateTransitionEvent<S>>>()
{
self.add_event::<StateTransitionEvent<S>>();
let schedule = self.get_schedule_mut(StateTransition).expect(
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_computed_state?"
);
S::register_computed_state_systems(schedule);
let state = self
.world()
.get_resource::<State<S>>()
.map(|s| s.get().clone());
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: state,
});
} else {
let name = core::any::type_name::<S>();
warn!("Computed state {} is already initialized.", name);
}
self
}
fn add_sub_state<S: SubStates>(&mut self) -> &mut Self {
warn_if_no_states_plugin_installed(self);
if !self
.world()
.contains_resource::<Events<StateTransitionEvent<S>>>()
{
self.init_resource::<NextState<S>>();
self.add_event::<StateTransitionEvent<S>>();
let schedule = self.get_schedule_mut(StateTransition).expect(
"The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling add_sub_state?"
);
S::register_sub_state_systems(schedule);
let state = self
.world()
.get_resource::<State<S>>()
.map(|s| s.get().clone());
self.world_mut().send_event(StateTransitionEvent {
exited: None,
entered: state,
});
} else {
let name = core::any::type_name::<S>();
warn!("Sub state {} is already initialized.", name);
}
self
}
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self {
if !self
.world()
.contains_resource::<Events<StateTransitionEvent<S>>>()
{
let name = core::any::type_name::<S>();
warn!("State scoped entities are enabled for state `{}`, but the state isn't installed in the app!", name);
}
// We work with [`StateTransition`] in set [`StateTransitionSteps::ExitSchedules`] as opposed to [`OnExit`],
// because [`OnExit`] only runs for one specific variant of the state.
self.add_systems(
StateTransition,
clear_state_scoped_entities::<S>.in_set(StateTransitionSteps::ExitSchedules),
)
}
#[cfg(feature = "bevy_reflect")]
fn register_type_state<S>(&mut self) -> &mut Self
where
S: States + FromReflect + GetTypeRegistration + Typed,
{
self.register_type::<S>();
self.register_type::<State<S>>();
self.register_type_data::<S, crate::reflect::ReflectState>();
self
}
#[cfg(feature = "bevy_reflect")]
fn register_type_mutable_state<S>(&mut self) -> &mut Self
where
S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed,
{
self.register_type::<S>();
self.register_type::<State<S>>();
self.register_type::<NextState<S>>();
self.register_type_data::<S, crate::reflect::ReflectState>();
self.register_type_data::<S, crate::reflect::ReflectFreelyMutableState>();
self
}
}
impl AppExtStates for App {
fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self {
self.main_mut().init_state::<S>();
self
}
fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self {
self.main_mut().insert_state::<S>(state);
self
}
fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self {
self.main_mut().add_computed_state::<S>();
self
}
fn add_sub_state<S: SubStates>(&mut self) -> &mut Self {
self.main_mut().add_sub_state::<S>();
self
}
fn enable_state_scoped_entities<S: States>(&mut self) -> &mut Self {
self.main_mut().enable_state_scoped_entities::<S>();
self
}
#[cfg(feature = "bevy_reflect")]
fn register_type_state<S>(&mut self) -> &mut Self
where
S: States + FromReflect + GetTypeRegistration + Typed,
{
self.main_mut().register_type_state::<S>();
self
}
#[cfg(feature = "bevy_reflect")]
fn register_type_mutable_state<S>(&mut self) -> &mut Self
where
S: FreelyMutableState + FromReflect + GetTypeRegistration + Typed,
{
self.main_mut().register_type_mutable_state::<S>();
self
}
}
/// Registers the [`StateTransition`] schedule in the [`MainScheduleOrder`] to enable state processing.
#[derive(Default)]
pub struct StatesPlugin;
impl Plugin for StatesPlugin {
fn build(&self, app: &mut App) {
let mut schedule = app.world_mut().resource_mut::<MainScheduleOrder>();
schedule.insert_after(PreUpdate, StateTransition);
schedule.insert_startup_before(PreStartup, StateTransition);
setup_state_transitions_in_world(app.world_mut());
}
}
#[cfg(test)]
mod tests {
use crate::{
self as bevy_state,
app::StatesPlugin,
state::{State, StateTransition, StateTransitionEvent},
};
use bevy_app::App;
use bevy_ecs::event::Events;
use bevy_state_macros::States;
use super::AppExtStates;
#[derive(States, Default, PartialEq, Eq, Hash, Debug, Clone)]
enum TestState {
#[default]
A,
B,
C,
}
#[test]
fn insert_state_can_overwrite_init_state() {
let mut app = App::new();
app.add_plugins(StatesPlugin);
app.init_state::<TestState>();
app.insert_state(TestState::B);
let world = app.world_mut();
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<TestState>>().0, TestState::B);
let events = world.resource::<Events<StateTransitionEvent<TestState>>>();
assert_eq!(events.len(), 1);
let mut reader = events.get_cursor();
let last = reader.read(events).last().unwrap();
assert_eq!(last.exited, None);
assert_eq!(last.entered, Some(TestState::B));
}
#[test]
fn insert_state_can_overwrite_insert_state() {
let mut app = App::new();
app.add_plugins(StatesPlugin);
app.insert_state(TestState::B);
app.insert_state(TestState::C);
let world = app.world_mut();
world.run_schedule(StateTransition);
assert_eq!(world.resource::<State<TestState>>().0, TestState::C);
let events = world.resource::<Events<StateTransitionEvent<TestState>>>();
assert_eq!(events.len(), 1);
let mut reader = events.get_cursor();
let last = reader.read(events).last().unwrap();
assert_eq!(last.exited, None);
assert_eq!(last.entered, Some(TestState::C));
}
}