[Boost::ext].SML是一个轻量级的,header only且高性能的状态机语言库,它使得我们可以用十分清晰简洁的代码去构建一个状态机。

相比于Boost::MSMBoost::statechart,它更为轻量和高效,从作者给出的benchmark来看,在编译时间,执行时间,可执行文件大小,代码长度等方面[Boost::ext].SML都完胜后者。而相比于传统的if/elseenum+switch/case的方式,它有着更好的拓展性和可读性。关于这几种方式之间更详细的benchmark对比可以参考作者在cppcon2018上的presentation

本文将结合例子展示[Boost::ext].SML的基本用法。

本文中的例子来自官方tutorial/example和作者的presentation。状态图中基本概念的详细定义可以参考UML Short Guide,关于[Boost::ext]/SML中各个概念更详细的语义约束(concept),可以参考user guide

States, Events, Transitions and State Machines

事件是状态机状态转移的条件之一,在SML中,事件与状态都是用特定的类型来标识的。用*"sinit_state"_s表示一个状态机的起始状态。转移可以写成前缀式或后缀式。

GCC/Clang中的非标准拓展功能使得我们可以使用"state"_s,"state"_e这种方式来标识状态和事件,而在MSVC中则只能使用sml::state<class state>,sml::event<my_event>来标识。

状态机自身也可以作为一个状态出现在另一个状态中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <boost/sml.hpp>
#include <cassert>
#include <iostream>
namespace sml = boost::sml;

namespace {
struct e1 {};
struct e2 {};
struct e3 {};
struct transitions {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
       *"idle"_s                  / [] { std::cout << "anonymous transition" << std::endl; } = "s1"_s
      , "s1"_s + event<e1>        / [] { std::cout << "internal transition" << std::endl; }
      , "s1"_s + event<e2>        / [] { std::cout << "self transition" << std::endl; } = "s1"_s
      , "s1"_s + sml::on_entry<_> / [] { std::cout << "s1 entry" << std::endl; }
      , "s1"_s + sml::on_exit<_>  / [] { std::cout << "s1 exit" << std::endl; }
      , "s1"_s + event<e3>        / [] { std::cout << "external transition" << std::endl; } = X
    );
  }
};
}  // namespace

int main() {
  sml::sm<transitions> sm;
  sm.process_event(e1{});
  //anonymous transition
  //s1 entry
  sm.process_event(e2{});
  //internal transition
  sm.process_event(e3{});
  //s1 exit
  //self transition
  //s1 entry
  assert(sm.is(sml::X));
  //s1 exit
  //external transition
  return 0;
}

除了像上面这样定义结构体来构建状态机,我们还可以利用lambda表达式来构建状态机。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <boost/sml.hpp>
#include <cassert>
#include <iostream>
int main() {
#if defined(__cpp_deduction_guides)
  namespace sml = boost::sml;
  struct start {};
  sml::front::sm sm = [] {
    using namespace sml;
    return make_transition_table(*"idle"_s + event<start> / [] { std::cout << "action\n"; } = X);
  };
  sm.process_event(start{});
  assert(sm.is(sml::X));
#endif
  return 0;
}

Actions and Guards

guard可以用来限制状态机的状态转移,只有当guard中的布尔表达式为真时,才会发生相应的状态转移。action相当于触发器,当状态机沿某条转移边转移时,会执行相应的动作。我们还可以用括号和逗号将多个事件串联起来。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <boost/sml.hpp>
#include <cassert>
#include <iostream>
#include <typeinfo>
namespace sml = boost::sml;

namespace {
struct e1 {};
struct e2 {};
struct e3 {};
struct e4 {};
struct e5 {};
bool guard2_impl(int i);
struct actions_guards {
  using self = actions_guards;

