我也不知道你能不能堅(jiān)持看到這里,我以為的驚喜,可能對(duì)你來(lái)說(shuō)是一本天書(shū)(是我講的不好),這可能是我最大的遺憾,做技術(shù),痛苦的是無(wú)止盡的重復(fù)做項(xiàng)目,快樂(lè)的是掌握一種新技術(shù),它不能讓你變得富有,只是為你將眼前的大門多推開(kāi)了一丟丟~!沒(méi)有什么能比一句我已經(jīng)在路上了,更能讓人興奮的。
———程序小白
2020.09.08
繼續(xù)我們的炸彈項(xiàng)目,首先我們要講一下第三種狀態(tài)機(jī)的構(gòu)造模式,就是基于面向?qū)ο蟮乃枷雭?lái)實(shí)現(xiàn)我們的有限狀態(tài)機(jī),這里用到的是完全的面向?qū)ο?,例如在我們的GUI系統(tǒng)中基于事件驅(qū)動(dòng)型設(shè)計(jì)FSM(現(xiàn)實(shí)中可能比這復(fù)雜的多,但也算是種一粒種子吧,萬(wàn)一哪天你真的要搞呢)。這里用到的并不是C語(yǔ)言,因?yàn)镃語(yǔ)言在實(shí)現(xiàn)多態(tài)方面會(huì)很復(fù)雜,不如直接使用C++(C的哥哥),我們先看一下基于面向?qū)ο笤O(shè)計(jì)的QEP的構(gòu)架,如下圖:
BombState是一個(gè)類,這是一個(gè)抽象類,基于這個(gè)類派生出兩個(gè)子類,SettingState和TimingState,基于這兩個(gè)子類,實(shí)例化兩個(gè)對(duì)象,同時(shí)構(gòu)建我們的Bomb3狀態(tài)機(jī)類,該類的成員中有BombState類型的成員,實(shí)際以指針的方式定義,在狀態(tài)機(jī)運(yùn)行時(shí),改變狀態(tài),實(shí)際就是改變指針來(lái)指向不同的對(duì)象, 通過(guò)其重載的函數(shù)(以多態(tài)的形式),來(lái)實(shí)現(xiàn)不同的狀態(tài)處理函數(shù),這種方式利用C++可以很容易的實(shí)現(xiàn),我們并不做過(guò)多的討論。
總結(jié)一下:基于面向?qū)ο箢愋偷恼Z(yǔ)言(例如C++)來(lái)實(shí)現(xiàn)狀態(tài)機(jī),實(shí)際可以做的更簡(jiǎn)單,雖然C++學(xué)起來(lái)并不簡(jiǎn)單。
以上三種方式都稱不上完美,第三種嚴(yán)重依賴C++,我們的目的是用C來(lái)實(shí)現(xiàn)一個(gè)通用的QEP,實(shí)際上一個(gè)通用的QEP集成了以上三種方式的優(yōu)勢(shì),他的構(gòu)架來(lái)了,如下:
很重要的題外話:QEP事件處理器是QP構(gòu)架的一部分,除了他之外,還有QK(內(nèi)核),QF(框架服務(wù)),上圖中我們能看到分為QEP部分,和應(yīng)用程序部分,也就是如何在我們的應(yīng)用程序中使用構(gòu)架服務(wù),主要應(yīng)用C語(yǔ)言的單繼承,利用繼承,你就可以使用構(gòu)架的服務(wù)。
接下來(lái)進(jìn)入我們的主題基于通用版QEP實(shí)現(xiàn)的QFSM,代碼講解部分我們以QP5.3.1為模板(QP所有的歷史版本都可以從官網(wǎng)下載到),為什么不以QP6.9.0為模板講,并不是因?yàn)樾碌牟缓茫切碌膭h掉了BOMB的例子,我們無(wú)處可講,所以只能用老版本,實(shí)戰(zhàn)應(yīng)用的話建議大家應(yīng)用新版本,因?yàn)槊恳淮蔚母驴倳?huì)帶來(lái)驚喜。
首先從簡(jiǎn)單的QEvent事件結(jié)構(gòu)開(kāi)始,分析,在新版本中改名字了,更簡(jiǎn)潔叫QEvt,對(duì)比一下文檔中的樣子和實(shí)際版本中的樣子,如下:
書(shū)中的版本:
QP5.3.1中的版本:
雖然擴(kuò)展的變量不一樣了,但并沒(méi)有復(fù)雜多少,可以先忽略。
函數(shù)指針類型定義,書(shū)中版本和QP5.3.1區(qū)別不大,如下:
接下來(lái)使我們的QFSM了,這里的差別就大了,其實(shí)在是實(shí)戰(zhàn)中QFSM的應(yīng)用意義并不大,所以升級(jí)版把他融合到了 HSM中,作為了層次式狀態(tài)機(jī)的子集,理解這段話你就理解了新版本更新后差別出在哪里。
文檔中的版本,還保存著其本真的東西,如下:
接下來(lái)我們看看真實(shí)的QP5.3.1中他是什么樣子,如下
神奇的事情發(fā)生了,F(xiàn)SM和HSM居然都是一個(gè)MSM的別名,也就是兩者居然是一樣的,其實(shí)就是我們前面講的,以前可能分兩個(gè)狀態(tài)機(jī)結(jié)構(gòu),現(xiàn)在通過(guò)一個(gè)MSM實(shí)現(xiàn)了兩者的融合,那么在程序看來(lái),一個(gè)MSM既可以當(dāng)做FSM也可以當(dāng)做HSM,這樣做會(huì)有個(gè)缺點(diǎn),就是MSM有點(diǎn)復(fù)雜和有點(diǎn)大。如下:
挺復(fù)雜,那么我們只關(guān)注成員state,繼續(xù)跟蹤他的結(jié)構(gòu),如下:
看不懂的部分,直接忽略掉,他的意義當(dāng)我們用到的時(shí)候會(huì)再講,看一下其成員fun的類型,就是我們的函數(shù)指針了。
接下來(lái)還有FSM必要的三個(gè)函數(shù),來(lái)支持其功能,ctor構(gòu)造,init初始化,dispatch事件分發(fā),這里我們不再對(duì)比文檔中的代碼,那個(gè)相對(duì)簡(jiǎn)單,而且太老版本支持,我們直接上QP5.3.1,如下:
定義在這個(gè)文件中,路徑如下:
這個(gè)定義很神奇,我們?cè)拘枰P(guān)注的ctor中me->temp.fun = initial;這一句,但是在構(gòu)造函數(shù)中,除了構(gòu)造我們的初始狀態(tài)以外,還幫我們綁定了init初始化和事件分發(fā)函數(shù),很驚喜也很以外。
接下來(lái)我們看看這個(gè)init函數(shù),定義如下:
這個(gè)函數(shù)一看巨復(fù)雜,QS開(kāi)頭的是軟件追蹤部分代碼,忽略,重要的部分,
/*執(zhí)行具體的初始化函數(shù),例如我們的bomb4代碼中的init,要求我們初始化函數(shù)必須返回Q_RET_TRAN*/
(*me->temp.fun)(me,e) == (QState)Q_RET_TRAN
完成初始化,觸發(fā)一個(gè)Q_ENTRY_SIG事件,接收該事件的時(shí)間處理函數(shù)為me-temp.fun
也就是執(zhí)行temp.fun的進(jìn)入動(dòng)作,如下:
(void)QEP_TRIG_(me-temp.fun,Q_ENTRY_SIG);這是一個(gè)宏,展開(kāi)后
執(zhí)行完畢,后temp.fun更新到state.fun中。
最后還有一個(gè)函數(shù)沒(méi)有講,dispatch,我先找找他在哪,如下:
這個(gè)函數(shù)有點(diǎn)復(fù)雜,但我還是能看得懂,真到了HSM說(shuō)實(shí)話,我真沒(méi)看懂,我也不斷講HSM中dispatch的實(shí)現(xiàn),說(shuō)實(shí)話太復(fù)雜了,能學(xué)會(huì)說(shuō)明你算法基礎(chǔ)好,到哪里我們只講規(guī)則,如何應(yīng)用,不講實(shí)現(xiàn),有興趣筒子們自己研究源碼,這里我們把FSM的dispatch貼出來(lái),如下:
代碼很長(zhǎng),其實(shí)很簡(jiǎn)單,首先進(jìn)入的時(shí)候,有兩個(gè)狀態(tài)處理函數(shù),代碼兩個(gè)狀態(tài),state和temp,這倆咋進(jìn)入的時(shí)候是相等的 ,會(huì)有一個(gè)斷言判斷,如果不相等,那么說(shuō)明狀態(tài)機(jī)有異常,如下:
68:Q_REQUIRE_ID(100, me->state.fun == me->temp.fun);
接下來(lái)執(zhí)行事件處理函數(shù),返回狀態(tài)很重要,r很重要,如下:
r = (*me->state.fun)(me, e); /* call the event handler */
如果if (r != (QState)Q_RET_TRAN),dispatch就結(jié)束了,如果發(fā)生了轉(zhuǎn)換,
那么先執(zhí)行源的退出,在執(zhí)行目標(biāo)的進(jìn)入,最后再把目標(biāo)賦值給源,完成了狀態(tài)轉(zhuǎn)換,如下,
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 */
到這里一個(gè)完整的QFSM(基于QEP)講完了,上面講的這些都不需要我們?nèi)ゾ幋a,這是框架的部分,QP幫我們寫好了,直接應(yīng)用就好了,假如你真的按我的步驟把QPC5.3.1的這部分代碼通讀了一遍,那么,關(guān)于bomb如何基于他實(shí)現(xiàn),就不需要任何言語(yǔ)了,直接上代碼.
bomb第一部分,如下:
第二部分,如下:
main.c如下:
基于通用的QEP實(shí)現(xiàn)的bomb,到此完結(jié),再見(jiàn)~!