boost::statechart研究報告


    基於 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 部分。

  異步狀態機  沒有細研究,暫不做記錄

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM