001.SML状态机

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设计。

后缀表示法:

ExpressionDescription
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
    );
  }
};

配置好转换表后,我们可以创建一个状态机

sml::sm<example> sm;

状态机构造函数为动作和守护提供必需的依赖项

                           /---- 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

* "idle"_s

Terminate Pseudostate

sml::X

External transition

"src_state"_s + event [ guard ] / action = "dst_state"_s

Anonymous transition

"src_state"_s = "dst_state"_s

 

 

 

 

 

 

 

 

 

 
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值