/* * Copyright 2013-2020 Software Radio Systems Limited * * This file is part of srsLTE. * * srsLTE is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * srsLTE is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * A copy of the GNU Affero General Public License can be found in * the LICENSE file in the top-level directory of this distribution * and at http://www.gnu.org/licenses/. * */ #ifndef SRSLTE_FSM_H #define SRSLTE_FSM_H #include "srslte/common/logmap.h" #include "srslte/common/move_callback.h" #include "type_utils.h" #include #include #include #include #include namespace srslte { //! 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 current state's name string struct state_name_visitor { template void operator()(State&& s) { name = get_type_name(s); } std::string name = "invalid"; }; //! 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; 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 using disable_if_subfsm = typename std::enable_if()>::type; //! 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; //! Detection of enter/exit methods of a state. template auto call_enter(FSM* f, State* s) -> decltype(s->enter(f)) { s->enter(f); } inline void call_enter(...) {} template auto call_exit(FSM* f, State* s) -> decltype(s->exit(f)) { s->exit(f); } inline void call_exit(...) {} //! Find State in FSM recursively (e.g. find State in FSM,FSM::parentFSM,FSM::parentFSM::parentFSM,...) template static auto get_state_recursive(FSM* f) -> enable_if_fsm_state { return &f->states.template get_unchecked(); } template typename FSM::derived_view* get_derived(FSM* f) { return static_cast(f); } 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"); return get_state_recursive(get_derived(f->parent_fsm())); } //! Helper type for FSM state-related operations template struct state_traits { static_assert(FSM::template can_hold_state(), "FSM type does not hold provided State\n"); using state_t = State; using is_subfsm = std::integral_constant()>; //! enter new state. enter is called recursively for subFSMs static void enter_state(FSM* f, State* s) { enter_(f, s, is_subfsm{}); } //! Change state. If DestState is not a state of FSM, call same function for parentFSM recursively template static enable_if_fsm_state transit_state(FSM* f) { call_exit(f, &f->states.template get_unchecked()); f->states.template transit(); state_traits::enter_state(f, &f->states.template get_unchecked()); } template static disable_if_fsm_state transit_state(FSM* f) { using parent_state_traits = state_traits; call_exit(f, &f->states.template get_unchecked()); parent_state_traits::template transit_state(get_derived(f->parent_fsm())); } private: //! In case of State is a subFSM static void enter_(FSM* f, State* s, std::true_type) { using init_type = typename fsm_state_list_type::init_state_t; // set default FSM type get_derived(s)->states.template transit(); // call FSM enter function call_enter(f, s); // call initial substate enter state_traits::enter_state( get_derived(s), &get_derived(s)->states.template get_unchecked()); } //! In case of State is basic state static void enter_(FSM* f, State* s, std::false_type) { call_enter(f, s); } }; //! Trigger Event reaction for the 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_fsm_activity("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_fsm_activity("FSM \"%s\": Transition detected - %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 state_traits::template transit_state(f); } return true; } return apply_first_guard_pass >::trigger(f, s, ev); } }; 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 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 disable_if_subfsm operator()(CurrentState& s) { result = call_react(s); } template enable_if_subfsm operator()(CurrentState& s) { // Enter here for SubFSMs result = s.trigger(std::forward(ev)); if (not result) { result = call_react(s); } } template bool call_react(SrcState& s) { using trigger_list = typename filter_transition_type::type; return apply_first_guard_pass::trigger(f, s, ev); } FSM* f; Event ev; bool result = false; }; } // namespace fsm_details template class nested_fsm_t; //! CRTP Class for all non-nested FSMs template class fsm_t { protected: using base_t = fsm_t; template using subfsm_t = nested_fsm_t; public: //! get access to derived protected members from the base class derived_view : public Derived { public: using derived_t = Derived; // propagate user fsm methods using Derived::states; using typename 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::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::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; static const bool is_nested = false; //! Struct used to store FSM states template struct state_list : public std::tuple { using tuple_base_t = std::tuple; using init_state_t = typename std::decay(std::declval()))>::type; static_assert(not type_list_contains(), "An FSM cannot contain itself as state\n"); template state_list(fsm_t* f, Args&&... args) : tuple_base_t(std::forward(args)...) { if (not Derived::is_nested) { // If Root FSM, call initial state enter method fsm_details::state_traits::enter_state(f->derived(), &get_unchecked()); } } template bool is() const { return type_idx() == current_idx; } template State& get_unchecked() { return std::get()>(*this); } template const State& get_unchecked() const { return std::get()>(*this); } template void transit() { current_idx = type_idx(); } template constexpr static bool can_hold_type() { return srslte::type_list_contains(); } template constexpr static size_t type_idx() { return get_type_index(); } size_t get_type_idx() const { return current_idx; } private: size_t current_idx = 0; }; explicit fsm_t(srslte::log_ref log_) : log_h(log_) {} // Push Events to FSM template bool trigger(Ev&& e) { if (trigger_locked) { scheduled_event(std::forward(e), typename std::is_lvalue_reference::type{}); return false; } trigger_locked = true; bool ret = process_event(std::forward(e)); while (not pending_events.empty()) { pending_events.front()(); pending_events.pop_front(); } trigger_locked = false; return ret; } template bool is_in_state() const { return derived()->states.template is(); } template const State* get_if_current_state() const { return is_in_state() ? get_state() : nullptr; } template State* get_state() { return &derived()->states.template get_unchecked(); } template const State* get_state() const { return &derived()->states.template get_unchecked(); } std::string current_state_name() const { fsm_details::state_name_visitor visitor{}; srslte::visit(visitor, derived()->states); return visitor.name; } //! Static method to check if State belongs to the list of possible states template constexpr static bool can_hold_state() { return fsm_details::fsm_state_list_type >::template can_hold_type(); } 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; } //! Log FSM activity method, e.g. state transitions template void log_fsm_activity(const char* format, Args&&... args) { switch (fsm_event_log_level) { case LOG_LEVEL_DEBUG: log_h->debug(format, std::forward(args)...); break; case LOG_LEVEL_INFO: log_h->info(format, std::forward(args)...); break; case LOG_LEVEL_WARNING: log_h->warning(format, std::forward(args)...); break; case LOG_LEVEL_ERROR: log_h->error(format, std::forward(args)...); break; default: break; } } protected: // Access to CRTP derived class derived_view* derived() { return static_cast(this); } const derived_view* derived() const { return static_cast(this); } template bool process_event(Ev&& e) { fsm_details::trigger_visitor visitor{derived(), std::forward(e)}; srslte::visit(visitor, derived()->states); return visitor.result; } template void scheduled_event(Ev&& e, std::true_type) { pending_events.emplace_back([this, e]() { process_event(e); }); } template void scheduled_event(Ev&& e, std::false_type) { pending_events.emplace_back(std::bind([this](Ev& e) { process_event(std::move(e)); }, std::move(e))); } srslte::log_ref log_h; srslte::LOG_LEVEL_ENUM fsm_event_log_level = LOG_LEVEL_INFO; bool trigger_locked = false; std::list > pending_events; }; template class nested_fsm_t : public fsm_t { public: using base_t = nested_fsm_t; using parent_t = ParentFSM; static const bool is_nested = true; explicit nested_fsm_t(ParentFSM* parent_fsm_) : fsm_t(parent_fsm_->get_log()), fsm_ptr(parent_fsm_) {} nested_fsm_t(nested_fsm_t&&) = default; nested_fsm_t& operator=(nested_fsm_t&&) = default; // 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; } protected: using parent_fsm_t = ParentFSM; ParentFSM* fsm_ptr = nullptr; }; // event template struct proc_launch_ev { std::tuple args; explicit proc_launch_ev(Args&&... args_) : args(std::forward(args_)...) {} }; template class proc_fsm_t : public fsm_t { using fsm_type = Derived; using fsm_t::derived; protected: using fsm_t::log_h; public: using base_t = proc_fsm_t; using fsm_t::trigger; // events struct reset_ev {}; // states struct idle_st { idle_st() = default; template idle_st(bool success_, T&& r) : success(success_), result(std::forward(r)), value_set(true) {} void enter(Derived* f) { if (f->launch_counter > 0) { f->log_h->info("FSM \"%s\": Finished run no. %d %s\n", get_type_name().c_str(), f->launch_counter, is_success() ? "successfully" : "with an error"); if (not is_result_set()) { f->log_h->error( "FSM \"%s\": No result was set for run no. %d\n", get_type_name().c_str(), f->launch_counter); } } } void exit(Derived* f) { value_set = false; success = false; f->launch_counter++; f->log_h->info("FSM \"%s\": Starting run no. %d\n", get_type_name().c_str(), f->launch_counter); } bool is_result_set() const { return value_set; } bool is_success() const { return value_set and success; } const Result& get_result() const { return result; } private: bool success = false, value_set = false; Result result = {}; }; explicit proc_fsm_t(srslte::log_ref log_) : fsm_t(log_) {} bool is_running() const { return base_t::template is_in_state(); } bool is_success() const { return base_t::template get_state()->is_success(); } const Result& get_result() const { if (is_success()) { return base_t::template get_state->get_result(); } THROW_BAD_ACCESS("in proc_fsm_t::get_result"); } template void launch(Args&&... args) { trigger(proc_launch_ev(std::forward(args)...)); } private: int launch_counter = 0; }; // Generic events struct success_ev {}; struct failure_ev {}; } // namespace srslte #endif // SRSLTE_FSM_H