基於 http://www.boost.org/doc/libs/1_59_0/libs/statechart/doc/tutorial.html#AsynchronousStateMachines 的文檔說明html
git地址和例子在:https://github.com/boostorg/statechartreact
最近項目老大對遊戲服務器的施法狀態機進行了重構,藉此機會研究了兩天,並且中文文檔比較少,整理一下本身備忘的同時也許可以給別人一點參考。我僅限於對同步狀態機的實現作了一點驗證性的試驗,異步狀態機的實如今example中有,用到的時候再研究,通常若是沒有涉及到線程間交互的話同步狀態機應該夠用了。git
statechart大概能夠包含三塊,狀態機:state_machine,狀態:state或simple_state以及事件 event。state_machine做爲載體,state做爲內容,event做爲事件傳輸介質, 官方的helloword是一個最簡單的初始化程序,能夠看一下,下面介紹一些其餘使用注意事項和擴展,以及羅列本身在寫demo時候的一些問題。github
關於定義須要注意的問題:數組
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(二層狀態)..的方式就沒什麼問題。函數
事件,行爲及狀態切換post
事件是狀態機中可能被觸發的消息,當事件被觸發的時候,若是不採起特殊措施,當前所處狀態會首先接受到事件,若是註冊了事件處理函數,則交由事件處理函數進行處理,不然將向外層拋出事件,直到找到對應的消息處理函數。異常傳播也是相似的機制,若是本身這層沒有捕捉到異常異常就會向外層擴散,只不過是若是連最外層的狀態找不到對應的事件處理函數,這個消息就失效了,可是異常會一直向外傳播直到本身的客戶端程序。this
行爲,表示如何處理事件。有一種簡單的方式是收到某個消息直接進行狀態切換 相似於 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 部分。
異步狀態機 沒有細研究,暫不作記錄