1,创建事件和状态
状态机由有限数量的状态和转换组成,这些状态和转换通过事件触发
事件只是一种唯一的类型,它将由状态机处理。
struct my_event { ... };
您还可以创建事件实例来简化转换表表示法
auto event = sml::event<my_event>;
如果您碰巧有一个Clang/GCC编译器,您可以动态创建一个Event
using namespace sml;
auto event = "event"_e;
注:这种创建的方式不会存储任何的数据信息
当机器进入/离开State时,State可以执行进入/退出行为,并表示状态机流的当前位置。
auto idle = sml::state<class idle>;
如果你碰巧有一个Clang/GCC编译器,你可以动态地创建一个State。
using namespace sml;
auto state = "idle"_s;
注:SML状态不能有数据,因为数据直接被注入到守卫/动作中
状态机可能是状态本身
sml::state<state_machine> composite;
SML支持终止状态,终止状态将停止要处理的事件。它由X定义。回到本来状态的本身
"state"_s = X;
状态也可以打印
assert(string("idle") == "idle"_s.c_str());
2,创建守卫和动作
守卫和动作是可调用的对象,将由状态机执行,以验证转换后的操作是否应该发生。
注:守卫必须返回一个bool的值
auto guard1 = [] {
return true;
};
auto guard2 = [](int, double) { // guard with dependencies
return true;
};
auto guard3 = [](int, auto event, double) { // guard with an event and dependencies
return true;
};
struct guard4 {
bool operator()() const noexcept {
return true;
}
};
动作不需要返回值
auto action1 = [] { };
auto action2 = [](int, double) { }; // action with dependencies
auto action3 = [](int, auto event, double) { }; // action with an event and dependencies
struct action4 {
void operator()() noexcept { }
};
3,创建一个转换表
当我们有了状态和事件,我们最终可以创建一个转换表来表示我们的转换。
SML使用类似于euml的DSL是为了尽可能接近UML设计。
后缀表示法:
Expression | Description |
state + event [ guard ] | 警戒时事件e的内部过渡 |
src_state / [] {} = dst_state | 带有操作的匿名转换 |
src_state / [] {} = src_state | 自转换 (调用 on_exit/on_entry) |
src_state + event = dst_state | 事件e的外部过渡,没有警戒或动作 |
src_state + event [ guard ] / action = dst_state | 在事件e上使用保护和动作从src_state转换到dst_state |
src_state + event [ guard && (![]{return true;} && guard2) ] / (action, action2, []{}) = dst_state | 在事件e上使用保护和动作从src_state转换到dst_state |
过度流:
src_state + event [ guard ] / action = dst_state
^
|
|
1. src_state + on_exit
2. dst_state + on_entry
为了创建一个转换表,提供了make_transition_table。
using namespace sml; // Postfix Notation
make_transition_table(
*"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s
, "dst_state"_s + "other_event"_e = X
);
4,设置初始化状态
初始状态告诉状态机从哪里开始。它可以通过在State前加上*来设置
using namespace sml;
make_transition_table(
*"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s,
"dst_state"_s + event<game_over> = X
);
可以有多个初始态。所有初始状态将以伪并行的方式执行。这样的状态称为正交区域
using namespace sml;
make_transition_table(
*"region_1"_s + event<my_event1> [ guard ] / action = "dst_state1"_s,
"dst_state1"_s + event<game_over> = X,
*"region_2"_s + event<my_event2> [ guard ] / action = "dst_state2"_s,
"dst_state2"_s + event<game_over> = X
);
5,创建一个状态机
状态机是保存当前状态和处理事件的转换表的抽象。要创建状态机,我们必须添加一个转换表
class example {
public:
auto opeartor()() {
using namespace sml;
return make_transition_table(
*"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s,
"dst_state"_s + event<game_over> = X
);
}
};
配置好转换表后,我们可以创建一个状态机
状态机构造函数为动作和守护提供必需的依赖项
/---- event (injected from process_event)
|
auto guard = [](double d, auto event) { return true; }
|
\--------\
|
auto action = [](int i){} |
| |
| |
\-\ /---/
| |
sml::sm<example> s{42, 87.0};
sml::sm<example> s{87.0, 42}; // order in which parameters have to passed is not specificied
传递和维护大量的依赖关系可能是乏味的,并且需要大量的样板代码。
为了避免这种情况,可以使用依赖注入库来自动化这个过程。例如,我们可以使用ext Boost.DI。
auto injector = di::make_injector(
di::bind<>.to(42)
, di::bind<interface>.to<implementation>()
);
auto sm = injector.create<sm<example>>();
sm.process_event(e1{});
6,处理事件
状态机是一种简单的creature。它的主要目的是处理事件。要做到这一点,可以使用process_event方法。
sml::sm<example> sm;
sm.process_event(my_event{}); // handled
sm.process_event(int{}); // not handled -> unexpected_event<int>
也可能在转换表上触发流程事件,也就是一个事件触发后,接着在动作的位置触发事件流
using namespace sml;
return make_transition_table(
*"s1"_s + event<my_event> / process_event(other_event{}) = "s2"_s,
"s2"_s + event<other_event> = X
);
8,解决错误
当状态机不能处理给定事件时,将触发一个unexpected_event。
make_transition_table(
*"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s
, "src_state"_s + unexpected_event<some_event> = X
);
任何意外事件也可以通过使用unexpected_event<_>来处理。
make_transition_table(
*"src_state"_s + event<my_event> [ guard ] / action = "dst_state"_s
, "src_state"_s + unexpected_event<some_event> / [] { std::cout << "unexpected 'some_event' << '\n'; "}
, "src_state"_s + unexpected_event<_> = X // any event
);
sm.process_event (some_event {});// "意外的'some_event' sm.process_event(int{});/ /terminate assert(sm.is (X));
Usually, it's handy to create additional `Orthogonal region` to cover this scenario,
This way State causing unexpected event does not matter.
```cpp
make_transition_table(
*"idle"_s + event<my_event> [ guard ] / action = "s1"_s
, "s1"_s + event<other_event> [ guard ] / action = "s2"_s
, "s2"_s + event<yet_another_event> [ guard ] / action = X
// terminate (=X) the Machine or reset to another state
,*"error_handler"_s + unexpected_event<some_event> = X
);
我们总是可以通过。来检查状态机是否处于终止状态。
assert(sm.is(sml::X)); // doesn't matter how many regions there are
当异常被启用(project不是用-fno-exceptions编译)时,可以使用exception<name>语法捕获它们。
</name>异常处理程序将按照它们被定义的顺序进行处理,Exception <>可以用来捕获任何东西(相当于catch(…))。
请注意,当转换表中没有定义异常处理程序时,状态机将不会处理异常。
make_transition_table(
*"idle"_s + event<event> / [] { throw std::runtime_error{"error"}; }
,*"error_handler"_s + exception<std::runtime_error> = X
, "error_handler"_s + exception<std::logic_error> = X
, "error_handler"_s + exception<> / [] { cleanup...; } = X // any exception
);
9,测试它
有时,验证状态机是否处于特定状态是很有用的,例如,我们是否处于终止状态。我们可以使用is或visit_current_states功能来使用SML。
sml::sm<example> sm;
sm.process_event(my_event{});
assert(sm.is(X)); // is(X, s1, ...) when you have orthogonal regions
//or
sm.visit_current_states([](auto state) { std::cout << state.c_str() << std::endl; });
在此基础上,SML提供测试工具来整体检查状态机。在test::sm中可以使用Set_current_states方法来将状态机设置为请求状态。
sml::sm<example, sml::testing> sm{fake_data...};
sm.set_current_states("s3"_s); // set_current_states("s3"_s, "s1"_s, ...) for orthogonal regions
sm.process_event(event{});
assert(sm.is(X));
番外篇:
Initial Pseudostate
Terminate Pseudostate
External transition
Anonymous transition