From 04e192eb0fce2f8e4f05febed721b56c45180f13 Mon Sep 17 00:00:00 2001 From: Francisco Paisana Date: Tue, 28 Apr 2020 19:45:44 +0100 Subject: [PATCH] redesign fsm to use transition table --- lib/include/srslte/common/fsm.h | 368 ++++++++++++++++--------- lib/include/srslte/common/type_utils.h | 120 ++++++-- lib/test/common/fsm_test.cc | 223 ++++++--------- 3 files changed, 408 insertions(+), 303 deletions(-) diff --git a/lib/include/srslte/common/fsm.h b/lib/include/srslte/common/fsm.h index 8768b997e..4bd7530b9 100644 --- a/lib/include/srslte/common/fsm.h +++ b/lib/include/srslte/common/fsm.h @@ -31,35 +31,26 @@ namespace srslte { -//! Transition Type -template -struct to_state { - using next_state = NextState; -}; - -template -struct to_states { - template - to_states(to_state) : state_idx(get_type_index()) - {} - - template - bool is() const - { - return get_type_index() == state_idx; - } - - size_t get_type_idx() const { return state_idx; } - - size_t state_idx; -}; - -//! Forward declaration +//! Forward declarations template class fsm_t; namespace fsm_details { +//! Meta-function to filter transition list by types +template +struct filter_transition_type; +template +struct filter_transition_type > { + template + using predicate = typename Row::template is_match; + using type = typename type_utils::filter::type; +}; +template +struct filter_transition_type > { + using type = type_list<>; +}; + //! Visitor to get a state's name string struct state_name_visitor { template @@ -67,39 +58,34 @@ struct state_name_visitor { { name = get_type_name(s); } + std::string name = "invalid"; }; -//! Visitor to convert a to_state back to a single state -template -struct to_state_visitor { - to_state_visitor(FSM* f_, PrevState* p_) : f(f_), p(p_) {} - template - void operator()(); - FSM* f; - PrevState* p; -}; +//! Enable/Disable meta-function if is part of state list +template +using enable_if_fsm_state = typename std::enable_if(), T>::type; +template +using disable_if_fsm_state = typename std::enable_if(), T>::type; -//! Helper metafunctions -template -using enable_if_fsm_state = typename std::enable_if()>::type; -template -using disable_if_fsm_state = typename std::enable_if()>::type; template constexpr bool is_fsm() { return std::is_base_of, FSM>::value; } + template constexpr typename std::enable_if(), bool>::type is_subfsm() { return FSM::is_nested; } + template constexpr typename std::enable_if(), bool>::type is_subfsm() { return false; } + template using enable_if_subfsm = typename std::enable_if()>::type; template @@ -109,8 +95,10 @@ struct fsm_helper { //! Metafunction to determine if FSM can hold given State type template using fsm_state_list_type = decltype(std::declval().states); + template + using fsm_transitions = typename FSM::derived_view::transitions; - //! Call FSM/State enter method + //! Call FSM/State enter method. If subfsm, call enter of subfsm init state as well template static enable_if_subfsm call_enter(FSM* f, State* s) { @@ -122,55 +110,135 @@ struct fsm_helper { // call initial substate enter call_enter(s->derived(), &s->derived()->states.template get_unchecked()); } + template static disable_if_subfsm call_enter(FSM* f, State* s) { f->enter(*s); } - //! TargetState is type-erased (a choice). Apply its stored type to the fsm current state - template - static void handle_state_change(FSM* f, to_states* s, PrevState* p) + //! Simple state transition in FSM + template + static auto handle_state_transition(FSM* f) -> enable_if_fsm_state { - to_state_visitor visitor{f, p}; - srslte::static_visit(visitor, *s); + f->exit(f->states.template get_unchecked()); + f->states.template transit(); + call_enter(f, &f->states.template get_unchecked()); } - //! Simple state transition in FSM (no same_state of entry in nested FSM) - template - static auto handle_state_change(FSM* f, to_state* s, PrevState* p) -> enable_if_fsm_state + + //! State not present in current FSM. Attempt state transition in parent FSM in the case of NestedFSM + template + static auto handle_state_transition(FSM* f) -> disable_if_fsm_state { - if (std::is_same::value) { - f->log_h->info("FSM \"%s\": No transition occurred while in state \"%s\"\n", - get_type_name().c_str(), - get_type_name().c_str()); - return; - } - f->exit(f->states.template get_unchecked()); - f->states.template transit(); - f->log_h->info("FSM \"%s\": Detected transition \"%s\" -> \"%s\"", - get_type_name().c_str(), - get_type_name().c_str(), - get_type_name().c_str()); - call_enter(f, &f->states.template get_unchecked()); + static_assert(FSM::is_nested, "State is not present in the FSM list of valid states"); + f->exit(f->states.template get_unchecked()); + handle_state_transition( + f->parent_fsm()->derived()); } - //! State not present in current FSM. Attempt state transition in parent FSM in the case of NestedFSM - template - static auto handle_state_change(FSM* f, to_state* s, PrevState* p) -> disable_if_fsm_state + + template + static auto get_state_recursive(FSM* f) -> enable_if_fsm_state + { + return &f->states.template get_unchecked(); + } + + template + static auto get_state_recursive(FSM* f) -> disable_if_fsm_state { static_assert(FSM::is_nested, "State is not present in the FSM list of valid states"); - f->exit(f->states.template get_unchecked()); - handle_state_change(f->parent_fsm()->derived(), s, static_cast(f)); + return get_state_recursive(f->parent_fsm()->derived()); } + //! Trigger in first Row for which the Guard passes + template + struct apply_first_guard_pass; + + template + struct apply_first_guard_pass > { + template + static bool trigger(FSM* f, SrcState& s, const typename First::event_t& ev) + { + using src_state = SrcState; + using dest_state = typename First::dest_state_t; + using event_type = typename First::event_t; + + bool triggered = First::react(f, s, ev); + if (triggered) { + // Log Transition + if (std::is_same::value) { + f->log_h->info("FSM \"%s\": Event \"%s\" updated state \"%s\"\n", + get_type_name().c_str(), + get_type_name().c_str(), + get_type_name().c_str()); + } else { + f->log_h->info("FSM \"%s\": Transition %s-->%s (cause: %s)", + get_type_name().c_str(), + get_type_name().c_str(), + get_type_name().c_str(), + get_type_name().c_str()); + // Apply state change operations + handle_state_transition(f); + } + return true; + } + return apply_first_guard_pass >::trigger(f, s, ev); + } + + // template + // bool test_react(FSM *f, SrcState &s, const typename First::event_t &ev) { + // using dest_state = typename First::dest_state_t; + // if (First::guard == nullptr or (f->*First::guard)(ev)) { + // // Guard Passed. Apply react method + // dest_state *d = get_state_recursive(f); + // if (First::react != nullptr) { + // (f->*First::react)(*d, ev); + // } + // return true; + // } + // return false; + // } + // + // template<> + // bool + // test_react(FSM *f, typename First::src_state_t &s, const typename First::event_t + // &ev) { + // using dest_state = typename First::dest_state_t; + // + // if (First::guard == nullptr or (f->*First::guard)(s, ev)) { + // // Guard Passed. Apply react method + // dest_state *d = get_state_recursive(f); + // if (First::react != nullptr) { + // (f->*First::react)(s, *d, ev); + // } + // return true; + // } + // return false; + // } + }; + + template + struct apply_first_guard_pass > { + template + static bool trigger(FSM* f, SrcState& s, const Event& ev) + { + f->log_fsm_activity("FSM \"%s\": Unhandled event caught: \"%s\"\n", + get_type_name().c_str(), + get_type_name().c_str()); + return false; + } + }; + //! Trigger Event, that will result in a state transition template struct trigger_visitor { + using event_t = typename std::decay::type; + trigger_visitor(FSM* f_, Event&& ev_) : f(f_), ev(std::forward(ev_)) {} /** * @brief Trigger visitor callback for the current state. - * @description tries to find an fsm::trigger method in case the current state is a nested fsm. If it does not - * find it, searches for a react(current_state&, event) method at the current level + * @description tries to find an fsm::trigger method in case the current state is a subfsm. If it does not + * find it, searches for a react(current_state&, dest_state&, event) method at the current level * Stores True in "result" if state changed. False otherwise */ template @@ -178,6 +246,7 @@ struct fsm_helper { { result = call_react(s); } + template enable_if_subfsm operator()(CurrentState& s) { @@ -188,23 +257,12 @@ struct fsm_helper { } } - template - using enable_if_react = decltype(std::declval().react(std::declval(), std::declval()), - bool()); - //! In case there is a react method - template - auto call_react(State& s) -> decltype(std::declval().react(s, std::declval()), bool()) - { - auto target_state = f->react(s, std::forward(ev)); - fsm_helper::handle_state_change(f, &target_state, &s); - return true; - } - template - bool call_react(Args...) + template + bool call_react(SrcState& s) { - f->log_fsm_activity( - "FSM \"%s\": Unhandled event caught: \"%s\"\n", get_type_name().c_str(), get_type_name().c_str()); - return false; + using trigger_list = + typename filter_transition_type::type; + return apply_first_guard_pass::trigger(f, s, ev); } FSM* f; @@ -213,25 +271,8 @@ struct fsm_helper { }; }; -template -template -void to_state_visitor::operator()() -{ - to_state t; - fsm_helper::handle_state_change(f, &t, p); -} - } // namespace fsm_details -//! Gets the typename currently stored in the choice_t -template -std::string get_type_name(const srslte::to_states& t) -{ - fsm_details::state_name_visitor v{}; - srslte::visit(v, t); - return v.name; -} - template class nested_fsm_t; @@ -244,7 +285,6 @@ protected: template using subfsm_t = nested_fsm_t; -public: //! get access to derived protected members from the base class derived_view : public Derived { @@ -255,15 +295,75 @@ public: // propagate user fsm methods using Derived::enter; using Derived::exit; - using Derived::react; using Derived::states; + using Derived::transitions; + }; + + //! Params of a state transition + template + struct row { + using src_state_t = SrcState; + using dest_state_t = DestState; + using event_t = Event; + + constexpr static void (Derived::*react_fn)(SrcState&, DestState&, const Event&) = ReactFn; + + constexpr static bool (Derived::*guard_fn)(SrcState&, const Event&) const = GuardFn; + + static bool react(derived_view* f, src_state_t& s, const event_t& ev) + { + if (guard_fn == nullptr or (f->*guard_fn)(s, ev)) { + dest_state_t* d = fsm_details::fsm_helper::get_state_recursive(f); + if (react_fn != nullptr) { + (f->*react_fn)(s, *d, ev); + } + return true; + } + return false; + } + + template + using is_match = std::is_same, type_list >; + }; + + template + struct from_any_state { + using dest_state_t = DestState; + using event_t = Event; + + constexpr static void (Derived::*react_fn)(DestState&, const Event&) = ReactFn; + + constexpr static bool (Derived::*guard_fn)(const Event&) const = GuardFn; + + template + static bool react(derived_view* f, SrcState& s, const event_t& ev) + { + if (guard_fn == nullptr or (f->*guard_fn)(ev)) { + dest_state_t* d = fsm_details::fsm_helper::get_state_recursive(f); + if (react_fn != nullptr) { + (f->*react_fn)(*d, ev); + } + return true; + } + return false; + } + + template + using is_match = std::is_same; }; + template + using transition_table = type_list; + +public: static const bool is_nested = false; - template - using to_state = srslte::to_state; - template - using to_states = srslte::to_states; //! Struct used to store FSM states template @@ -372,14 +472,16 @@ public: return fsm_details::fsm_helper::fsm_state_list_type >::template can_hold_type(); } - void set_fsm_event_log_level(srslte::LOG_LEVEL_ENUM e) { fsm_event_log_level = e; } + void set_fsm_event_log_level(srslte::LOG_LEVEL_ENUM e) { fsm_event_log_level = e; } + srslte::log_ref get_log() const { return log_h; } protected: friend struct fsm_details::fsm_helper; // Access to CRTP derived class - derived_view* derived() { return static_cast(this); } + derived_view* derived() { return static_cast(this); } + const derived_view* derived() const { return static_cast(this); } template @@ -387,12 +489,14 @@ protected: { // do nothing by default } + template void exit(State& s) { // do nothing by default } + //! Log FSM activity method, e.g. state transitions template void log_fsm_activity(const char* format, Args&&... args) { @@ -430,7 +534,8 @@ public: // Get pointer to outer FSM in case of HSM const parent_t* parent_fsm() const { return fsm_ptr; } - parent_t* parent_fsm() { return fsm_ptr; } + + parent_t* parent_fsm() { return fsm_ptr; } protected: using parent_fsm_t = ParentFSM; @@ -443,6 +548,7 @@ protected: template struct proc_complete_ev { proc_complete_ev(bool success_) : success(success_) {} + bool success; }; @@ -450,6 +556,7 @@ struct proc_complete_ev { template struct proc_launch_ev { std::tuple args; + explicit proc_launch_ev(Args&&... args_) : args(std::forward(args_)...) {} }; @@ -465,13 +572,6 @@ protected: using fsm_t::enter; using fsm_t::exit; - template - auto react(State&, srslte::proc_launch_ev e) -> to_state - { - log_h->warning("Unhandled event \"launch\" caught when procedure is already running\n"); - return {}; - } - public: using base_t = proc_fsm_t; using fsm_t::trigger; @@ -480,8 +580,10 @@ public: struct reset_ev {}; // states - struct idle_st {}; - struct complete_st {}; + struct idle_st { + bool success = false; + Result result = {}; + }; explicit proc_fsm_t(srslte::log_ref log_) : fsm_t(log_) {} @@ -494,39 +596,31 @@ public: } protected: + void enter(idle_st& s) + { + if (launch_counter > 0) { + log_h->info("Finished run no. %d %s\n", launch_counter, s.success ? "successfully" : "with an error"); + } + } + void exit(idle_st& s) { launch_counter++; - log_h->info("Starting run no. %d\n", launch_counter); + log_h->info("FSM \"%s\": Starting run no. %d\n", get_type_name().c_str(), launch_counter); } - void enter(complete_st& s) { trigger(reset_ev{}); } - auto react(complete_st& s, reset_ev ev) -> to_state { return {}; } + bool is_success() const { return base_t::template get_state()->success; } - srslte::to_state set_success(Result&& r = {}) - { - result = std::forward(r); - success = true; - return {}; - } - srslte::to_state set_failure() - { - success = false; - return {}; - } - bool is_success() const { return success; } const Result& get_result() const { if (is_success()) { - return result; + return base_t::template get_state->result; } THROW_BAD_ACCESS("in proc_fsm_t::get_result"); } private: - int launch_counter = 0; - bool success = false; - Result result = {}; + int launch_counter = 0; }; } // namespace srslte diff --git a/lib/include/srslte/common/type_utils.h b/lib/include/srslte/common/type_utils.h index 6edd6b9e2..fd31faa4f 100644 --- a/lib/include/srslte/common/type_utils.h +++ b/lib/include/srslte/common/type_utils.h @@ -45,6 +45,10 @@ public: std::abort() #endif +/************************************ + * get_type_name methods + ***********************************/ + //! Helper to print the name of a type for logging /** * @brief Helper function that returns a type name string @@ -97,9 +101,26 @@ std::string get_type_name(const T& t) return get_type_name(); } +/************************************ + * type_list + ***********************************/ + constexpr size_t invalid_type_index = std::numeric_limits::max(); -namespace type_utils_details { +template +struct type_list {}; + +template +constexpr size_t type_list_size(type_list t) +{ + return sizeof...(Args); +} + +/************************************ + * get_index_type/get_type_index + ***********************************/ + +namespace type_utils { //! Get Index of a type in a list of types (in reversed order) template @@ -131,6 +152,70 @@ struct get_type_reverse { using type = void; }; +} // namespace type_utils + +//! Get index of T in Types... +template +constexpr size_t get_type_index() +{ + using namespace type_utils; + return (type_list_reverse_index::index == invalid_type_index) + ? invalid_type_index + : sizeof...(Types) - type_list_reverse_index::index - 1; +} + +//! If T is in Types... +template +constexpr bool type_list_contains() +{ + return get_type_index() != invalid_type_index; +} + +//! Get type of index I in Types... +template +struct get_index_type { + using type = typename type_utils::get_type_reverse::type; +}; + +/************************** + * Filter utils + *************************/ + +namespace type_utils { + +//! pushes type to front of type_list +template +struct push_front; +template +struct push_front > { + using type = type_list; +}; +template <> +struct push_front<> { + using type = type_list<>; +}; + +//! Creates a type_list with the "Types..." for which Predicate::value is true +template