  auto operator()() {
    using namespace sml;

    auto guard1 = [] {
      std::cout << "guard1" << std::endl;
      return true;
    };
    auto guard2 = wrap(&guard2_impl);
    auto action1 = [](auto e) { std::cout << "action1: " << typeid(e).name() << std::endl; };
    struct action2 {
      void operator()(int i) {
        assert(42 == i);
        std::cout << "action2" << std::endl;
      }
    };

    return make_transition_table(
       *"idle"_s + event<e1> = "s1"_s
      , "s1"_s + event<e2> [ guard1 ] / action1 = "s2"_s
      , "s2"_s + event<e3> [ guard1 && ![] { return false;} ] / (action1, action2{}) = "s3"_s
      , "s3"_s + event<e4> [ !guard1 || guard2 ] / (action1, [] { std::cout << "action3" << std::endl; }) = "s4"_s
      , "s3"_s + event<e4> [ guard1 ] / ([] { std::cout << "action4" << std::endl; }, [this] { action4(); } ) = "s5"_s
      , "s5"_s + event<e5> [ &self::guard3 ] / &self::action5 = X
    );
  }
  bool guard3(int i) const noexcept {
    assert(42 == i);
    std::cout << "guard3" << std::endl;
    return true;
  }
  void action4() const { std::cout << "action4" << std::endl; }
  void action5(int i, const e5&) {
    assert(42 == i);
    std::cout << "action5" << std::endl;
  }
};
bool guard2_impl(int i) {
  assert(42 == i);
  std::cout << "guard2" << std::endl;
  return false;
}
} 

int main() {
  actions_guards ag{};
  sml::sm<actions_guards> sm{ag, 42};
  sm.process_event(e1{});
  sm.process_event(e2{});
  sm.process_event(e3{});
  sm.process_event(e4{});
  sm.process_event(e5{});
  assert(sm.is(sml::X));
  return 0;
}
/*  output:
guard1
action1: N12_GLOBAL__N_12e2E
guard1
action1: N12_GLOBAL__N_12e3E
action2
guard1
guard2
guard1
action4
action4
guard3
action5
*/

这里状态机中的guards和actions的参数都是通过状态机本身的构造函数传入的,需要注意的是,状态机构造函数中参数传入的顺序事无关紧要的,他们会被自动派发到正确的位置上,但是相同的类型至多只能出现一次,下面我们来看它是如何实现的。

首先来看sm的构造函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
template <class TSM>
class sm {
  using sm_t = typename TSM::sm;
 public:
  using states = aux::apply_t<aux::unique_t, aux::apply_t<get_states, transitions_t>>;
  using state_machines = aux::apply_t<get_sub_sms, states>;

 private:
  using deps = aux::apply_t<merge_deps, transitions_t>;
  using deps_t =
      aux::apply_t<aux::pool,
                   aux::apply_t<aux::unique_t, aux::join_t<deps, sm_all_t, logger_dep_t, aux::apply_t<merge_deps, sub_sms_t>>>>;
  struct events_ids : aux::apply_t<aux::inherit, events> {};
  
 public:
  //...
  template <class TDeps, __BOOST_SML_REQUIRES(!aux::is_same<aux::remove_reference_t<TDeps>, sm>::value)>
  explicit sm(TDeps &&deps) : deps_{aux::init{}, aux::pool<TDeps>{deps}}, sub_sms_{aux::pool<TDeps>{deps}} {
    aux::get<sm_impl<TSM>>(sub_sms_).start(deps_, sub_sms_);
  }

  template <class... TDeps, __BOOST_SML_REQUIRES((sizeof...(TDeps) > 1) && aux::is_unique_t<TDeps...>::value)>
  explicit sm(TDeps &&... deps) : deps_{aux::init{}, aux::pool<TDeps...>{deps...}}, sub_sms_{aux::pool<TDeps...>{deps...}} {
    aux::get<sm_impl<TSM>>(sub_sms_).start(deps_, sub_sms_);
  }
  
  //...
    
 private:
  deps_t deps_;
  sub_sms_t sub_sms_;
};

