談 C++17 裏的 State 模式之二

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是第二部分,有關有限狀態機(FSM)的 C++ 實作部分,也等同於狀態模式實現","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Prologue","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上一篇 ","attrs":{}},{"type":"link","attrs":{"href":"/c++/algorithm/cxx17-state-pattern/","title":"","type":null},"content":[{"type":"text","text":"談 C++17 裏的 State 模式之一","attrs":{}}]},{"type":"text","text":" 對於狀態模式所牽扯到的基本概念做了一個綜述性的梳理。所以是時候從這些概念中抽取我們感興趣的部分予以實作了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"C++ 實現(元編程實現)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果不採用 DFA 理論推動的手段,而是在 C++11/17 的語境裏考慮實現狀態模式,那麼我們應該重新梳理一下理論:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"狀態機 FSM:狀態機總是有限的(我們不可能去處理無限的狀態集合)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開始狀態 S:Start State/Initial State","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當前狀態 C:Current State","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下一狀態 N:Next State","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"終止狀態:Terminated State (Optional)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"進入狀態時的動作:enter-action","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"離開狀態時的動作:exit-action","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸入動作/輸入流:input action,也可以是輸入條件、或者事件對象等","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉換:Transition","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上下文:Context","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"負載:Payload","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有的時候,Input Action 也被稱作 Transition Condition/Guard。它的內涵始終如一,是指在進入下一狀態前通過條件進行判定狀態變遷是否被許可。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"狀態機","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"核心定義","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據以上的設定,我們決定了 fsm machine 的基礎定義如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"namespace fsm_cxx {\n\n AWESOME_MAKE_ENUM(Reason,\n Unknown,\n FailureGuard,\n StateNotFound)\n\n template,\n typename ContextT = context_t,\n typename ActionT = action_t,\n typename CharT = char,\n typename InT = std::basic_istream>\n class machine_t final {\n public:\n machine_t() {}\n ~machine_t() {}\n machine_t(machine_t const &) = default;\n machine_t &operator=(machine_t &) = delete;\n\n using Event = EventT;\n using State = StateT;\n using Context = ContextT;\n using Payload = PayloadT;\n using Action = ActionT;\n using Actions = detail::actions_t;\n using Transition = transition_t;\n using TransitionTable = std::unordered_map;\n using OnAction = std::function;\n using OnErrorAction = std::function;\n using StateActions = std::unordered_map;\n using lock_guard_t = util::cool::lock_guard;\n\n // ...\n };\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是反覆迭代之後的成果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你一定要明白,多數人,和我一樣,都是那種腦容量普通的人,我們做設計時一開始都是簡陋的,然後不斷修正枝蔓、改善設計後才能得到看起來似乎還算完備的結果,如同上面給出的主機器定義那樣。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"早期版本","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作爲一個信心增強,下面給出首次跑通一個事件觸發和狀態推進時的 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/hedzr/fsm-cxx/blob/648fde035a98e67b9aa06efae65a90b7bfe5d34d/include/fsm_cxx/fsm-sm.hh#L221","title":"","type":null},"content":[{"type":"text","text":"machine_t 定義","attrs":{}}]},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":" template,\n typename ActionT = action_t,\n typename CharT = char,\n typename InT = std::basic_istream>\n class machine_t final {\n public:\n machine_t() {}\n ~machine_t() {}\n\n using State = StateT;\n using Action = ActionT;\n using Context = ContextT;\n using Transition = transition_t;\n using TransitionTable = std::unordered_map;\n using on_action = std::function;\n \n // ...\n };\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你得知道的是,狀態機的設計有一定的複雜度,這個規模不能算大規模,中規模都算不上,但是也不算小。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不會有多少人能夠一次性將其設計並編碼到位。除非這個人腦容量特大,再不然就是他習慣於首先做完備的 UML 圖,然後 convert to C++ codes...,不過這種功能應該是在 IBM Rational Rose 時代才比較行得通的步驟了,現在已經不太可能借助什麼 UML 工具這樣來做設計了,我不清楚 PlantUML 今天的發展情況,但我自己是很久沒有畫過 UML 圖了,還不如我手寫出 classes 來得直觀呢,至少對我的腦路是這樣的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"闡釋","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"machine_t","attrs":{}}],"attrs":{}},{"type":"text","text":" 的頭部定義了一堆模板參數。我覺得無需要什麼額外的解釋,它們的用意大約是能夠直白地傳遞給你的,如果不能,你可能需要回顧一下狀態機的各種背景,嗯,問題絕對不會在我身上。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"S","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"StateT","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要特別提及的是,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"S","attrs":{}}],"attrs":{}},{"type":"text","text":" 是 State 的枚舉類傳入,我們要求你一定要在這裏傳入一個枚舉類作爲狀態表,並且我們建議你的枚舉類採用 AWESOME_MAKE_ENUM 宏來幫助定義(不是必需)。注意在稍後 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"S","attrs":{}}],"attrs":{}},{"type":"text","text":" 會被 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"state_t","attrs":{}}],"attrs":{}},{"type":"text","text":" 所裝飾,在 machine_t 內部的一切場所,我們只會使用這個包裝過後的類來訪問和操作狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是一種防禦性的編程手法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假如未來我們想要引入其他機制,例如一個狀態類體系而不是枚舉類型的值表示,那麼我們可以提供一個不同的 state_t 包裝方案,從而將新的機制無破壞地引入到現有的 machine_t 體系中。甚至於我們連 state_t 也可以不必破壞,僅僅是對其做帶有 enable_if 的模板特化就足矣。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"StateT","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"State","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可能注意到模板參數 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"StateT","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"using","attrs":{}}],"attrs":{}},{"type":"text","text":" 別名 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"State","attrs":{}}],"attrs":{}},{"type":"text","text":" 了:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"using State = StateT;\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義別名的用意至少有兩個:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在 machine_t 的內部和外部,使用類型別名 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"State","attrs":{}}],"attrs":{}},{"type":"text","text":" 比使用 machine_t 的模板參數名要可靠的多,並且多數時候(尤其是在 machine_t 的外部)你只能使用類型別名","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"採用抽象後的類型別名有利於調整調優設計","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"State","attrs":{}}],"attrs":{}},{"type":"text","text":" 上,我們可以直接使用 StateT,也可以使用更復雜的定義,這些變更(幾乎)不會影響到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"State","attrs":{}}],"attrs":{}},{"type":"text","text":" 的使用者。例如:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"using State = std::optional;\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也是行得通的。當然實際工程中這麼做沒有什麼必要性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"CharT","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"InT","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它們會在未來某一時間點有用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於喫進字符流並作 DFA 推動的場景它們可能是有用的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但目前只是停留在唸頭上。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"OnAction","attrs":{}}],"attrs":{}},{"type":"text","text":" 以及 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"OnErrorAction","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"OnAction","attrs":{}}],"attrs":{}},{"type":"text","text":" 實際上是 on_transition_made / on_state_changed 的意思。暫時來講我們沒有 rename 令其更顯著,因爲當初只想着要有一個可以調試輸出的 callback,還沒有想過 on_state_changed 的 Hook 的必要性。直到後來做了 OnErrorAction 的設計之後才察覺到有必要關聯兩個 callbacks。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"其它定義以及如何使用","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"狀態集合","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有可能有多種方式提供狀態集合,如:枚舉量,整數,短字符串,甚至是小型結構。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不過在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"fsm-cxx","attrs":{}}],"attrs":{}},{"type":"text","text":" 中,我們約定你總是定義枚舉量作爲 fsm machine 的狀態集合。你的枚舉類型將作爲 machine 的模板參數 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"S","attrs":{}}],"attrs":{}},{"type":"text","text":" 而傳遞,machine 將以此爲基礎進行若干的封裝。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"指定狀態的枚舉量集合","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以使用時的代碼類似於這樣:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"AWESOME_MAKE_ENUM(my_state,\n Empty,\n Error,\n Initial,\n Terminated,\n Opened,\n Closed)\n\nmachine_t m;\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 ","attrs":{}},{"type":"link","attrs":{"href":"https://hedzr.com/c++/algorithm/cxx-enum-class/","title":"","type":null},"content":[{"type":"text","text":"cxx 枚舉類型","attrs":{}}]},{"type":"text","text":" 中我們曾經介紹過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AWESOME_MAKE_ENUM","attrs":{}}],"attrs":{}},{"type":"text","text":" 可以簡化枚舉類型的定義,在這裏你只需要將其看成是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"enum class my_state {\n Empty,\n Error,\n Initial,\n Terminated,\n Opened,\n Closed \n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就可以了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"設定 states","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來可以聲明一些基本狀態:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"machine_t m;\nusing M = decltype(m);\n\n// states\n m.state().set(my_state::Initial).as_initial().build();\nm.state().set(my_state::Terminated).as_terminated().build();\nm.state().set(my_state::Error).as_error()\n .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cerr << \" .. entering\" << '\\n'; })\n .build();\nm.state().set(my_state::Opened)\n .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &) -> bool { return true; })\n .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &p) -> bool { return p._ok; })\n .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. entering\" << '\\n'; })\n .exit_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. exiting\" << '\\n'; })\n .build();\nm.state().set(my_state::Closed)\n .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. entering\" << '\\n'; })\n .exit_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. exiting\" << '\\n'; })\n .build();\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Initial 是必需的初始狀態。狀態機總是呆在這裏,直到有信號推動它。初始狀態可以用 as_initial() 來授予。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"terminated,error 狀態是可選的。而且暫時來講它們沒有顯著的作用——但你可以在你的 action 中檢測這些狀態並做出相應的應變。類似的,有 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"as_terminated()","attrs":{}}],"attrs":{}},{"type":"text","text":"/","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"as_eeror()","attrs":{}}],"attrs":{}},{"type":"text","text":" 來完成相應的指定。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於每一個狀態枚舉量來說,你可以根據需要爲它們關聯 entry/exit_action,如同上面的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"entry_action()/exit_action()","attrs":{}}],"attrs":{}},{"type":"text","text":" 調用所展示的那樣。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"guards","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於一個將要轉進的 state,你也可以爲其定義 guards。和 Transition Guards 相同地,一個 State guard 是一個可以返回 bool 結果的函數。而且,該 guard 的用途也相似:在將要轉進某個 state 時,根據上下文環境做出判斷,以決定是否應該轉進到該 state 中。返回 false 時轉進動作將不會執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定義 state guards 的方式如同這樣:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"// guards\nm.state().set(my_state::Opened)\n .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &) -> bool { return true; })\n .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &p) -> bool { return p._ok; })\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以爲一個 state 定義多條 guards。在上面的實例中,第二條 guard 將會根據 payload 中攜帶的 _ok 布爾類型值來決定要不要轉進到 Opened 狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 guard 表示不可以轉進,則狀態機停留在原位置,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"machine_t::on_error()","attrs":{}}],"attrs":{}},{"type":"text","text":" 回調函數將會獲得一個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"reason == Reasion::FailureGuard","attrs":{}}],"attrs":{}},{"type":"text","text":" 的通知,你可以在此時操縱 context 轉進到另一狀態,但要注意這時候將會是一個內部操作:通過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"context.current(new_state)","attrs":{}}],"attrs":{}},{"type":"text","text":" 進行到內部轉進操作是不會觸發任何條件約束和回調機會的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣道理,在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"guard()","attrs":{}}],"attrs":{}},{"type":"text","text":" 所添加的 guard 函數中你也可以操作 context 去修改新的轉進狀態而不會觸發進一步的條件約束和回調機會。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"事件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事件,或者說步進信號,需要以一個公共的基類 event_t 爲基準,event_t 被用作模板參數傳遞給 fsm machine,所以你可以使用這個默認設定。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你當然也可以傳遞一個不同的自定義的基類作爲模板參數。例如:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"struct event_base {};\nstruct begin : public event_base {};\nstruct end : public event_base {};\nstruct open : public event_base {};\nstruct close : public event_base {};\n\nmachine_t m;\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但這樣的 event 體系有可能過於簡單了,並且存在着類型丟失的風險(沒有虛析構函數聲明的類體系是危險的)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們建議你採用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"fsm-cxx","attrs":{}}],"attrs":{}},{"type":"text","text":" 預置的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"event_t","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"event_type","attrs":{}}],"attrs":{}},{"type":"text","text":" 來實現你的事件類體系,也就是這樣:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"struct begin : public fsm_cxx::event_type {\n virtual ~begin() {}\n int val{9};\n};\nstruct end : public fsm_cxx::event_type {\n virtual ~end() {}\n};\nstruct open : public fsm_cxx::event_type {\n virtual ~open() {}\n};\nstruct close : public fsm_cxx::event_type {\n virtual ~close() {}\n};\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣擴充之後,也可以免去顯式聲明 event 模板參數:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"machine_t m;\n// Or\nmachine_t m;\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除了上面的好處之外,最大的好處是你可以使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"(begin{}).to_string()","attrs":{}}],"attrs":{}},{"type":"text","text":" 來得到類名。它是依賴 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"event_t","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"event_type","attrs":{}}],"attrs":{}},{"type":"text","text":" 的簡要包裝所提供的支撐:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"namespace fsm_cxx {\n struct event_t {\n virtual ~event_t() {}\n virtual std::string to_string() const { return \"\"; }\n };\n template\n struct event_type : public event_t {\n virtual ~event_type() {}\n std::string to_string() const { return detail::shorten(std::string(debug::type_name())); }\n };\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這對於未來的騰挪留下了充分的餘地。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你覺得爲每個事件類寫一個虛析構函數太過於弱爆了,那麼用一個輔助的宏好了:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"struct begin : public fsm_cxx::event_type {\n virtual ~begin() {}\n int val{9};\n};\nFSM_DEFINE_EVENT(end);\nFSM_DEFINE_EVENT(open);\nFSM_DEFINE_EVENT(closed);\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"上下文","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 machine_t 中維持一份內部的上下文環境 Context,這在發生狀態轉換時是非常重要的核心結構。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Context 提供了當前所處的狀態位置,並允許你修改該位置。但要注意如果你通過這個能力進行狀態修改的話,條件約束和回調函數將會被你的操作所略過。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果查看 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"context_t","attrs":{}}],"attrs":{}},{"type":"text","text":" 的源代碼你會發現在這個上下文環境中 fsm-cxx 還管理了和 states 相關的 entry/exit_action 及其校驗代碼。這個設計本來是爲未來的 HFSM 而準備的。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"負載","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"負載 Payload 從使用者編碼的角度來看是遊離在上下文、事件之外的。但對於狀態機理論來說,它是隨着事件一起被傳遞給狀態機的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在每一次推動狀態機步進時,你可以通過 m.step_by() 攜帶一些有效載荷。這些載荷可以參與 guards 決策,也可以在 entry/exit_actions 中參與動作執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默認時 machine_t 使用 payload_t 作爲其 PayloadT 模板參數。所以你只需要從 payload_t 派生你的類就可以自定義想要攜帶的負載了:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"struct my_payload: public fsm_cxx::payload_t {};\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你也可以採用 payload_type 模板包裝的方式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"struct my_payload: public fsm_cxx::payload_type {\n // ...\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至於 machine_t 的模板參數無需做什麼修改。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用時通過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"m.step_by(event, payload)","attrs":{}}],"attrs":{}},{"type":"text","text":" 直接傳遞 my_payload 實例即可。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"轉換表","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們的實現中準備簡單地建立兩級 hash_map,但第二級中使用一種較笨拙的構造方式。目前看來還沒有必要應該在這個部位進行額外的優化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具體的定義是這樣的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"c++"},"content":[{"type":"text","text":"using Transition = transition_t;\nusing TransitionTable = std::unordered_map;\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉換表以 from_state 爲第一層的 key,並關聯一個 transition_t 結構。在 transition_t 中,實際上又有第二級 hash_map 是關聯到 EventT 的類名上的,所以一個 EventT 實例信號會索引到關聯的 trans_item_t 結構,但這裏需要注意的是 EventT 實例本身不重要,重要的是它的類名。你看到我們之前約定事件信號應該分別以最小型 struct 的方式予以聲明,而 struct 的結構體成員是被忽視的,machine_t 只需要它的類型名稱。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"template,\n typename ContextT = context_t,\n typename ActionT = action_t>\n struct transition_t {\n using Event = EventT;\n using State = StateT;\n using Context = ContextT;\n using Payload = PayloadT;\n using Action = ActionT;\n using First = std::string;\n using Second = detail::trans_item_t;\n using Maps = std::unordered_map;\n\n Maps m_;\n\n //...\n };\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按照上述定義,你在使用時應該這麼定義轉換表:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"// transistions\nm.transition(my_state::Initial, begin{}, my_state::Closed)\n .transition(\n my_state::Closed, open{}, my_state::Opened,\n [](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. opened> entering\" << '\\n'; },\n [](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. opened> exiting\" << '\\n'; })\n .transition(my_state::Opened, close{}, my_state::Closed)\n .transition(my_state::Closed, end{}, my_state::Terminated);\n\nm.transition(my_state::Opened,\n M::Transition{end{}, my_state::Terminated,\n [](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. \" << '\\n'; },\n nullptr});\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類似於 state(),定義一條轉換表規則時,可以爲規則掛鉤專屬的 entry/exit_action,你可以根據你的實際需求來選擇是在 state 還是 transition-rule 的恰當位置 hook 事件並執行 action。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以選擇採用 Builder Pattern 的風格來構造轉換表條目:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"m.builder()\n .transition(my_state::Closed, open{}, my_state::Opened)\n .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &p) -> bool { return p._ok; })\n .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. opened> entering\" << '\\n'; })\n .exit_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. opened> exiting\" << '\\n'; })\n .build();\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它和一次 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"m.transition()","attrs":{}}],"attrs":{}},{"type":"text","text":" 調用是等價的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"Guard for A Transition","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在轉換表定義時,你可以爲一個變換(Transition)定義一個前提條件。在轉換將要發生時,狀態機將會校驗該 guard 期待的條件是否滿足,只有在滿足時纔會執行轉換動作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們曾經提到過,通過 event 信號面向 from_state 的轉換路徑可以有多條,實際轉進中在多條路徑中如何選擇呢?就是通過 guard 條件來挑選的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在具體實現中,還隱含着順序挑選原則:最先滿足 guard 條件的路徑被優先擇出,後續的路徑則被放棄探查。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"無限制","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一條轉換表條目代表着從 from_state 因爲事件 ev 的激勵而轉進到 to_state。我們既不限制 from 到 (ev, to_state) 的轉換路徑,而是利用 guard 條件進行選擇(但實際上是一個順序優先的選擇)。具體情況可以參考源碼的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"transition_t::get()","attrs":{}}],"attrs":{}},{"type":"text","text":" 部分。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"推動狀態機運行","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當上述的主要定義完成之後,狀態機就處於可工作狀態。此時你需要某種機制來推動狀態機運行。例如當接收到一個鼠標事件時,你可以調用 m.step_by() 去推動狀態機。如果推動成功,則狀態機將會變換到新的狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如下面的代碼做了簡單的推動:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"m.step_by(begin{}); // goto Closed\nif (!m.step_by(open{}, payload_t{false}))\n std::cout << \" E. cannot step to next with a false payload\\n\";\nm.step_by(open{}); // goto Opened\nm.step_by(close{});\nm.step_by(open{});\nm.step_by(end{});\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其輸出結果如同這樣:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" [Closed] -- begin --> [Closed] (payload = a payload)\n .. entering\n Error: reason = Reason::FailureGuard\n E. cannot step to next with a false payload\n .. opened> exiting\n .. exiting\n [Opened] -- open --> [Opened] (payload = a payload)\n .. opened> entering\n .. entering\n .. exiting\n [Closed] -- close --> [Closed] (payload = a payload)\n .. entering\n .. opened> exiting\n .. exiting\n [Opened] -- open --> [Opened] (payload = a payload)\n .. opened> entering\n .. entering\n .. exiting\n [Closed] -- end --> [Closed] (payload = a payload)\n .. entering\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意推動代碼中的第二行會因爲 guard 的緣故導致推動不成功,所以輸出行中會有 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Error: reason = Reason::FailureGuard","attrs":{}}],"attrs":{}},{"type":"text","text":" 這樣的輸出信息。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"線程安全","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你需要一個線程安全的狀態機,那麼可以給 machine_t 傳入第三個模板參數爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"std::mutex","attrs":{}}],"attrs":{}},{"type":"text","text":"。如同這樣:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"fsm_cxx::machine_t m;\nusing M = decltype(m);\n\n// Or:\nfsm_cxx::safe_machine_t m;\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 m.step_by 的內部進行了競態條件控制。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是在定義功能中(例如定義 state/guard/transition 的時候)並沒有進行保護,所以線程安全僅適用於 machine_t 開始運行之後。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,如果你自定義、或者擴展了你的上下文類,在上下文的內部操作中必需進行競態條件保護。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"示例代碼完整一覽","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面提到的測試用的代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"cpp"},"content":[{"type":"text","text":"namespace fsm_cxx { namespace test {\n\n // states\n\n AWESOME_MAKE_ENUM(my_state,\n Empty,\n Error,\n Initial,\n Terminated,\n Opened,\n Closed)\n\n // events\n\n struct begin : public fsm_cxx::event_type {\n virtual ~begin() {}\n int val{9};\n };\n struct end : public fsm_cxx::event_type {\n virtual ~end() {}\n };\n struct open : public fsm_cxx::event_type {\n virtual ~open() {}\n };\n struct close : public fsm_cxx::event_type {\n virtual ~close() {}\n };\n\n void test_state_meta() {\n machine_t m;\n using M = decltype(m);\n\n // @formatter:off\n // states\n m.state().set(my_state::Initial).as_initial().build();\n m.state().set(my_state::Terminated).as_terminated().build();\n m.state().set(my_state::Error).as_error()\n .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cerr << \" .. entering\" << '\\n'; })\n .build();\n m.state().set(my_state::Opened)\n .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &) -> bool { return true; })\n .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &p) -> bool { return p._ok; })\n .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. entering\" << '\\n'; })\n .exit_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. exiting\" << '\\n'; })\n .build();\n m.state().set(my_state::Closed)\n .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. entering\" << '\\n'; })\n .exit_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. exiting\" << '\\n'; })\n .build();\n\n // transistions\n m.transition().set(my_state::Initial, begin{}, my_state::Closed).build();\n m.transition()\n .set(my_state::Closed, open{}, my_state::Opened)\n .guard([](M::Event const &, M::Context &, M::State const &, M::Payload const &p) -> bool { return p._ok; })\n .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. opened> entering\" << '\\n'; })\n .exit_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. opened> exiting\" << '\\n'; })\n .build();\n m.transition().set(my_state::Opened, close{}, my_state::Closed).build()\n .transition().set(my_state::Closed, end{}, my_state::Terminated).build();\n m.transition().set(my_state::Opened, end{}, my_state::Terminated)\n .entry_action([](M::Event const &, M::Context &, M::State const &, M::Payload const &) { std::cout << \" .. \" << '\\n'; })\n .build();\n // @formatter:on\n\n m.on_error([](Reason reason, M::State const &, M::Context &, M::Event const &, M::Payload const &) {\n std::cout << \" Error: reason = \" << reason << '\\n';\n });\n\n // debug log\n m.on_transition([&m](auto const &from, fsm_cxx::event_t const &ev, auto const &to, auto const &actions, auto const &payload) {\n std::printf(\" [%s] -- %s --> [%s] (payload = %s)\\n\", m.state_to_sting(from).c_str(), ev.to_string().c_str(), m.state_to_sting(to).c_str(), to_string(payload).c_str());\n UNUSED(actions);\n });\n\n // processing\n\n m.step_by(begin{});\n if (!m.step_by(open{}, payload_t{false}))\n std::cout << \" E. cannot step to next with a false payload\\n\";\n m.step_by(open{});\n m.step_by(close{});\n m.step_by(open{});\n m.step_by(end{});\n\n std::printf(\"---- END OF test_state_meta()\\n\\n\\n\");\n }\n\n}}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Epilogue","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這一次,代碼的細節太多,所以我們偏重於解釋如何使用 fsm-cxx。並且由於篇幅的原因,也沒有足夠的地盤提供完整的代碼,所以請參考 repo: ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/hedzr/fsm-cxx","title":"","type":null},"content":[{"type":"text","text":"https://github.com/hedzr/fsm-cxx","attrs":{}}]},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總的來說,這一次寫的自己都不滿意。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種文章總是會非常無趣的吧,不管怎麼寫都覺得一片散亂。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":":end:","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章