/* * 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 "type_utils.h" #include #include #include #include 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 template class fsm_t; namespace fsm_details { //! Visitor to get a state's name string struct state_name_visitor { template void operator()(State&& s) { 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; }; //! 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 using disable_if_subfsm = typename std::enable_if()>::type; struct fsm_helper { //! Metafunction to determine if FSM can hold given State type template using fsm_state_list_type = decltype(std::declval().states); //! Call FSM/State enter method template static enable_if_subfsm call_enter(FSM* f, State* s) { using init_type = typename fsm_state_list_type::init_state_t; // set default FSM type s->derived()->states.template transit(); // call FSM enter function f->enter(*s); // 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) { to_state_visitor visitor{f, p}; srslte::static_visit(visitor, *s); } //! 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 { 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()); } //! 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 { 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)); } //! Trigger Event, that will result in a state transition template struct trigger_visitor { 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 * 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 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...) { f->log_fsm_activity( "FSM \"%s\": Unhandled event caught: \"%s\"\n", get_type_name().c_str(), get_type_name().c_str()); return false; } FSM* f; Event ev; bool result = false; }; }; 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; //! 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; using derived_t::base_t::enter; using derived_t::base_t::exit; // propagate user fsm methods using Derived::enter; using Derived::exit; using Derived::react; using Derived::states; }; 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 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::fsm_helper::call_enter(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) { fsm_details::fsm_helper::trigger_visitor visitor{derived(), std::forward(e)}; srslte::visit(visitor, derived()->states); return visitor.result; } 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_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; } 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); } const derived_view* derived() const { return static_cast(this); } template void enter(State& s) { // do nothing by default } template void exit(State& s) { // do nothing by default } 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; } } srslte::log_ref log_h; srslte::LOG_LEVEL_ENUM fsm_event_log_level = LOG_LEVEL_INFO; }; 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_) {} // 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; using fsm_t::enter; using fsm_t::exit; ParentFSM* fsm_ptr = nullptr; }; template struct proc_complete_ev { proc_complete_ev(bool success_) : success(success_) {} bool success; }; // 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; friend struct fsm_details::fsm_helper; protected: using fsm_t::log_h; 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; // events struct reset_ev {}; // states struct idle_st {}; struct complete_st {}; explicit proc_fsm_t(srslte::log_ref log_) : fsm_t(log_) {} bool is_running() const { return base_t::template is_in_state(); } template void launch(Args&&... args) { trigger(proc_launch_ev(std::forward(args)...)); } protected: void exit(idle_st& s) { launch_counter++; log_h->info("Starting run no. %d\n", launch_counter); } void enter(complete_st& s) { trigger(reset_ev{}); } auto react(complete_st& s, reset_ev ev) -> to_state { return {}; } 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; } THROW_BAD_ACCESS("in proc_fsm_t::get_result"); } private: int launch_counter = 0; bool success = false; Result result = {}; }; } // namespace srslte #endif // SRSLTE_FSM_H