diff --git a/crates/bevy_app/src/app_builder.rs b/crates/bevy_app/src/app_builder.rs index 82a5aeaf1e823..954e87ade6cf9 100644 --- a/crates/bevy_app/src/app_builder.rs +++ b/crates/bevy_app/src/app_builder.rs @@ -7,7 +7,7 @@ use crate::{ use bevy_ecs::{ component::Component, schedule::{ - RunOnce, Schedule, Stage, StageLabel, StateStage, SystemDescriptor, SystemSet, SystemStage, + RunOnce, Schedule, Stage, StageLabel, State, SystemDescriptor, SystemSet, SystemStage, }, system::{IntoExclusiveSystem, IntoSystem}, world::{FromWorld, World}, @@ -177,37 +177,18 @@ impl AppBuilder { self } - pub fn on_state_enter( - &mut self, - stage: impl StageLabel, - state: T, - system: impl Into, - ) -> &mut Self { - self.stage(stage, |stage: &mut StateStage| { - stage.on_state_enter(state, system) - }) + pub fn add_state(&mut self, initial: T) -> &mut Self { + self.insert_resource(State::new(initial)) + .add_system_set(State::::make_driver()) } - pub fn on_state_update( + pub fn add_state_to_stage( &mut self, stage: impl StageLabel, - state: T, - system: impl Into, - ) -> &mut Self { - self.stage(stage, |stage: &mut StateStage| { - stage.on_state_update(state, system) - }) - } - - pub fn on_state_exit( - &mut self, - stage: impl StageLabel, - state: T, - system: impl Into, + initial: T, ) -> &mut Self { - self.stage(stage, |stage: &mut StateStage| { - stage.on_state_exit(state, system) - }) + self.insert_resource(State::new(initial)) + .add_system_set_to_stage(stage, State::::make_driver()) } pub fn add_default_stages(&mut self) -> &mut Self { diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 81d55c85ff65c..e3afbdc113eb7 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -19,7 +19,7 @@ pub mod prelude { query::{Added, Changed, Flags, Mutated, Or, QueryState, With, WithBundle, Without}, schedule::{ AmbiguitySetLabel, ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion, - Schedule, Stage, StageLabel, State, StateStage, SystemLabel, SystemStage, + Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage, }, system::{ Commands, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend, diff --git a/crates/bevy_ecs/src/schedule/state.rs b/crates/bevy_ecs/src/schedule/state.rs index a590910267d59..2622aafc9597e 100644 --- a/crates/bevy_ecs/src/schedule/state.rs +++ b/crates/bevy_ecs/src/schedule/state.rs @@ -1,262 +1,550 @@ use crate::{ component::Component, - schedule::{Stage, SystemDescriptor, SystemStage}, - world::World, + schedule::{ShouldRun, SystemSet}, + system::{In, IntoChainSystem, IntoSystem, Local, Res, ResMut, System}, }; -use bevy_utils::HashMap; -use std::{mem::Discriminant, ops::Deref}; use thiserror::Error; -pub(crate) struct StateStages { - update: Box, - enter: Box, - exit: Box, -} - -impl Default for StateStages { - fn default() -> Self { - Self { - enter: Box::new(SystemStage::parallel()), - update: Box::new(SystemStage::parallel()), - exit: Box::new(SystemStage::parallel()), - } - } +/// ### Stack based state machine +/// +/// This state machine has three operations: Push, Pop, and Next. +/// * Push pushes a new state to the state stack, pausing the previous state +/// * Pop removes the current state, and unpauses the last paused state. +/// * Next unwinds the state stack, and replaces the entire stack with a single new state +#[derive(Debug)] +pub struct State { + transition: Option>, + stack: Vec, + scheduled: Option>, + end_next_loop: bool, } -pub struct StateStage { - stages: HashMap, StateStages>, +#[derive(Debug)] +enum StateTransition { + PreStartup, + Startup, + // The parameter order is always (leaving, entering) + ExitingToResume(T, T), + ExitingFull(T, T), + Entering(T, T), + Resuming(T, T), + Pausing(T, T), } -impl Default for StateStage { - fn default() -> Self { - Self { - stages: Default::default(), - } - } +#[derive(Debug)] +enum ScheduledOperation { + Next(T), + Pop, + Push(T), } -#[allow(clippy::mem_discriminant_non_enum)] -impl StateStage { - pub fn with_enter_stage(mut self, state: T, stage: S) -> Self { - self.set_enter_stage(state, stage); - self - } - - pub fn with_exit_stage(mut self, state: T, stage: S) -> Self { - self.set_exit_stage(state, stage); - self +impl State { + pub fn on_update(s: T) -> impl System { + (|state: Res>, pred: Local>| { + state.stack.last().unwrap() == pred.as_ref().unwrap() && state.transition.is_none() + }) + .system() + .config(|(_, pred)| *pred = Some(Some(s))) + .chain(should_run_adapter::.system()) } - pub fn with_update_stage(mut self, state: T, stage: S) -> Self { - self.set_update_stage(state, stage); - self + pub fn on_inactive_update(s: T) -> impl System { + (|state: Res>, mut is_inactive: Local, pred: Local>| match &state + .transition + { + Some(StateTransition::Pausing(ref relevant, _)) + | Some(StateTransition::Resuming(_, ref relevant)) => { + if relevant == pred.as_ref().unwrap() { + *is_inactive = !*is_inactive; + } + false + } + Some(_) => false, + None => *is_inactive, + }) + .system() + .config(|(_, _, pred)| *pred = Some(Some(s))) + .chain(should_run_adapter::.system()) } - pub fn set_enter_stage(&mut self, state: T, stage: S) -> &mut Self { - let stages = self.state_stages(state); - stages.enter = Box::new(stage); - self + pub fn on_in_stack_update(s: T) -> impl System { + (|state: Res>, mut is_in_stack: Local, pred: Local>| match &state + .transition + { + Some(StateTransition::Entering(ref relevant, _)) + | Some(StateTransition::ExitingToResume(_, ref relevant)) => { + if relevant == pred.as_ref().unwrap() { + *is_in_stack = !*is_in_stack; + } + false + } + Some(StateTransition::ExitingFull(_, ref relevant)) => { + if relevant == pred.as_ref().unwrap() { + *is_in_stack = !*is_in_stack; + } + false + } + Some(StateTransition::Startup) => { + if state.stack.last().unwrap() == pred.as_ref().unwrap() { + *is_in_stack = !*is_in_stack; + } + false + } + Some(_) => false, + None => *is_in_stack, + }) + .system() + .config(|(_, _, pred)| *pred = Some(Some(s))) + .chain(should_run_adapter::.system()) } - pub fn set_exit_stage(&mut self, state: T, stage: S) -> &mut Self { - let stages = self.state_stages(state); - stages.exit = Box::new(stage); - self + pub fn on_enter(s: T) -> impl System { + (|state: Res>, pred: Local>| { + state + .transition + .as_ref() + .map_or(false, |transition| match transition { + StateTransition::Entering(_, entering) => entering == pred.as_ref().unwrap(), + StateTransition::Startup => { + state.stack.last().unwrap() == pred.as_ref().unwrap() + } + _ => false, + }) + }) + .system() + .config(|(_, pred)| *pred = Some(Some(s))) + .chain(should_run_adapter::.system()) } - pub fn set_update_stage(&mut self, state: T, stage: S) -> &mut Self { - let stages = self.state_stages(state); - stages.update = Box::new(stage); - self + pub fn on_exit(s: T) -> impl System { + (|state: Res>, pred: Local>| { + state + .transition + .as_ref() + .map_or(false, |transition| match transition { + StateTransition::ExitingToResume(exiting, _) + | StateTransition::ExitingFull(exiting, _) => exiting == pred.as_ref().unwrap(), + _ => false, + }) + }) + .system() + .config(|(_, pred)| *pred = Some(Some(s))) + .chain(should_run_adapter::.system()) } - pub fn on_state_enter(&mut self, state: T, system: impl Into) -> &mut Self { - self.enter_stage(state, |system_stage: &mut SystemStage| { - system_stage.add_system(system) + pub fn on_pause(s: T) -> impl System { + (|state: Res>, pred: Local>| { + state + .transition + .as_ref() + .map_or(false, |transition| match transition { + StateTransition::Pausing(pausing, _) => pausing == pred.as_ref().unwrap(), + _ => false, + }) }) + .system() + .config(|(_, pred)| *pred = Some(Some(s))) + .chain(should_run_adapter::.system()) } - pub fn on_state_exit(&mut self, state: T, system: impl Into) -> &mut Self { - self.exit_stage(state, |system_stage: &mut SystemStage| { - system_stage.add_system(system) + pub fn on_resume(s: T) -> impl System { + (|state: Res>, pred: Local>| { + state + .transition + .as_ref() + .map_or(false, |transition| match transition { + StateTransition::Resuming(_, resuming) => resuming == pred.as_ref().unwrap(), + _ => false, + }) }) + .system() + .config(|(_, pred)| *pred = Some(Some(s))) + .chain(should_run_adapter::.system()) } - pub fn on_state_update(&mut self, state: T, system: impl Into) -> &mut Self { - self.update_stage(state, |system_stage: &mut SystemStage| { - system_stage.add_system(system) - }) + pub fn on_update_set(s: T) -> SystemSet { + SystemSet::new().with_run_criteria(Self::on_update(s)) } - pub fn enter_stage &mut S>( - &mut self, - state: T, - func: F, - ) -> &mut Self { - let stages = self.state_stages(state); - func( - stages - .enter - .downcast_mut() - .expect("'Enter' stage does not match the given type"), - ); - self + pub fn on_inactive_update_set(s: T) -> SystemSet { + SystemSet::new().with_run_criteria(Self::on_inactive_update(s)) } - pub fn exit_stage &mut S>( - &mut self, - state: T, - func: F, - ) -> &mut Self { - let stages = self.state_stages(state); - func( - stages - .exit - .downcast_mut() - .expect("'Exit' stage does not match the given type"), - ); - self + pub fn on_enter_set(s: T) -> SystemSet { + SystemSet::new().with_run_criteria(Self::on_enter(s)) } - pub fn update_stage &mut S>( - &mut self, - state: T, - func: F, - ) -> &mut Self { - let stages = self.state_stages(state); - func( - stages - .update - .downcast_mut() - .expect("'Update' stage does not match the given type"), - ); - self + pub fn on_exit_set(s: T) -> SystemSet { + SystemSet::new().with_run_criteria(Self::on_exit(s)) } - fn state_stages(&mut self, state: T) -> &mut StateStages { - self.stages - .entry(std::mem::discriminant(&state)) - .or_default() + pub fn on_pause_set(s: T) -> SystemSet { + SystemSet::new().with_run_criteria(Self::on_pause(s)) } -} -#[allow(clippy::mem_discriminant_non_enum)] -impl Stage for StateStage { - fn run(&mut self, world: &mut World) { - let current_stage = loop { - let (next_stage, current_stage) = { - let mut state = world - .get_resource_mut::>() - .expect("Missing state resource"); - let result = ( - state.next.as_ref().map(|next| std::mem::discriminant(next)), - std::mem::discriminant(&state.current), - ); - - state.apply_next(); - - result - }; - - // if next_stage is Some, we just applied a new state - if let Some(next_stage) = next_stage { - if next_stage != current_stage { - if let Some(current_state_stages) = self.stages.get_mut(¤t_stage) { - current_state_stages.exit.run(world); - } - } + pub fn on_resume_set(s: T) -> SystemSet { + SystemSet::new().with_run_criteria(Self::on_resume(s)) + } - if let Some(next_state_stages) = self.stages.get_mut(&next_stage) { - next_state_stages.enter.run(world); - } - } else { - break current_stage; - } - }; + /// Creates a driver set for the State. + /// + /// Important note: this set must be inserted **before** all other state-dependant sets to work properly! + pub fn make_driver() -> SystemSet { + SystemSet::default().with_run_criteria(state_cleaner::.system()) + } - if let Some(current_state_stages) = self.stages.get_mut(¤t_stage) { - current_state_stages.update.run(world); + pub fn new(initial: T) -> Self { + Self { + stack: vec![initial], + transition: Some(StateTransition::PreStartup), + scheduled: None, + end_next_loop: false, } } -} -#[derive(Debug, Error)] -pub enum StateError { - #[error("Attempted to change the state to the current state.")] - AlreadyInState, - #[error("Attempted to queue a state change, but there was already a state queued.")] - StateAlreadyQueued, -} -#[derive(Debug)] -pub struct State { - previous: Option, - current: T, - next: Option, -} + /// Schedule a state change that replaces the full stack with the given state. + /// This will fail if there is a scheduled operation, or if the given `state` matches the current state + pub fn set_next(&mut self, state: T) -> Result<(), StateError> { + if self.stack.last().unwrap() == &state { + return Err(StateError::AlreadyInState); + } -#[allow(clippy::mem_discriminant_non_enum)] -impl State { - pub fn new(state: T) -> Self { - Self { - current: state.clone(), - previous: None, - // add value to queue so that we "enter" the state - next: Some(state), + if self.scheduled.is_some() { + return Err(StateError::StateAlreadyQueued); } - } - pub fn current(&self) -> &T { - &self.current + self.scheduled = Some(ScheduledOperation::Next(state)); + Ok(()) } - pub fn previous(&self) -> Option<&T> { - self.previous.as_ref() - } + /// Same as [Self::set_next], but if there is already a next state, it will be overwritten instead of failing + pub fn overwrite_next(&mut self, state: T) -> Result<(), StateError> { + if self.stack.last().unwrap() == &state { + return Err(StateError::AlreadyInState); + } - pub fn next(&self) -> Option<&T> { - self.next.as_ref() + self.scheduled = Some(ScheduledOperation::Next(state)); + Ok(()) } - /// Queue a state change. This will fail if there is already a state in the queue, or if the - /// given `state` matches the current state - pub fn set_next(&mut self, state: T) -> Result<(), StateError> { - if std::mem::discriminant(&self.current) == std::mem::discriminant(&state) { + /// Same as [Self::set_next], but does a push operation instead of a next operation + pub fn set_push(&mut self, state: T) -> Result<(), StateError> { + if self.stack.last().unwrap() == &state { return Err(StateError::AlreadyInState); } - if self.next.is_some() { + if self.scheduled.is_some() { return Err(StateError::StateAlreadyQueued); } - self.next = Some(state); + self.scheduled = Some(ScheduledOperation::Push(state)); Ok(()) } - /// Same as [Self::set_next], but if there is already a next state, it will be overwritten - /// instead of failing - pub fn overwrite_next(&mut self, state: T) -> Result<(), StateError> { - if std::mem::discriminant(&self.current) == std::mem::discriminant(&state) { + /// Same as [Self::set_push], but if there is already a next state, it will be overwritten instead of failing + pub fn overwrite_push(&mut self, state: T) -> Result<(), StateError> { + if self.stack.last().unwrap() == &state { return Err(StateError::AlreadyInState); } - self.next = Some(state); + self.scheduled = Some(ScheduledOperation::Push(state)); + Ok(()) + } + + /// Same as [Self::set_next], but does a pop operation instead of a next operation + pub fn set_pop(&mut self) -> Result<(), StateError> { + if self.scheduled.is_some() { + return Err(StateError::StateAlreadyQueued); + } + + if self.stack.len() == 1 { + return Err(StateError::StackEmpty); + } + + self.scheduled = Some(ScheduledOperation::Pop); + Ok(()) + } + + /// Same as [Self::set_pop], but if there is already a next state, it will be overwritten instead of failing + pub fn overwrite_pop(&mut self) -> Result<(), StateError> { + if self.stack.len() == 1 { + return Err(StateError::StackEmpty); + } + self.scheduled = Some(ScheduledOperation::Pop); Ok(()) } - fn apply_next(&mut self) { - if let Some(next) = self.next.take() { - let previous = std::mem::replace(&mut self.current, next); - if std::mem::discriminant(&previous) != std::mem::discriminant(&self.current) { - self.previous = Some(previous) + pub fn current(&self) -> &T { + self.stack.last().unwrap() + } + + pub fn inactives(&self) -> &[T] { + &self.stack[0..self.stack.len() - 2] + } +} + +#[derive(Debug, Error)] +pub enum StateError { + #[error("Attempted to change the state to the current state.")] + AlreadyInState, + #[error("Attempted to queue a state change, but there was already a state queued.")] + StateAlreadyQueued, + #[error("Attempted to queue a pop, but there is nothing to pop.")] + StackEmpty, +} + +fn should_run_adapter( + In(cmp_result): In, + state: Res>, +) -> ShouldRun { + if state.end_next_loop { + return ShouldRun::No; + } + if cmp_result { + ShouldRun::YesAndCheckAgain + } else { + ShouldRun::NoAndCheckAgain + } +} + +fn state_cleaner( + mut state: ResMut>, + mut prep_exit: Local, +) -> ShouldRun { + if *prep_exit { + *prep_exit = false; + if state.scheduled.is_none() { + state.end_next_loop = true; + return ShouldRun::YesAndCheckAgain; + } + } else if state.end_next_loop { + state.end_next_loop = false; + return ShouldRun::No; + } + match state.scheduled.take() { + Some(ScheduledOperation::Next(next)) => { + if state.stack.len() <= 1 { + state.transition = Some(StateTransition::ExitingFull( + state.stack.last().unwrap().clone(), + next, + )); + } else { + state.scheduled = Some(ScheduledOperation::Next(next)); + match state.transition.take() { + Some(StateTransition::ExitingToResume(p, n)) => { + state.stack.pop(); + state.transition = Some(StateTransition::Resuming(p, n)); + } + _ => { + state.transition = Some(StateTransition::ExitingToResume( + state.stack[state.stack.len() - 1].clone(), + state.stack[state.stack.len() - 2].clone(), + )); + } + } } } + Some(ScheduledOperation::Push(next)) => { + let last_type_id = state.stack.last().unwrap().clone(); + state.transition = Some(StateTransition::Pausing(last_type_id, next)); + } + Some(ScheduledOperation::Pop) => { + state.transition = Some(StateTransition::ExitingToResume( + state.stack[state.stack.len() - 1].clone(), + state.stack[state.stack.len() - 2].clone(), + )); + } + None => match state.transition.take() { + Some(StateTransition::ExitingFull(p, n)) => { + state.transition = Some(StateTransition::Entering(p, n.clone())); + state.stack[0] = n; + } + Some(StateTransition::Pausing(p, n)) => { + state.transition = Some(StateTransition::Entering(p, n.clone())); + state.stack.push(n); + } + Some(StateTransition::ExitingToResume(p, n)) => { + state.stack.pop(); + state.transition = Some(StateTransition::Resuming(p, n)); + } + Some(StateTransition::PreStartup) => { + state.transition = Some(StateTransition::Startup); + } + _ => {} + }, + }; + if state.transition.is_none() { + *prep_exit = true; } + + ShouldRun::YesAndCheckAgain } -impl Deref for State { - type Target = T; +#[cfg(test)] +mod test { + use super::*; + use crate::prelude::*; + + #[derive(Copy, Clone, Eq, PartialEq, Debug)] + enum MyState { + S1, + S2, + S3, + S4, + S5, + S6, + Final, + } - fn deref(&self) -> &Self::Target { - &self.current + #[test] + fn state_test() { + let mut world = World::default(); + + world.insert_resource(Vec::<&'static str>::new()); + world.insert_resource(State::new(MyState::S1)); + + let mut stage = SystemStage::parallel(); + + stage.add_system_set(State::::make_driver()); + stage + .add_system_set( + State::on_enter_set(MyState::S1) + .with_system((|mut r: ResMut>| r.push("startup")).system()), + ) + .add_system_set( + State::on_update_set(MyState::S1).with_system( + (|mut r: ResMut>, mut s: ResMut>| { + r.push("update S1"); + s.overwrite_next(MyState::S2).unwrap(); + }) + .system(), + ), + ) + .add_system_set( + State::on_enter_set(MyState::S2) + .with_system((|mut r: ResMut>| r.push("enter S2")).system()), + ) + .add_system_set( + State::on_update_set(MyState::S2).with_system( + (|mut r: ResMut>, mut s: ResMut>| { + r.push("update S2"); + s.overwrite_next(MyState::S3).unwrap(); + }) + .system(), + ), + ) + .add_system_set( + State::on_exit_set(MyState::S2) + .with_system((|mut r: ResMut>| r.push("exit S2")).system()), + ) + .add_system_set( + State::on_enter_set(MyState::S3) + .with_system((|mut r: ResMut>| r.push("enter S3")).system()), + ) + .add_system_set( + State::on_update_set(MyState::S3).with_system( + (|mut r: ResMut>, mut s: ResMut>| { + r.push("update S3"); + s.overwrite_push(MyState::S4).unwrap(); + }) + .system(), + ), + ) + .add_system_set( + State::on_pause_set(MyState::S3) + .with_system((|mut r: ResMut>| r.push("pause S3")).system()), + ) + .add_system_set( + State::on_update_set(MyState::S4).with_system( + (|mut r: ResMut>, mut s: ResMut>| { + r.push("update S4"); + s.overwrite_push(MyState::S5).unwrap(); + }) + .system(), + ), + ) + .add_system_set( + State::on_inactive_update_set(MyState::S4).with_system( + (|mut r: ResMut>| r.push("inactive S4")) + .system() + .label("inactive s4"), + ), + ) + .add_system_set( + State::on_update_set(MyState::S5).with_system( + (|mut r: ResMut>, mut s: ResMut>| { + r.push("update S5"); + s.overwrite_push(MyState::S6).unwrap(); + }) + .system() + .after("inactive s4"), + ), + ) + .add_system_set( + State::on_inactive_update_set(MyState::S5).with_system( + (|mut r: ResMut>| r.push("inactive S5")) + .system() + .label("inactive s5") + .after("inactive s4"), + ), + ) + .add_system_set( + State::on_update_set(MyState::S6).with_system( + (|mut r: ResMut>, mut s: ResMut>| { + r.push("update S6"); + s.overwrite_push(MyState::Final).unwrap(); + }) + .system() + .after("inactive s5"), + ), + ) + .add_system_set( + State::on_resume_set(MyState::S4) + .with_system((|mut r: ResMut>| r.push("resume S4")).system()), + ) + .add_system_set( + State::on_exit_set(MyState::S5) + .with_system((|mut r: ResMut>| r.push("exit S4")).system()), + ); + + const EXPECTED: &[&str] = &[ + // + "startup", + "update S1", + // + "enter S2", + "update S2", + // + "exit S2", + "enter S3", + "update S3", + // + "pause S3", + "update S4", + // + "inactive S4", + "update S5", + // + "inactive S4", + "inactive S5", + "update S6", + // + "inactive S4", + "inactive S5", + ]; + + stage.run(&mut world); + let mut collected = world.get_resource_mut::>().unwrap(); + let mut count = 0; + for (found, expected) in collected.drain(..).zip(EXPECTED) { + assert_eq!(found, *expected); + count += 1; + } + // If not equal, some elements weren't executed + assert_eq!(EXPECTED.len(), count); + assert_eq!( + world.get_resource::>().unwrap().current(), + &MyState::Final + ); } } diff --git a/crates/bevy_ecs/src/schedule/system_set.rs b/crates/bevy_ecs/src/schedule/system_set.rs index 1b1f5827d3aac..cd46bfe01d5d2 100644 --- a/crates/bevy_ecs/src/schedule/system_set.rs +++ b/crates/bevy_ecs/src/schedule/system_set.rs @@ -1,5 +1,6 @@ use crate::{ - schedule::{RunCriteria, ShouldRun, SystemDescriptor}, + component::Component, + schedule::{RunCriteria, ShouldRun, State, SystemDescriptor}, system::System, }; @@ -45,4 +46,28 @@ impl SystemSet { self.descriptors.push(system.into()); self } + + pub fn on_update(s: T) -> SystemSet { + Self::new().with_run_criteria(State::::on_update(s)) + } + + pub fn on_inactive_update(s: T) -> SystemSet { + Self::new().with_run_criteria(State::::on_inactive_update(s)) + } + + pub fn on_enter(s: T) -> SystemSet { + Self::new().with_run_criteria(State::::on_enter(s)) + } + + pub fn on_exit(s: T) -> SystemSet { + Self::new().with_run_criteria(State::::on_exit(s)) + } + + pub fn on_pause(s: T) -> SystemSet { + Self::new().with_run_criteria(State::::on_pause(s)) + } + + pub fn on_resume(s: T) -> SystemSet { + Self::new().with_run_criteria(State::::on_resume(s)) + } } diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index b5460f2aa8090..94515a6768c40 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -6,18 +6,14 @@ fn main() { App::build() .init_resource::() .add_plugins(DefaultPlugins) - .insert_resource(State::new(AppState::Setup)) - .add_stage_after(CoreStage::Update, Stage, StateStage::::default()) - .on_state_enter(Stage, AppState::Setup, load_textures.system()) - .on_state_update(Stage, AppState::Setup, check_textures.system()) - .on_state_enter(Stage, AppState::Finished, setup.system()) + .add_state(AppState::Setup) + .add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures.system())) + .add_system_set(SystemSet::on_update(AppState::Setup).with_system(check_textures.system())) + .add_system_set(SystemSet::on_enter(AppState::Finished).with_system(setup.system())) .run(); } -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -struct Stage; - -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] enum AppState { Setup, Finished, diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index 681cf585e5f56..dfa3365a32a2a 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -6,21 +6,20 @@ fn main() { App::build() .add_plugins(DefaultPlugins) .init_resource::() - .insert_resource(State::new(AppState::Menu)) - .add_stage_after(CoreStage::Update, Stage, StateStage::::default()) - .on_state_enter(Stage, AppState::Menu, setup_menu.system()) - .on_state_update(Stage, AppState::Menu, menu.system()) - .on_state_exit(Stage, AppState::Menu, cleanup_menu.system()) - .on_state_enter(Stage, AppState::InGame, setup_game.system()) - .on_state_update(Stage, AppState::InGame, movement.system()) - .on_state_update(Stage, AppState::InGame, change_color.system()) + .add_state(AppState::Menu) + .add_system_set(SystemSet::on_enter(AppState::Menu).with_system(setup_menu.system())) + .add_system_set(SystemSet::on_update(AppState::Menu).with_system(menu.system())) + .add_system_set(SystemSet::on_exit(AppState::Menu).with_system(cleanup_menu.system())) + .add_system_set(SystemSet::on_enter(AppState::InGame).with_system(setup_game.system())) + .add_system_set( + SystemSet::on_update(AppState::InGame) + .with_system(movement.system()) + .with_system(change_color.system()), + ) .run(); } -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -struct Stage; - -#[derive(Clone)] +#[derive(Clone, Eq, PartialEq)] enum AppState { Menu, InGame, diff --git a/examples/game/alien_cake_addict.rs b/examples/game/alien_cake_addict.rs index 0d4f60f0f68de..dc7213bbdce42 100644 --- a/examples/game/alien_cake_addict.rs +++ b/examples/game/alien_cake_addict.rs @@ -1,17 +1,12 @@ use bevy::{ core::FixedTimestep, + ecs::schedule::SystemSet, prelude::*, render::{camera::Camera, render_graph::base::camera::CAMERA_3D}, }; use rand::Rng; -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -enum GameStage { - InGame, - BonusUpdate, -} - -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, Eq, PartialEq, Debug)] enum GameState { Playing, GameOver, @@ -22,38 +17,26 @@ fn main() { .insert_resource(Msaa { samples: 4 }) .init_resource::() .add_plugins(DefaultPlugins) - .insert_resource(State::new(GameState::Playing)) + .add_state(GameState::Playing) .add_startup_system(setup_cameras.system()) - .add_stage_after( - CoreStage::Update, - GameStage::InGame, - StateStage::::default(), - ) - .on_state_enter(GameStage::InGame, GameState::Playing, setup.system()) - .on_state_update(GameStage::InGame, GameState::Playing, move_player.system()) - .on_state_update(GameStage::InGame, GameState::Playing, focus_camera.system()) - .on_state_update(GameStage::InGame, GameState::Playing, rotate_bonus.system()) - .on_state_update( - GameStage::InGame, - GameState::Playing, - scoreboard_system.system(), + .add_system_set(SystemSet::on_enter(GameState::Playing).with_system(setup.system())) + .add_system_set( + SystemSet::on_update(GameState::Playing) + .with_system(move_player.system()) + .with_system(focus_camera.system()) + .with_system(rotate_bonus.system()) + .with_system(scoreboard_system.system()), ) - .on_state_exit(GameStage::InGame, GameState::Playing, teardown.system()) - .on_state_enter( - GameStage::InGame, - GameState::GameOver, - display_score.system(), + .add_system_set(SystemSet::on_exit(GameState::Playing).with_system(teardown.system())) + .add_system_set( + SystemSet::on_enter(GameState::GameOver).with_system(display_score.system()), ) - .on_state_update( - GameStage::InGame, - GameState::GameOver, - gameover_keyboard.system(), + .add_system_set( + SystemSet::on_update(GameState::GameOver).with_system(gameover_keyboard.system()), ) - .on_state_exit(GameStage::InGame, GameState::GameOver, teardown.system()) - .add_stage_after( - CoreStage::Update, - GameStage::BonusUpdate, - SystemStage::parallel() + .add_system_set(SystemSet::on_exit(GameState::GameOver).with_system(teardown.system())) + .add_system_set( + SystemSet::new() .with_run_criteria(FixedTimestep::step(5.0)) .with_system(spawn_bonus.system()), ) diff --git a/examples/window/multiple_windows.rs b/examples/window/multiple_windows.rs index 57c60265aa914..c291d8f8f6988 100644 --- a/examples/window/multiple_windows.rs +++ b/examples/window/multiple_windows.rs @@ -16,11 +16,14 @@ use bevy::{ fn main() { App::build() .insert_resource(Msaa { samples: 4 }) - .insert_resource(State::new(AppState::CreateWindow)) + .add_state(AppState::CreateWindow) .add_plugins(DefaultPlugins) - .add_stage_after(CoreStage::Update, Stage, StateStage::::default()) - .on_state_update(Stage, AppState::CreateWindow, setup_window.system()) - .on_state_enter(Stage, AppState::Setup, setup_pipeline.system()) + .add_system_set( + SystemSet::on_update(AppState::CreateWindow).with_system(setup_window.system()), + ) + .add_system_set( + SystemSet::on_enter(AppState::CreateWindow).with_system(setup_pipeline.system()), + ) .run(); } @@ -29,7 +32,7 @@ pub struct Stage; // NOTE: this "state based" approach to multiple windows is a short term workaround. // Future Bevy releases shouldn't require such a strict order of operations. -#[derive(Clone)] +#[derive(Clone, Eq, PartialEq)] enum AppState { CreateWindow, Setup,