這個是從同事那邊知道的好東西, Nunni FSM Generator,它能自動根據你輸入的設定檔產生狀態機的程式碼,詳細的資料請查閱軟體的使用說明,但我要講的重點在於它”自動產生的程式碼部份”,因為自動產生的code封裝得十分漂亮,所以我想對code做點介紹

以前寫狀態機的東西,不脫離if,else,case,switch…等這幾種語法搭配,可是寫出來的code”不大容易讀懂”,比如說我今天有個狀態機如下圖,假設有一張桌子,上面只能擺一個瓶子,我如果拿走了這個瓶子,那我就不能從桌子上再拿走瓶子了,如果桌子上已經擺了一個瓶子,那桌子上就再也不能多加瓶子上去

如果用if-else語法寫寫看,可能會像下面這樣

#include <stdio.h> 
 
int bottles=1;
 
void GetBottleAPI() 
{ 
if (bottles==1) 
{ 
printf("You got bottle\n");
bottles--;
} 
else printf("No bootle to get\n");
} 
 
void PutBottleAPI() 
{ 
if (bottles==0) 
{ 
printf("You put a bottle\n");
bottles++;
} 
else printf("Too much bootles\n");
} 
 
int main() 
{ 
 
GetBottleAPI();
GetBottleAPI();
PutBottleAPI();
PutBottleAPI();
GetBottleAPI();
}

用int bottles當global變數判斷get or put的動作是否合法,這種寫法如果用在小型的狀態機還OK,如果狀態機又多又大又複雜,可以想見程式碼會為這些狀態多寫好幾個判斷變數

試試看下面的程式碼,我只要定義好每個狀態與外來動作的觸發機制,就可以寫出蠻漂亮的code

#include <stdio.h> 
 
struct TableState ;
 
struct TableFSM 
{ 
int (*GetBottleStatus)( struct TableFSM *fsm, void * o );
int (*PutBottleStatus)( struct TableFSM *fsm, void * o );
void (*changeState)( struct TableFSM *fsm, struct TableState *nextState );
struct TableState *m_state;
};
 
struct TableState 
{ 
int (*GetBottleStatus)( struct TableFSM *fsm, void * o );
int (*PutBottleStatus)( struct TableFSM *fsm, void * o );
};
 
static struct TableState TableFull;
static struct TableState TableEmpty;
 
void GetBottle() 
{ 
printf("You get a bottle\n");
} 
 
void GiveBottle() 
{ 
printf("You put bottle on the table\n");
} 
 
void NoBottle() 
{ 
printf("There is no bottle on the table\n");
} 
 
void BottleFull() 
{ 
printf("There is too much bottle on the table\n");
} 
 
int FSMGetBottleStat( struct TableFSM *fsm, void * o ) 
{ 
return fsm->m_state->GetBottleStatus( fsm, o );
} 
 
int FSMPutBottleStat( struct TableFSM *fsm, void * o ) 
{ 
return fsm->m_state->PutBottleStatus( fsm, o );
} 
 
void FSMChangeState( struct TableFSM *fsm, struct BottleState *newState ) 
{ 
fsm->m_state = newState;
} 
 
int GetBottleAPI( struct TableFSM *fsm, void * o ) 
{ 
GetBottle();
fsm->changeState( fsm, &TableEmpty);
} 
 
 
int GiveBottleAPI(struct TableFSM *fsm, void * o ) 
{ 
GiveBottle();
fsm->changeState( fsm, &TableFull );
} 
 
int TooFewBottleAPI(struct TableFSM *fsm, void * o ) 
{ 
NoBottle();
fsm->changeState( fsm, &TableEmpty );
 
} 
 
int TooMuchBottleAPI(struct TableFSM *fsm, void * o ) 
{ 
BottleFull();
fsm->changeState( fsm, &TableFull );
 
} 
 
 
int main() 
{ 
static struct TableFSM bf;
 
TableFull.GetBottleStatus = GetBottleAPI;
TableFull.PutBottleStatus = TooMuchBottleAPI;
TableEmpty.GetBottleStatus = TooFewBottleAPI;
TableEmpty.PutBottleStatus = GiveBottleAPI;
bf.GetBottleStatus=FSMGetBottleStat;
bf.PutBottleStatus=FSMPutBottleStat;
bf.changeState=FSMChangeState;
bf.m_state=&TableFull;
 
FSMGetBottleStat(&bf,0);
FSMGetBottleStat(&bf,0);
FSMPutBottleStat(&bf,0);
FSMPutBottleStat(&bf,0);
}

上面的程式碼相當簡潔,當外部動作觸發內部狀態改變,會把FSM指定的函式指定到轉入後的state callback,所以programmer可以完全專注在GetBottleAPI之類的函式實作,而不用考慮state轉換的程式複雜度

最後修改日期: 3 6 月, 2022

作者

留言

撰寫回覆或留言

發佈留言必須填寫的電子郵件地址不會公開。