mountcontrol/cxx/mcc_finite_state_machine.h
2025-06-04 11:37:53 +03:00

725 lines
27 KiB
C++

#pragma once
#include <cxxabi.h>
#include <iostream>
#include <concepts>
#include <functional>
#include <memory>
#include <mutex>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <variant>
#include "mcc_traits.h"
namespace mcc::fsm
{
enum class MccFiniteStateMachineErrorCode : int { ERROR_OK, ERROR_UNREGISTERED_EVENT_TYPE, ERROR_UNHANDLED_TRANSITION };
} // namespace mcc::fsm
namespace std
{
template <>
class is_error_code_enum<mcc::fsm::MccFiniteStateMachineErrorCode> : public true_type
{
};
} // namespace std
namespace mcc::fsm
{
// error category
struct MccFiniteStateMachineCategory : public std::error_category {
MccFiniteStateMachineCategory() : std::error_category() {}
const char* name() const noexcept
{
return "ADC_GENERIC_DEVICE";
}
std::string message(int ec) const
{
MccFiniteStateMachineErrorCode err = static_cast<MccFiniteStateMachineErrorCode>(ec);
switch (err) {
case MccFiniteStateMachineErrorCode::ERROR_OK:
return "OK";
case MccFiniteStateMachineErrorCode::ERROR_UNREGISTERED_EVENT_TYPE:
return "unregistered event type";
case MccFiniteStateMachineErrorCode::ERROR_UNHANDLED_TRANSITION:
return "unhandled transition";
default:
return "UNKNOWN";
}
}
static const MccFiniteStateMachineCategory& get()
{
static const MccFiniteStateMachineCategory constInst;
return constInst;
}
};
inline std::error_code make_error_code(MccFiniteStateMachineErrorCode ec)
{
return std::error_code(static_cast<int>(ec), MccFiniteStateMachineCategory::get());
}
namespace traits
{
/*
The only requirement to Event-class is public-accepted static constant 'ID'
*/
template <typename T>
concept fsm_event_c = std::is_default_constructible_v<T> && std::is_move_constructible_v<T> && std::movable<T> &&
requires { requires std::same_as<const std::string_view, decltype(T::ID)>; };
/*
The only requirements to State-class is public-accepted static constant 'ID' and
definition of type transition_table_t
*/
template <typename T>
concept fsm_state_c =
std::is_default_constructible_v<T> && std::is_move_constructible_v<T> && std::movable<T> && requires {
requires std::same_as<const std::string_view, decltype(T::ID)>;
typename T::transition_t;
};
// concept for std::pair
template <typename T>
concept fsm_pair_of_types_c =
requires { []<typename T1, typename T2>(std::type_identity<std::pair<T1, T2>>) {}(std::type_identity<T>()); };
template <typename T>
concept fsm_tuple_of_pairs_c =
requires { []<fsm_pair_of_types_c... PTs>(std::type_identity<std::tuple<PTs...>>) {}(std::type_identity<T>()); };
template <typename T>
concept fsm_tuple_of_events_c =
requires { []<fsm_event_c... EvTs>(std::type_identity<std::tuple<EvTs...>>) {}(std::type_identity<T>()); };
} // namespace traits
/* Event-to-State transition table definition */
template <traits::fsm_pair_of_types_c... PTs>
struct fsm_transition_table_t;
template <traits::fsm_pair_of_types_c PT>
struct fsm_transition_table_t<PT> {
using events_t = std::tuple<typename PT::first_type>;
using event_state_pair_t = std::tuple<PT>;
using unique_states_t = std::tuple<typename PT::second_type>;
template <typename EvT>
using find_state_by_event_t =
std::conditional_t<std::same_as<EvT, typename PT::first_type>, typename PT::second_type, void>;
};
template <traits::fsm_pair_of_types_c PT, traits::fsm_pair_of_types_c... PTs>
struct fsm_transition_table_t<PT, PTs...> {
private:
static constexpr bool non_unique_event = (std::same_as<typename PT::first_type, typename PTs::first_type> || ...);
public:
using events_t =
std::conditional_t<non_unique_event,
typename fsm_transition_table_t<PTs...>::events_t,
decltype(std::tuple_cat(std::declval<std::tuple<typename PT::first_type>>(),
std::declval<typename fsm_transition_table_t<PTs...>::events_t>()))>;
using event_state_pair_t =
std::conditional_t<non_unique_event,
typename fsm_transition_table_t<PTs...>::event_state_pair_t,
decltype(std::tuple_cat(
std::declval<std::tuple<PT>>(),
std::declval<typename fsm_transition_table_t<PTs...>::event_state_pair_t>()))>;
using unique_states_t = std::conditional_t<
non_unique_event,
typename fsm_transition_table_t<PTs...>::unique_states_t,
std::conditional_t<(std::same_as<typename PT::second_type, typename PTs::second_type> || ...),
typename fsm_transition_table_t<PTs...>::unique_states_t,
decltype(std::tuple_cat(
std::declval<std::tuple<typename PT::second_type>>(),
std::declval<typename fsm_transition_table_t<PTs...>::unique_states_t>()))>>;
private:
template <typename EvT, traits::fsm_tuple_of_pairs_c TplT>
struct find_state_by_event;
template <typename LookEvT, typename EvT, typename StT>
struct find_state_by_event<LookEvT, std::tuple<std::pair<EvT, StT>>> {
using state_t = std::conditional_t<std::same_as<LookEvT, EvT>, StT, void>;
};
template <typename LookEvT, typename EvT, typename StT, traits::fsm_pair_of_types_c... PairTs>
struct find_state_by_event<LookEvT, std::tuple<std::pair<EvT, StT>, PairTs...>> {
using state_t = std::conditional_t<std::same_as<LookEvT, EvT>,
StT,
typename find_state_by_event<LookEvT, std::tuple<PairTs...>>::state_t>;
};
public:
// template <typename EvT, traits::fsm_tuple_of_pairs_c TplT>
template <typename EvT>
using find_state_by_event_t = typename find_state_by_event<EvT, event_state_pair_t>::state_t;
};
class MccFiniteStateMachine
{
protected:
/* helper types definition */
// merge N std::tuple types with filtering dublicates
// (NOTE: the first std::tuple must contain unique types!!!)
template <typename... TplTs>
struct merge_tuples;
template <typename TplT>
struct merge_tuples<TplT> {
using result_t = TplT;
};
template <typename TplT1, typename TplT2>
struct merge_tuples<TplT1, TplT2> {
using result_t = TplT1;
};
template <typename... T1, typename T2, typename... T2s>
struct merge_tuples<std::tuple<T1...>, std::tuple<T2, T2s...>>
: std::conditional_t<(std::same_as<T1, T2> || ...),
merge_tuples<std::tuple<T1...>, std::tuple<T2s...>>,
merge_tuples<std::tuple<T1..., T2>, std::tuple<T2s...>>> {
};
template <typename TplT1, typename TplT2, typename TplT3, typename... TplTs>
struct merge_tuples<TplT1, TplT2, TplT3, TplTs...>
: merge_tuples<typename merge_tuples<TplT1, TplT2>::result_t, TplT3, TplTs...> {
};
template <typename... TplTs>
using merge_tuples_t = typename merge_tuples<TplTs...>::result_t;
// deduce all unique states from the initial one
template <bool stop, typename ResTplT, typename... InTplTs>
struct deduce_states;
template <typename ResTplT, typename... InTplTs>
struct deduce_states<true, ResTplT, InTplTs...> {
using states_t = ResTplT;
};
template <typename ResTplT, traits::fsm_state_c... StTs, typename... InTplTs>
struct deduce_states<false, ResTplT, std::tuple<StTs...>, InTplTs...> {
using curr_collection_t = merge_tuples_t<ResTplT, std::tuple<StTs...>>;
// using curr_collection_t =
// merge_tuples_t<ResTplT, std::tuple<StTs...>, typename StTs::transition_t::unique_states_t...>;
using states_t = typename deduce_states<std::tuple_size_v<ResTplT> == std::tuple_size_v<curr_collection_t>,
curr_collection_t,
merge_tuples_t<typename StTs::transition_t::unique_states_t...>,
// typename StTs::transition_t::unique_states_t...,
InTplTs...>::states_t;
};
template <traits::fsm_state_c InitStateT>
using deduce_states_t = typename deduce_states<false, std::tuple<>, std::tuple<InitStateT>>::states_t;
// deduce all unique events from the initial state transition table
template <bool stop, typename ResTplT, typename InTplT>
struct deduce_events;
template <typename ResTplT, typename InTplT>
struct deduce_events<true, ResTplT, InTplT> {
using events_t = ResTplT;
};
template <traits::fsm_tuple_of_events_c ResTplT, traits::fsm_state_c... StTs>
struct deduce_events<false, ResTplT, std::tuple<StTs...>> {
using curr_collection_t = merge_tuples_t<ResTplT, typename StTs::transition_t::events_t...>;
using events_t =
typename deduce_events<std::tuple_size_v<ResTplT> == std::tuple_size_v<curr_collection_t>,
curr_collection_t,
merge_tuples_t<typename StTs::transition_t::unique_states_t...>>::events_t;
};
template <traits::fsm_state_c InitStateT>
using deduce_events_t = typename deduce_events<false, std::tuple<>, std::tuple<InitStateT>>::events_t;
template <typename TplT>
struct variant_from_tuple;
template <typename... Ts>
struct variant_from_tuple<std::tuple<Ts...>> {
using variant_t = std::variant<Ts*...>;
};
template <typename TplT>
using variant_from_tuple_t = typename variant_from_tuple<TplT>::variant_t;
// call given function for all event-types in the given std::tuple
template <traits::fsm_tuple_of_events_c EvTplT, typename FT, size_t... Is>
static void for_each_event(FT&& func, std::index_sequence<Is...>)
{
(func(std::get<Is>(EvTplT{})), ...);
};
template <traits::fsm_tuple_of_events_c EvTplT, typename FT>
static void for_each_event(FT&& func)
{
for_each_event<EvTplT>(std::forward<FT>(func), std::make_index_sequence<std::tuple_size_v<EvTplT>>());
};
template <traits::fsm_state_c StT, typename FT>
static void for_each_event_in_each_state(FT&& func)
{
for_each_event<typename StT::transition_t::events_t>(std::forward<FT>(func));
}
template <typename StTplT, typename FT, size_t... Is>
static void for_each_event_in_each_state(FT&& func, std::index_sequence<Is...>)
{
(for_each_event_in_each_state<std::tuple_element_t<Is, StTplT>>(std::forward<FT>(func)), ...);
}
template <typename StTplT, typename FT>
static void for_each_event_in_each_state(FT&& func)
{
for_each_event_in_each_state<StTplT>(std::forward<FT>(func),
std::make_index_sequence<std::tuple_size_v<StTplT>>());
}
// check if given event-type is in std::tuple of event-types
template <traits::fsm_event_c EvT, typename EvTplT>
struct in_tuple;
template <traits::fsm_event_c EvT, traits::fsm_event_c... EvTplTs>
struct in_tuple<EvT, std::tuple<EvTplTs...>> : std::disjunction<std::is_same<EvT, EvTplTs>...> {
};
template <traits::fsm_event_c EvT, typename EvTplT>
static constexpr bool in_tuple_v = in_tuple<EvT, EvTplT>::value;
template <traits::fsm_event_c EvT>
inline static std::unordered_map<const MccFiniteStateMachine*, std::function<void(EvT&)>> _dispatchEventFunc{};
std::vector<std::function<void(MccFiniteStateMachine*, MccFiniteStateMachine*)>> _moveFunc{};
std::vector<std::function<void(const MccFiniteStateMachine*, MccFiniteStateMachine*)>> _copyFunc{};
std::vector<std::function<void(const MccFiniteStateMachine*)>> _destroyFunc{};
std::string_view _currentStateID;
std::vector<std::string_view> _stateID;
std::vector<std::string_view> _eventID;
std::mutex _transitionMutex;
template <traits::fsm_tuple_of_events_c EvTplT, typename FT, size_t... Is>
void setupInstanceFuncs(FT&& func, std::index_sequence<Is...>)
{
(_dispatchEventFunc<std::tuple_element_t<Is, EvTplT>>.emplace(this, func), ...);
(_moveFunc.emplace_back([](MccFiniteStateMachine* from, MccFiniteStateMachine* to) {
_dispatchEventFunc<std::tuple_element_t<Is, EvTplT>>[to] =
std::move(_dispatchEventFunc<std::tuple_element_t<Is, EvTplT>>[from]);
}),
...);
(_copyFunc.emplace_back([](const MccFiniteStateMachine* from, MccFiniteStateMachine* to) {
_dispatchEventFunc<std::tuple_element_t<Is, EvTplT>>[to] =
_dispatchEventFunc<std::tuple_element_t<Is, EvTplT>>[from];
}),
...);
(_destroyFunc.emplace_back([](const MccFiniteStateMachine* inst) {
//
_dispatchEventFunc<std::tuple_element_t<Is, EvTplT>>.erase(inst);
}),
...);
(_eventID.emplace_back(std::tuple_element_t<Is, EvTplT>::ID), ...);
};
template <traits::fsm_tuple_of_events_c EvTplT, typename FT>
void setupInstanceFuncs(FT&& func)
{
setupInstanceFuncs<EvTplT>(std::forward<FT>(func), std::make_index_sequence<std::tuple_size_v<EvTplT>>());
};
static MccFiniteStateMachine& copyInstance(const MccFiniteStateMachine* from, MccFiniteStateMachine* to)
{
if (from != to) {
for (auto& func : from->_copyFunc) {
func(from, to);
}
to->_currentStateID = from->_currentStateID;
to->_moveFunc = from->_moveFunc;
to->_copyFunc = from->_copyFunc;
to->_destroyFunc = from->_destroyFunc;
}
return *to;
}
static MccFiniteStateMachine& moveInstance(MccFiniteStateMachine* from, MccFiniteStateMachine* to)
{
if (from != to) {
for (auto& func : from->_moveFunc) {
func(from, to);
}
to->_currentStateID = std::move(from->_currentStateID);
to->_moveFunc = std::move(from->_moveFunc);
to->_copyFunc = std::move(from->_copyFunc);
to->_destroyFunc = std::move(from->_destroyFunc);
}
return *to;
}
public:
template <traits::fsm_state_c InitStateT>
MccFiniteStateMachine(InitStateT) : _currentStateID(InitStateT::ID)
{
using states_t = deduce_states_t<InitStateT>;
auto states = std::make_shared<states_t>();
auto currentState = std::make_shared<variant_from_tuple_t<states_t>>();
*currentState = &std::get<InitStateT>(*states);
_stateID = []<typename... STs>(std::tuple<STs...>&) {
return std::vector<std::string_view>({STs::ID...});
}(*states);
int status;
char* aa = abi::__cxa_demangle(typeid(states_t).name(), NULL, NULL, &status);
std::cout << "deduced states_t = " << aa << '\n';
free(aa);
aa = abi::__cxa_demangle(typeid(typename InitStateT::transition_t::event_state_pair_t).name(), NULL, NULL,
&status);
std::cout << "event_state_pair_t = " << aa << '\n';
free(aa);
aa = abi::__cxa_demangle(typeid(typename InitStateT::transition_t::events_t).name(), NULL, NULL, &status);
std::cout << "events_t = " << aa << '\n';
free(aa);
aa =
abi::__cxa_demangle(typeid(typename InitStateT::transition_t::unique_states_t).name(), NULL, NULL, &status);
std::cout << "unique_states_t = " << aa << '\n';
free(aa);
aa = abi::__cxa_demangle(typeid(deduce_events_t<InitStateT>).name(), NULL, NULL, &status);
std::cout << "deduced events_t = " << aa << '\n';
free(aa);
// setup dispatch event functions
using all_events_t = deduce_events_t<InitStateT>;
/*
for_each_event<all_events_t>([this]<traits::fsm_event_c EvT>(EvT) { _eventID.emplace_back(EvT::ID); });
for_each_event<all_events_t>([states, currentState, this]<traits::fsm_event_c EvT>(EvT) mutable {
if constexpr (!in_tuple_v<EvT, all_events_t>) {
throw std::system_error(MccFiniteStateMachineErrorCode::ERROR_UNREGISTERED_EVENT_TYPE);
}
_dispatchEventFunc<EvT>[this] = [states, currentState, this](EvT& event) mutable {
std::lock_guard lock(_transitionMutex);
std::visit(
[&event, states, currentState, this]<traits::fsm_state_c curr_state_t>(curr_state_t*) {
using to_state_t = curr_state_t::transition_t::template find_state_by_event_t<EvT>;
if constexpr (!std::is_void_v<to_state_t>) {
// exit from current
if constexpr (requires(curr_state_t inst) {
{ inst.exit(std::declval<EvT&>()) };
}) {
std::get<curr_state_t>(*states).exit(event);
} else if constexpr (requires(curr_state_t inst) {
{ inst.exit() };
}) {
std::get<curr_state_t>(*states).exit();
}
// transit ...
if constexpr (requires(EvT inst) {
{ inst.onTransit() };
}) {
event.onTransit();
}
*currentState = &std::get<to_state_t>(*states);
_currentStateID = to_state_t::ID;
// enter to new
if constexpr (requires(to_state_t inst) {
{ inst.enter(std::declval<EvT&>()) };
}) {
std::get<to_state_t>(*states).enter(event);
} else if constexpr (requires(to_state_t inst) {
{ inst.enter() };
}) {
std::get<to_state_t>(*states).enter();
}
} else {
throw std::system_error(MccFiniteStateMachineErrorCode::ERROR_UNHANDLED_TRANSITION);
}
},
*currentState);
};
_moveFunc.emplace_back([](MccFiniteStateMachine* from, MccFiniteStateMachine* to) {
_dispatchEventFunc<EvT>[to] = std::move(_dispatchEventFunc<EvT>[from]);
});
_copyFunc.emplace_back([](const MccFiniteStateMachine* from, MccFiniteStateMachine* to) {
_dispatchEventFunc<EvT>[to] = _dispatchEventFunc<EvT>[from];
});
_destroyFunc.emplace_back([](const MccFiniteStateMachine* inst) {
//
_dispatchEventFunc<EvT>.erase(inst);
});
});
setupInstanceFuncs<all_events_t>([states, currentState, this]<traits::fsm_event_c EvT>(EvT& event) mutable {
std::lock_guard lock(_transitionMutex);
std::visit(
[&event, states, currentState, this]<traits::fsm_state_c curr_state_t>(curr_state_t*) {
using to_state_t = curr_state_t::transition_t::template find_state_by_event_t<EvT>;
if constexpr (!std::is_void_v<to_state_t>) {
// exit from current
if constexpr (requires(curr_state_t inst) {
{ inst.exit(std::declval<EvT&>()) };
}) {
std::get<curr_state_t>(*states).exit(event);
} else if constexpr (requires(curr_state_t inst) {
{ inst.exit() };
}) {
std::get<curr_state_t>(*states).exit();
}
// transit ...
if constexpr (requires(EvT inst) {
{ inst.onTransit() };
}) {
event.onTransit();
}
*currentState = &std::get<to_state_t>(*states);
_currentStateID = to_state_t::ID;
// enter to new
if constexpr (requires(to_state_t inst) {
{ inst.enter(std::declval<EvT&>()) };
}) {
std::get<to_state_t>(*states).enter(event);
} else if constexpr (requires(to_state_t inst) {
{ inst.enter() };
}) {
std::get<to_state_t>(*states).enter();
}
} else {
throw std::system_error(MccFiniteStateMachineErrorCode::ERROR_UNHANDLED_TRANSITION);
}
},
*currentState);
});
*/
[states, currentState, this]<size_t... Is>(std::index_sequence<Is...>) {
((_dispatchEventFunc<std::tuple_element_t<Is, all_events_t>>[this] =
[states, currentState, this]<traits::fsm_event_c EvT>(EvT& event) mutable {
std::lock_guard lock(_transitionMutex);
std::visit(
[&event, states, currentState, this]<traits::fsm_state_c curr_state_t>(curr_state_t*) {
using to_state_t = curr_state_t::transition_t::template find_state_by_event_t<EvT>;
if constexpr (!std::is_void_v<to_state_t>) {
// exit from current
if constexpr (requires(curr_state_t inst) {
{ inst.exit(std::declval<EvT&>()) };
}) {
std::get<curr_state_t>(*states).exit(event);
} else if constexpr (requires(curr_state_t inst) {
{ inst.exit() };
}) {
std::get<curr_state_t>(*states).exit();
}
// transit ...
if constexpr (requires(EvT inst) {
{ inst.onTransit() };
}) {
event.onTransit();
}
*currentState = &std::get<to_state_t>(*states);
_currentStateID = to_state_t::ID;
// enter to new
if constexpr (requires(to_state_t inst) {
{ inst.enter(std::declval<EvT&>()) };
}) {
std::get<to_state_t>(*states).enter(event);
} else if constexpr (requires(to_state_t inst) {
{ inst.enter() };
}) {
std::get<to_state_t>(*states).enter();
}
} else {
throw std::system_error(MccFiniteStateMachineErrorCode::ERROR_UNHANDLED_TRANSITION);
}
},
*currentState);
}),
...);
(_moveFunc.emplace_back([](MccFiniteStateMachine* from, MccFiniteStateMachine* to) {
_dispatchEventFunc<std::tuple_element_t<Is, all_events_t>>[to] =
std::move(_dispatchEventFunc<std::tuple_element_t<Is, all_events_t>>[from]);
}),
...);
(_copyFunc.emplace_back([](const MccFiniteStateMachine* from, MccFiniteStateMachine* to) {
_dispatchEventFunc<std::tuple_element_t<Is, all_events_t>>[to] =
_dispatchEventFunc<std::tuple_element_t<Is, all_events_t>>[from];
}),
...);
(_destroyFunc.emplace_back([](const MccFiniteStateMachine* inst) {
//
_dispatchEventFunc<std::tuple_element_t<Is, all_events_t>>.erase(inst);
}),
...);
(_eventID.emplace_back(std::tuple_element_t<Is, all_events_t>::ID), ...);
}(std::make_index_sequence<std::tuple_size_v<all_events_t>>());
std::cout << "MOVE VEC: " << _moveFunc.size() << "\n";
}
MccFiniteStateMachine(const MccFiniteStateMachine& other)
{
copyInstance(&other, this);
}
MccFiniteStateMachine(MccFiniteStateMachine&& other)
{
moveInstance(&other, this);
}
MccFiniteStateMachine& operator=(const MccFiniteStateMachine& other)
{
return copyInstance(&other, this);
}
MccFiniteStateMachine& operator=(MccFiniteStateMachine&& other)
{
return moveInstance(&other, this);
}
virtual ~MccFiniteStateMachine()
{
for (auto& func : _destroyFunc) {
func(this);
}
}
template <traits::fsm_event_c EvT>
auto dispatchEvent(EvT& event)
{
if (!_dispatchEventFunc<EvT>[this]) {
throw std::system_error(MccFiniteStateMachineErrorCode::ERROR_UNREGISTERED_EVENT_TYPE);
}
_dispatchEventFunc<EvT>[this](event);
}
template <traits::fsm_event_c EvT>
auto dispatchEvent()
{
static EvT event;
return dispatchEvent(event);
}
std::string_view currentStateID() const
{
return _currentStateID;
}
template <mcc::traits::mcc_range_of_input_char_range R>
R stateIDs() const
{
R r;
for (auto& el : _stateID) {
std::back_inserter(r) = {el.begin(), el.end()};
}
return r;
}
std::vector<std::string_view> stateIDs() const
{
return stateIDs<std::vector<std::string_view>>();
}
template <mcc::traits::mcc_range_of_input_char_range R>
R eventIDs() const
{
R r;
for (auto& el : _eventID) {
std::back_inserter(r) = {el.begin(), el.end()};
}
return r;
}
std::vector<std::string_view> eventIDs() const
{
return eventIDs<std::vector<std::string_view>>();
}
};
} // namespace mcc::fsm