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