可以看到状态机将参数转发给了aux::pool的构造函数,我们继续来看pool的构造函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
template <class... Ts>
struct pool : pool_type<Ts>... {
  using boost_di_inject__ = type_list<Ts...>;
  pool() = default;
  explicit pool(Ts... ts) : pool_type<Ts>(ts)... {}
  template <class... TArgs>
  pool(init, const pool<TArgs...> &p) : pool_type<Ts>(try_get<aux::remove_const_t<aux::remove_reference_t<Ts>>>(&p))... {}
  template <class... TArgs>
  pool(const pool<TArgs...> &p) : pool_type<Ts>(init{}, p)... {}
};

这里,pool通过variadic template pack extension多继承不同参数类型的pool_type<Ts>并将参数转发给pool_type<Ts>进行构造。也正是这里的限制使得一种类型至多只能使用一次。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct pool_type_base {
  __BOOST_SML_ZERO_SIZE_ARRAY(byte);
};
template <class T, class = void>
struct pool_type_impl : pool_type_base {
  explicit pool_type_impl(T object) : value{object} {}
  template <class TObject>
  pool_type_impl(init i, TObject object) : value{i, object} {}
  T value;
};
template <class T>
struct pool_type_impl<T &, aux::enable_if_t<aux::is_constructible<T>::value && aux::is_constructible<T, T>::value>>
    : pool_type_base {
  explicit pool_type_impl(T &value) : value{value} {}
  template <class TObject>
  explicit pool_type_impl(TObject value) : value_{value}, value{value_} {}
  template <class TObject>
  pool_type_impl(const init &i, const TObject &object) : value(i, object) {}
  T value_{};
  T &value;
};
template <class T>
struct pool_type : pool_type_impl<T> {
  using pool_type_impl<T>::pool_type_impl;
};

最后,在pool_type_impl中通过SFINAE匹配到对应的构造函数上并完成真正的构造。

Data

这个例子展示了状态机中数据的不同类型。通过event进入状态机的是临时数据,而状态机的成员变量则是所有状态的共享数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <boost/sml.hpp>
#include <cassert>
#include <iostream>
namespace sml = boost::sml;

namespace {
struct connect {
  int id{};
};
struct disconnect {};
struct interrupt {};

struct Disconnected {};
struct Connected {
  int id{};  // per state data
};
struct Interrupted {
  int id{};  // per state data
};

class data {
  using Self = data;
 public:
  explicit data(const std::string& address) : address{address} {}

  auto operator()() {
    using namespace boost::sml;

    const auto set = [](const auto& event, Connected& state) { state.id = event.id; };
    const auto update = [](Connected& src_state, Interrupted& dst_state) { dst_state.id = src_state.id; };

    return make_transition_table(
      * state<Disconnected> + event<connect>    / (set, &Self::print)    = state<Connected>
      , state<Connected>    + event<interrupt>  / (update, &Self::print) = state<Interrupted>
      , state<Interrupted>  + event<connect>    / (set, &Self::print)    = state<Connected>
      , state<Connected>    + event<disconnect> / (&Self::print)         = X
    );
  }

 private:
  void print(Connected& state) { std::cout << address << ':' << state.id << '\n'; };
  std::string address{};  // shared data between states
};
} 

int main() {
  data d{std::string{"127.0.0.1"}};
  sml::sm<data> sm{d, Connected{1}};
  sm.process_event(connect{1024});
  sm.process_event(interrupt{});
  sm.process_event(connect{1025});
  sm.process_event(disconnect{});
  assert(sm.is(sml::X));
  return 0;
}

Defer/Process

当状态机处于某些状态时,我们希望它不处理外来事件,而是将这些事件保存起来,之后再去处理。比如,当某个能响应外部请求的系统被我们人为挂起时,我们希望将直到它解挂前这段时间内的外部请求保存起来,当系统被解挂后再去处理。我们需要用sml::defer_queue来完成上述功能。在状态发生转移后,sml::defer_queue中的事件会依照先进先出的原则依次被转移边check(),当deferred events不能发生转移时,它将继续留在sml::derfer_queue中。注意这里sml::defer_queue不能用std::queue实例化,只能用std::deque否则会报错。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <boost/sml.hpp>
#include <cassert>
#include <deque>
#include <queue>
namespace sml = boost::sml;

