|
||||
File indexing completed on 2025-01-18 09:11:10
0001 // This file is part of the ACTS project. 0002 // 0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project 0004 // 0005 // This Source Code Form is subject to the terms of the Mozilla Public 0006 // License, v. 2.0. If a copy of the MPL was not distributed with this 0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/. 0008 0009 #pragma once 0010 0011 #include <optional> 0012 #include <string_view> 0013 #include <variant> 0014 0015 namespace Acts { 0016 0017 /// Implementation of a finite state machine engine 0018 /// 0019 /// Allows setting up a system of states and transitions between them. States 0020 /// are definedd as empty structs (footprint: 1 byte). Transitions call 0021 /// functions using overload resolution. This works by subclassing this class, 0022 /// providing the deriving type as the first template argument (CRTP) and 0023 /// providing methods like 0024 /// 0025 /// ```cpp 0026 /// event_return on_event(const S&, const E&); 0027 /// ``` 0028 /// 0029 /// The arguments are the state `S` and the triggered event `E`. Their values 0030 /// can be discarded (you can attach values to events of course, if you like) 0031 /// The return type of these functions is effectively `std::optional<State>`, so 0032 /// you can either return `std::nullopt` to remain in the same state, or an 0033 /// instance of another state. That state will then become active. 0034 /// 0035 /// You can also define a method template, which will serve as a catch-all 0036 /// handler (due to the fact that it will match any state/event combination): 0037 /// 0038 /// ```cpp 0039 /// template <typename State, typename Event> 0040 /// event_return on_event(const State&, const Event&) const { 0041 /// return Terminated{}; 0042 /// } 0043 /// ``` 0044 /// 0045 /// If for a given state and event no suitable overload of `on_event` (and you 0046 /// also haven't defined a catch-all as described above), a transition to 0047 /// `Terminated` will be triggered. This is essentially equivalent to the method 0048 /// template above. 0049 /// 0050 /// If this triggers, it will switch to the `Terminated` state (which is always 0051 /// included in the FSM). 0052 /// 0053 /// Additionally, the FSM will attempt to call functions like 0054 /// ```cpp 0055 /// void on_enter(const State&); 0056 /// void on_exit(const State&); 0057 /// ``` 0058 /// when entering/exiting a state. This can be used to 0059 /// perform actions regardless of the source or destination state in a 0060 /// transition to a given state. This is also fired in case a transition to 0061 /// `Terminated` occurs. 0062 /// 0063 /// The base class also calls 0064 /// ```cpp 0065 /// void on_process(const Event&); 0066 /// void on_process(const State&, const Event&); 0067 /// void on_process(const State1& const Event&, const State2&); 0068 /// ``` 0069 /// during event processing, and allow for things like event and 0070 /// transition logging. 0071 /// 0072 /// The `on_event`, `on_enter`, `on_exit` and `on_process` methods need to be 0073 /// implemented exhaustively, i.e. for all state/event combinations. This might 0074 /// require you to add catch-all no-op functions like 0075 /// ```cpp 0076 /// template <typename...Args> 0077 /// event_return on_event(Args&&...args) {} // noop 0078 /// ``` 0079 /// and so on. 0080 /// 0081 /// The public interface for the user of the FSM are the 0082 /// ```cpp 0083 /// template <typename... Args> 0084 /// void setState(StateVariant state, Args&&... args); 0085 /// 0086 /// template <typename Event, typename... Args> 0087 /// void dispatch(Event&& event, Args&&... args) { 0088 /// ``` 0089 /// 0090 /// `setState` triggers a transition to a given state, `dispatch` triggers 0091 /// processing on an event from the given state. Both will call the appropriate 0092 /// `on_exit` and `on_enter` overloads. Both also accept an arbitrary number of 0093 /// additional arguments that are passed to the `on_event`, `on_exit` and 0094 /// `on_enter` overloads. 0095 /// 0096 /// @tparam Derived Class deriving from the FSM 0097 /// @tparam States Argument pack with the state types that the FSM can be 0098 /// handled. 0099 template <typename Derived, typename... States> 0100 class FiniteStateMachine { 0101 public: 0102 /// Contractual termination state. Is transitioned to if State+Event do not 0103 /// have a transition defined. 0104 struct Terminated { 0105 /// Name of this state (useful for logging) 0106 constexpr static std::string_view name = "Terminated"; 0107 }; 0108 0109 /// Variant type allowing tagged type erased storage of the current state of 0110 /// the FSM. 0111 using StateVariant = std::variant<Terminated, States...>; 0112 0113 protected: 0114 using fsm_base = FiniteStateMachine<Derived, States...>; 0115 0116 using event_return = std::optional<StateVariant>; 0117 0118 public: 0119 /// Default constructor. The default state is taken to be the first in the 0120 /// `States` template arguments 0121 FiniteStateMachine() 0122 : m_state(typename std::tuple_element_t<0, std::tuple<States...>>{}) {} 0123 0124 /// Constructor from an explicit state. The FSM is initialized to this state. 0125 /// @param state Initial state for the FSM. 0126 FiniteStateMachine(StateVariant state) : m_state(std::move(state)) {} 0127 0128 /// Get the current state of the FSM (as a variant). 0129 /// @return StateVariant The current state of the FSM. 0130 const StateVariant& getState() const noexcept { return m_state; } 0131 0132 public: 0133 /// Sets the state to a given one. Triggers `on_exit` and `on_enter` for the 0134 /// given states. 0135 /// @tparam State Type of the target state 0136 /// @tparam Args Additional arguments passed through callback overloads. 0137 /// @param state Instance of the target state 0138 /// @param args The additional arguments 0139 template <typename State, typename... Args> 0140 void setState(State state, Args&&... args) { 0141 Derived& child = static_cast<Derived&>(*this); 0142 0143 // call on exit function 0144 std::visit([&](auto& s) { child.on_exit(s, std::forward<Args>(args)...); }, 0145 m_state); 0146 0147 m_state = std::move(state); 0148 0149 // call on enter function, the type is known from the template argument. 0150 child.on_enter(std::get<State>(m_state), std::forward<Args>(args)...); 0151 } 0152 0153 /// Returns whether the FSM is in the specified state 0154 /// @tparam State type to check against 0155 /// @return Whether the FSM is in the given state. 0156 template <typename S> 0157 bool is(const S& /*state*/) const noexcept { 0158 return is<S>(); 0159 } 0160 0161 /// Returns whether the FSM is in the specified state. Alternative version 0162 /// directly taking only the template argument. 0163 /// @tparam State type to check against 0164 /// @return Whether the FSM is in the given state. 0165 template <typename S> 0166 bool is() const noexcept { 0167 if (std::get_if<S>(&m_state)) { 0168 return true; 0169 } 0170 return false; 0171 } 0172 0173 /// Returns whether the FSM is in the terminated state. 0174 /// @return Whether the FSM is in the terminated state. 0175 bool terminated() const noexcept { return is<Terminated>(); } 0176 0177 protected: 0178 /// Handles processing of an event. 0179 /// @note This should only be called from inside the class Deriving from FSM. 0180 /// @tparam Event Type of the event being processed 0181 /// @tparam Args Arguments being passed to the overload handlers. 0182 /// @param event Instance of the event 0183 /// @param args Additional arguments 0184 /// @return Variant state type, signifying if a transition is supposed to 0185 /// happen. 0186 template <typename Event, typename... Args> 0187 event_return process_event(Event&& event, Args&&... args) { 0188 Derived& child = static_cast<Derived&>(*this); 0189 0190 child.on_process(event); 0191 0192 auto new_state = std::visit( 0193 [&](auto& s) -> std::optional<StateVariant> { 0194 auto s2 = child.on_event(s, std::forward<Event>(event), 0195 std::forward<Args>(args)...); 0196 0197 if (s2) { 0198 std::visit([&](auto& s2_) { child.on_process(s, event, s2_); }, 0199 *s2); 0200 } else { 0201 child.on_process(s, event); 0202 } 0203 return s2; 0204 }, 0205 m_state); 0206 return new_state; 0207 } 0208 0209 public: 0210 /// Public interface to handle an event. Will call the appropriate event 0211 /// handlers and perform any required transitions. 0212 /// @tparam Event Type of the event being triggered 0213 /// @tparam Args Additional arguments being passed to overload handlers. 0214 /// @param event Instance of the event being triggere 0215 /// @param args Additional arguments 0216 template <typename Event, typename... Args> 0217 void dispatch(Event&& event, Args&&... args) { 0218 auto new_state = process_event(std::forward<Event>(event), args...); 0219 if (new_state) { 0220 std::visit( 0221 [&](auto& s) { setState(std::move(s), std::forward<Args>(args)...); }, 0222 *new_state); 0223 } 0224 } 0225 0226 private: 0227 StateVariant m_state; 0228 }; 0229 0230 } // namespace Acts
[ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |