有限状态机(FSM)是表示有限个状态及在这些状态之间的转移和动作等行为的数学模型,在计算机领域有着广泛的应用。通常FSM包含几个要素:状态的管理、状态的监控、状态的触发、状态触发后引发的动作。本文主要阐述一下状态机的几种设计方法。
1:switch case/if else设计方法
curEvent = getEvent();
curState = getCurState();
switch(curState)
{
case state1:
{
switch(curEvent )
{
TODO...
setCurState();
break;
}
break;
}
...
}
|
这种设计方法最简单,通过一大堆判断来处理,适合小规模的状态切换流程,但如果规模扩大难以扩展和维护。
2:基于表结构的状态机设计方法:建立相应的状态表和动作查询表,根据状态表、事件、动作表定位相应的动作处理函数,执行完成后再进行状态的切换。
一个通用的状态机处理模块的设计如下:
/*状态表注册*/ void FSM_Regist(FSM_T* pFsm,STATE_TABLE_S* pStateTable) { pFsm->FsmTable = pStateTable; return; } /*状态迁移*/ void FSM_MoveState(FSM_T* pFsm,int state) { pFsm->curState = state; return; } /*事件处理*/ void FSM_EventHandle(FSM_T* pFsm,int event) { ACT_TABLE_T* pActTable = NULL; ActFun eventActFun = NULL; /*获取当前状态动作表*/ pActTable = FSM_getActTable(pFsm); /*获取当前动作函数*/ for(int i=0;i<MAX_ACT_NUM;i++) { if(event == pActTable[i].event) { eventActFun = pActTable[i].eventActFun; break; } } /*动作执行*/ if(eventActFun) { eventActFun(pFsm); } } |
假设我们的状态图如下:
相应的状态机设置如下:
/*状态1的动作表*/ ACT_TABLE_T state1ActTable[] = { {EVENT1,state1Event1Fun}, {EVENT3,state1Event3Fun}, }; /*状态2的动作表*/ ACT_TABLE_T state2ActTable[] = { {EVENT2,state2Event2Fun}, }; /*状态表*/ STATE_TABLE_T FsmTable[] = { {STATE1,state1ActTable}, {STATE2,state2ActTable}, };
int main(int argc, _TCHAR* argv[])
{
FSM_T fsm;
/*状态表注册*/
FSM_Regist(&fsm,FsmTable);
FSM_MoveState(&fsm,STATE1);
FSM_EventHandle(&fsm,EVENT1);
FSM_EventHandle(&fsm,EVENT2);
return 0;
}
/*客户端提供的状态处理函数*/
void state1Event1Fun(void* pFsm)
{
FSM_MoveState((FSM_T*)pFsm,STATE2);
return;
}
void state1Event3Fun(void* pFsm)
{
FSM_MoveState((FSM_T*)pFsm,STATE3);
return;
}
void state2Event2Fun(void* pFsm)
{
FSM_MoveState((FSM_T*)pFsm,STATE3);
return;
} |
通过设计一个通用的基于表结构的状态机模块,针对不同的状态图,我们只需要根据状态图得到其状态表结构,然后通过FSM_Regist注册,就可以方便的使用了状态机的功能了。这种机制便于我们添加新的状态流程,并且可以很好的进行分层状态机的设计。
分层状态机的设计:
对于状态较多的状态机,通常的设计会维护一个庞大的二维矩阵,所有状态耦合在一起,这往往导致维护困难,由于可能存在许多公共的特性,也会导致许多状态具有相同的处理函数。针对这些问题我们可以通过设计分层状态机来解决,主要的思想就是根据不同的功能模块设计出多个状态机,各个状态机分布在不同的层次上。上层状态机调用下层状态机时,上层状态机入栈,下层状态机变为当前处理状态机。通常我们使用堆栈来保存当前状态机的上层状态机信息。
下图描述一个分层状态机设计实现:
如上图所示,假设L1为上层状态机,L1状态机在L1_STATE2中可以通过L1L2_EVENT1事件触发进入L2状态机,L2状态机在L2_STATE2中通过L1L2_EVENT2事件触发返回L1状态机,L1和L2各自维护自己的状态表。
数据结构:
struct FSM_S { int curState; //当前状态机状态 int curFsmTableSize; //当前状态机查询表大小 STATE_TABLE_T* curFsmTable; //当前状态机查询表 FSM_STACK_T stack[MAX_FSM_STACK_DEP];//状态机堆栈 int curStackTop; //栈顶 FSM_REGIST_T registFsm[MAX_FSM_NUM]; //注册状态机 int registFsmNum; //注册状态机个数 }; |
1:通过堆栈数据结构维护上层状态机信息。
2:保存所有可以注册状态机信息。
3:记录当前运行状态机信息。
主要接口:
void FSM_Init(FSM_T* pFsm); void FSM_Regist(FSM_T* pFsm,STATE_TABLE_S* pStateTable,int FsmId,int curFsmTableSize); void FSM_Begin(FSM_T* pFsm,int FsmId); void FSM_MoveState(FSM_T* pFsm,int state); void FSM_EventHandle(FSM_T* pFsm,int event); void FSM_Push(FSM_T* pFsm);void FSM_Pop(FSM_T* pFsm); |
1:FSM_Regist 对所有的状态机信息进行注册
void FSM_Regist(FSM_T* pFsm,STATE_TABLE_S* pStateTable,int FsmId, int curFsmTableSize) { pFsm->registFsm[pFsm->registFsmNum].fsmId = FsmId; pFsm->registFsm[pFsm->registFsmNum].FsmTable = pStateTable; pFsm->registFsm[pFsm->registFsmNum].fsmTableSize = curFsmTableSize;
pFsm->registFsmNum++;
return;
} |
2:FSM_Begin 用于开始一个新的状态机流程,切换状态表信息。
void FSM_Begin(FSM_T* pFsm,int FsmId) { for(int i=0;i<pFsm->registFsmNum;i++) { if(FsmId == pFsm->registFsm[i].fsmId) { pFsm->curFsmTable = pFsm->registFsm[i].FsmTable; pFsm->curFsmTableSize = pFsm->registFsm[i].fsmTableSize; break; } } return; } |
3:FSM_Push/FSM_Pop 用于状态机切换的出入堆栈操作。
void FSM_Push(FSM_T* pFsm) { if(pFsm->curStackTop < MAX_FSM_STACK_DEP) { pFsm->curStackTop++; pFsm->stack[pFsm->curStackTop].state = pFsm->curState; pFsm->stack[pFsm->curStackTop].pFsmTable = pFsm->curFsmTable; pFsm->stack[pFsm->curStackTop].fsmTableSize = pFsm->curFsmTableSize; }
return;
}
void FSM_Pop(FSM_T* pFsm)
{
if(pFsm->curStackTop > -1)
{
pFsm->curState = pFsm->stack[pFsm->curStackTop].state;
pFsm->curFsmTable = pFsm->stack[pFsm->curStackTop].pFsmTable;
pFsm->curFsmTableSize = pFsm->stack[pFsm->curStackTop].fsmTableSize;
pFsm->curStackTop--;
}
return;
} |
接口的使用:
/*L1 状态机定义*/ ACT_TABLE_T L1state1ActTable[] = { {L1_EVENT1,L1state1_Event1Fun}, {L1_EVENT3,L1state1_Event3Fun}, }; ACT_TABLE_T L1state2ActTable[] = { {L1_EVENT2,L1state2_Event2Fun}, {L1_L2_EVENT1,L1state2_L1L2EventFun}, }; STATE_TABLE_T L1FsmTable[] = { {L1_STATE1,sizeof(L1state1ActTable)/sizeof(ACT_TABLE_T),L1state1ActTable}, {L1_STATE2,sizeof(L1state2ActTable)/sizeof(ACT_TABLE_T),L1state2ActTable}, }; /*L2 状态机定义*/ ACT_TABLE_T L2state1ActTable[] = { {L2_EVENT1,L2state1_L2Event1Fun}, }; ACT_TABLE_T L2state2ActTable[] = { {L1_L2_EVENT2,L2state2_L1L2EvenFun}, }; STATE_TABLE_T L2FsmTable[] = { {L2_STATE1,sizeof(L2state1ActTable)/sizeof(ACT_TABLE_T),L2state1ActTable}, {L2_STATE2,sizeof(L2state2ActTable)/sizeof(ACT_TABLE_T),L2state2ActTable}, };
int main(int argc, _TCHAR* argv[])
{
FSM_T pFsm;
FSM_Init(&pFsm);
/*状态机注册*/
FSM_Regist(&pFsm,L1FsmTable,FSM_L1,sizeof(L1FsmTable)/sizeof(STATE_TABLE_T));
FSM_Regist(&pFsm,L2FsmTable,FSM_L2,sizeof(L2FsmTable)/sizeof(STATE_TABLE_T));
/*开始L1状态机*/
FSM_Begin(&pFsm,FSM_L1);
FSM_MoveState(&pFsm,L1_STATE1);
FSM_EventHandle(&pFsm,L1_EVENT1);
/*push 状态机*/
FSM_EventHandle(&pFsm,L1_L2_EVENT1);
/*L2状态机处理*/
FSM_EventHandle(&pFsm,L2_EVENT1);
/*pop 状态机*/
FSM_EventHandle(&pFsm,L1_L2_EVENT2);
/*L1状态机处理*/
FSM_EventHandle(&pFsm,L1_EVENT2);
return 0;
} |
1:首先通过FSM_Regist注册所有的状态机。
2:FSM_EventHandle(&pFsm,L1_L2_EVENT1)中的动作处理函数中进行压栈操作同时进入L2状态机。
void L1state2_L1L2EventFun(void* pFsm) { FSM_Push((FSM_T*)pFsm); FSM_Begin((FSM_T*)pFsm,FSM_L2); FSM_MoveState((FSM_T*)pFsm,L2_STATE1);
return;
} |
3:FSM_EventHandle(&pFsm,L1_L2_EVENT2)中的动作处理函数中进行出栈操作返回到L1状态机。
void L2state2_L1L2EvenFun(void* pFsm) { FSM_Pop((FSM_T*)pFsm);
return;
} |
结论:
通过分层状态机的设计,各个功能实体维护自身的强相关的一套状态机,可以有效的减小状态机的复杂度,通过构建公共流程状态机,可以减小规模。综上所述:在针对规模较大、流程复杂的状态机设计,我们考虑使用分层的设计方法。
|