namespace {
struct e1 {};
struct e2 {};
struct e3 {};
struct e4 {};

struct defer_and_process {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
       *"idle"_s + event<e1> / defer
      , "idle"_s + event<e2> = "s1"_s
      , "s1"_s   + event<e1> = "s2"_s
      , "s2"_s   + event<e3> / process(e4{})
      , "s2"_s   + event<e4> = X
    );
  }
};
} 

int main() {
  using namespace sml;
  sm<defer_and_process, sml::defer_queue<std::deque>, sml::process_queue<std::queue>> sm; 
  // defer_queue policy to enable deferred events using std::queue
  assert(sm.is("idle"_s));
  sm.process_event(e1{});
  assert(sm.is("idle"_s));
  sm.process_event(e2{});  /// triggers idle -> s1 and s1 -> s2 (via deferred e1)
  assert(sm.is("s2"_s));
  sm.process_event(e3{});  /// triggers s2.process(e4) -> X (via processed e4)
  assert(sm.is(sml::X));
  return 0;
}

Orthogonal Regions

一个状态机中可以同时存在多个不相交的区域,这使得我们同时可以从不同的维度去控制状态机。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <boost/sml.hpp>
#include <cassert>
namespace sml = boost::sml;

namespace {
struct e1 {};
struct e2 {};
struct e3 {};
struct orthogonal_regions {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
     *"idle"_s + event<e1> = "s1"_s
    , "s1"_s + event<e2> = X
    ,*"idle2"_s + event<e2> = "s2"_s
    , "s2"_s + event<e3> = X
    );
  }
};
}  // namespace

int main() {
  sml::sm<orthogonal_regions> sm;
  using namespace sml;
  assert(sm.is("idle"_s, "idle2"_s));
  sm.process_event(e1{});
  assert(sm.is("s1"_s, "idle2"_s));
  sm.process_event(e2{});
  assert(sm.is(X, "s2"_s));
  sm.process_event(e3{});
  assert(sm.is(X, X));
  return 0;
}

History

通过"idle"_s(H)可以将一个状态机标识为history模式(同时它也是该状态机的初始状态)。标识为history模式意味着每次进入该状态机时被激活的状态是最近使用的状态(上一次离开时的最后的激活状态)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <boost/sml.hpp>
#include <cassert>
#include <iostream>
namespace sml = boost::sml;

namespace {
struct sub {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
      "s1"_s <= "idle"_s(H) + "e1"_e / [] { std::cout << "in sub" << std::endl; }
    , X      <= "s1"_s      + "e2"_e / [] { std::cout << "in sub again" << std::endl; }
    );
  }
};

struct history {
  auto operator()() const noexcept {
    using namespace sml;
    return make_transition_table(
      state<sub> <= *"idle"_s  + "e1"_e / [] { std::cout << "enter sub" << std::endl; }
    , "s1"_s     <= state<sub> + "e3"_e / [] { std::cout << "exit sub" << std::endl; }
    , state<sub> <= "s1"_s     + "e4"_e / [] { std::cout << "enter sub again" << std::endl; }
    );
  }
};
}  // namespace

int main() {
  sml::sm<history> sm;
  using namespace sml;
  sm.process_event("e1"_e());
  //enter sub
  sm.process_event("e1"_e());
  //in sub
  sm.process_event("e3"_e()); 
  // exit sub
  sm.process_event("e4"_e());  
  // enter sub again
  sm.process_event("e2"_e());  
  // in sub again (history)
  return 0;
}

Runtime Dispatcher

我们可以通过给事件标号,然后在运行时通过标号来表示发生的事件进行转移。既可以通过定义静态常量来标号,也可以通过继承sml::utility::id来进行标号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <boost/sml/utility/dispatch_table.hpp>
#include <boost/sml.hpp>
#include <cassert>
namespace sml = boost::sml;

