我也不知道你能不能堅持看到這里,我以為的驚喜,可能對你來說是一本天書(是我講的不好),這可能是我最大的遺憾,做技術,痛苦的是無止盡的重復做項目,快樂的是掌握一種新技術,它不能讓你變得富有,只是為你將眼前的大門多推開了一丟丟~!沒有什么能比一句我已經在路上了,更能讓人興奮的。
———程序小白
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,到此完結,再見~!