$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 <optional> // for optional<>
30 #include <utility> // for move
31 
32 #include <cloe/core.hpp> // for Json
33 #include <cloe/utility/statistics.hpp> // for Accumulator
34 #include <cloe/utility/timer.hpp> // for DurationTimer<>
35 
36 #define DEFINE_STATE_STRUCT(MachineType, ContextType, Id, StructName) \
37  static constexpr StateId Id = #Id; \
38  struct StructName : public State<MachineType, ContextType> { \
39  using State<MachineType, ContextType>::State; \
40  StateId id() const override { return Id; } \
41  StateId impl(ContextType& ctx) override; \
42  }
43 
44 namespace engine {
45 
46 using StateId = const char*;
47 
48 template <typename MachineType, typename ContextType>
49 class State {
50  // The number of times the state machine has entered this state.
51  uint64_t calls_;
52 
53  // A statistic of the durations that this state has been active.
54  cloe::utility::Accumulator timing_ms_;
55 
56  // The number of transitions from this state to other states.
57  std::map<StateId, uint64_t> transitions_;
58 
59  // Pointer to machine that contains this state.
60  MachineType* machine_;
61 
62  public:
63  State(MachineType* ptr) : machine_(ptr) { assert(machine_ != nullptr); }
64  virtual ~State() = default;
65 
72  virtual StateId id() const = 0;
73 
80  MachineType* state_machine() const { return machine_; }
81 
87  virtual StateId run(ContextType& ctx) {
88  calls_++;
89  logger()->trace("Enter state: {}", this->id());
91  [&](timer::Milliseconds timing) { this->timing_ms_.push_back(timing.count()); });
92 
93  StateId next = impl(ctx);
94  if (next != nullptr) {
95  transitions_[next]++;
96  }
97  return next;
98  }
99 
103  virtual cloe::Logger logger() const { return cloe::logger::get("cloe"); }
104 
105  friend void to_json(cloe::Json& j, const State<MachineType, ContextType>& s) {
106  j = cloe::Json{
107  {"id", s.id()},
108  {"count", s.calls_},
109  {"transitions", s.transitions_},
110  {"timing_ms", s.timing_ms_},
111  };
112  }
113 
114  protected:
118  virtual StateId impl(ContextType& ctx) = 0;
119 };
120 
121 template <typename StateType, typename ContextType>
123  using StateMap = std::map<StateId, std::shared_ptr<StateType>>;
124  StateMap states_{};
125  StateId prev_state_{nullptr};
126  StateId interrupt_{nullptr};
127  std::mutex interrupt_mtx_;
128 
129  public:
131  virtual ~StateMachine<StateType, ContextType>() = default;
132 
136  const StateMap& states() const { return states_; }
137 
141  StateId previous_state() const { return prev_state_; }
142 
146  template <typename StateImpl = StateType>
147  std::shared_ptr<StateImpl> get_state(StateId id) {
148  return std::dynamic_pointer_cast<StateImpl>(states_.at(id));
149  }
150 
151  StateId run_state(StateId id, ContextType& ctx) {
152  assert(states_.count(id) == 1);
153  auto s = states_.at(id);
154  try {
155  auto next_id = s->run(ctx);
156  prev_state_ = id;
157  return next_id;
158  } catch (...) {
159  prev_state_ = id;
160  throw;
161  }
162  }
163 
164  void register_state(StateType* s) {
165  assert(s != nullptr);
166  register_state(std::shared_ptr<StateType>(s));
167  }
168 
169  void register_state(std::shared_ptr<StateType> s) {
170  auto id = s->id();
171  assert(states_.count(id) == 0);
172  states_[id] = std::move(s);
173  }
174 
175  void register_states(std::initializer_list<StateType*> init) {
176  for (auto& s : init) {
177  register_state(s);
178  }
179  }
180 
187  void push_interrupt(StateId id) {
188  logger()->trace("Push interrupt: {}", id);
189  std::lock_guard<std::mutex> guard(interrupt_mtx_);
190  if (interrupt_ != nullptr && interrupt_ != id) {
191  throw std::logic_error{"interrupt queuing is currently not available, already processing: " +
192  std::string(interrupt_)};
193  }
194  interrupt_ = id;
195  }
196 
197  std::optional<StateId> pop_interrupt() {
198  std::lock_guard<std::mutex> guard(interrupt_mtx_);
199  if (interrupt_ == nullptr) {
200  return std::nullopt;
201  } else {
202  auto tmp = interrupt_;
203  interrupt_ = nullptr;
204  return tmp;
205  }
206  }
207 
214  virtual StateId handle_interrupt(StateId nominal, StateId interrupt, ContextType& ctx) = 0;
215 
219  virtual cloe::Logger logger() const { return cloe::logger::get("cloe"); }
220 };
221 
222 } // namespace engine
Definition: statistics.hpp:110
void push_back(double x)
Definition: statistics.hpp:132
Definition: state_machine.hpp:122
virtual StateId handle_interrupt(StateId nominal, StateId interrupt, ContextType &ctx)=0
virtual cloe::Logger logger() const
Definition: state_machine.hpp:219
std::shared_ptr< StateImpl > get_state(StateId id)
Definition: state_machine.hpp:147
StateId previous_state() const
Definition: state_machine.hpp:141
const StateMap & states() const
Definition: state_machine.hpp:136
void push_interrupt(StateId id)
Definition: state_machine.hpp:187
Definition: state_machine.hpp:49
virtual cloe::Logger logger() const
Definition: state_machine.hpp:103
MachineType * state_machine() const
Definition: state_machine.hpp:80
virtual StateId impl(ContextType &ctx)=0
virtual StateId run(ContextType &ctx)
Definition: state_machine.hpp:87
virtual StateId id() const =0
Definition: timer.hpp:68