有没有一个典型的状态机实现模式?
我们需要在C中实现一个简单的状态机。
标准的switch语句是最好的方法吗?
我们有一个现状(州)和过渡的触发器。
switch(state) { case STATE_1: state = DoState1(transition); break; case STATE_2: state = DoState2(transition); break; } ... DoState2(int transition) { // Do State Work ... if(transition == FROM_STATE_2) { // New state when doing STATE 2 -> STATE 2 } if(transition == FROM_STATE_1) { // New State when moving STATE 1 -> STATE 2 } return new_state; }
简单的状态机有更好的方法吗?
编辑:对于C ++,我认为Boost 状态图库可能是要走的路。 但是,它并没有帮助C.让我们把注意力集中在C用例上。
我更喜欢对大多数状态机使用表驱动方法:
typedef enum { STATE_INITIAL, STATE_FOO, STATE_BAR, NUM_STATES } state_t; typedef struct instance_data instance_data_t; typedef state_t state_func_t( instance_data_t *data ); state_t do_state_initial( instance_data_t *data ); state_t do_state_foo( instance_data_t *data ); state_t do_state_bar( instance_data_t *data ); state_func_t* const state_table[ NUM_STATES ] = { do_state_initial, do_state_foo, do_state_bar }; state_t run_state( state_t cur_state, instance_data_t *data ) { return state_table[ cur_state ]( data ); }; int main( void ) { state_t cur_state = STATE_INITIAL; instance_data_t data; while ( 1 ) { cur_state = run_state( cur_state, &data ); // do other program logic, run other state machines, etc } }
这当然可以扩展为支持多个状态机等。也可以适应转换操作:
typedef void transition_func_t( instance_data_t *data ); void do_initial_to_foo( instance_data_t *data ); void do_foo_to_bar( instance_data_t *data ); void do_bar_to_initial( instance_data_t *data ); void do_bar_to_foo( instance_data_t *data ); void do_bar_to_bar( instance_data_t *data ); transition_func_t * const transition_table[ NUM_STATES ][ NUM_STATES ] = { { NULL, do_initial_to_foo, NULL }, { NULL, NULL, do_foo_to_bar }, { do_bar_to_initial, do_bar_to_foo, do_bar_to_bar } }; state_t run_state( state_t cur_state, instance_data_t *data ) { state_t new_state = state_table[ cur_state ]( data ); transition_func_t *transition = transition_table[ cur_state ][ new_state ]; if ( transition ) { transition( data ); } return new_state; };
表驱动的方法更容易维护和扩展,更简单的映射到状态图。
你可能已经看到了我提到FSM的另一个C问题的答案! 下面是我如何做到这一点:
FSM { STATE(x) { ... NEXTSTATE(y); } STATE(y) { ... if (x == 0) NEXTSTATE(y); else NEXTSTATE(x); } }
用下面的macros定义
#define FSM #define STATE(x) s_##x : #define NEXTSTATE(x) goto s_##x
这可以修改,以适应特定的情况。 例如,你可能有一个文件FSMFILE
,你想驱动你的FSM,所以你可以把读取下一个字符的操作合并到macros中:
#define FSM #define STATE(x) s_##x : FSMCHR = fgetc(FSMFILE); sn_##x : #define NEXTSTATE(x) goto s_##x #define NEXTSTATE_NR(x) goto sn_##x
现在你有两种types的转换:一种转换到一个状态并读取一个新的字符,另一个转换到一个没有消耗任何input的状态。
你也可以用类似的东西自动处理EOF:
#define STATE(x) s_##x : if ((FSMCHR = fgetc(FSMFILE) == EOF)\ goto sx_endfsm;\ sn_##x : #define ENDFSM sx_endfsm:
这种方法的好处在于,您可以将您绘制的状态图直接转换为工作代码,相反,您可以轻松地从代码中绘制状态图。
在用于实现FSM的其他技术中,转换的结构被掩埋在控制结构中(同时,如果切换…)并且由variables值(最终为state
variables)控制,并且将好的关系图一个令人费解的代码。
我从“计算机语言”杂志上的一篇文章中学到了这个技巧,不幸的是,这个技术已经不再出版。
我也使用了表格方法。 但是,有开销。 为什么要存储第二个指针列表? C中没有()的函数是一个const指针。 所以你可以这样做:
struct state; typedef void (*state_func_t)( struct state* ); typedef struct state { state_func_t function; // other stateful data } state_t; void do_state_initial( state_t* ); void do_state_foo( state_t* ); void do_state_bar( state_t* ); void run_state( state_t* i ) { i->function(i); }; int main( void ) { state_t state = { do_state_initial }; while ( 1 ) { run_state( state ); // do other program logic, run other state machines, etc } }
当然,根据你的恐惧因素(即安全与速度),你可能想要检查有效的指针。 对于大于三个状态的状态机,上面的方法应该比等效的交换机或表方法less一些指令。 你甚至可以macros观化为:
#define RUN_STATE(state_ptr_) ((state_ptr_)->function(state_ptr_))
另外,我从OP的例子中感觉到,在思考/devise一个状态机时应该做一些简化。 我不应该把过渡状态用于逻辑。 每个国家职能部门都应该能够在不明确过去状态的情况下履行其职责。 基本上你devise如何从你所处的状态转换到另一个状态。
最后,不要启动基于“function”边界的状态机的devise,为此使用子function。 取而代之的是根据什么时候需要等待什么事情发生才能继续下去。 这将有助于最大限度地减less在获得结果之前运行状态机的次数。 编写I / O函数或中断处理程序时,这可能很重要。
另外,经典switch语句的一些优缺点:
优点:
- 它是在语言,所以它是logging和明确的
- 状态被定义在哪里被调用
- 可以在一个函数调用中执行多个状态
- 可以在switch语句之前和之后执行所有状态共有的代码
缺点:
- 可以在一个函数调用中执行多个状态
- 可以在switch语句之前和之后执行所有状态共有的代码
- 开关执行可能会很慢
请注意这两个属性是亲和con。 我认为转换允许在各州之间分享太多的机会,并且国家之间的相互依赖性可能变得难以pipe理。 但是对于less数几个州来说,这可能是最具可读性和可维护性的。
随着状态机变大,还有更可维护的逻辑网格
对于一个简单的状态机,只需使用一个switch语句和一个枚举types为你的状态。 根据您的input在switch语句中进行转换。 在一个真正的程序中,你显然会改变“if(input)”来检查你的转换点。 希望这可以帮助。
typedef enum { STATE_1 = 0, STATE_2, STATE_3 } my_state_t; my_state_t state = STATE_1; void foo(char input) { ... switch(state) { case STATE_1: if(input) state = STATE_2; break; case STATE_2: if(input) state = STATE_3; else state = STATE_1; break; case STATE_3: ... break; } ... }
switch()是C中实现状态机的一种强大而标准的方法,但是如果你有大量的状态,它可以降低可维护性。 另一种常见的方法是使用函数指针来存储下一个状态。 这个简单的例子实现了一个设置/重置触发器:
/* Implement each state as a function with the same prototype */ void state_one(int set, int reset); void state_two(int set, int reset); /* Store a pointer to the next state */ void (*next_state)(int set, int reset) = state_one; /* Users should call next_state(set, reset). This could also be wrapped by a real function that validated input and dealt with output rather than calling the function pointer directly. */ /* State one transitions to state one if set is true */ void state_one(int set, int reset) { if(set) next_state = state_two; } /* State two transitions to state one if reset is true */ void state_two(int set, int reset) { if(reset) next_state = state_one; }
对于简单的情况,你可以使用你的开关样式方法。 我发现过去运作良好的也是处理过渡:
static int current_state; // should always hold current state -- and probably be an enum or something void state_leave(int new_state) { // do processing on what it means to enter the new state // which might be dependent on the current state } void state_enter(int new_state) { // do processing on what is means to leave the current atate // might be dependent on the new state current_state = new_state; } void state_process() { // switch statement to handle current state }
我对boost库没有任何了解,但是这种方法很简单,不需要任何外部依赖,而且很容易实现。
我在Edx.org课程embedded式系统 – 塑造世界UTAustinX – UT.6.02x,第10章,由Jonathan Valvano和Ramesh Yerraballi发现了一个非常漂亮的C实现Moore FSM ….
struct State { unsigned long Out; // 6-bit pattern to output unsigned long Time; // delay in 10ms units unsigned long Next[4]; // next state for inputs 0,1,2,3 }; typedef const struct State STyp; //this example has 4 states, defining constants/symbols using #define #define goN 0 #define waitN 1 #define goE 2 #define waitE 3 //this is the full FSM logic coded into one large array of output values, delays, //and next states (indexed by values of the inputs) STyp FSM[4]={ {0x21,3000,{goN,waitN,goN,waitN}}, {0x22, 500,{goE,goE,goE,goE}}, {0x0C,3000,{goE,goE,waitE,waitE}}, {0x14, 500,{goN,goN,goN,goN}}}; unsigned long currentState; // index to the current state //super simple controller follows int main(void){ volatile unsigned long delay; //embedded micro-controller configuration omitteed [...] currentState = goN; while(1){ LIGHTS = FSM[currentState].Out; // set outputs lines (from FSM table) SysTick_Wait10ms(FSM[currentState].Time); currentState = FSM[currentState].Next[INPUT_SENSORS]; } }
您可能需要查看libero FSM生成器软件。 从状态描述语言和/或(Windows)状态图编辑器,您可以生成C,C ++,Java和其他许多代码…加上不错的文档和图表。 来自iMatix的源代码和二进制文件
这篇文章是一个很好的状态模式(尽pipe它是C ++,而不是C)。
如果你能把手放在“ 头一个devise模式 ”这本书上,说明和例子都很清楚。
根据我的经验,使用“switch”语句是处理多种可能状态的标准方法。 虽然我觉得你正在向每个状态处理传递一个转换值。 我认为一个状态机的全部重点是每个状态都执行一个动作。 然后下一个动作/input决定要转换到哪个新状态。 所以我会期望每个状态处理函数立即执行任何固定的进入状态,然后决定是否需要转换到另一个状态。
我最喜欢的模式之一是国家devise模式。 对同一组给定的input做出不同的反应或行为。
使用状态机的switch / case语句的一个问题是,当你创build更多的状态时,switch / case变得更难/笨拙的读取/维护,促进无组织的意大利面代码,并且越来越难以改变而不会破坏某些东西。 我发现使用devise模式可以帮助我更好地组织数据,这是抽象的全部要点。 而不是围绕你来自什么样的状态来devise你的状态码,而是构造你的代码,以便当你进入一个新的状态时logging状态。 这样,你就可以有效地logging你以前的状态。 我喜欢@JoshPetit的回答,并且已经把他的解决办法更进了一步,直接从GoF书中拿出来:
stateCtxt.h:
#define STATE (void *) typedef enum fsmSignal { eEnter =0, eNormal, eExit }FsmSignalT; typedef struct fsm { FsmSignalT signal; // StateT is an enum that you can define any which way you want StateT currentState; }FsmT; extern int STATECTXT_Init(void); /* optionally allow client context to set the target state */ extern STATECTXT_Set(StateT stateID); extern void STATECTXT_Handle(void *pvEvent);
stateCtxt.c:
#include "stateCtxt.h" #include "statehandlers.h" typedef STATE (*pfnStateT)(FsmSignalT signal, void *pvEvent); static FsmT fsm; static pfnStateT UsbState ; int STATECTXT_Init(void) { UsbState = State1; fsm.signal = eEnter; // use an enum for better maintainability fsm.currentState = '1'; (*UsbState)( &fsm, pvEvent); return 0; } static void ChangeState( FsmT *pFsm, pfnStateT targetState ) { // Check to see if the state has changed if (targetState != NULL) { // Call current state's exit event pFsm->signal = eExit; STATE dummyState = (*UsbState)( pFsm, pvEvent); // Update the State Machine structure UsbState = targetState ; // Call the new state's enter event pFsm->signal = eEnter; dummyState = (*UsbState)( pFsm, pvEvent); } } void STATECTXT_Handle(void *pvEvent) { pfnStateT newState; if (UsbState != NULL) { fsm.signal = eNormal; newState = (*UsbState)( &fsm, pvEvent ); ChangeState( &fsm, newState ); } } void STATECTXT_Set(StateT stateID) { prevState = UsbState; switch (stateID) { case '1': ChangeState( State1 ); break; case '2': ChangeState( State2); break; case '3': ChangeState( State3); break; } }
statehandlers.h:
/* define state handlers */ extern STATE State1(void); extern STATE State2(void); extern STATE State3(void);
statehandlers.c:
#include "stateCtxt.h:" /* Define behaviour to given set of inputs */ STATE State1(FsmT *fsm, void *pvEvent) { STATE nextState; /* do some state specific behaviours * here */ /* fsm->currentState currently contains the previous state * just before it gets updated, so you can implement behaviours * which depend on previous state here */ fsm->currentState = '1'; /* Now, specify the next state * to transition to, or return null if you're still waiting for * more stuff to process. */ switch (fsm->signal) { case eEnter: nextState = State2; break; case eNormal: nextState = null; break; case eExit: nextState = State2; break; } return nextState; } STATE State3(FsmT *fsm, void *pvEvent) { /* do some state specific behaviours * here */ fsm->currentState = '2'; /* Now, specify the next state * to transition to */ return State1; } STATE State2(FsmT *fsm, void *pvEvent) { /* do some state specific behaviours * here */ fsm->currentState = '3'; /* Now, specify the next state * to transition to */ return State3; }
对于大多数状态机来说, 有限状态机,每个状态将知道下一个状态应该是什么,以及转换到下一个状态的标准。 对于松散状态devise,这可能不是这种情况,因此可以select公开API以用于转换状态。 如果你想要更多的抽象,每个状态处理程序可以被分离到它自己的文件中,这相当于GoF书中的具体状态处理程序。 如果你的devise很简单,只有less数几个状态,为了简单起见,stateCtxt.c和statehandlers.c可以合并成一个文件。
对于支持__COUNTER__
编译器,可以将它们用于简单(但很大)的状态mashines。
#define START 0 #define END 1000 int run = 1; state = START; while(run) { switch (state) { case __COUNTER__: //do something state++; break; case __COUNTER__: //do something if (input) state = END; else state++; break; . . . case __COUNTER__: //do something if (input) state = START; else state++; break; case __COUNTER__: //do something state++; break; case END: //do something run = 0; state = START; break; default: state++; break; } }
使用__COUNTER__
而不是硬编码的好处在于,您可以在其他状态中添加状态,而不必每次都重新编号。 如果编译器不支持__COUNTER__
, __COUNTER__
有限的方式使用__LINE__
在C ++中,考虑状态模式 。
有一本书名为“ C / C ++实用状态图” 。 但是,对于我们所需要的,它太重了。
你的问题类似于“是否有典型的数据库实现模式”? 答案取决于你想达到什么目的? 如果你想实现一个更大的确定性状态机,你可以使用一个模型和一个状态机生成器。 示例可以在www.StateSoft.org – SM Gallery中查看。 Janusz Dobrowolski
在Martin Fowler的UML Distilled中 ,他在第10章“状态机图”(强调我的)中声明(没有双关语意思):
状态图可以用三种主要的方式来实现: 嵌套开关 , 状态模式和状态表 。
我们来看一个手机显示状态的简单例子:
嵌套开关
福勒给了一个C#代码的例子,但我已经适应了我的例子。
public void HandleEvent(PhoneEvent anEvent) { switch (CurrentState) { case PhoneState.ScreenOff: switch (anEvent) { case PhoneEvent.PressButton: if (powerLow) { // guard condition DisplayLowPowerMessage(); // action // CurrentState = PhoneState.ScreenOff; } else { CurrentState = PhoneState.ScreenOn; } break; case PhoneEvent.PlugPower: CurrentState = PhoneState.ScreenCharging; break; } break; case PhoneState.ScreenOn: switch (anEvent) { case PhoneEvent.PressButton: CurrentState = PhoneState.ScreenOff; break; case PhoneEvent.PlugPower: CurrentState = PhoneState.ScreenCharging; break; } break; case PhoneState.ScreenCharging: switch (anEvent) { case PhoneEvent.UnplugPower: CurrentState = PhoneState.ScreenOff; break; } break; } }
状态模式
下面是我的示例与GoF状态模式的实现:
状态表
从福勒那里得到灵感,下面是我的例子:
源状态目标状态事件保护操作 -------------------------------------------------- ------------------------------------ ScreenOff ScreenOff按下buttonpowerLow displayLowPowerMessage ScreenOff ScreenOn按下buttonpowerLow ScreenOn ScreenOffbutton ScreenOff ScreenCharging plugPower ScreenOn ScreenCharging plugPower ScreenCharging ScreenOff拔掉电源
对照
嵌套开关将所有的逻辑保存在一个地方,但是当有许多状态和转换时,代码可能很难读取。 与其他方法(无多态或解释)相比,它可能更安全,更容易validation。
状态模式实现可能会将逻辑分散在若干个独立的类上,这可能会使整体上理解为一个问题。 另一方面,小class分开容易理解。 如果您通过添加或删除转换来更改行为,则devise特别脆弱,因为它们是层次结构中的方法,并且可能会对代码进行大量更改。 如果你依靠小界面的devise原则,你会发现这种模式并没有那么好。 但是,如果状态机是稳定的,那么这种改变就不需要了。
状态表的方法需要为内容编写某种解释器(如果你正在使用你正在使用的语言进行反思,这可能会更容易),这可能需要很多工作来做。 正如Fowler指出的那样,如果你的表与你的代码是分开的,你可以修改你的软件的行为而不用重新编译。 不过,这有一些安全隐患。 该软件的行为是基于外部文件的内容。
编辑(不是真的用于C语言)
有一个stream畅的接口(也称为内部域特定语言)方法,这可能是由具有一streamfunction的语言所促成的。 无状态库存在 ,博客显示了一个简单的代码示例。 讨论Java实现(Java8之前) 。 我在GitHub上也看到了一个Python示例 。
在C中,对于简单的机器,这样的东西是我通常最终使用的。
事件驱动的FSM由与动作(FsmAction)关联的状态对象(FsmState)和由当前状态和事件定义的转换(FsmEdge)来描述。
它还提供存储和传递FSM和用户数据,分离FSM绑定和用户绑定信息,并允许同一个FSM的多个实例(即使用相同的描述,但传递不同的用户数据)。
事件由整数types(FsmEvent)表示。 负值由实现保留,以允许特殊的常见事件(Reset,None,Any,Empty,End)。 非负面事件是用户定义的。
为了简单起见,转换被列在一个数组中,并且按照数组顺序进行匹配,从本质上提供了转换优先级。 他们有可选的保护function。 下一个状态可以直接在转换列表中指定,也可以通过跳转function指示,这样可以提供更多的灵活性来实现dynamicFSM行为。
在转换描述中,NULL当前状态将匹配任何状态,通配符事件(Any)将匹配任何事件。 无论如何,触发转换的事件的实际值将被传递给跳转和保护function。
对于复杂的FSM,简单的边缘arrays解决scheme可能效率太低。 在这种情况下,可以使用边arrays和事件条目转换成转换matrix或状态邻接表来实现适当的跳转function。
国家行为意味着用可重入的函数来实现,该函数区分进入状态(Enter),状态退出(Leave)和状态(State)操作。 通过这种方式,可以使用静态函数variables封装和保存本地状态信息。
通常情况下,状态进入和退出操作不会显着执行,不会返回新的事件(无)。 如果不是,则新事件被困并立即返回。 这将有效地防止在退出当前状态时发生转换。
FSM步进函数(fsmStep)将执行FSM的单个步骤,使用新事件触发转换,或者不执行当前状态的处于活动状态的事件(无)。 step函数返回一个新的事件,可以处理或重新发送到FSM; 如果没有事件,则分别为None(无),Empty(空)和End(结束)。
#ifndef FSM_H_ #define FSM_H_ #include <stdbool.h> #include <stdint.h> /** FSM enum type */ typedef enum { // Events and return values fsm_User = 0, ///< User events start with this id fsm_Reset = -1, ///< Reset event fsm_None = -2, ///< No event fsm_Any = -3, ///< Any event, used as a wildcard fsm_Empty = -4, ///< No transition found for event fsm_End = -5, ///< Final state event generated when FSM reaches end state, or stop processing when used in transition // Action types fsm_Enter = 0, ///< Entry action fsm_State, ///< In-state action fsm_Leave ///< Exit action } fsm_e; typedef int FsmEvent; ///< Type for events typedef struct FsmState FsmState; ///< Type for states typedef struct FsmEdge FsmEdge; ///< Type for edges (transitions) /** State action functor @param state Pointer to this state @param type Action type (Enter/State/Leave) @param frto Pointer to from(Enter)/to(Leave) state, NULL otherwise @param data User data @return Event id in case of a new triggered event, fsm_None otherwise */ typedef FsmEvent (*FsmAction)(FsmState *state, fsm_e type, FsmState *frto, void *data); /** FSM state object */ struct FsmState { FsmAction action; ///< Per-state action void *data; ///< Per-state data }; /** State jump functor @param edge Pointer to this edge @param state Pointer to the actual current state @param event Event id that triggered the transition @param data User data @return Pointer to the next state and NULL for end state */ typedef FsmState *(*FsmJump)(FsmEdge *edge, FsmState *state, FsmEvent event, void *data); /** Guard function @param edge Pointer to this edge @param state Pointer to the actual current state @param event Event id that triggered the transition @param data User data @return Guard result */ typedef bool (*FsmGuard)(FsmEdge *edge, FsmState *state, FsmEvent event, void *data); /** FSM edge transition */ struct FsmEdge { FsmState *state; ///< Matching current state pointer FsmEvent event; ///< Matching event id or fsm_Any for wildcard void *next; ///< Next state pointer (FsmState *) or jump function (FsmJump) FsmGuard guard; ///< Transition guard function void *data; ///< Per-edge data }; typedef struct Fsm Fsm; struct Fsm { FsmState *state; ///< Pointer to the state array size_t states; ///< Number of states void **stateData; ///< Pointer to user state data array FsmEdge *edge; ///< Pointer to the edge transitions array size_t edges; ///< Number of edges void **edgeData; ///< Pointer to user edge data array FsmEvent event; ///< Current/last event fsm_e type; ///< Current/last action type FsmState *current; ///< Pointer to the current state void *data; ///< Per-fsm data }; #define fsm_INIT { 0 } static inline FsmEvent fsmStep(Fsm *f, FsmEvent e) { FsmState *cp = f->current; // Store current state FsmEvent ne = fsm_None; // Next event // User state data void *us = (f->stateData && cp) ? f->stateData[cp - f->state] : NULL; if (!cp && e == fsm_None) e = fsm_Reset; // Inject reset into uninitialized FSM f->event = e; if (e == fsm_Reset) { // Exit current state if (cp && cp->action) { f->type = fsm_Leave, ne = cp->action(cp, fsm_Leave, f->state, us); if (ne != fsm_None) return ne; // Leave action emitted event } FsmState *ps = cp; cp = f->current = f->state; // First state in array is entry state if (!cp) return fsm_End; // Null state machine us = f->stateData ? f->stateData[cp - f->state] : NULL; if (cp->action) f->type = fsm_Enter, ne = cp->action(cp, fsm_Enter, ps, us); } else if (e == fsm_None) // No event, run in-state action { if (cp->action) f->type = fsm_State, ne = cp->action(cp, fsm_State, NULL, us); } else // Process user transition event { ne = fsm_Empty; // Default return // Search transition in listing order for (size_t i = 0; i < f->edges; ++i) { FsmEdge *ep = &f->edge[i]; if (ep->state && ep->state != cp) continue; // Not a match // Check for stop filter if (ep->event == fsm_End) break; // Check for state/event match (null edge state matches any state) if (ep->event == e || ep->event == fsm_Any) { // User edge data void *ue = f->edgeData ? f->edgeData[i] : NULL; // Check transition guard if (!ep->guard || ep->guard(ep, cp, e, ue)) { // Resolve next state pointer (NULL, new state pointer or jump function) FsmState *np = (!ep->next) ? NULL : ((FsmState *)(ep->next) >= f->state && (FsmState *)(ep->next) < (f->state + f->states)) ? (FsmState *)(ep->next) : ((FsmJump)(ep->next))(ep, cp, e, ue); // Check state change if (cp != np) { if (cp->action) { f->type = fsm_Leave, ne = cp->action(cp, fsm_Leave, np, us); if (ne != fsm_None) return ne; // Leave action emitted event } if (np) { us = f->stateData ? f->stateData[np - f->state] : NULL; if (np->action) f->type = fsm_Enter, ne = np->action(np, fsm_Enter, cp, us); else ne = fsm_None; } else // Final state reached if next state is NULL ne = fsm_End; cp = np; // New state value } // Transition executed, stop break; } } } } f->current = cp; // Commit current state return ne; // Last event value } static inline FsmEvent fsmReset(Fsm *f) { return fsmStep(f, fsm_Reset); } #endif // FSM_H_
下面是一个非常简单的testing,以了解如何定义和使用FSM实现的想法。 没有用户定义的事件,只有FSM数据(string),每个状态的状态动作和共享跳转function相同:
#include <stdio.h> #include "fsm.h" // State action example static FsmEvent state_action(FsmState *s, fsm_e t, FsmState *ft, void *us) { FsmEvent e = fsm_None; // State event const char *q = "?"; switch (t) { case fsm_Enter: q = "enter"; break; case fsm_Leave: q = "leave"; break; default /* fsm_State */: q = "state"; } printf("%s %s\n", (const char *)s->data, q); return e; } // States FsmState fs[] = { { state_action, "S0" }, { state_action, "S1" }, { state_action, "S2" }, }; // Transition jump example static FsmState * state_jump(FsmEdge *t, FsmState *s, FsmEvent e, void *ue) { if (s == &fs[0]) return &fs[1]; if (s == &fs[2]) return NULL; // End state return NULL; // Trap } // Transition guard example static bool count_attempt(FsmEdge *t, FsmState *s, FsmEvent e, void *ue) { static int c = 0; ++c; bool b = c == 3; printf("guard is %s\n", b ? "true" : "false"); return b; } // Transitions FsmEdge fe[] = { { &fs[0], fsm_Any, state_jump }, // Using jump function, no guard { &fs[1], fsm_Any, &fs[2], count_attempt }, // Using direct state and guard { &fs[2], fsm_Any, state_jump }, // Using jump function, no guard }; int main(int argc, char **argv) { Fsm f = { fs, 3, NULL, fe, 3, NULL, }; fsmReset(&f); do { fsmStep(&f, fsm_None); } while (fsmStep(&f, fsm_Any) != fsm_End); return 0; }
Boost has the statechart library. http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html
I can't speak to the use of it, though. Not used it myself (yet)