/** * Copyright 2013-2022 Software Radio Systems Limited * * This file is part of srsRAN. * * srsRAN 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. * * srsRAN 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 SRSRAN_FSM_H #define SRSRAN_FSM_H #include "srsran/adt/detail/type_utils.h" #include "srsran/adt/move_callback.h" #include "srsran/srslog/srslog.h" #include #include #include #include #define otherfsmDebug(f, fmt, ...) f->get_logger().debug("FSM \"%s\" - " fmt, get_type_name(*f).c_str(), ##__VA_ARGS__) #define otherfsmInfo(f, fmt, ...) f->get_logger().info("FSM \"%s\" - " fmt, get_type_name(*f).c_str(), ##__VA_ARGS__) #define otherfsmWarning(f, fmt, ...) \ f->get_logger().warning("FSM \"%s\" - " fmt, get_type_name(*f).c_str(), ##__VA_ARGS__) #define otherfsmError(f, fmt, ...) f->get_logger().error("FSM \"%s\" - " fmt, get_type_name(*f).c_str(), ##__VA_ARGS__) #define fsmDebug(fmt, ...) otherfsmDebug(this, fmt, ##__VA_ARGS__) #define fsmInfo(fmt, ...) otherfsmInfo(this, fmt, ##__VA_ARGS__) #define fsmWarning(fmt, ...) otherfsmWarning(this, fmt, ##__VA_ARGS__) #define fsmError(fmt, ...) otherfsmError(this, fmt, ##__VA_ARGS__) namespace srsran { /// Forward declarations template class base_fsm_t; template class composite_fsm_t; /// Check if type T is an FSM template using is_fsm = std::is_base_of, T>; /// Check if type T is a composite FSM template struct is_composite_fsm : public std::false_type {}; template struct is_composite_fsm::value>::type> { const static bool value = T::is_nested; }; namespace fsm_details { /// check whether to log unhandled event template auto should_log_unhandled_event(const Event* ev) -> decltype(Event::log_verbose) { return Event::log_verbose; } inline bool should_log_unhandled_event(...) { return true; } //! 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 SFINAE meta-function to check 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 using enable_if_subfsm = typename std::enable_if::value>::type; template using disable_if_subfsm = typename std::enable_if::value>::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_enter2(FSM* f, State* s) -> decltype(s->enter(f)) { s->enter(f); } inline void call_enter2(...) { // do nothing } template auto call_enter(FSM* f, State* s, const Event& ev) -> decltype(s->enter(f, ev)) { // pass event to enter method s->enter(f, ev); } template inline void call_enter(FSM* f, State* s, Args&&...) { call_enter2(f, s); } 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"); using state_t = State; using is_subfsm = std::integral_constant::value>; //! enter new state. enter is called recursively for subFSMs template static void enter_state(FSM* f, State* s, const Event& ev) { enter_(f, s, ev, 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, const Event& ev) { call_exit(f, &f->states.template get_unchecked()); f->states.template transit(); state_traits::enter_state(f, &f->states.template get_unchecked(), ev); } template static disable_if_fsm_state transit_state(FSM* f, const Event& ev) { 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()), ev); } private: //! In case of State is a subFSM template static void enter_(FSM* f, State* s, const Event& ev, 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, ev); // call initial substate enter state_traits::enter_state( get_derived(s), &get_derived(s)->states.template get_unchecked(), ev); } //! In case of State is basic state template static void enter_(FSM* f, State* s, const Event& ev, std::false_type) { call_enter(f, s, ev); } }; //! 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) { otherfsmInfo(static_cast(f), "Event \"%s\" triggered state \"%s\" update", get_type_name().c_str(), get_type_name().c_str()); } else { otherfsmInfo(static_cast(f), "transition detected - %s -> %s (cause: %s)", 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, ev); } 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) { if (should_log_unhandled_event(&ev)) { otherfsmDebug(static_cast(f), "unhandled event caught in state \"%s\": \"%s\"", get_type_name().c_str(), get_type_name().c_str()); } return false; } }; /// Trigger Event that may 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.process_event(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 base_fsm_t { public: using derived_t = Derived; //! get access to derived protected members from the base class derived_view : public Derived { public: // propagate user fsm methods using Derived::states; using typename Derived::transitions; }; template using transition_table = type_list; //! 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&, const Event&) = ReactFn; constexpr static bool (Derived::*guard_fn)(SrcState&, const Event&) = GuardFn; // ignore warning "never nullptr" for template specialization w/wo defaults for ReactFn or GuardFn _Pragma("GCC diagnostic push"); // save current diagnostic config _Pragma("GCC diagnostic ignored \"-Waddress\""); // ignore -Waddress static bool react(derived_view* f, src_state_t& s, const event_t& ev) { if (guard_fn == nullptr or (f->*guard_fn)(s, ev)) { if (react_fn != nullptr) { (f->*react_fn)(s, ev); } return true; } return false; } _Pragma("GCC diagnostic pop"); // restore diagnostic config template using is_match = std::is_same, type_list >; }; template using upd = row; template struct to_state { using dest_state_t = DestState; using event_t = Event; constexpr static void (Derived::*react_fn)(const Event&) = ReactFn; constexpr static bool (Derived::*guard_fn)(const Event&) = GuardFn; // ignore warning "never nullptr" for template specialization w/wo defaults for ReactFn or GuardFn _Pragma("GCC diagnostic push"); // save current diagnostic config _Pragma("GCC diagnostic ignored \"-Waddress\""); // ignore -Waddress template static bool react(derived_view* f, SrcState& s, const event_t& ev) { if (guard_fn == nullptr or (f->*guard_fn)(ev)) { if (react_fn != nullptr) { (f->*react_fn)(ev); } return true; } return false; } _Pragma("GCC diagnostic pop"); // restore diagnostic config template using is_match = std::is_same; }; //! 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"); template state_list(base_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(), std::false_type{}); } } 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 srsran::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; }; 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{}; srsran::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(); } 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)}; srsran::visit(visitor, derived()->states); return visitor.result; } }; template class composite_fsm_t; //! CRTP Class for all non-nested FSMs template class fsm_t : public base_fsm_t { protected: using base_t = fsm_t; template using subfsm_t = composite_fsm_t; public: static const bool is_nested = false; explicit fsm_t(srslog::basic_logger& logger) : logger(logger) {} // 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; } void set_fsm_event_log_level(srslog::basic_levels lvl) { log_level = lvl; } srslog::basic_logger& get_logger() const { return logger; } bool is_trigger_locked() const { return trigger_locked; } //! Log FSM activity method, e.g. state transitions template void log_fsm_activity(const char* format, Args&&... args) { switch (log_level) { case srslog::basic_levels::debug: logger.debug(format, std::forward(args)...); break; case srslog::basic_levels::info: logger.info(format, std::forward(args)...); break; case srslog::basic_levels::warning: logger.warning(format, std::forward(args)...); break; case srslog::basic_levels::error: logger.error(format, std::forward(args)...); break; default: break; } } protected: using base_fsm_t::derived; using base_fsm_t::process_event; template void scheduled_event(Ev&& e, std::true_type t) { pending_events.emplace_back([this, e]() { process_event(e); }); } template void scheduled_event(Ev&& e, std::false_type t) { pending_events.emplace_back(std::bind([this](Ev& e) { process_event(std::move(e)); }, std::move(e))); } srslog::basic_logger& logger; srslog::basic_levels log_level = srslog::basic_levels::info; bool trigger_locked = false; std::deque > pending_events; }; template class composite_fsm_t : public base_fsm_t { public: using base_t = composite_fsm_t; using parent_t = ParentFSM; static const bool is_nested = true; explicit composite_fsm_t(ParentFSM* parent_fsm_) : fsm_ptr(parent_fsm_) {} composite_fsm_t(composite_fsm_t&&) noexcept = default; composite_fsm_t& operator=(composite_fsm_t&&) noexcept = default; // Get pointer to outer FSM in case of HFSM const parent_t* parent_fsm() const { return fsm_ptr; } parent_t* parent_fsm() { return fsm_ptr; } srslog::basic_logger& get_logger() const { return parent_fsm()->get_logger(); } // Push Events to root FSM template bool trigger(Ev&& e) { return parent_fsm()->trigger(std::forward(e)); } // Push events to this subFSM using base_fsm_t::process_event; protected: using parent_fsm_t = ParentFSM; ParentFSM* fsm_ptr = nullptr; }; /************************** * Procedure FSM *************************/ template struct proc_launch_ev { T args; }; template struct proc_complete_ev { Result result; }; struct failure_ev {}; template class proc_fsm_t : public fsm_t { using fsm_type = Derived; using fsm_t::derived; protected: using fsm_t::logger; public: using base_t = proc_fsm_t; using fsm_t::trigger; // events template using launch_ev = srsran::proc_launch_ev; using complete_ev = srsran::proc_complete_ev; // states struct idle_st { void enter(Derived* f) { if (f->launch_counter > 0) { f->logger.warning( "FSM \"%s\": No result was set for run no. %d", get_type_name().c_str(), f->launch_counter); } } void enter(Derived* f, const complete_ev& ev) { f->logger.info("FSM \"%s\": Finished run no. %d", get_type_name().c_str(), f->launch_counter); f->last_result = ev.result; for (auto& func : f->listening_fsms) { func(ev); } f->listening_fsms.clear(); } void exit(Derived* f) { f->launch_counter++; f->logger.info("FSM \"%s\": Starting run no. %d", get_type_name().c_str(), f->launch_counter); } }; explicit proc_fsm_t(srslog::basic_logger& logger) : fsm_t(logger) {} bool is_running() const { return not base_t::template is_in_state(); } const Result& get_result() const { srsran_assert(launch_counter > 0 and base_t::template is_in_state(), "in proc_fsm_t::get_result"); return last_result; } template void await(OtherFSM* f) { if (is_running()) { listening_fsms.push_back([f](const complete_ev& ev) { return f->trigger(ev); }); } else { f->trigger(last_result); } } private: int launch_counter = 0; Result last_result = {}; std::vector > listening_fsms; }; template class proc_wait_st { public: explicit proc_wait_st(ProcFSM* proc_ptr_) : proc_ptr(proc_ptr_) {} template void enter(FSM* f, const Ev& ev) { if (proc_ptr->is_running()) { f->get_logger().error("Unable to launch proc1"); f->trigger(typename ProcFSM::complete_ev{false}); } proc_ptr->trigger(srsran::proc_launch_ev{ev}); proc_ptr->await(f); } private: ProcFSM* proc_ptr = nullptr; }; /************************************** * Event Trigger Scheduling *************************************/ template struct event_callback { event_callback() = default; template explicit event_callback(FSM* f) { callback = [f](const Event& ev) { f->trigger(ev); }; } void operator()(const Event& ev) { callback(ev); } void operator()(const Event& ev) const { callback(ev); } srsran::move_task_t to_move_task(const Event& ev) { auto& copied_callback = callback; return [copied_callback, ev]() { copied_callback(ev); }; } std::function callback; }; template srsran::move_task_t make_move_task(const event_callback& callback, const Event& ev) { auto& copied_callback = callback; return [copied_callback, ev]() { copied_callback(ev); }; } template srsran::move_task_t make_move_task(std::vector >&& callbacks, const Event& ev) { return std::bind( [ev](const std::vector >& callbacks) { for (const auto& callback : callbacks) { callback(ev); } }, std::move(callbacks)); } } // namespace srsran #endif // SRSRAN_FSM_H