add state swapping support
This commit is contained in:
parent
af09c61796
commit
ee9474b231
|
@ -29,6 +29,7 @@ pub enum State {
|
||||||
|
|
||||||
pub enum StateChange<ContextType> {
|
pub enum StateChange<ContextType> {
|
||||||
Push(Box<dyn GameState<ContextType>>),
|
Push(Box<dyn GameState<ContextType>>),
|
||||||
|
Swap(Box<dyn GameState<ContextType>>),
|
||||||
Pop,
|
Pop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +71,10 @@ impl<ContextType> StateContainer<ContextType> {
|
||||||
self.current_state
|
self.current_state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_pending_state_change(&self) -> bool {
|
||||||
|
self.pending_state_change.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn pending_state_change(&mut self) -> Option<State> {
|
pub fn pending_state_change(&mut self) -> Option<State> {
|
||||||
self.pending_state_change.take()
|
self.pending_state_change.take()
|
||||||
|
@ -192,10 +197,23 @@ impl<ContextType> States<ContextType> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn swap_boxed_state(&mut self, boxed_state: Box<dyn GameState<ContextType>>) -> Result<(), StateError> {
|
||||||
|
if !self.can_push_or_pop() {
|
||||||
|
Err(StateError::HasPendingStateChange)
|
||||||
|
} else {
|
||||||
|
self.command = Some(StateChange::Swap(boxed_state));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, state: impl GameState<ContextType> + 'static) -> Result<(), StateError> {
|
pub fn push(&mut self, state: impl GameState<ContextType> + 'static) -> Result<(), StateError> {
|
||||||
self.push_boxed_state(Box::new(state))
|
self.push_boxed_state(Box::new(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn swap(&mut self, state: impl GameState<ContextType> + 'static) -> Result<(), StateError> {
|
||||||
|
self.swap_boxed_state(Box::new(state))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pop(&mut self) -> Result<(), StateError> {
|
pub fn pop(&mut self) -> Result<(), StateError> {
|
||||||
if !self.can_push_or_pop() {
|
if !self.can_push_or_pop() {
|
||||||
Err(StateError::HasPendingStateChange)
|
Err(StateError::HasPendingStateChange)
|
||||||
|
@ -222,12 +240,19 @@ impl<ContextType> States<ContextType> {
|
||||||
match command {
|
match command {
|
||||||
StateChange::Push(new_state) => {
|
StateChange::Push(new_state) => {
|
||||||
self.pending_state = Some(new_state);
|
self.pending_state = Some(new_state);
|
||||||
}
|
},
|
||||||
StateChange::Pop => {
|
StateChange::Pop => {
|
||||||
if let Some(state) = self.states.front_mut() {
|
if let Some(state) = self.states.front_mut() {
|
||||||
state.pending_transition_out(TransitionTo::Dead);
|
state.pending_transition_out(TransitionTo::Dead);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
StateChange::Swap(new_state) => {
|
||||||
|
// swap is basically pop+push combined together in one step
|
||||||
|
if let Some(state) = self.states.front_mut() {
|
||||||
|
state.pending_transition_out(TransitionTo::Dead);
|
||||||
|
}
|
||||||
|
self.pending_state = Some(new_state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +267,11 @@ impl<ContextType> States<ContextType> {
|
||||||
// if the current state is active and there is a pending state waiting to be added,
|
// if the current state is active and there is a pending state waiting to be added,
|
||||||
// we need to start transitioning out the active state towards a 'paused' state
|
// we need to start transitioning out the active state towards a 'paused' state
|
||||||
let state = self.states.front_mut().unwrap();
|
let state = self.states.front_mut().unwrap();
|
||||||
state.pending_transition_out(TransitionTo::Paused);
|
// if this state is being swapped out for another, it will already have a
|
||||||
|
// pending state change to TransitionOut(Dead) here ...
|
||||||
|
if !state.has_pending_state_change() {
|
||||||
|
state.pending_transition_out(TransitionTo::Paused);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,6 +369,7 @@ impl<ContextType> States<ContextType> {
|
||||||
if let Some(state_change) = state.state().update(current_state, context) {
|
if let Some(state_change) = state.state().update(current_state, context) {
|
||||||
match state_change {
|
match state_change {
|
||||||
StateChange::Push(state) => self.push_boxed_state(state)?,
|
StateChange::Push(state) => self.push_boxed_state(state)?,
|
||||||
|
StateChange::Swap(state) => self.swap_boxed_state(state)?,
|
||||||
StateChange::Pop => self.pop()?,
|
StateChange::Pop => self.pop()?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -967,6 +997,109 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swap_states() -> Result<(), StateError> {
|
||||||
|
use LogEntry::*;
|
||||||
|
use State::*;
|
||||||
|
|
||||||
|
const FIRST: u32 = 1;
|
||||||
|
const SECOND: u32 = 2;
|
||||||
|
|
||||||
|
let mut states = States::<TestContext>::new();
|
||||||
|
let mut context = TestContext::new();
|
||||||
|
|
||||||
|
// push first state
|
||||||
|
states.push(TestState::new(FIRST))?;
|
||||||
|
assert_eq!(context.take_log(), vec![]);
|
||||||
|
|
||||||
|
// first state will transition in
|
||||||
|
tick(&mut states, &mut context)?;
|
||||||
|
assert_eq!(
|
||||||
|
context.take_log(),
|
||||||
|
vec![
|
||||||
|
StateChange(FIRST, Pending, Dead),
|
||||||
|
StateChange(FIRST, TransitionIn, Pending),
|
||||||
|
Transition(FIRST, TransitionIn),
|
||||||
|
Update(FIRST, TransitionIn),
|
||||||
|
Render(FIRST, TransitionIn)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
// first state finished transitioning in, now moves to active
|
||||||
|
tick(&mut states, &mut context)?;
|
||||||
|
assert_eq!(
|
||||||
|
context.take_log(),
|
||||||
|
vec![
|
||||||
|
StateChange(FIRST, Active, TransitionIn),
|
||||||
|
Update(FIRST, Active),
|
||||||
|
Render(FIRST, Active)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// swap in second state
|
||||||
|
states.swap(TestState::new(SECOND))?;
|
||||||
|
assert_eq!(context.take_log(), vec![]);
|
||||||
|
|
||||||
|
// first state begins to transition out to 'dead'
|
||||||
|
tick(&mut states, &mut context)?;
|
||||||
|
assert_eq!(
|
||||||
|
context.take_log(),
|
||||||
|
vec![
|
||||||
|
StateChange(FIRST, TransitionOut(TransitionTo::Dead), Active),
|
||||||
|
Transition(FIRST, TransitionOut(TransitionTo::Dead)),
|
||||||
|
Update(FIRST, TransitionOut(TransitionTo::Dead)),
|
||||||
|
Render(FIRST, TransitionOut(TransitionTo::Dead))
|
||||||
|
]
|
||||||
|
);
|
||||||
|
// first state finished transitioning out, now dies.
|
||||||
|
// second state starts up, will transition in
|
||||||
|
tick(&mut states, &mut context)?;
|
||||||
|
assert_eq!(
|
||||||
|
context.take_log(),
|
||||||
|
vec![
|
||||||
|
StateChange(FIRST, Dead, TransitionOut(TransitionTo::Dead)),
|
||||||
|
StateChange(SECOND, Pending, Dead),
|
||||||
|
StateChange(SECOND, TransitionIn, Pending),
|
||||||
|
Transition(SECOND, TransitionIn),
|
||||||
|
Update(SECOND, TransitionIn),
|
||||||
|
Render(SECOND, TransitionIn)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
// second state finished transitioning in, now moves to active
|
||||||
|
tick(&mut states, &mut context)?;
|
||||||
|
assert_eq!(
|
||||||
|
context.take_log(),
|
||||||
|
vec![
|
||||||
|
StateChange(SECOND, Active, TransitionIn),
|
||||||
|
Update(SECOND, Active),
|
||||||
|
Render(SECOND, Active)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
states.pop()?;
|
||||||
|
assert_eq!(context.take_log(), vec![]);
|
||||||
|
|
||||||
|
// state begins to transition out to 'dead'
|
||||||
|
tick(&mut states, &mut context)?;
|
||||||
|
assert_eq!(
|
||||||
|
context.take_log(),
|
||||||
|
vec![
|
||||||
|
StateChange(SECOND, TransitionOut(TransitionTo::Dead), Active),
|
||||||
|
Transition(SECOND, TransitionOut(TransitionTo::Dead)),
|
||||||
|
Update(SECOND, TransitionOut(TransitionTo::Dead)),
|
||||||
|
Render(SECOND, TransitionOut(TransitionTo::Dead))
|
||||||
|
]
|
||||||
|
);
|
||||||
|
// state finished transitioning out, now dies
|
||||||
|
tick(&mut states, &mut context)?;
|
||||||
|
assert_eq!(context.take_log(), vec![StateChange(SECOND, Dead, TransitionOut(TransitionTo::Dead))]);
|
||||||
|
|
||||||
|
// nothing! no states anymore!
|
||||||
|
tick(&mut states, &mut context)?;
|
||||||
|
assert_eq!(context.take_log(), vec![]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
struct SelfPushPopState {
|
struct SelfPushPopState {
|
||||||
id: u32,
|
id: u32,
|
||||||
counter: u32,
|
counter: u32,
|
||||||
|
|
Loading…
Reference in a new issue