[Boost::ext].SML
是一个轻量级的,header only且高性能的状态机语言库,它使得我们可以用十分清晰简洁的代码去构建一个状态机。
相比于Boost::MSM
和Boost::statechart
,它更为轻量和高效,从作者给出的benchmark来看,在编译时间,执行时间,可执行文件大小,代码长度等方面[Boost::ext].SML
都完胜后者。而相比于传统的if/else
和enum+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 ¤t_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 ¤t_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 ¤t_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 ¤t_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]
|