namespace {
struct runtime_event {
  int id = 0;
};
struct event1 {
  static constexpr auto id = 1;
  event1(const runtime_event &) {}
};
struct event2: sml::utility::id<2> {};

struct dispatch_table {
  auto operator()() noexcept {
    using namespace sml;
    return make_transition_table(
       *"idle"_s + event<event1> = "s1"_s
      , "s1"_s + event<event2> = X
    );
  }
};
}

int main() {
  sml::sm<dispatch_table> sm;
  auto dispatch_event = sml::utility::make_dispatch_table<runtime_event, 1 /*min*/, 5 /*max*/>(sm);

  {
    runtime_event event{1};
    dispatch_event(event, event.id);
  }

  {
    runtime_event event{2};
    dispatch_event(event, event.id);
  }
  assert(sm.is(sml::X));
  return 0;
}

Error Handling

我们可以在状态转移表中定制状态机发生异常时的行为,这里异常既可以是std::exception,也可以是某些不正常的转移。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <boost/sml.hpp>
#include <cassert>
#include <iostream>
#include <stdexcept>
namespace sml = boost::sml;

namespace {
struct some_event {};
struct error_handling {
  auto operator()() const {
    using namespace sml;
    return make_transition_table(
        *("idle"_s) + "event1"_e / [] { throw std::runtime_error{"error"}; }
      ,   "idle"_s  + "event2"_e / [] { throw 0; }

      , *("exceptions handling"_s) + exception<std::runtime_error> / [] { std::cout << "exception caught" << std::endl; }
      ,   "exceptions handling"_s  + exception<_> / [] { std::cout << "generic exception caught" << std::endl; } = X

      , *("unexpected events handling"_s) + unexpected_event<some_event> / [] { std::cout << "unexpected event 'some_event'" << std::endl; }
      ,   "unexpected events handling"_s  + unexpected_event<_> / [] { std::cout << "generic unexpected event" << std::endl; } = X
    );
  }
};
}  // namespace

int main() {
  using namespace sml;
  sm<error_handling> sm;
  sm.process_event("event1"_e());  // throws runtime_error
  assert(sm.is("idle"_s, "exceptions handling"_s, "unexpected events handling"_s));
  sm.process_event("event2"_e());  // throws 0
  assert(sm.is("idle"_s, X, "unexpected events handling"_s));
  sm.process_event(some_event{});  // unexpected event
  assert(sm.is("idle"_s, X, "unexpected events handling"_s));
  sm.process_event(int{});  // unexpected any event
  assert(sm.is("idle"_s, X, X));
  return 0;
}

Testing

[Boost::ext].SML为我们提供了set_current_states成员函数来方便我们更好地测试与调试。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <boost/sml.hpp>
#include <cassert>
namespace sml = boost::sml;

namespace {
struct e1 {};
struct e2 {};
struct e3 {};

struct data {
  int value = 0;
};

struct testing {
  auto operator()() const noexcept {
    using namespace sml;

    const auto guard = [](data& d) { return !d.value; };
    const auto action = [](data& d) { d.value = 42; };

    return make_transition_table(
       *"idle"_s + event<e1> = "s1"_s
      , "s1"_s + event<e2> = "s2"_s
      , "s2"_s + event<e3> [guard] / action = X // transition under test
    );
  }
};
}  // namespace

int main() {
  using namespace sml;
  data fake_data{0};
  sml::sm<::testing, sml::testing> sm{fake_data};
  sm.set_current_states("s2"_s);
  sm.process_event(e3{});
  assert(sm.is(X));
  assert(fake_data.value == 42);
  return 0;
}

Logging

[Boost::ext].SML还为我们提供了日志功能让我们更方便的跟踪和debug。详见最后一个例子。

Policy

[Boost::ext].SML为我们提供了四种dispatch的实现方式,分别是jump table,switch/case,if/else和fold expression,我们可以在创建状态机时指定其中的任意一个。

