基於 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 部分。
異步狀態機 沒有細研究,暫不做記錄