基于 http://www.boost.org/doc/libs/1_59_0/libs/statechart/doc/tutorial.html#AsynchronousStateMachines 的文档说明
git地址和例子在:https://github.com/boostorg/statechart
最近项目老大对游戏服务器的施法状态机进行了重构,借此机会研究了两天,而且中文文档比较少,整理一下自己备忘的同时也许能够给别人一点参考。我仅限于对同步状态机的实现做了一点验证性的试验,异步状态机的实现在example中有,用到的时候再研究,一般如果没有涉及到线程间交互的话同步状态机应该够用了。
statechart大概可以包含三块,状态机:state_machine,状态:state或simple_state以及事件 event。state_machine作为载体,state作为内容,event作为事件传输介质, 官方的helloword是一个最简单的初始化程序,可以看一下,下面介绍一些其他使用注意事项和扩展,以及罗列自己在写demo时候的一些问题。
关于定义需要注意的问题:
1:如何定义一个state_machine,一个state和simple_state和如何定义一个event,看文档都能明白,注意自定义的状态继承state和simple_state有一点微小区别,继承 state 需要传入一个my_context作为上下文参数,如果你想在一个状态的构造函数中 使用以下函数族的话:
simple_state<>::post_event() simple_state<>::clear_shallow_history<>() simple_state<>::clear_deep_history<>() simple_state<>::outermost_context() simple_state<>::context<>() simple_state<>::state_cast<>() simple_state<>::state_downcast<>() simple_state<>::state_begin() simple_state<>::state_end()
乖乖从state派生否则会报错,源码中有对应说明,以上函数组应该都会调用到下面的接口,因此在模块内部进行了限制,所以我觉得是可以默认从state继承用的。
from:simple_state.hpp
outermost_context_type & outermost_context() { // This assert fails when an attempt is made to access the state machine // from a constructor of a state that is *not* a subtype of state<>. // To correct this, derive from state<> instead of simple_state<>. BOOST_ASSERT( get_pointer( pContext_ ) != 0 ); return pContext_->outermost_context(); } const outermost_context_type & outermost_context() const { // This assert fails when an attempt is made to access the state machine // from a constructor of a state that is *not* a subtype of state<>. // To correct this, derive from state<> instead of simple_state<>. BOOST_ASSERT( get_pointer( pContext_ ) != 0 ); return pContext_->outermost_context(); }
2: 还有需要注意的是状态定义的先后问题,基本上遵从从外到里的方式,即 evnet, state_machine, state, sub_state(二层状态)..的方式就没什么问题。
事件,行为及状态切换
事件是状态机中可能被触发的消息,当事件被触发的时候,如果不采取特殊措施,当前所处状态会首先接受到事件,如果注册了事件处理函数,则交由事件处理函数进行处理,否则将向外层抛出事件,直到找到对应的消息处理函数。异常传播也是类似的机制,如果自己这层没有捕捉到异常异常就会向外层扩散,只不过是如果连最外层的状态找不到对应的事件处理函数,这个消息就失效了,但是异常会一直向外传播直到自己的客户端程序。
行为,表示如何处理事件。有一种简单的方式是收到某个消息直接进行状态切换 类似于 sc::transition<EvStartStop, MyState_0>的方式,收到EvStartStop就跳转到MyState_0状态了,比较暴力直接;还有一种方式是使用react函数进行自定义处理,如sc::custom_reaction<EvBingo>,这时候在cpp中实现一个react,比如下面这样:
//达成目标切换到初始 sc::result MyState_2_1::react(const EvBingo& event) { //post_event(EvStartStop()); //允许从构造函数中发放消息 std::cout << "恭喜你完成了状态机训练,让我们从头开始!!" << std::endl; std::cout << context<MyStateMachine>().rtStr() << std::endl; return discard_event(); }
如果是自定义了react消息,表示当前状态接受并处理了EvBingo消息,他有权抛弃事件(discard_event),抛出其他消息但是会延迟到本函数执行完毕后抛出(post_event(xxx)),立即抛出消息(process_event(xxx)),继续向上层状态抛出同一事件(forward_event),或者直接跳转 (transit),但是要注意的是,如果使用的是transit或者process_event这种可能导致状态即时切换的函数时,最好在之后的流程中不要对当前state进行操作了,因为它相当于你delete了一个类对象但是还在继续使用对象的数据,这个操作是相当危险的。所以 transit和process_event一般是放在 react函数流程的最后执行的。
上面介绍的几个函数包含了行为切换的一部分,既可以在当前状态内自由的抛弃,传播(新)事件,甚至直接做状态切换等,这里顺便提一句,还可以吧事件定义为defferal,如 sc::deferral< EvBingo >,那这个事件也会被延迟抛出,只不过它延迟的时间点更靠后,上面说到 post_event(xxx)和forward_event可能会等到react函数执行完毕之后再进行事件触发,但是deferral保证只有当前状态exit之后才分发这个事件。也就是说如果没有出当前状态这个事件将被永久积压!
以上几种行为都是在某种状态中进行的,实际上无论处在react函数中,还是状态的析构函数中,都还没脱离当前状态,那么如何在状态切换的中间做一点事情,即当前状态机不处于任何状态,这就用到了 transaction function,他在本状态切换出去之后并且尚未进入下一状态之前被调用
sc::transition<EvRtToLast, sc::deep_history<MyState_2_0>, MyStateMachine, &MyStateMachine::onEvStartState1>, //中间状态
在EvRtToLast事件被分发后,状态机脱离了本状态准备切换到下一状态,此时会在中间调用 MyStateMachine::onEvStartState1函数。
历史状态回溯 History
假如我们有这样一个模型,状态1是单一状态记为S1,状态2是多层状态记为S2,里面包含n个子状态,S20,S21...S2n, 那么假设在第一次从S1切换到S2的时候,在S2内部进行了一顿鼓捣又回到了S1,此时S1希望再次切换到S2时能够自动切换到S2内部的最近一次的历史状态。按照官方的例子来说就是你用照相机的时候,半松开按键的时候并不希望照相机总是回到空闲状态,而是希望回到进入拍照状态之前界面。这时候需要用到历史回溯功能,我的例子因为是随便敲的比较粗糙,将就配合解释一下:
struct MyState_1 : public sc::state< MyState_1, MyStateMachine> { public: MyState_1(my_context ctx); virtual ~MyState_1(); //状态列表 typedef boost::mpl::list< .... sc::transition< EvStartState2, sc::deep_history<MyState_2_1> > > reactions; .... }; struct MyState_2 : public sc::state< MyState_2, MyStateMachine, MyState_2_0, sc::has_deep_history > { public: MyState_2(my_context ctx); virtual ~MyState_2(); .... }; struct MyState_2_0 : public sc::state<MyState_2_0, MyState_2> {...} struct MyState_2_1 : public sc::state<MyState_2_1, MyState_2> {...} int main() { sm.process_event(EvStartState2()); // 第一次进入state2,进入history默认的状态 MyState_2_1 sm.process_event(EvStartState2_0()); //回到 MyState_2_0 sm.process_event(EvStartState1()); //回到 MyState_1 sm.process_event(EvStartState2()); //第二次进入state2,将进入MyState_2_0,因为历史列表上MyState_2_0在最顶上 return 0; }
正交状态机
正交状态机的意思是可能有多个初始状态,但是这些状态链是互不联系的,既不能从一个正交状态切换到另一个正交状态,官方的例子已经很详尽了, 见 http://www.boost.org/doc/libs/1_59_0/libs/statechart/doc/tutorial.html#AsynchronousStateMachines 中 Orthogonal states 部分。
异步状态机 没有细研究,暂不做记录