Core/StateMachine: Add Disable and Enable methods
This commit is contained in:
parent
a06769ab2c
commit
3d18052e45
|
|
@ -18,18 +18,22 @@ namespace Nz
|
||||||
class StateMachine
|
class StateMachine
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
inline StateMachine(std::shared_ptr<State> originalState);
|
inline StateMachine(std::shared_ptr<State> initialState = {});
|
||||||
StateMachine(const StateMachine&) = delete;
|
StateMachine(const StateMachine&) = delete;
|
||||||
StateMachine(StateMachine&&) = default;
|
StateMachine(StateMachine&&) = default;
|
||||||
inline ~StateMachine();
|
inline ~StateMachine();
|
||||||
|
|
||||||
inline void ChangeState(std::shared_ptr<State> state);
|
inline void ChangeState(std::shared_ptr<State> state);
|
||||||
|
|
||||||
|
inline void Disable(std::shared_ptr<State> state);
|
||||||
|
inline void Enable(std::shared_ptr<State> state);
|
||||||
|
|
||||||
|
inline bool IsStateEnabled(const State* state) const;
|
||||||
inline bool IsTopState(const State* state) const;
|
inline bool IsTopState(const State* state) const;
|
||||||
|
|
||||||
inline void PopState();
|
inline void PopState();
|
||||||
inline void PopStatesUntil(std::shared_ptr<State> state);
|
inline void PopStatesUntil(std::shared_ptr<State> state);
|
||||||
inline void PushState(std::shared_ptr<State> state);
|
inline void PushState(std::shared_ptr<State> state, bool enabled = true);
|
||||||
|
|
||||||
inline void ResetState(std::shared_ptr<State> state);
|
inline void ResetState(std::shared_ptr<State> state);
|
||||||
|
|
||||||
|
|
@ -41,18 +45,27 @@ namespace Nz
|
||||||
private:
|
private:
|
||||||
enum class TransitionType
|
enum class TransitionType
|
||||||
{
|
{
|
||||||
|
Disable,
|
||||||
|
Enable,
|
||||||
Pop,
|
Pop,
|
||||||
PopUntil,
|
PopUntil,
|
||||||
Push,
|
Push,
|
||||||
|
PushDisabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StateInfo
|
||||||
|
{
|
||||||
|
std::shared_ptr<State> state;
|
||||||
|
bool enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StateTransition
|
struct StateTransition
|
||||||
{
|
{
|
||||||
TransitionType type;
|
|
||||||
std::shared_ptr<State> state;
|
std::shared_ptr<State> state;
|
||||||
|
TransitionType type;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::shared_ptr<State>> m_states;
|
std::vector<StateInfo> m_states;
|
||||||
std::vector<StateTransition> m_transitions;
|
std::vector<StateTransition> m_transitions;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ namespace Nz
|
||||||
*
|
*
|
||||||
* \param originalState State which is the entry point of the application, a nullptr will create an empty state machine
|
* \param originalState State which is the entry point of the application, a nullptr will create an empty state machine
|
||||||
*/
|
*/
|
||||||
inline StateMachine::StateMachine(std::shared_ptr<State> originalState)
|
inline StateMachine::StateMachine(std::shared_ptr<State> initialState)
|
||||||
{
|
{
|
||||||
if (originalState)
|
if (initialState)
|
||||||
PushState(std::move(originalState));
|
PushState(std::move(initialState));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -33,8 +33,11 @@ namespace Nz
|
||||||
inline StateMachine::~StateMachine()
|
inline StateMachine::~StateMachine()
|
||||||
{
|
{
|
||||||
// Leave state from top to bottom (as if states were popped out)
|
// Leave state from top to bottom (as if states were popped out)
|
||||||
for (auto it = m_states.rbegin(); it != m_states.rend(); ++it)
|
for (auto rit = m_states.rbegin(); rit != m_states.rend(); ++rit)
|
||||||
(*it)->Leave(*this);
|
{
|
||||||
|
if (rit->enabled)
|
||||||
|
rit->state->Leave(*this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -47,24 +50,70 @@ namespace Nz
|
||||||
*/
|
*/
|
||||||
inline void StateMachine::ChangeState(std::shared_ptr<State> state)
|
inline void StateMachine::ChangeState(std::shared_ptr<State> state)
|
||||||
{
|
{
|
||||||
|
// Change state is just a pop followed by a push
|
||||||
|
StateTransition& popTransition = m_transitions.emplace_back();
|
||||||
|
popTransition.type = TransitionType::Pop;
|
||||||
|
|
||||||
if (state)
|
if (state)
|
||||||
{
|
{
|
||||||
// Change state is just a pop followed by a push
|
StateTransition& pushTransition = m_transitions.emplace_back();
|
||||||
StateTransition transition;
|
pushTransition.state = std::move(state);
|
||||||
transition.type = TransitionType::Pop;
|
pushTransition.type = TransitionType::Push;
|
||||||
m_transitions.emplace_back(std::move(transition));
|
|
||||||
|
|
||||||
transition.state = std::move(state);
|
|
||||||
transition.type = TransitionType::Push;
|
|
||||||
m_transitions.emplace_back(std::move(transition));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Disables a state, calling its Leave method and no longer Updating it
|
||||||
|
*
|
||||||
|
* \param state State to disable
|
||||||
|
*
|
||||||
|
* \remark Does nothing if the state is already disabled
|
||||||
|
* \remark Like all actions popping or pushing a state, this is not immediate and will only take effect when state machine is updated
|
||||||
|
*/
|
||||||
|
inline void StateMachine::Disable(std::shared_ptr<State> state)
|
||||||
|
{
|
||||||
|
StateTransition& disableTransition = m_transitions.emplace_back();
|
||||||
|
disableTransition.state = std::move(state);
|
||||||
|
disableTransition.type = TransitionType::Disable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Enables a state, calling its Enter method and updating it
|
||||||
|
*
|
||||||
|
* \param state State to enable
|
||||||
|
*
|
||||||
|
* \remark Does nothing if the state is already enabled
|
||||||
|
* \remark Like all actions popping or pushing a state, this is not immediate and will only take effect when state machine is updated
|
||||||
|
*/
|
||||||
|
inline void StateMachine::Enable(std::shared_ptr<State> state)
|
||||||
|
{
|
||||||
|
StateTransition& disableTransition = m_transitions.emplace_back();
|
||||||
|
disableTransition.state = std::move(state);
|
||||||
|
disableTransition.type = TransitionType::Enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Checks whether the state is enabled on this state machine
|
||||||
|
* \return true If it is the case
|
||||||
|
*
|
||||||
|
* \param state State to compare the top with
|
||||||
|
*
|
||||||
|
* \remark Because all actions popping or pushing a state don't take effect until next state machine update, this can return false on a just enabled state
|
||||||
|
*/
|
||||||
|
inline bool StateMachine::IsStateEnabled(const State* state) const
|
||||||
|
{
|
||||||
|
auto it = std::find_if(m_states.begin(), m_states.end(), [&](const StateInfo& stateInfo) { return stateInfo.state.get() == state; });
|
||||||
|
assert(it != m_states.end());
|
||||||
|
|
||||||
|
return it->enabled;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Checks whether the state is on the top of the machine
|
* \brief Checks whether the state is on the top of the machine
|
||||||
* \return true If it is the case
|
* \return true If it is the case
|
||||||
*
|
*
|
||||||
* \param state State to compare the top with
|
* \param state State to compare the top with
|
||||||
|
*
|
||||||
* \remark Because all actions popping or pushing a state don't take effect until next state machine update, this can return false on a just pushed state
|
* \remark Because all actions popping or pushing a state don't take effect until next state machine update, this can return false on a just pushed state
|
||||||
*/
|
*/
|
||||||
inline bool StateMachine::IsTopState(const State* state) const
|
inline bool StateMachine::IsTopState(const State* state) const
|
||||||
|
|
@ -72,7 +121,7 @@ namespace Nz
|
||||||
if (m_states.empty())
|
if (m_states.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return m_states.back().get() == state;
|
return m_states.back().state.get() == state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -83,10 +132,8 @@ namespace Nz
|
||||||
*/
|
*/
|
||||||
inline void StateMachine::PopState()
|
inline void StateMachine::PopState()
|
||||||
{
|
{
|
||||||
StateTransition transition;
|
StateTransition& transition = m_transitions.emplace_back();
|
||||||
transition.type = TransitionType::Pop;
|
transition.type = TransitionType::Pop;
|
||||||
|
|
||||||
m_transitions.emplace_back(std::move(transition));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
@ -101,11 +148,9 @@ namespace Nz
|
||||||
{
|
{
|
||||||
if (state)
|
if (state)
|
||||||
{
|
{
|
||||||
StateTransition transition;
|
StateTransition& transition = m_transitions.emplace_back();
|
||||||
transition.state = std::move(state);
|
transition.state = std::move(state);
|
||||||
transition.type = TransitionType::PopUntil;
|
transition.type = TransitionType::PopUntil;
|
||||||
|
|
||||||
m_transitions.emplace_back(std::move(transition));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,19 +158,18 @@ namespace Nz
|
||||||
* \brief Pushes a new state on the top of the machine
|
* \brief Pushes a new state on the top of the machine
|
||||||
*
|
*
|
||||||
* \param state Next state to represent if it is nullptr, it performs no action
|
* \param state Next state to represent if it is nullptr, it performs no action
|
||||||
|
* \param enable If the state should be enabled right after pushing it
|
||||||
*
|
*
|
||||||
* \remark It is forbidden for a state machine to have (at any moment) the same state in its list multiple times
|
* \remark It is forbidden for a state machine to have (at any moment) the same state in its list multiple times
|
||||||
* \remark Like all actions popping or pushing a state, this is not immediate and will only take effect when state machine is updated
|
* \remark Like all actions popping or pushing a state, this is not immediate and will only take effect when state machine is updated
|
||||||
*/
|
*/
|
||||||
inline void StateMachine::PushState(std::shared_ptr<State> state)
|
inline void StateMachine::PushState(std::shared_ptr<State> state, bool enabled)
|
||||||
{
|
{
|
||||||
if (state)
|
if (state)
|
||||||
{
|
{
|
||||||
StateTransition transition;
|
StateTransition& transition = m_transitions.emplace_back();
|
||||||
transition.state = std::move(state);
|
transition.state = std::move(state);
|
||||||
transition.type = TransitionType::Push;
|
transition.type = (enabled) ? TransitionType::Push : TransitionType::PushDisabled;
|
||||||
|
|
||||||
m_transitions.emplace_back(std::move(transition));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,15 +183,14 @@ namespace Nz
|
||||||
*/
|
*/
|
||||||
inline void StateMachine::ResetState(std::shared_ptr<State> state)
|
inline void StateMachine::ResetState(std::shared_ptr<State> state)
|
||||||
{
|
{
|
||||||
StateTransition transition;
|
StateTransition& transition = m_transitions.emplace_back();
|
||||||
transition.type = TransitionType::PopUntil; //< Pop until nullptr, which basically clears the state machine
|
transition.type = TransitionType::PopUntil; //< Pop until nullptr, which basically clears the state machine
|
||||||
m_transitions.emplace_back(std::move(transition));
|
|
||||||
|
|
||||||
if (state)
|
if (state)
|
||||||
{
|
{
|
||||||
transition.state = std::move(state);
|
StateTransition& pushTransition = m_transitions.emplace_back();
|
||||||
transition.type = TransitionType::Push;
|
pushTransition.state = std::move(state);
|
||||||
m_transitions.emplace_back(std::move(transition));
|
pushTransition.type = TransitionType::Push;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,10 +211,39 @@ namespace Nz
|
||||||
|
|
||||||
switch (transition.type)
|
switch (transition.type)
|
||||||
{
|
{
|
||||||
|
case TransitionType::Disable:
|
||||||
|
{
|
||||||
|
auto it = std::find_if(m_states.begin(), m_states.end(), [&](const StateInfo& stateInfo) { return stateInfo.state == transition.state; });
|
||||||
|
assert(it != m_states.end());
|
||||||
|
|
||||||
|
if (it->enabled)
|
||||||
|
{
|
||||||
|
it->state->Leave(*this);
|
||||||
|
it->enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TransitionType::Enable:
|
||||||
|
{
|
||||||
|
auto it = std::find_if(m_states.begin(), m_states.end(), [&](const StateInfo& stateInfo) { return stateInfo.state == transition.state; });
|
||||||
|
assert(it != m_states.end());
|
||||||
|
|
||||||
|
if (!it->enabled)
|
||||||
|
{
|
||||||
|
it->enabled = true;
|
||||||
|
it->state->Enter(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case TransitionType::Pop:
|
case TransitionType::Pop:
|
||||||
{
|
{
|
||||||
std::shared_ptr<State>& topState = m_states.back();
|
StateInfo& topState = m_states.back();
|
||||||
topState->Leave(*this); //< Call leave before popping to ensure consistent IsTopState behavior
|
if (topState.enabled)
|
||||||
|
topState.state->Leave(*this); //< Call leave before popping to ensure consistent IsTopState behavior
|
||||||
|
|
||||||
m_states.pop_back();
|
m_states.pop_back();
|
||||||
break;
|
break;
|
||||||
|
|
@ -179,9 +251,12 @@ namespace Nz
|
||||||
|
|
||||||
case TransitionType::PopUntil:
|
case TransitionType::PopUntil:
|
||||||
{
|
{
|
||||||
while (!m_states.empty() && m_states.back() != transition.state)
|
while (!m_states.empty() && m_states.back().state != transition.state)
|
||||||
{
|
{
|
||||||
m_states.back()->Leave(*this);
|
StateInfo& topState = m_states.back();
|
||||||
|
if (topState.enabled)
|
||||||
|
topState.state->Leave(*this);
|
||||||
|
|
||||||
m_states.pop_back();
|
m_states.pop_back();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -189,17 +264,36 @@ namespace Nz
|
||||||
|
|
||||||
case TransitionType::Push:
|
case TransitionType::Push:
|
||||||
{
|
{
|
||||||
m_states.emplace_back(std::move(transition.state));
|
StateInfo& stateInfo = m_states.emplace_back();
|
||||||
m_states.back()->Enter(*this);
|
stateInfo.enabled = true;
|
||||||
|
stateInfo.state = std::move(transition.state);
|
||||||
|
stateInfo.state->Enter(*this);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TransitionType::PushDisabled:
|
||||||
|
{
|
||||||
|
StateInfo& stateInfo = m_states.emplace_back();
|
||||||
|
stateInfo.enabled = false;
|
||||||
|
stateInfo.state = std::move(transition.state);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_transitions.clear();
|
m_transitions.clear();
|
||||||
|
|
||||||
return std::all_of(m_states.begin(), m_states.end(), [=](std::shared_ptr<State>& state) {
|
for (StateInfo& stateInfo : m_states)
|
||||||
return state->Update(*this, elapsedTime);
|
{
|
||||||
});
|
if (!stateInfo.enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!stateInfo.state->Update(*this, elapsedTime))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue