libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
msm.hpp
Go to the documentation of this file.
1 #ifndef LIBJMMCG_CORE_MSM_HPP
2 #define LIBJMMCG_CORE_MSM_HPP
3 /******************************************************************************
4 ** Copyright © 2014 by J.M.McGuiness, coder@hussar.me.uk
5 **
6 ** This library is free software; you can redistribute it and/or
7 ** modify it under the terms of the GNU Lesser General Public
8 ** License as published by the Free Software Foundation; either
9 ** version 2.1 of the License, or (at your option) any later version.
10 **
11 ** This library is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 ** Lesser General Public License for more details.
15 **
16 ** You should have received a copy of the GNU Lesser General Public
17 ** License along with this library; if not, write to the Free Software
18 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20 
21 #include "unordered_tuple.hpp"
22 
23 namespace jmmcg { namespace LIBJMMCG_VER_NAMESPACE {
24 
25 /// A meta (or finite)-state machine that can represent UML-style state tables in C++.
26 /**
27  This FSM is a very cut-down version of boost.msm [1,2], but it is much more simple to use!
28 */
29 namespace msm {
30 
31 struct unroll {
32 
33  /// A convenience class to make the rows a bit smaller when declaring the meta-state machine table.
34  template<class States, class EndStates>
35  struct row_types {
36  using states=States;
37  using end_states=EndStates;
38 
39  /// A row in the meta-state machine table.
40  template<
41  states Start, ///< The start state.
42  class Event, ///< The event that will be executed to transition from the start to the next state. Must be a functor. This can only take a single argument in the ctor.
43  end_states Next ///< The state transitioned to.
44  >
45  class row;
46  /// A row in the meta-state machine table that also has a guard.
47  template<
48  states Start, ///< The start state.
49  class Event, ///< The event that will be executed to transition from the start to the next state. Must be a functor. This can only take a single argument in the ctor.
50  end_states Next, ///< The state transitioned to.
51  class Guard ///< The guard: if true the transition occurs, otherwise there is no change in state.
52  >
53  class g_row;
54  };
55 
56  /// Derive from this object to implement your meta-state machine.
57  /**
58  The state transition table must be called or aliassed to the type state_transition_table to be accessible within this class.
59  */
60  template<
61  class Deriv ///< The derived meta-state machine, that must itself be be stateless.
62  >
64  public:
65  struct no_op {
66  template<class ...Args>
67  explicit no_op(Args &&...) noexcept(true) {}
68 
69  template<auto state, auto next, class ...Args>
70  constexpr auto operator()(Args &&...) const noexcept(true) FORCE_INLINE {return next;}
71  static constexpr void noop() noexcept(true) {}
72  };
73 
74  /// The derived, implementation of the meta-state table, each row in rows is a state-transition of the meta-sate machine.
75  /**
76  The derived class must contain a type member called transition_table which implements the meta-state table as a collection of unroll::row_types::row. Note that the last row in the transisiton_table will not have the Start States checked, so will effectively "match all" incoming states.
77 
78  This technique is used because on gcc v>=8.1.0 it can optimise this into a jump table, so O(1)! (Or as a switch-statement, for O(log(n)).) Sadly clang & icc were baffled (like lower versions of gcc) & just produced the if-chain, for O(n).
79 
80  \see unroll::row_types::row
81  */
82  template<class Row, class... Rows>
83  class rows;
84 
85  private:
86  state_transition_table()=delete;
87  };
88 
89  /// Instantiate this object to use the created state-machine.
90  template<
91  class STT ///< The state transition table, the type derived from state_transition_table, above.
92  >
93  class machine {
94  public:
95  using element_type=STT;
96  using states=typename element_type::transition_table::states;
97  using end_states=typename element_type::transition_table::end_states;
98  using rows_t=typename element_type::transition_table;
99 
100  constexpr machine() noexcept(true)=default;
101  template<
102  class Arg,
103  class... Args,
104  class =typename std::enable_if<!std::is_convertible<Arg, machine>::value>::type
105  > explicit constexpr
106  machine(Arg &&arg, Args &&...args) noexcept(noexcept(rows_t(std::forward<Arg>(arg), std::forward<Args>(args)...)));
107  constexpr machine(machine const &) noexcept(noexcept(rows_t(std::declval<rows_t>())));
108 
109  /// Call this method to perform your state transition from the start state to the next state.
110  /**
111  Algorithmic complexity: see comment for state_transition_table::rows, above.
112 
113  \param s The start state for the transition to be selected. If s is not found in the state_transition_table, then there is no effect, nothing happens.
114  \param p The parameters to pass to the event selected by the start state from the state_transition_table.
115  \return The resultant state of the row selected by the input state, s. If the row was a guard-row, then if the guard permitted it the resultant state, otherwise the input state, s.
116  */
117  template<class... Params>
118  constexpr end_states process(states s, Params &&...p) const noexcept(noexcept(std::declval<rows_t>().process(s, std::forward<Params>(p)...)));
119 
120  /// Call this method to perform your state transition from the start state to the next state.
121  /**
122  Algorithmic complexity: see comment for rows, above.
123 
124  \param s The start state for the transition to be selected. If s is not found in the state_transition_table, then there is no effect, nothing happens.
125  \param p The parameters to pass to the event selected by the start state from the state_transition_table.
126  \return The resultant state of the row selected by the input state, s. If the row was a guard-row, then if the guard permitted it the resultant state, otherwise the input state, s.
127  */
128  template<class... Params>
129  constexpr end_states process(states s, Params &&...p) noexcept(noexcept(std::declval<rows_t>().process(s, std::forward<Params>(p)...)));
130 
131  private:
132  rows_t rows_;
133  };
134 
135 };
136 
137 /**
138  gcc & clang can get somewhat confused when the event has a complex body and can create either if-else chains or switch-statements respectively. So force the compiler to always implement a jump-table instead.
139 
140  Consider:
141  <a href="https://stackoverflow.com/questions/1777990/is-it-possible-to-store-the-address-of-a-label-in-a-variable-and-use-goto-to-jum"/> and taking the address of labels...
142 */
143 struct jump_table {
144 
145  /// A convenience class to make the rows a bit smaller when declaring the meta-state machine table.
146  template<class States, class EndStates>
147  struct row_types {
148  using states=States;
149  using end_states=EndStates;
150 
151  /// A row in the meta-state machine table.
152  template<
153  states Start, ///< The start state.
154  class Event, ///< The event that will be executed to transition from the start to the next state. Must be a functor. This can only take a single argument in the ctor.
155  end_states Next ///< The state transitioned to.
156  >
157  class row;
158  /// A row in the meta-state machine table that also has a guard.
159  template<
160  states Start, ///< The start state.
161  class Event, ///< The event that will be executed to transition from the start to the next state. Must be a functor. This can only take a single argument in the ctor.
162  end_states Next, ///< The state transitioned to.
163  class Guard ///< The guard: if true the transition occurs, otherwise there is no change in state.
164  >
165  class g_row;
166  };
167 
168  /// Derive from this object to implement your meta-state machine.
169  /**
170  The state transition table must be called or aliassed to the type state_transition_table to be accessible within this class.
171  */
172  template<
173  class Deriv ///< The derived meta-state machine, that must itself be be stateless.
174  >
176  public:
177  struct no_op {
178  template<class ...Args>
179  explicit no_op(Args &&...) noexcept(true) {}
180 
181  template<auto state, auto next, class ...Args>
182  constexpr auto operator()(Args &&...) const noexcept(true) FORCE_INLINE {return next;}
183  static constexpr void noop() noexcept(true) {}
184  };
185 
186  /// The derived, implementation of the meta-state table, each row in rows is a state-transition of the meta-sate machine.
187  /**
188  The derived class must contain a type member called transition_table which implements the meta-state table as a collection of unroll::row_types::row.
189 
190  This technique is used because on gcc v>=8.1.0 it can optimise this into a jump table, so O(1)! (Or as a switch-statement, for O(log(n)).) Sadly clang & icc were baffled (like lower versions of gcc) & just produced the if-chain, for O(n).
191 
192  \see unroll::row_types::row
193  */
194  template<class Row, class... Rows>
195  class rows;
196 
197  private:
198  template<class Row, class... Rows>
199  class rows_details;
200  struct rows_base;
201 
202  state_transition_table()=delete;
203  };
204 
205  /// Instantiate this object to use the created state-machine.
206  template<
207  class STT ///< The state transition table, the type derived from state_transition_table, above.
208  >
209  class machine {
210  public:
211  using element_type=STT;
212  using states=typename element_type::transition_table::states;
213  using end_states=typename element_type::transition_table::end_states;
214  using rows_t=typename element_type::transition_table;
215 
216  constexpr machine() noexcept(true)=default;
217  template<
218  class Arg,
219  class... Args,
220  class =typename std::enable_if<!std::is_convertible<Arg, machine>::value>::type
221  > explicit constexpr
222  machine(Arg &&arg, Args &&...args) noexcept(noexcept(rows_t(std::forward<Arg>(arg), std::forward<Args>(args)...)));
223  constexpr machine(machine const &) noexcept(noexcept(rows_t(std::declval<rows_t>())));
224 
225  /// Call this method to perform your state transition from the start state to the next state.
226  /**
227  Algorithmic complexity: see comment for rows, above.
228 
229  \param s The start state for the transition to be selected. If s is not found in the state_transition_table, then there is no effect, nothing happens. The states must be uniformly, linearly, increasing in underlying value.
230  \param p The parameters to pass to the event selected by the start state from the state_transition_table.
231  \return The resultant state of the row selected by the input state, s. If the row was a guard-row, then if the guard permitted it the resultant state, otherwise the input state, s.
232  */
233  template<class... Params>
234  constexpr end_states process(states s, Params &&...p) const noexcept(false);
235 
236  /// Call this method to perform your state transition from the start state to the next state.
237  /**
238  Algorithmic complexity: see comment for rows, above.
239 
240  \param s The start state for the transition to be selected. If s is not found in the state_transition_table, then there is no effect, nothing happens. The states must be uniformly, linearly, increasing in underlying value.
241  \param p The parameters to pass to the event selected by the start state from the state_transition_table.
242  \return The resultant state of the row selected by the input state, s. If the row was a guard-row, then if the guard permitted it the resultant state, otherwise the input state, s.
243  */
244  template<class... Params>
245  constexpr end_states process(states s, Params &&...p) noexcept(false);
246 
247  private:
248  rows_t rows_;
249  };
250 
251 };
252 
253 /**
254  In this case we shall force the compiler to generate a computed goto. This should be optimal.
255 */
257 
258  /// A convenience class to make the rows a bit smaller when declaring the meta-state machine table.
259  template<class States, class EndStates>
260  struct row_types {
261  using states=States;
262  using end_states=EndStates;
263 
264  /// A row in the meta-state machine table.
265  template<
266  states Start, ///< The start state.
267  class Event, ///< The event that will be executed to transition from the start to the next state. Must be a functor. This can only take a single argument in the ctor.
268  end_states Next ///< The state transitioned to.
269  >
270  class row;
271  /// A row in the meta-state machine table that also has a guard.
272  template<
273  states Start, ///< The start state.
274  class Event, ///< The event that will be executed to transition from the start to the next state. Must be a functor. This can only take a single argument in the ctor.
275  end_states Next, ///< The state transitioned to.
276  class Guard ///< The guard: if true the transition occurs, otherwise there is no change in state.
277  >
278  class g_row;
279  };
280 
281  /// Derive from this object to implement your meta-state machine.
282  /**
283  The state transition table must be called or aliassed to the type state_transition_table to be accessible within this class.
284  */
285  template<
286  class Deriv ///< The derived meta-state machine, that must itself be be stateless.
287  >
289  public:
290  struct no_op {
291  template<class ...Args>
292  explicit no_op(Args &&...) noexcept(true) {}
293 
294  template<auto state, auto next, class ...Args>
295  constexpr auto operator()(Args &&...) const noexcept(true) FORCE_INLINE {return next;}
296  static constexpr void noop() noexcept(true) {}
297  };
298 
299  /// The derived, implementation of the meta-state table, each row in rows is a state-transition of the meta-sate machine.
300  /**
301  The derived class must contain a type member called transition_table which implements the meta-state table as a collection of unroll::row_types::row.
302 
303  This technique is used because on gcc v>=8.1.0 it can optimise this into a jump table, so O(1)! (Or as a switch-statement, for O(log(n)).) Sadly clang & icc were baffled (like lower versions of gcc) & just produced the if-chain, for O(n).
304 
305  \see unroll::row_types::row
306  */
307  template<class Row, class... Rows>
308  class rows;
309 
310  private:
311  template<class Row, class... Rows>
312  class rows_details;
313 
314  state_transition_table()=delete;
315  };
316 
317  /// Instantiate this object to use the created state-machine.
318  template<
319  class STT ///< The state transition table, the type derived from state_transition_table, above.
320  >
321  class machine {
322  public:
323  using element_type=STT;
324  using states=typename element_type::transition_table::states;
325  using end_states=typename element_type::transition_table::end_states;
326  using rows_t=typename element_type::transition_table;
327 
328  constexpr machine() noexcept(true)=default;
329  template<
330  class Arg,
331  class... Args,
332  class =typename std::enable_if<!std::is_convertible<Arg, machine>::value>::type
333  > explicit constexpr
334  machine(Arg &&arg, Args &&...args) noexcept(noexcept(rows_t(std::forward<Arg>(arg), std::forward<Args>(args)...)));
335  constexpr machine(machine const &) noexcept(noexcept(rows_t(std::declval<rows_t>())));
336 
337  /// Call this method to perform your state transition from the start state to the next state.
338  /**
339  Algorithmic complexity: see comment for rows, above.
340 
341  \param s The start state for the transition to be selected. If s is not found in the state_transition_table, then there is no effect, nothing happens. The states must be uniformly, linearly, increasing in underlying value.
342  \param p The parameters to pass to the event selected by the start state from the state_transition_table.
343  \return The resultant state of the row selected by the input state, s. If the row was a guard-row, then if the guard permitted it the resultant state, otherwise the input state, s.
344  */
345  template<class... Params>
346  constexpr end_states process(states s, Params &&...p) const noexcept(false);
347 
348  /// Call this method to perform your state transition from the start state to the next state.
349  /**
350  Algorithmic complexity: see comment for rows, above.
351 
352  \param s The start state for the transition to be selected. If s is not found in the state_transition_table, then there is no effect, nothing happens. The states must be uniformly, linearly, increasing in underlying value.
353  \param p The parameters to pass to the event selected by the start state from the state_transition_table.
354  \return The resultant state of the row selected by the input state, s. If the row was a guard-row, then if the guard permitted it the resultant state, otherwise the input state, s.
355  */
356  template<class... Params>
357  constexpr end_states process(states s, Params &&...p) noexcept(false);
358 
359  private:
360  rows_t rows_;
361  };
362 
363 };
364 
365 struct hash {
366 
367  /// A convenience class to make the rows a bit smaller when declaring the meta-state machine table.
368  template<class States, class EndStates>
369  struct row_types {
370  using states=States;
371  using end_states=EndStates;
372 
373  struct compat_row_base;
374  /// A row in the meta-state machine table.
375  template<
376  states Start, ///< The start state.
377  class Event, ///< The event that will be executed to transition from the start to the next state. Must be a functor.
378  end_states Next ///< The state transitioned to.
379  >
380  class row;
381  /// A row in the meta-state machine table that also has a guard.
382  template<
383  states Start, ///< The start state.
384  class Event, ///< The event that will be executed to transition from the start to the next state. Must be a functor.
385  end_states Next, ///< The state transitioned to.
386  class Guard ///< The guard: if true the transition occurs, otherwise there is no change in state.
387  >
388  class g_row;
389  };
390 
391  /// Derive from this object to implement your meta-state machine.
392  /**
393  The state transition table must be called or aliassed to the type state_transition_table to be accessible within this class.
394  */
395  template<
396  class Deriv ///< The derived meta-state machine, that must itself be be stateless.
397  >
399  public:
400  struct no_op {
401  template<class ...Args>
402  explicit no_op(Args &&...) noexcept(true) {}
403 
404  template<auto state, auto next, class ...Args>
405  constexpr auto operator()(Args &&...) const noexcept(true) FORCE_INLINE {return next;}
406  static constexpr void noop() noexcept(true) {}
407  };
408 
409  /// The derived, implementation of the meta-state table.
410  /**
411  The derived class must contain a type member called transition_table which implements the meta-state table as a collection of hash::row_types::row.
412 
413  \see hash::row_types::row
414  */
415  template<class Row, class... Rows>
416  struct rows;
417 
418  private:
419  template<class Row> struct get_state_as_hash;
420 
421  state_transition_table()=delete;
422  };
423 
424  /// Instantiate this object to use the created state-machine.
425  template<
426  class STT ///< The state transition table, the type derived from state_transition_table, above.
427  >
428  class machine {
429  public:
430  using element_type=STT;
431  using states=typename element_type::transition_table::states;
432  using end_states=typename element_type::transition_table::end_states;
433 
434  private:
435  using states_to_actions_table_t=typename element_type::transition_table::states_to_actions_table_t;
436 
437  public:
438  constexpr machine()=default;
439  template<
440  class Arg,
441  class... Args,
442  class =typename std::enable_if<!std::is_convertible<Arg, machine>::value>::type
443  > explicit constexpr
444  machine(Arg &&arg, Args &&...args) noexcept(noexcept(states_to_actions_table_t(std::forward<Arg>(arg), std::forward<Args>(args)...)));
445  constexpr machine(machine const &) noexcept(noexcept(states_to_actions_table_t(std::declval<states_to_actions_table_t>())));
446 
447  /// Call this method to perform your state transition from the start state to the next state.
448  /**
449  Algorithmic complexity: O(1) where n is the number of entries in the meta-state table with a small constant. This is thread-safe (thus re-entrant) only if all of the called actions are.
450 
451  \param s The start state for the transition to be selected. If s is not found in the state_transition_table, then there is no effect, nothing happens.
452  \param p The parameters to pass to the event selected by the start state from the state_transition_table. Note that the events must have a member-using called argument_type that specifies this type, otherwise there will be a compile-time error.
453  \return The resultant state of the row selected by the input state, s. If the row was a guard-row, then if the guard permitted it the resultant state, otherwise the input state, s.
454  */
455  template<class Params>
456  end_states process(states s, Params &p) const noexcept(
457  noexcept(
458  std::declval<states_to_actions_table_t>()[s].process(reinterpret_cast<void *>(&p))
459  )
460  );
461 
462  /// Call this method to perform your state transition from the start state to the next state.
463  /**
464  Algorithmic complexity: O(1) where n is the number of entries in the meta-state table with a small constant. This is thread-safe (thus re-entrant) only if all of the called actions are.
465 
466  \param s The start state for the transition to be selected. If s is not found in the state_transition_table, then there is no effect, nothing happens.
467  \param p The parameters to pass to the event selected by the start state from the state_transition_table. Note that the events must have a member-using called argument_type that specifies this type, otherwise there will be a compile-time error.
468  \return The resultant state of the row selected by the input state, s. If the row was a guard-row, then if the guard permitted it the resultant state, otherwise the input state, s.
469  */
470  template<class Params>
471  end_states process(states s, Params &p) noexcept(
472  noexcept(
473  std::declval<states_to_actions_table_t>()[s].process(reinterpret_cast<void *>(&p))
474  )
475  );
476 
477  private:
478  const states_to_actions_table_t tbl;
479  };
480 
481 };
482 
483 } } }
484 
485 /**
486  \todo How can this be used...? Inspired by talks with Jon Chesterfield.
487 
488 #include <functional>
489 
490 enum states : std::uint64_t {
491  state1=0, ///< Need to set this...
492  state2=134726432, ///< Need to set this...
493  state3=24664 ///< Need to set this...
494 };
495 
496 struct row_base {
497 // virtual states process()=0;
498 };
499 struct row : row_base {
500  states process();// override;
501 };
502 struct row1 : row {
503  states process();// override;
504 };
505 struct row2 : row1 {
506  states process();// override;
507 };
508 
509 // target pointer, possible targets
510 #define jumpto(a, ...) asm goto("jmp *%0" : : "r"(a) : : __VA_ARGS__)
511 
512 states process(states s, row_base &r)
513 {
514  void* the_label_pointer=reinterpret_cast<void *>(s);
515  jumpto(the_label_pointer, state1, state2, state3);
516 
517 state1:
518  // 32-byte boundary.
519  asm ("p2align 10,0xff,16");
520  return static_cast<row &>(r).process();
521 state2:
522  // 32-byte boundary.
523  asm ("p2align 5");
524  return static_cast<row1 &>(r).process();
525 
526 state3:
527  // 32-byte boundary.
528  asm ("p2align 5");
529  return static_cast<row2 &>(r).process();
530  // 32-byte boundary.
531  asm ("p2align 5");
532 }
533 
534 states foo(row_base &r) {
535  return process(state3, r);
536 }
537 */
538 
539 #include "msm_impl.hpp"
540 
541 #endif