一,前言
轻量级状态机软件QM入门--Apple的学习笔记中可以看出,我已经入门工具的使用及集成调试,那么可以比喻为昨天我已经拿到了旅游路线图以及到达车站。今天高铁就出发了,我的探秘之旅开始啦~
二,QHsm层次状态机源码分析
1. 用源码来集成整个工程
一开始编译不过,后来看了编译库用的makefile,原来src中不是所有c代码都参与编译的。只有需要如下文件就编译过了(当前我选择的是win32单线程的port文件夹win32-pv),宏定义添加了_DEBUG
-- qpc
|-- include
| |-- qassert.h
| |-- qep.h
| |-- qequeue.h
| |-- qf.h
| |-- qf_pkg.h
| |-- qk.h
| |-- qmpool.h
| |-- qpc.h
| |-- qs.h
| |-- qs_dummy.h
| |-- qs_pkg.h
| |-- qstamp.c
| |-- qstamp.h
| |-- qv.h
| `-- qxk.h
|-- ports
| `-- win32-qv
| |-- qep_port.h
| |-- qf_port.c
| |-- qf_port.h
| |-- qs_port.h
| |-- qwin_gui.h
| `-- safe_std.h
`-- src
`-- qf
|-- qep_hsm.c
|-- qep_msm.c
|-- qf_act.c
|-- qf_actq.c
|-- qf_defer.c
|-- qf_dyn.c
|-- qf_mem.c
|-- qf_ps.c
|-- qf_qact.c
|-- qf_qeq.c
|-- qf_qmact.c
`-- qf_time.c
2. 有了源码调试分析代码我理解会更加方便。
可以比喻为我还雇用了一个导游,接着开始看代码,发现设计思路确实比较容易理解的。
简单来说就是一个事件处理驱动,类似于我之前看的windows GUI的设计,和SDL2或者QT框架类似。就是等待事件放入队列,然后while(1)中处理事件。
3. 特别之处
我之前自己设计稍微复杂的状态机,也就是用表驱动法,这里的状态机处理用了while来判断状态机返回值为Q_HANDLED( )则退出while。这样就可以想象为在matlab simulink中模拟运行的时候,一个个状态机会变颜色。这个while只要不返回退出状态,那么就一直可以调用每个状态进行处理。
3.1 重点分析
typedef QState (* QStateHandler )(void * const me, QEvt const * const e);
a. 每个状态处理函数有 2 个参数:状态机指针 me 和指向 QEvent的恒指针 e 。它返回QState ,携带的事件处理状态的信息会交给事件处理器。
b. 事件指针被声明为 const,以避免在状态处理函数内部修改事件。换句话说,状态处理函数被确保对事件只有只读操作。
c. 从一个层次式状态处理函数返回的 Q_HANDLED( ) 通知 QEP 事件处理器一个特定的事件已经
被处理了。
d. 状态转换完成后返回宏 Q_TRAN( ) ,它需要使用转换的目标作为参数。
e. 如果没有 case 被执行,状态处理函数将返回宏 Q_SUPER( ),它指派超状态并把它通知事件处理器。
f. 关于每个状态都有一个default返回到它的上一层次状态机。
g. 关于进入层次状态机要先进入高状态再进入低层级状态,但是我们的继承设计方法default是返回的上一级状态,所以正好相反,解决方法就是先利用default返回值来记录上一级的函数到临时数组path中,最后再反向执行path即可。所以我觉得这个default返回上级状态机,设计的非常好。
ip = 0;
path[0] = me->temp.fun;
/* find superstate */
(void)QEP_TRIG_(me->temp.fun, QEP_EMPTY_SIG_);
while (me->temp.fun != t) {
++ip;
path[ip] = me->temp.fun;
/* find superstate */
(void)QEP_TRIG_(me->temp.fun, QEP_EMPTY_SIG_);
}
me->temp.fun = path[0];
/* entry path must not overflow */
Q_ASSERT_ID(410, ip < QHSM_MAX_NEST_DEPTH_);
/* retrace the entry path in reverse (correct) order... */
do {
QEP_ENTER_(path[ip], qs_id); /* enter path[ip] */
--ip;
} while (ip >= 0);
t = path[0]; /* current state becomes the new source */
三,小结
由于我写代码不会用while1来判断逻辑退出,因为我觉得while1是危险的,万一逻辑错误就会导致一直循环无法退出,所以我在项目中不会采用类似的设计,但是我之前看littlevgl等GUI好像都是类似设计,所以若让我设计GUI引擎那么就另当别论了。
另外,今天的源码就用了轻量级的事件驱动框架(pf)框架。任务调度微内核(pk,pv,pxk)和实时跟踪调试器(ps)都没用到。虽然我不会把此状态机框架列入我的学习清单,因为我不用while来做逻辑判断,但是其它模块的代码设计和实现,后续我还是会抽空了解下,一定还有可圈可点的地方。
网友评论