Back to home page

EIC code displayed by LXR

 
 

    


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