關(guān)于狀態(tài)機(jī),基礎(chǔ)的知識(shí)點(diǎn)可以自行理解,講解的很多,這里主要是想寫一個(gè)有限狀態(tài)機(jī)FSM通用的寫法,目的在于更好理解,移植,節(jié)省代碼閱讀與調(diào)試時(shí)間,體現(xiàn)出編程之美。
傳統(tǒng)的實(shí)現(xiàn)方案
先來看看最近做的一個(gè)項(xiàng)目,無線通信協(xié)議實(shí)現(xiàn)的狀態(tài)機(jī)是什么樣子的:
有三種類型的事件:上層下達(dá)的命令事件;下層到達(dá)的標(biāo)志和數(shù)據(jù)傳輸事件;超時(shí)定時(shí)器超時(shí)事件。有10種狀態(tài),關(guān)聯(lián)性很大,復(fù)雜了吧,這要是各種if/else的要寫到什么時(shí)候呢。
偷偷放一張討論的圖狀態(tài)機(jī)模式 c語言,亂七八糟形容很恰當(dāng)。
在事件中判斷狀態(tài),在狀態(tài)中判斷事件,橫豎兩種寫法的代碼都比較冗長,看起來呢也不大好,一旦增減,就又要?jiǎng)幽X子重新梳理一遍,很累的。
怎么去寫呢?其狀態(tài)機(jī)原理:在根據(jù)當(dāng)前狀態(tài)() 下,發(fā)生事件(event)后,轉(zhuǎn)移到下一個(gè)狀態(tài)號(hào)(),決定執(zhí)行的動(dòng)作()。盜用一個(gè)圖吧
這里我們首先定義一個(gè)結(jié)構(gòu)體如下:
typedef struct {
State curState;//當(dāng)前狀態(tài)
EventID eventId;//事件ID
State nextState;//下個(gè)狀態(tài)
Action action;//具體表現(xiàn)
}StateTransform;
我們假設(shè)有3種狀態(tài),這里可以隨意增加,狀態(tài)枚舉如下:
typedef enum {
state_1=1,
state_2,
state_3
}State;
我們假設(shè)有5個(gè)事件,也可以隨意增加,事件ID枚舉如下:
typedef enum{
event_1=1,
event_2,
event_3,
event_4,
event_5
}EventID;
將其封裝起來在中:
typedef struct{
State state;
int transNum;
StateTransform* transform;
}StateMachine;
具體流程:當(dāng)前狀態(tài)-有事件觸發(fā)-跳到下個(gè)狀態(tài)-具體表現(xiàn),重構(gòu)代碼
StateTransform* findTranss(StateMachine* pSM, const EventID evt)
{
int i;
for (i = 0; i < pSM->transNum; i++) {
if ((pSM->transform[i].curState == pSM->state) && (pSM->transform[i].eventId == evt)) {
return &pSM->transform[i];
}
}
return NULL;
}
狀態(tài)機(jī)實(shí)現(xiàn)如下:
void runStateMachine(StateMachine* pSM, EventID evt) {
StateTransform* pTrans;
pTrans = findTranss(pSM, evt);
if (pTrans == NULL)
{
xil_printf( "CurState= %s Do not process enent: %s\r\n", pSM->state,evt);
return;
}
pSM->state = pTrans->nextState;
Action act = pTrans->action;
if (act == NULL) {
xil_printf( "change state to %s. No action\r\n",pSM->state);
return;
}
act(&evt);
}
最后我模擬一些隨機(jī)事件,我們只需要弄清楚事件ID,狀態(tài)切換,具體表現(xiàn)就可以了,在代碼中就是填寫[] 這個(gè)表,一旦有增減事件,狀態(tài)等等,也不需要再去使用/case狀態(tài)機(jī)模式 c語言,特費(fèi)腦,其代碼如下:
int run()
{
StateMachine stateMachine;
state_1; =
7; =
StateTransform stateTran[] = {
{state_1,event_3,state_2,f121},
{state_1,event_4,state_2,NULL},
{state_2,event_1,state_3,f231},
{state_2,event_4,state_2,f221},
{state_3,event_2,state_1,f311},
{state_3,event_3,state_2,f321},
{state_3,event_5,state_3,f331}
};
stateTran; =
EventID inputEvent[15] =
event_1, event_2, event_3, event_4, event_5,
event_2, event_3, event_4, event_5,
event_2, event_3, event_4, event_5 };
int i;
for (i = 0; i < 15; i++) {
inputEvent[i]);
}
return 0;
}
最后運(yùn)行結(jié)果如下
總結(jié):
狀態(tài)機(jī)應(yīng)用很廣泛,也可以鍛煉我們寫代碼的邏輯思維,看清問題的本質(zhì),寫的代碼才能賞心悅目,希望大家能夠多多指點(diǎn),找到編程的樂趣,欣賞到編程之美。