$darkmode
state_machine.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2020 Robert Bosch GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  */
22 #pragma once
23 
24 #include <cstdint> // for uint64_t
25 #include <initializer_list> // for initializer_list<>
26 #include <map> // for map<>
27 #include <memory> // for shared_ptr<>
28 #include <mutex> // for mutex, lock_guard<>
29 #include <utility> // for move
30 
31 #include <boost/optional.hpp> // for optional<>
32 
33 #include <cloe/core.hpp> // for Json
34 #include <cloe/utility/statistics.hpp> // for Accumulator
35 #include <cloe/utility/timer.hpp> // for DurationTimer<>
36 
37 #define DEFINE_STATE_STRUCT(MachineType, ContextType, Id, StructName) \
38  static constexpr StateId Id = #Id; \
39  struct StructName : public State<MachineType, ContextType> { \
40  using State<MachineType, ContextType>::State; \
41  StateId id() const override { return Id; } \
42  StateId impl(ContextType& ctx) override; \
43  }
44 
45 namespace engine {
46 
47 using StateId = const char*;
48 
49 template <typename MachineType, typename ContextType>
50 class State {
51  // The number of times the state machine has entered this state.
52  uint64_t calls_;
53 
54  // A statistic of the durations that this state has been active.
55  cloe::utility::Accumulator timing_ms_;
56 
57  // The number of transitions from this state to other states.
58  std::map<StateId, uint64_t> transitions_;
59 
60  // Pointer to machine that contains this state.
61  MachineType* machine_;
62 
63  public:
64  State(MachineType* ptr) : machine_(ptr) { assert(machine_ != nullptr); }
65  virtual ~State() = default;
66 
73  virtual StateId id() const = 0;
74 
81  MachineType* state_machine() const { return machine_; }
82 
88  virtual StateId run(ContextType& ctx) {
89  calls_++;
90  logger()->trace("Enter state: {}", this->id());
92  [&](timer::Milliseconds timing) { this->timing_ms_.push_back(timing.count()); });
93 
94  StateId next = impl(ctx);
95  if (next != nullptr) {
96  transitions_[next]++;
97  }
98  return next;
99  }
100 
104  virtual cloe::Logger logger() const { return cloe::logger::get("cloe"); }
105 
106  friend void to_json(cloe::Json& j, const State<MachineType, ContextType>& s) {
107  j = cloe::Json{
108  {"id", s.id()},
109  {"count", s.calls_},
110  {"transitions", s.transitions_},
111  {"timing_ms", s.timing_ms_},
112  };
113  }
114 
115  protected:
119  virtual StateId impl(ContextType& ctx) = 0;
120 };
121 
122 template <typename StateType, typename ContextType>
124  using StateMap = std::map<StateId, std::shared_ptr<StateType>>;
125  StateMap states_{};
126  StateId prev_state_{nullptr};
127  StateId interrupt_{nullptr};
128  std::mutex interrupt_mtx_;
129 
130  public:
132  virtual ~StateMachine<StateType, ContextType>() = default;
133 
137  const StateMap& states() const { return states_; }
138 
142  StateId previous_state() const { return prev_state_; }
143 
147  template <typename StateImpl = StateType>
148  std::shared_ptr<StateImpl> get_state(StateId id) {
149  return std::dynamic_pointer_cast<StateImpl>(states_.at(id));
150  }
151 
152  StateId run_state(StateId id, ContextType& ctx) {
153  assert(states_.count(id) == 1);
154  auto s = states_.at(id);
155  try {
156  auto next_id = s->run(ctx);
157  prev_state_ = id;
158  return next_id;
159  } catch (...) {
160  prev_state_ = id;
161  throw;
162  }
163  }
164 
165  void register_state(StateType* s) {
166  assert(s != nullptr);
167  register_state(std::shared_ptr<StateType>(s));
168  }
169 
170  void register_state(std::shared_ptr<StateType> s) {
171  auto id = s->id();
172  assert(states_.count(id) == 0);
173  states_[id] = std::move(s);
174  }
175 
176  void register_states(std::initializer_list<StateType*> init) {
177  for (auto& s : init) {
178  register_state(s);
179  }
180  }
181 
188  void push_interrupt(StateId id) {
189  logger()->trace("Push interrupt: {}", id);
190  std::lock_guard<std::mutex> guard(interrupt_mtx_);
191  if (interrupt_ != nullptr) {
192  throw std::logic_error{"interrupt queuing is currently not available, already processing: " +
193  std::string(interrupt_)};
194  }
195  interrupt_ = id;
196  }
197 
198  boost::optional<StateId> pop_interrupt() {
199  std::lock_guard<std::mutex> guard(interrupt_mtx_);
200  if (interrupt_ == nullptr) {
201  return boost::none;
202  } else {
203  auto tmp = interrupt_;
204  interrupt_ = nullptr;
205  return tmp;
206  }
207  }
208 
215  virtual StateId handle_interrupt(StateId nominal, StateId interrupt, ContextType& ctx) = 0;
216 
220  virtual cloe::Logger logger() const { return cloe::logger::get("cloe"); }
221 };
222 
223 } // namespace engine
Definition: statistics.hpp:110
void push_back(double x)
Definition: statistics.hpp:132
Definition: state_machine.hpp:123
virtual StateId handle_interrupt(StateId nominal, StateId interrupt, ContextType &ctx)=0
virtual cloe::Logger logger() const
Definition: state_machine.hpp:220
std::shared_ptr< StateImpl > get_state(StateId id)
Definition: state_machine.hpp:148
StateId previous_state() const
Definition: state_machine.hpp:142
const StateMap & states() const
Definition: state_machine.hpp:137
void push_interrupt(StateId id)
Definition: state_machine.hpp:188
Definition: state_machine.hpp:50
virtual cloe::Logger logger() const
Definition: state_machine.hpp:104
MachineType * state_machine() const
Definition: state_machine.hpp:81
virtual StateId impl(ContextType &ctx)=0
virtual StateId run(ContextType &ctx)
Definition: state_machine.hpp:88
virtual StateId id() const =0
Definition: timer.hpp:68