谈 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":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章