状态机的一种实现jc::fsm介绍(1):状态机的模型
一、 状态的组成
在解释jc::fsm状态的组成前,先介绍一个概念,在jc::fsm中,存在从状态s进入状态s的操作,这种称为ReEnter。ReEnter方式的存在是为了让状态机规则表示更加简单规范。一般在需要保持状态不变情况下,执行一些操作时会使用ReEnter方式,该方式一般随后会紧跟一条code驱动规则。具体见”内部返回码code驱动”一节
jc::fsm状态机包含以下元素:状态(status),外部事件(event),返回码(code),处理函数集(handler)及由这些元素共同组成的转移规则(rule)。
状态内部结构如下:
Status_name: 状态名称
Enter():当非ReEnter该状态时,调用该函数
Exit(): 当退出后非ReEnter该状态时,调用的函数
Handler1(), handler2(),……,handlerN()函数集:进入状态,并在调用enter()后,根据转移条件决定调用其中某个函数。
一个状态节点,可以没有enter()和exit(),但必定至少有一个 handler()。
二、 状态转移
在jc::fsm中存在两类状态转移操作:外部事件event驱动和内部返回码code驱动。
1. 外部事件event驱动
Event驱动分两种情况:ReEnter和非ReEnter方式。如下图所示:
由event1驱动的状态转移是 ReEnter方式的转移。这种状态转移,enter()和exit()函数都不会被调用,仅调用 handler1()函数。在实际应用中,这种转移后将会根据handler1()函数的返回码,通过返回码驱动转移到下一个状态。
由event2驱动的状态转移是非ReEnter方式,这种转移会先调用status_1::exit()函数,然后调用把当前状态切换为status_2,接着执行status_2::enter()函数,最后再执行status_2::handler2()函数,然后根据handler2()返回码,进行返回码驱动。
当然由event2驱动时,也可能出现另一种情况,如下图所示中的event2:
这种情况下,进入status_2后,不执行任何函数,而是停在该状态下等待外部事件进行下一步驱动。
code驱动是为了实现函数调用后,根据执行结果进行流程选择的需求。换种方式考虑,在状态机处理函数内触发事件驱动,也可以实现这种需求,但是这种方式使用不当时容易导致状态机调用栈嵌套,从而引入bug。因此jc::fsm中,在各hanlder函数内禁止向本状态机发送事件,事件只能在状态机外部被触发,因此事件又叫做外部事件。
Code驱动理论上也包含ReEnter和非ReEnter方式,也就是说jc::fsm支持这两种方式,不过实际应用中,code驱动一般都使用非ReEnter方式。
如上图所示,经event1驱动后状态由status_1进入status_2,并执行status_2::handler3()函数。当Handler3函数返回code1时,状态由status_2转到status_3;当返回code2时,状态由status_2转到status_4,并执行status_4::handler1()函数。那如果handler3返回code3呢?code3不在图中,那么状态机将停止在status_2状态上,等待外部事件的触发。
code驱动有种情况比较特殊,这里单独提一下,大家看下图:
Jc::fsm在status_1下,收到事件event1,以ReEnter 方式进入status_1,并执行status_1::handler1(),根据handler1返回值是否为code1决定状态机是否转移到状态status_2。由于以ReEnter方式进入status_1时,status_1::enter()和status_1::exit()都不会被调用,仅调用status_1::handler1(),因此这种方式也可以理解为,status_1下接收到event1事件时,状态不转移,直接执行handler1函数。因此这种情况,在后面提到的“jc::fsm的图形化工具”中,一般以另一种更加直观的图形方式来表示:
从上图上可以很容易看出,status_1下收到event1事件时,执行handler1函数,如果handler1返回code1,则转移到status_2状态,否则保持在status_1状态不变。