我也不知道你能不能坚持看到这里,我以为的惊喜,可能对你来说是一本天书(是我讲的不好),这可能是我最大的遗憾,做技术,痛苦的是无止尽的重复做项目,快乐的是掌握一种新技术,它不能让你变得富有,只是为你将眼前的大门多推开了一丢丢~!没有什么能比一句我已经在路上了,更能让人兴奋的。
———程序小白
2020.09.08
继续我们的炸弹项目,首先我们要讲一下第三种状态机的构造模式,就是基于面向对象的思想来实现我们的有限状态机,这里用到的是完全的面向对象,例如在我们的GUI系统中基于事件驱动型设计FSM(现实中可能比这复杂的多,但也算是种一粒种子吧,万一哪天你真的要搞呢)。这里用到的并不是C语言,因为C语言在实现多态方面会很复杂,不如直接使用C++(C的哥哥),我们先看一下基于面向对象设计的QEP的构架,如下图:
BombState是一个类,这是一个抽象类,基于这个类派生出两个子类,SettingState和TimingState,基于这两个子类,实例化两个对象,同时构建我们的Bomb3状态机类,该类的成员中有BombState类型的成员,实际以指针的方式定义,在状态机运行时,改变状态,实际就是改变指针来指向不同的对象, 通过其重载的函数(以多态的形式),来实现不同的状态处理函数,这种方式利用C++可以很容易的实现,我们并不做过多的讨论。
总结一下:基于面向对象类型的语言(例如C++)来实现状态机,实际可以做的更简单,虽然C++学起来并不简单。
以上三种方式都称不上完美,第三种严重依赖C++,我们的目的是用C来实现一个通用的QEP,实际上一个通用的QEP集成了以上三种方式的优势,他的构架来了,如下:
很重要的题外话:QEP事件处理器是QP构架的一部分,除了他之外,还有QK(内核),QF(框架服务),上图中我们能看到分为QEP部分,和应用程序部分,也就是如何在我们的应用程序中使用构架服务,主要应用C语言的单继承,利用继承,你就可以使用构架的服务。
接下来进入我们的主题基于通用版QEP实现的QFSM,代码讲解部分我们以QP5.3.1为模板(QP所有的历史版本都可以从官网下载到),为什么不以QP6.9.0为模板讲,并不是因为新的不好,而是新的删掉了BOMB的例子,我们无处可讲,所以只能用老版本,实战应用的话建议大家应用新版本,因为每一次的更新总会带来惊喜。
首先从简单的QEvent事件结构开始,分析,在新版本中改名字了,更简洁叫QEvt,对比一下文档中的样子和实际版本中的样子,如下:
书中的版本:
QP5.3.1中的版本:
虽然扩展的变量不一样了,但并没有复杂多少,可以先忽略。
函数指针类型定义,书中版本和QP5.3.1区别不大,如下:
接下来使我们的QFSM了,这里的差别就大了,其实在是实战中QFSM的应用意义并不大,所以升级版把他融合到了 HSM中,作为了层次式状态机的子集,理解这段话你就理解了新版本更新后差别出在哪里。
文档中的版本,还保存着其本真的东西,如下:
接下来我们看看真实的QP5.3.1中他是什么样子,如下
神奇的事情发生了,FSM和HSM居然都是一个MSM的别名,也就是两者居然是一样的,其实就是我们前面讲的,以前可能分两个状态机结构,现在通过一个MSM实现了两者的融合,那么在程序看来,一个MSM既可以当做FSM也可以当做HSM,这样做会有个缺点,就是MSM有点复杂和有点大。如下:
挺复杂,那么我们只关注成员state,继续跟踪他的结构,如下:
看不懂的部分,直接忽略掉,他的意义当我们用到的时候会再讲,看一下其成员fun的类型,就是我们的函数指针了。
接下来还有FSM必要的三个函数,来支持其功能,ctor构造,init初始化,dispatch事件分发,这里我们不再对比文档中的代码,那个相对简单,而且太老版本支持,我们直接上QP5.3.1,如下:
定义在这个文件中,路径如下:
这个定义很神奇,我们原本需要关注的ctor中me->temp.fun = initial;这一句,但是在构造函数中,除了构造我们的初始状态以外,还帮我们绑定了init初始化和事件分发函数,很惊喜也很以外。
接下来我们看看这个init函数,定义如下:
这个函数一看巨复杂,QS开头的是软件追踪部分代码,忽略,重要的部分,
/*执行具体的初始化函数,例如我们的bomb4代码中的init,要求我们初始化函数必须返回Q_RET_TRAN*/
(*me->temp.fun)(me,e) == (QState)Q_RET_TRAN
完成初始化,触发一个Q_ENTRY_SIG事件,接收该事件的时间处理函数为me-temp.fun
也就是执行temp.fun的进入动作,如下:
(void)QEP_TRIG_(me-temp.fun,Q_ENTRY_SIG);这是一个宏,展开后
执行完毕,后temp.fun更新到state.fun中。
最后还有一个函数没有讲,dispatch,我先找找他在哪,如下:
这个函数有点复杂,但我还是能看得懂,真到了HSM说实话,我真没看懂,我也不断讲HSM中dispatch的实现,说实话太复杂了,能学会说明你算法基础好,到哪里我们只讲规则,如何应用,不讲实现,有兴趣筒子们自己研究源码,这里我们把FSM的dispatch贴出来,如下:
代码很长,其实很简单,首先进入的时候,有两个状态处理函数,代码两个状态,state和temp,这俩咋进入的时候是相等的 ,会有一个断言判断,如果不相等,那么说明状态机有异常,如下:
68:Q_REQUIRE_ID(100, me->state.fun == me->temp.fun);
接下来执行事件处理函数,返回状态很重要,r很重要,如下:
r = (*me->state.fun)(me, e); /* call the event handler */
如果if (r != (QState)Q_RET_TRAN),dispatch就结束了,如果发生了转换,
那么先执行源的退出,在执行目标的进入,最后再把目标赋值给源,完成了状态转换,如下,
QEP_EXIT_(me->state.fun); /* exit the source */
QEP_ENTER_(me->temp.fun); /* enter the target */
me->state.fun = me->temp.fun; /* record the new active state */
到这里一个完整的QFSM(基于QEP)讲完了,上面讲的这些都不需要我们去编码,这是框架的部分,QP帮我们写好了,直接应用就好了,假如你真的按我的步骤把QPC5.3.1的这部分代码通读了一遍,那么,关于bomb如何基于他实现,就不需要任何言语了,直接上代码.
bomb第一部分,如下:
第二部分,如下:
main.c如下:
基于通用的QEP实现的bomb,到此完结,再见~!