1
2
3
4
sml::sm<Connection, sml::dispatch<sml:🔙:policies::jump_table>> connection{};
sml::sm<Connection, sml::dispatch<sml:🔙:policies::switch_stm>> connection{};
sml::sm<Connection, sml::dispatch<sml:🔙:policies::branch_stm>> connection{};
sml::sm<Connection, sml::dispatch<sml:🔙:policies::fold_expr>> connection{};

各自的实现如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct jump_table {
  template <class TEvent, class... TStates>
  constexpr auto dispatch(State &current_state,
                          const TEvent &event,
                          type_list<TStates...>) {
    using dispatch_table_t = bool (*)(const TEvent&, State &);
    constexpr static dispatch_table_t dispatch_table[] = {
      &mappings_t<TStates>::template execute<TEvent>...
    };
    return dispatch_table[current_state](event);
  }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct switch_stm {
  template <class TEvent>
  constexpr auto dispatch(State &, const TEvent &, type_list<>) { }

  template <class TEvent, class TState, class... TStates>
  constexpr auto dispatch(State &current_state,
                          const TEvent &event,
                          type_list<TState, TStates...>) {
    switch (current_state) {
      default: return dispatch(current_state, type_list<TStates...>{});
      case N: return mappings_t<TState>::template execute<TEvent>(event);
    }
  }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct branch_stm {
  template <class TEvent>
  constexpr auto dispatch(State &, const TEvent &, type_list<>) { }

  template <class TEvent, class TState, class... TStates>
  constexpr auto dispatch(State &current_state,
                          const TEvent &event,
                          type_list<TState, TStates...>) {
    return current_state == N
      ? mappings_t<TState>::template execute<TEvent>(event)
      : dispatch(current_state, event, type_list<TStates...>{});
  }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct fold_expr {
  template <auto... Ns, class TEvent, class... TStates>
  constexpr auto dispatch_impl(State &current_state,
                              index_sequence<Ns...>,
                              const TEvent &event,
                              type_list<TStates...>) {
    return ((current_state == Ns
      ? mappings_t<TStates>::template
          execute<TEvent>(event)
      : false
    ) || ...);
  }
};

应用:一个模拟服务器demo的实现

最后,让我们来看一个综合性的例子,在这个例子中,我们需要实现如下的状态机:

这是一个能够处理外部请求的系统,系统开启后能够处理外来的连接,挂起时能保留外部请求并在恢复后重新处理,同时,它还有一个单独的分支(watchdog定时器)用于监视系统正常运行。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <boost/sml.hpp>
#include <boost/sml/utility/dispatch_table.hpp>
#include <cassert>
#include <fstream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <iostream>
namespace sml = boost::sml;

namespace {
/// logger
struct printf_logger {
  template <class SM, class TEvent>
  void log_process_event(const TEvent&) {
    printf("[%s][process_event] %s\n", sml::aux::get_type_name<SM>(), sml::aux::get_type_name<TEvent>());
  }
  template <class SM, class TGuard, class TEvent>
  void log_guard(const TGuard&, const TEvent&, bool result) {
    printf("[%s][guard] %s %s %s\n", sml::aux::get_type_name<SM>(), sml::aux::get_type_name<TEvent>(),
           sml::aux::get_type_name<TGuard>(), (result ? "[OK]" : "[Reject]"));
  }
  template <class SM, class TAction, class TEvent>
  void log_action(const TAction&, const TEvent&) {
    printf("[%s][action] %s %s\n", sml::aux::get_type_name<SM>(), sml::aux::get_type_name<TEvent>(),
           sml::aux::get_type_name<TAction>());
  }
  template <class SM, class TSrcState, class TDstState>
  void log_state_change(const TSrcState& src, const TDstState& dst) {
    printf("[%s][transition] %s -> %s\n", sml::aux::get_type_name<SM>(), src.c_str(), dst.c_str());
  }
};

/// events
struct connect : sml::utility::id<__COUNTER__> {};
struct ping : sml::utility::id<__COUNTER__> {
  explicit ping(const void* msg)
     : valid{msg} {}
  bool valid{};
};
struct established : sml::utility::id<__COUNTER__> {};
struct timeout : sml::utility::id<__COUNTER__> {};
struct disconnect : sml::utility::id<__COUNTER__> {};
struct power_up : sml::utility::id<__COUNTER__> {};
struct suspend : sml::utility::id<__COUNTER__> {};
struct resume : sml::utility::id<__COUNTER__> {};
struct tick : sml::utility::id<__COUNTER__> {};

/// guards
const auto is_valid = [](const auto& event) {
  std::puts("is_valid");
  return event.valid;
};
const auto is_healthy = [] {
  std::puts("is_healthy");
  return true;
};
const auto has_battery = [] {
  std::puts("has_battery");
  return true;
};

/// actions
const auto establish = []() { std::cout << "establish\n"; };
const auto close = []() { std::cout << "close\n"; };
const auto resetTimeout = []() { std::cout << "resetTimeout\n"; };
const auto setup = []() { std::cout << "setup\n"; };

class System {
  struct Connection {
    auto operator()() const {
      using namespace sml;
      return make_transition_table(
        "Disconnected"_s(H) + event<connect> / establish                = "Connecting"_s,
        "Connecting"_s      + event<established>                        = "Connected"_s,
        "Connected"_s       + event<ping> [ is_valid ] / resetTimeout,
        "Connected"_s       + event<timeout> / establish                = "Connecting"_s,
        "Connected"_s       + event<disconnect> / close                 = "Disconnected"_s
      );
    }
  };

 public:
  auto operator()() const {
    using namespace sml;
    // clang-format off
    return make_transition_table(
     * "Idle"_s          + event<power_up> [ has_battery and
                                             is_healthy ] / setup       = state<Connection>,
       state<Connection> + event<suspend>                               = "Suspended"_s,
       "Suspended"_s     + event<resume>                                = state<Connection>,
       "Suspended"_s     + event<ping> / defer,
     // --------------------------------------------------------------------------------- //
     * "Watchdog"_s      + event<tick> / resetTimeout,
       "Watchdog"_s      + event<timeout>                               = X
    );
  }
};

}

int main(int argc, char** argv) {
  using namespace sml;
  printf_logger log{};
  std::ofstream out{"out"};
  sm<System, logger<printf_logger>, defer_queue<std::deque>, dispatch<back::policies::switch_stm>> system{log};
  auto dispatch = sml::utility::make_dispatch_table<void*, 0 /*min*/, __COUNTER__ - 1 /*max*/>(system);

  for (auto i = 1; i < argc; ++i) {
    const auto event_id = std::atoi(argv[i]);
    dispatch(nullptr, event_id);
  }
  return 0;
}

测试结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
./a.out 5 0 2 1

[{anonymous}::System][process_event] {anonymous}::power_up
has_battery
[{anonymous}::System][guard] {anonymous}::power_up {anonymous}::<lambda()> [OK]
is_healthy
[{anonymous}::System][guard] {anonymous}::power_up {anonymous}::<lambda()> [OK]
[{anonymous}::System][transition] Idle -> {anonymous}::System::Connection
[{anonymous}::System][action] {anonymous}::power_up {anonymous}::<lambda()>
setup
[{anonymous}::System][process_event] {anonymous}::connect
[{anonymous}::System::Connection][process_event] {anonymous}::connect
[{anonymous}::System::Connection][transition] Disconnected -> Connecting
[{anonymous}::System::Connection][action] {anonymous}::connect {anonymous}::<lambda()>
establish
[{anonymous}::System][process_event] {anonymous}::established
[{anonymous}::System::Connection][process_event] {anonymous}::established
[{anonymous}::System::Connection][transition] Connecting -> Connected
[{anonymous}::System][process_event] {anonymous}::ping
[{anonymous}::System::Connection][process_event] {anonymous}::ping
is_valid
[{anonymous}::System::Connection][guard] {anonymous}::ping {anonymous}::<lambda(const auto:13&)> [Reject]