997 lines
34 KiB
C++
997 lines
34 KiB
C++
/******************************************************************************
|
|
The MIT License(MIT)
|
|
|
|
Embedded Template Library.
|
|
https://github.com/ETLCPP/etl
|
|
https://www.etlcpp.com
|
|
|
|
Copyright(c) 2017 John Wellbelove
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files(the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions :
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
******************************************************************************/
|
|
|
|
#ifndef ETL_FSM_INCLUDED
|
|
#define ETL_FSM_INCLUDED
|
|
|
|
#include "platform.h"
|
|
#include "array.h"
|
|
#include "error_handler.h"
|
|
#include "exception.h"
|
|
#include "integral_limits.h"
|
|
#include "largest.h"
|
|
#include "message_router.h"
|
|
#include "nullptr.h"
|
|
#include "user_type.h"
|
|
#if ETL_USING_CPP11
|
|
#include "tuple.h"
|
|
#include "type_list.h"
|
|
#endif
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "private/minmax_push.h"
|
|
|
|
namespace etl
|
|
{
|
|
class fsm;
|
|
class hfsm;
|
|
|
|
/// Allow alternative type for state id.
|
|
#if !defined(ETL_FSM_STATE_ID_TYPE)
|
|
typedef uint_least8_t fsm_state_id_t;
|
|
#else
|
|
typedef ETL_FSM_STATE_ID_TYPE fsm_state_id_t;
|
|
#endif
|
|
|
|
// For internal FSM use.
|
|
typedef typename etl::larger_type<etl::message_id_t>::type fsm_internal_id_t;
|
|
|
|
#if ETL_USING_CPP11 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++11 and above
|
|
template <typename, typename, etl::fsm_state_id_t, typename...>
|
|
class fsm_state;
|
|
#else
|
|
#include "private/fsm_fwd_decl_cpp03.h"
|
|
#endif
|
|
|
|
//***************************************************************************
|
|
/// Base exception class for FSM.
|
|
//***************************************************************************
|
|
class fsm_exception : public etl::exception
|
|
{
|
|
public:
|
|
|
|
fsm_exception(string_type reason_, string_type file_name_, numeric_type line_number_)
|
|
: etl::exception(reason_, file_name_, line_number_)
|
|
{
|
|
}
|
|
};
|
|
|
|
//***************************************************************************
|
|
/// Exception for null state pointer.
|
|
//***************************************************************************
|
|
class fsm_null_state_exception : public etl::fsm_exception
|
|
{
|
|
public:
|
|
|
|
fsm_null_state_exception(string_type file_name_, numeric_type line_number_)
|
|
: etl::fsm_exception(ETL_ERROR_TEXT("fsm:null state", ETL_FSM_FILE_ID"A"), file_name_, line_number_)
|
|
{
|
|
}
|
|
};
|
|
|
|
//***************************************************************************
|
|
/// Exception for invalid state id.
|
|
//***************************************************************************
|
|
class fsm_state_id_exception : public etl::fsm_exception
|
|
{
|
|
public:
|
|
|
|
fsm_state_id_exception(string_type file_name_, numeric_type line_number_)
|
|
: etl::fsm_exception(ETL_ERROR_TEXT("fsm:state id", ETL_FSM_FILE_ID"B"), file_name_, line_number_)
|
|
{
|
|
}
|
|
};
|
|
|
|
//***************************************************************************
|
|
/// Exception for incompatible state list.
|
|
//***************************************************************************
|
|
class fsm_state_list_exception : public etl::fsm_exception
|
|
{
|
|
public:
|
|
|
|
fsm_state_list_exception(string_type file_name_, numeric_type line_number_)
|
|
: etl::fsm_exception(ETL_ERROR_TEXT("fsm:state list", ETL_FSM_FILE_ID"C"), file_name_, line_number_)
|
|
{
|
|
}
|
|
};
|
|
|
|
//***************************************************************************
|
|
/// Exception for incompatible order state list.
|
|
//***************************************************************************
|
|
class fsm_state_list_order_exception : public etl::fsm_exception
|
|
{
|
|
public:
|
|
|
|
fsm_state_list_order_exception(string_type file_name_, numeric_type line_number_)
|
|
: etl::fsm_exception(ETL_ERROR_TEXT("fsm:state list order", ETL_FSM_FILE_ID"D"), file_name_, line_number_)
|
|
{
|
|
}
|
|
};
|
|
|
|
//***************************************************************************
|
|
/// Exception for message received but not started.
|
|
//***************************************************************************
|
|
class fsm_not_started : public etl::fsm_exception
|
|
{
|
|
public:
|
|
|
|
fsm_not_started(string_type file_name_, numeric_type line_number_)
|
|
: etl::fsm_exception(ETL_ERROR_TEXT("fsm:not started", ETL_FSM_FILE_ID"F"), file_name_, line_number_)
|
|
{
|
|
}
|
|
};
|
|
|
|
//***************************************************************************
|
|
/// Exception for call to receive/start/etc. while receive/start/etc. is
|
|
/// already happening. A call like that could result in an infinite loop or
|
|
/// landing in an incorrect state.
|
|
//***************************************************************************
|
|
class fsm_reentrant_transition_forbidden : public etl::fsm_exception
|
|
{
|
|
public:
|
|
|
|
fsm_reentrant_transition_forbidden(string_type file_name_, numeric_type line_number_)
|
|
: etl::fsm_exception(ETL_ERROR_TEXT("fsm:reentrant calls to start/receive/etc. forbidden", ETL_FSM_FILE_ID"G"), file_name_, line_number_)
|
|
{
|
|
}
|
|
};
|
|
|
|
namespace private_fsm
|
|
{
|
|
template <typename T = void>
|
|
class ifsm_state_helper
|
|
{
|
|
public:
|
|
|
|
// Pass this whenever no state change is desired.
|
|
// The highest unsigned value of fsm_state_id_t.
|
|
static ETL_CONSTANT fsm_state_id_t No_State_Change = etl::integral_limits<fsm_state_id_t>::max;
|
|
|
|
// Pass this when this event also needs to be passed to the parent.
|
|
static ETL_CONSTANT fsm_state_id_t Pass_To_Parent = No_State_Change - 1U;
|
|
|
|
// Pass this when this event should trigger a self transition.
|
|
static ETL_CONSTANT fsm_state_id_t Self_Transition = No_State_Change - 2U;
|
|
};
|
|
|
|
template <typename T>
|
|
ETL_CONSTANT fsm_state_id_t ifsm_state_helper<T>::No_State_Change;
|
|
|
|
template <typename T>
|
|
ETL_CONSTANT fsm_state_id_t ifsm_state_helper<T>::Pass_To_Parent;
|
|
|
|
template <typename T>
|
|
ETL_CONSTANT fsm_state_id_t ifsm_state_helper<T>::Self_Transition;
|
|
|
|
// Compile-time: TState::ID must equal its index in the type list (0..N-1)
|
|
template <size_t Id, typename...>
|
|
struct check_ids : etl::true_type
|
|
{
|
|
};
|
|
|
|
template <size_t Id, typename TState0, typename... TRest>
|
|
struct check_ids<Id, TState0, TRest...>
|
|
: etl::integral_constant< bool, (TState0::STATE_ID == Id) && private_fsm::check_ids<Id + 1, TRest...>::value>
|
|
{
|
|
};
|
|
|
|
//***************************************************************************
|
|
/// RAII detection mechanism to catch reentrant calls to methods that might
|
|
/// transition the state machine to a different state.
|
|
/// This is not a mutex.
|
|
//***************************************************************************
|
|
class fsm_reentrancy_guard
|
|
{
|
|
public:
|
|
|
|
//*******************************************
|
|
/// Constructor.
|
|
/// Checks if another method has locked reentrancy.
|
|
//*******************************************
|
|
fsm_reentrancy_guard(bool& transition_guard_flag)
|
|
: is_locked(transition_guard_flag)
|
|
{
|
|
ETL_ASSERT(!is_locked, ETL_ERROR(etl::fsm_reentrant_transition_forbidden));
|
|
is_locked = true;
|
|
}
|
|
|
|
//*******************************************
|
|
/// Destructor.
|
|
/// Releases lock on reentrancy.
|
|
//*******************************************
|
|
~fsm_reentrancy_guard() ETL_NOEXCEPT
|
|
{
|
|
is_locked = false;
|
|
}
|
|
|
|
private:
|
|
|
|
// Reference to the flag signifying a lock on the state machine.
|
|
bool& is_locked;
|
|
|
|
// Copy & move semantics disabled since this is a guard.
|
|
fsm_reentrancy_guard(fsm_reentrancy_guard const&) ETL_DELETE;
|
|
fsm_reentrancy_guard& operator=(fsm_reentrancy_guard const&) ETL_DELETE;
|
|
#if ETL_USING_CPP11
|
|
fsm_reentrancy_guard(fsm_reentrancy_guard&&) ETL_DELETE;
|
|
fsm_reentrancy_guard& operator=(fsm_reentrancy_guard&&) ETL_DELETE;
|
|
#endif
|
|
};
|
|
} // namespace private_fsm
|
|
|
|
class ifsm_state;
|
|
|
|
#if ETL_USING_CPP11
|
|
//***************************************************************************
|
|
/// A class to store FSM states.
|
|
//***************************************************************************
|
|
template <typename... TStates>
|
|
class fsm_state_pack
|
|
{
|
|
public:
|
|
|
|
friend class etl::fsm;
|
|
|
|
ETL_STATIC_ASSERT((private_fsm::check_ids<0, TStates...>::value), "State IDs must be 0..N-1 and in order");
|
|
ETL_STATIC_ASSERT(sizeof...(TStates) > 0, "At least one state is required");
|
|
ETL_STATIC_ASSERT(sizeof...(TStates) < private_fsm::ifsm_state_helper<>::No_State_Change,
|
|
"State IDs mst be less than ifsm_state::No_State_Change");
|
|
|
|
//*********************************
|
|
// The number of states.
|
|
//*********************************
|
|
static ETL_CONSTEXPR size_t size()
|
|
{
|
|
return sizeof...(TStates);
|
|
}
|
|
|
|
//*********************************
|
|
/// Gets a reference to the state.
|
|
//*********************************
|
|
template <typename TState>
|
|
TState& get()
|
|
{
|
|
return etl::get<TState>(storage);
|
|
}
|
|
|
|
//*********************************
|
|
/// Gets a const reference to the state.
|
|
//*********************************
|
|
template <typename TState>
|
|
const TState& get() const
|
|
{
|
|
return etl::get<TState>(storage);
|
|
}
|
|
|
|
private:
|
|
|
|
//*********************************
|
|
/// Gets a pointer to the state list.
|
|
//*********************************
|
|
etl::ifsm_state** get_state_list()
|
|
{
|
|
return &states[0];
|
|
}
|
|
|
|
/// A tuple to store the states.
|
|
etl::tuple<TStates...> storage{};
|
|
|
|
/// Pointers to the states.
|
|
etl::ifsm_state* states[sizeof...(TStates)]{&etl::get<TStates>(storage)...};
|
|
};
|
|
#endif
|
|
|
|
//***************************************************************************
|
|
/// Interface class for FSM states.
|
|
//***************************************************************************
|
|
class ifsm_state : public private_fsm::ifsm_state_helper<>
|
|
{
|
|
public:
|
|
|
|
/// Allows ifsm_state functions to be private.
|
|
friend class etl::fsm;
|
|
friend class etl::hfsm;
|
|
|
|
using private_fsm::ifsm_state_helper<>::No_State_Change;
|
|
using private_fsm::ifsm_state_helper<>::Pass_To_Parent;
|
|
using private_fsm::ifsm_state_helper<>::Self_Transition;
|
|
|
|
#if ETL_USING_CPP11 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++11 and above
|
|
template <typename, typename, etl::fsm_state_id_t, typename...>
|
|
friend class fsm_state;
|
|
#else
|
|
#include "private/fsm_friend_decl_cpp03.h"
|
|
#endif
|
|
|
|
//*******************************************
|
|
/// Gets the id for this state.
|
|
//*******************************************
|
|
etl::fsm_state_id_t get_state_id() const
|
|
{
|
|
return state_id;
|
|
}
|
|
|
|
//*******************************************
|
|
/// Adds a child to this state.
|
|
/// Only of use when part of an HFSM.
|
|
//*******************************************
|
|
void add_child_state(etl::ifsm_state& state)
|
|
{
|
|
ETL_ASSERT(state.p_parent == ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
|
|
state.p_parent = this;
|
|
|
|
if (p_default_child == ETL_NULLPTR)
|
|
{
|
|
p_default_child = &state;
|
|
}
|
|
}
|
|
|
|
//*******************************************
|
|
/// Adds a list of child states.
|
|
/// Only of use when part of an HFSM.
|
|
//*******************************************
|
|
template <typename TSize>
|
|
void set_child_states(etl::ifsm_state** state_list, TSize size)
|
|
{
|
|
p_active_child = ETL_NULLPTR;
|
|
p_default_child = ETL_NULLPTR;
|
|
|
|
for (TSize i = 0; i < size; ++i)
|
|
{
|
|
ETL_ASSERT(state_list[i] != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
|
|
add_child_state(*state_list[i]);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
|
|
//*******************************************
|
|
/// Constructor.
|
|
//*******************************************
|
|
ifsm_state(etl::fsm_state_id_t state_id_)
|
|
: state_id(state_id_)
|
|
, p_context(ETL_NULLPTR)
|
|
, p_parent(ETL_NULLPTR)
|
|
, p_active_child(ETL_NULLPTR)
|
|
, p_default_child(ETL_NULLPTR)
|
|
{
|
|
}
|
|
|
|
//*******************************************
|
|
/// Destructor.
|
|
//*******************************************
|
|
virtual ~ifsm_state() {}
|
|
|
|
//*******************************************
|
|
etl::fsm& get_fsm_context() const
|
|
{
|
|
return *p_context;
|
|
}
|
|
|
|
private:
|
|
|
|
virtual fsm_state_id_t process_event(const etl::imessage& message) = 0;
|
|
|
|
virtual fsm_state_id_t on_enter_state()
|
|
{
|
|
return No_State_Change;
|
|
} // By default, do nothing.
|
|
virtual void on_exit_state() {} // By default, do nothing.
|
|
|
|
//*******************************************
|
|
void set_fsm_context(etl::fsm& context)
|
|
{
|
|
p_context = &context;
|
|
}
|
|
|
|
// The state id.
|
|
const etl::fsm_state_id_t state_id;
|
|
|
|
// A pointer to the FSM context.
|
|
etl::fsm* p_context;
|
|
|
|
// A pointer to the parent.
|
|
ifsm_state* p_parent;
|
|
|
|
// A pointer to the active child.
|
|
ifsm_state* p_active_child;
|
|
|
|
// A pointer to the default active child.
|
|
ifsm_state* p_default_child;
|
|
|
|
// Disabled.
|
|
ifsm_state(const ifsm_state&) ETL_DELETE;
|
|
ifsm_state& operator=(const ifsm_state&) ETL_DELETE;
|
|
};
|
|
|
|
//***************************************************************************
|
|
/// The FSM class.
|
|
//***************************************************************************
|
|
class fsm : public etl::imessage_router
|
|
{
|
|
public:
|
|
|
|
friend class etl::hfsm;
|
|
using imessage_router::receive;
|
|
|
|
//*******************************************
|
|
/// Constructor.
|
|
//*******************************************
|
|
fsm(etl::message_router_id_t id)
|
|
: imessage_router(id)
|
|
, p_state(ETL_NULLPTR)
|
|
, state_list(ETL_NULLPTR)
|
|
, number_of_states(0U)
|
|
, is_processing_state_change(false)
|
|
{
|
|
}
|
|
|
|
//*******************************************
|
|
/// Set the states for the FSM
|
|
/// From a pointer to etl::ifsm_state and size.
|
|
//*******************************************
|
|
template <typename TSize>
|
|
void set_states(etl::ifsm_state** p_states, TSize size)
|
|
{
|
|
state_list = p_states;
|
|
number_of_states = etl::fsm_state_id_t(size);
|
|
|
|
ETL_ASSERT(number_of_states > 0, ETL_ERROR(etl::fsm_state_list_exception));
|
|
ETL_ASSERT(number_of_states < ifsm_state::No_State_Change, ETL_ERROR(etl::fsm_state_list_exception));
|
|
|
|
for (etl::fsm_state_id_t i = 0; i < size; ++i)
|
|
{
|
|
ETL_ASSERT(state_list[i] != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
|
|
ETL_ASSERT(state_list[i]->get_state_id() == i, ETL_ERROR(etl::fsm_state_list_order_exception));
|
|
state_list[i]->set_fsm_context(*this);
|
|
}
|
|
}
|
|
|
|
#if ETL_USING_CPP11
|
|
//*******************************************
|
|
/// Set the states for the FSM
|
|
/// From an etl::fsm_state_pack.
|
|
//*******************************************
|
|
template <typename... TStates>
|
|
void set_states(etl::fsm_state_pack<TStates...>& state_pack)
|
|
{
|
|
state_list = state_pack.get_state_list();
|
|
number_of_states = etl::fsm_state_id_t(state_pack.size());
|
|
|
|
for (etl::fsm_state_id_t i = 0; i < number_of_states; ++i)
|
|
{
|
|
state_list[i]->set_fsm_context(*this);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//*******************************************
|
|
/// Starts the FSM.
|
|
/// Can only be called once.
|
|
/// Subsequent calls will do nothing.
|
|
///\param call_on_enter_state If true will call on_enter_state() for the
|
|
/// first state. Default = true.
|
|
//*******************************************
|
|
virtual void start(bool call_on_enter_state = true)
|
|
{
|
|
private_fsm::fsm_reentrancy_guard transition_lock(is_processing_state_change);
|
|
|
|
// Can only be started once.
|
|
if (!is_started())
|
|
{
|
|
p_state = state_list[0];
|
|
ETL_ASSERT(p_state != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
|
|
|
|
if (call_on_enter_state)
|
|
{
|
|
etl::fsm_state_id_t next_state_id;
|
|
etl::ifsm_state* p_last_state;
|
|
|
|
do {
|
|
p_last_state = p_state;
|
|
next_state_id = p_state->on_enter_state();
|
|
if (next_state_id != ifsm_state::No_State_Change)
|
|
{
|
|
ETL_ASSERT(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception));
|
|
p_state = state_list[next_state_id];
|
|
}
|
|
} while (p_last_state != p_state);
|
|
}
|
|
}
|
|
}
|
|
|
|
//*******************************************
|
|
/// Top level message handler for the FSM.
|
|
//*******************************************
|
|
void receive(const etl::imessage& message) ETL_OVERRIDE
|
|
{
|
|
private_fsm::fsm_reentrancy_guard transition_lock(is_processing_state_change);
|
|
|
|
if (is_started())
|
|
{
|
|
etl::fsm_state_id_t next_state_id = p_state->process_event(message);
|
|
|
|
process_state_change(next_state_id);
|
|
}
|
|
else
|
|
{
|
|
ETL_ASSERT_FAIL(ETL_ERROR(etl::fsm_not_started));
|
|
}
|
|
}
|
|
|
|
//*******************************************
|
|
/// Invoke a state transition.
|
|
//*******************************************
|
|
etl::fsm_state_id_t transition_to(etl::fsm_state_id_t new_state_id)
|
|
{
|
|
private_fsm::fsm_reentrancy_guard transition_lock(is_processing_state_change);
|
|
|
|
if (is_started())
|
|
{
|
|
return process_state_change(new_state_id);
|
|
}
|
|
else
|
|
{
|
|
return ifsm_state::No_State_Change;
|
|
}
|
|
}
|
|
|
|
using imessage_router::accepts;
|
|
|
|
//*******************************************
|
|
/// Does this FSM accept the message id?
|
|
/// Yes, it accepts everything!
|
|
//*******************************************
|
|
bool accepts(etl::message_id_t) const ETL_OVERRIDE
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//*******************************************
|
|
/// Gets the current state id.
|
|
//*******************************************
|
|
etl::fsm_state_id_t get_state_id() const
|
|
{
|
|
ETL_ASSERT(p_state != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
|
|
return p_state->get_state_id();
|
|
}
|
|
|
|
//*******************************************
|
|
/// Gets a reference to the current state interface.
|
|
//*******************************************
|
|
ifsm_state& get_state()
|
|
{
|
|
ETL_ASSERT(p_state != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
|
|
return *p_state;
|
|
}
|
|
|
|
//*******************************************
|
|
/// Gets a const reference to the current state interface.
|
|
//*******************************************
|
|
const ifsm_state& get_state() const
|
|
{
|
|
ETL_ASSERT(p_state != ETL_NULLPTR, ETL_ERROR(etl::fsm_null_state_exception));
|
|
return *p_state;
|
|
}
|
|
|
|
//*******************************************
|
|
/// Checks if the FSM has been started.
|
|
//*******************************************
|
|
bool is_started() const
|
|
{
|
|
return p_state != ETL_NULLPTR;
|
|
}
|
|
|
|
//*******************************************
|
|
/// Reset the FSM to pre-started state.
|
|
///\param call_on_exit_state If true will call on_exit_state() for the
|
|
/// current state. Default = false.
|
|
//*******************************************
|
|
virtual void reset(bool call_on_exit_state = false)
|
|
{
|
|
private_fsm::fsm_reentrancy_guard transition_lock(is_processing_state_change);
|
|
|
|
if (is_started() && call_on_exit_state)
|
|
{
|
|
p_state->on_exit_state();
|
|
}
|
|
|
|
p_state = ETL_NULLPTR;
|
|
}
|
|
|
|
//********************************************
|
|
ETL_DEPRECATED
|
|
bool is_null_router() const ETL_OVERRIDE
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//********************************************
|
|
bool is_producer() const ETL_OVERRIDE
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//********************************************
|
|
bool is_consumer() const ETL_OVERRIDE
|
|
{
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
|
|
//********************************************
|
|
bool have_changed_state(etl::fsm_state_id_t next_state_id) const
|
|
{
|
|
return (next_state_id != p_state->get_state_id()) && (next_state_id != ifsm_state::No_State_Change)
|
|
&& (next_state_id != ifsm_state::Self_Transition);
|
|
}
|
|
|
|
//********************************************
|
|
bool is_self_transition(etl::fsm_state_id_t next_state_id) const
|
|
{
|
|
return (next_state_id == ifsm_state::Self_Transition);
|
|
}
|
|
|
|
//*******************************************
|
|
/// Core function to process a state change.
|
|
//*******************************************
|
|
virtual etl::fsm_state_id_t process_state_change(etl::fsm_state_id_t next_state_id)
|
|
{
|
|
if (is_self_transition(next_state_id))
|
|
{
|
|
p_state->on_exit_state();
|
|
next_state_id = p_state->on_enter_state();
|
|
}
|
|
|
|
if (have_changed_state(next_state_id))
|
|
{
|
|
ETL_ASSERT_OR_RETURN_VALUE(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception), p_state->get_state_id());
|
|
etl::ifsm_state* p_next_state = state_list[next_state_id];
|
|
|
|
do {
|
|
p_state->on_exit_state();
|
|
p_state = p_next_state;
|
|
|
|
next_state_id = p_state->on_enter_state();
|
|
|
|
if (have_changed_state(next_state_id))
|
|
{
|
|
ETL_ASSERT_OR_RETURN_VALUE(next_state_id < number_of_states, ETL_ERROR(etl::fsm_state_id_exception), p_state->get_state_id());
|
|
p_next_state = state_list[next_state_id];
|
|
}
|
|
} while (p_next_state != p_state); // Have we changed state again?
|
|
}
|
|
|
|
return p_state->get_state_id();
|
|
}
|
|
|
|
etl::ifsm_state* p_state; ///< A pointer to the current state.
|
|
etl::ifsm_state** state_list; ///< The list of added states.
|
|
etl::fsm_state_id_t number_of_states; ///< The number of states.
|
|
bool is_processing_state_change; ///< Whether a method call that could
|
|
///< potentially trigger a state change is
|
|
///< active
|
|
};
|
|
|
|
//*************************************************************************************************
|
|
// For C++11 and above.
|
|
//*************************************************************************************************
|
|
#if ETL_USING_CPP11 && !defined(ETL_FSM_FORCE_CPP03_IMPLEMENTATION) // For C++11 and above
|
|
//***************************************************************************
|
|
// The definition for all types.
|
|
//***************************************************************************
|
|
template <typename TContext, typename TDerived, etl::fsm_state_id_t STATE_ID_, typename... TMessageTypes>
|
|
class fsm_state : public ifsm_state
|
|
{
|
|
private:
|
|
|
|
using message_id_sequence = etl::index_sequence<TMessageTypes::ID...>;
|
|
|
|
public:
|
|
|
|
using message_types = etl::type_list<TMessageTypes...>;
|
|
using sorted_message_types = etl::type_list_sort_t<message_types, etl::compare_message_id_less>;
|
|
|
|
static_assert(etl::type_list_is_unique<message_types>::value, "All TMessageTypes must be unique");
|
|
static_assert(etl::type_list_all_of<message_types, etl::is_message_type>::value,
|
|
"All TMessageTypes must satisfy the condition etl::is_message_type");
|
|
static_assert(etl::index_sequence_is_unique<message_id_sequence>::value, "All message IDs must be unique");
|
|
|
|
static ETL_CONSTANT etl::fsm_state_id_t STATE_ID = STATE_ID_;
|
|
|
|
fsm_state()
|
|
: ifsm_state(STATE_ID)
|
|
{
|
|
}
|
|
|
|
protected:
|
|
|
|
~fsm_state() {}
|
|
|
|
TContext& get_fsm_context() const
|
|
{
|
|
return static_cast<TContext&>(ifsm_state::get_fsm_context());
|
|
}
|
|
|
|
private:
|
|
|
|
static constexpr size_t Number_Of_Messages = sizeof...(TMessageTypes);
|
|
static constexpr etl::message_id_t Message_Id_Start = etl::type_list_type_at_index_t<sorted_message_types, 0>::ID;
|
|
|
|
static_assert(Number_Of_Messages > 0, "Zero messages");
|
|
|
|
//**********************************************
|
|
// Checks that the message ids are contiguous.
|
|
//**********************************************
|
|
template <size_t Index, bool Last = (Index + 1U >= Number_Of_Messages)>
|
|
struct contiguous_impl;
|
|
|
|
template <size_t Index>
|
|
struct contiguous_impl<Index, true> : etl::true_type
|
|
{
|
|
};
|
|
|
|
template <size_t Index>
|
|
struct contiguous_impl<Index, false>
|
|
: etl::bool_constant< (etl::type_list_type_at_index_t<sorted_message_types, Index>::ID + 1U
|
|
== etl::type_list_type_at_index_t<sorted_message_types, Index + 1U>::ID)
|
|
&& contiguous_impl<Index + 1U>::value>
|
|
{
|
|
};
|
|
|
|
// The message ids are contiguous if there are 0 or 1 message types, or if
|
|
// each message id is one greater than the previous message id.
|
|
static constexpr bool Message_Ids_Are_Contiguous = (Number_Of_Messages <= 1U) ? true : contiguous_impl<0U>::value;
|
|
|
|
using handler_ptr = etl::fsm_state_id_t (*)(TDerived&,
|
|
const etl::imessage&); ///< Pointer to a handler function that takes a
|
|
///< reference to the derived class and a
|
|
///< reference to the message.
|
|
using message_dispatch_table_t = etl::array<handler_ptr,
|
|
Number_Of_Messages>; ///< The dispatch table type. An array of
|
|
///< handler pointers, one for each
|
|
///< message type.
|
|
using message_id_table_t = etl::array<etl::message_id_t,
|
|
Number_Of_Messages>; ///< The message id table type. An
|
|
///< array of message ids, one for
|
|
///< each message type.
|
|
|
|
//********************************************
|
|
etl::fsm_state_id_t process_event(const etl::imessage& message)
|
|
{
|
|
const etl::message_id_t id = message.get_message_id();
|
|
|
|
// The IDs are sorted, so an ID less than the first is not handled by this
|
|
// router.
|
|
if (id >= Message_Id_Start)
|
|
{
|
|
const size_t index = get_dispatch_index_from_message_id(id);
|
|
|
|
// If the index is less than Number_Of_Messages, then we have a handler
|
|
// for this message type, so dispatch it.
|
|
if (index < Number_Of_Messages)
|
|
{
|
|
const etl::fsm_state_id_t new_state_id = dispatch(message, index);
|
|
|
|
if (new_state_id != Pass_To_Parent)
|
|
{
|
|
return new_state_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "etl/private/diagnostic_array_bounds_push.h"
|
|
// If we get here, then we don't have a handler for this message type, so
|
|
// pass it to the parent if we have one, otherwise call on_event_unknown.
|
|
return (p_parent != nullptr) ? p_parent->process_event(message) : static_cast<TDerived*>(this)->on_event_unknown(message);
|
|
#include "etl/private/diagnostic_pop.h"
|
|
}
|
|
|
|
//**********************************************
|
|
// Call for a single message type
|
|
//**********************************************
|
|
template <typename TMessage>
|
|
static etl::fsm_state_id_t call_on_event(TDerived& derived, const imessage& msg)
|
|
{
|
|
return derived.on_event(static_cast<const TMessage&>(msg));
|
|
}
|
|
|
|
//**********************************************
|
|
// Get the handler for a single message type at the index in the sorted
|
|
// type_list. This will be called for each message type to generate the
|
|
// dispatch table.
|
|
//**********************************************
|
|
template <size_t Index>
|
|
static constexpr handler_ptr get_message_handler()
|
|
{
|
|
return &call_on_event< etl::type_list_type_at_index_t<sorted_message_types, Index>>;
|
|
}
|
|
|
|
//**********************************************
|
|
// Generate the dispatch table at compile time.
|
|
// This will create an array of handler pointers, one for each message type.
|
|
//**********************************************
|
|
template <size_t... Indices>
|
|
static constexpr message_dispatch_table_t make_message_dispatch_table(etl::index_sequence<Indices...>)
|
|
{
|
|
return message_dispatch_table_t{{get_message_handler<Indices>()...}};
|
|
}
|
|
|
|
//**********************************************
|
|
// Get the message id for a single message type at an index in the sorted
|
|
// type_list. This will be called for each message type to generate the
|
|
// message id table.
|
|
//**********************************************
|
|
template <size_t Index>
|
|
static constexpr etl::message_id_t get_message_id_from_index()
|
|
{
|
|
return etl::type_list_type_at_index_t<sorted_message_types, Index>::ID;
|
|
}
|
|
|
|
//**********************************************
|
|
// Generate the message id table at compile time.
|
|
// This will create an array of message ids, one for each message type.
|
|
//**********************************************
|
|
template <size_t... Indices>
|
|
static constexpr message_id_table_t make_message_id_table(etl::index_sequence<Indices...>)
|
|
{
|
|
return message_id_table_t{{get_message_id_from_index<Indices>()...}};
|
|
}
|
|
|
|
//**********************************************
|
|
// Get the dispatch index for a message id.
|
|
// This will be used at runtime to find the handler for a message id.
|
|
// If the message ids are contiguous, we can calculate the index directly.
|
|
// If they are not contiguous, we need to do a binary search. This will
|
|
// return Number_Of_Messages if the message id is not found.
|
|
//**********************************************
|
|
static size_t get_dispatch_index_from_message_id(etl::message_id_t id)
|
|
{
|
|
if ETL_IF_CONSTEXPR (Message_Ids_Are_Contiguous)
|
|
{
|
|
// The IDs are contiguous, so we can calculate the index directly.
|
|
return static_cast<size_t>(id - Message_Id_Start);
|
|
}
|
|
else
|
|
{
|
|
// The IDs are not contiguous, so we need to do a binary search.
|
|
size_t left = 0;
|
|
size_t right = Number_Of_Messages;
|
|
|
|
while (left < right)
|
|
{
|
|
size_t mid = (left + right) / 2;
|
|
|
|
if (message_id_table[mid] == id)
|
|
{
|
|
return mid;
|
|
}
|
|
else if (message_id_table[mid] < id)
|
|
{
|
|
left = mid + 1;
|
|
}
|
|
else
|
|
{
|
|
right = mid;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Number_Of_Messages; // Not found
|
|
}
|
|
|
|
//**********************************************
|
|
// Dispatch the message to the appropriate handler based on the index in the
|
|
// dispatch table.
|
|
//**********************************************
|
|
etl::fsm_state_id_t dispatch(const etl::imessage& msg, size_t index)
|
|
{
|
|
return message_dispatch_table[index](static_cast<TDerived&>(*this), msg);
|
|
}
|
|
|
|
//**********************************************
|
|
// The dispatch table is generated at compile time. The dispatch table
|
|
// contains pointers to the on_receive handlers for each message type.
|
|
//**********************************************
|
|
static ETL_INLINE_VAR constexpr message_dispatch_table_t message_dispatch_table =
|
|
etl::fsm_state<TContext, TDerived, STATE_ID_, TMessageTypes...>::make_message_dispatch_table(
|
|
etl::make_index_sequence< etl::fsm_state<TContext, TDerived, STATE_ID_, TMessageTypes...>::Number_Of_Messages>{});
|
|
|
|
//**********************************************
|
|
// The message id table is generated at compile time. The message id table
|
|
// contains the corresponding message ids for each message type.
|
|
//**********************************************
|
|
static ETL_INLINE_VAR constexpr message_id_table_t message_id_table =
|
|
etl::fsm_state<TContext, TDerived, STATE_ID_, TMessageTypes...>::make_message_id_table(
|
|
etl::make_index_sequence< etl::fsm_state<TContext, TDerived, STATE_ID_, TMessageTypes...>::Number_Of_Messages>{});
|
|
};
|
|
|
|
#if ETL_USING_CPP11 && !ETL_USING_CPP17
|
|
template <typename TContext, typename TDerived, etl::fsm_state_id_t STATE_ID_, typename... TMessageTypes>
|
|
constexpr const typename etl::fsm_state< TContext, TDerived, STATE_ID_, TMessageTypes...>::message_dispatch_table_t
|
|
etl::fsm_state<TContext, TDerived, STATE_ID_, TMessageTypes...>::message_dispatch_table;
|
|
|
|
template <typename TContext, typename TDerived, etl::fsm_state_id_t STATE_ID_, typename... TMessageTypes>
|
|
constexpr const typename etl::fsm_state<TContext, TDerived, STATE_ID_, TMessageTypes...>::message_id_table_t
|
|
etl::fsm_state<TContext, TDerived, STATE_ID_, TMessageTypes...>::message_id_table;
|
|
#endif
|
|
|
|
/// Definition of STATE_ID
|
|
template <typename TContext, typename TDerived, etl::fsm_state_id_t STATE_ID_, typename... TMessageTypes>
|
|
ETL_CONSTANT etl::fsm_state_id_t fsm_state<TContext, TDerived, STATE_ID_, TMessageTypes...>::STATE_ID;
|
|
|
|
//***************************************************************************
|
|
// The definition for no messages.
|
|
//***************************************************************************
|
|
template <typename TContext, typename TDerived, etl::fsm_state_id_t STATE_ID_>
|
|
class fsm_state<TContext, TDerived, STATE_ID_> : public ifsm_state
|
|
{
|
|
private:
|
|
|
|
using message_id_sequence = etl::index_sequence<>;
|
|
|
|
public:
|
|
|
|
using message_types = etl::type_list<>;
|
|
using sorted_message_types = etl::type_list<>;
|
|
|
|
static ETL_CONSTANT etl::fsm_state_id_t STATE_ID = STATE_ID_;
|
|
|
|
fsm_state()
|
|
: ifsm_state(STATE_ID)
|
|
{
|
|
}
|
|
|
|
protected:
|
|
|
|
~fsm_state() {}
|
|
|
|
TContext& get_fsm_context() const
|
|
{
|
|
return static_cast<TContext&>(ifsm_state::get_fsm_context());
|
|
}
|
|
|
|
private:
|
|
|
|
//********************************************
|
|
etl::fsm_state_id_t process_event(const etl::imessage& message)
|
|
{
|
|
return (p_parent != nullptr) ? p_parent->process_event(message) : static_cast<TDerived*>(this)->on_event_unknown(message);
|
|
}
|
|
};
|
|
|
|
#else
|
|
#include "private/fsm_cpp03.h"
|
|
#endif
|
|
} // namespace etl
|
|
|
|
#include "private/minmax_pop.h"
|
|
|
|
#endif
|