Существует ли типичный шаблон реализации конечного автомата?
нам нужно реализовать простую машину состояний в C.
Является ли стандартный оператор switch лучшим способом?
У нас есть текущее состояние (state) и триггер для перехода.
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++, я думаю, что повышение Statechart библиотека может быть путь. Тем не менее, это делает не помощь с C. позволяет сосредоточиться на использовании C случай.
19 ответов:
Я предпочитаю использовать табличный подход для большинства государственных машин:
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; };
табличный подход легче поддерживать и расширять и проще сопоставлять с диаграммами состояний.
возможно, вы видели мой ответ на другой вопрос C, где я упомянул FSM! Вот как я это делаю:
FSM { STATE(x) { ... NEXTSTATE(y); } STATE(y) { ... if (x == 0) NEXTSTATE(y); else NEXTSTATE(x); } }
со следующими определенными макросами
#define FSM #define STATE(x) s_##x : #define NEXTSTATE(x) goto s_##x
это может быть изменен в соответствии с конкретным случаем. Например, у вас может быть файл
FSMFILE
что вы хотите управлять своим FSM, чтобы вы могли включить действие чтения следующего символа в сам макрос:#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
теперь у вас есть два типа переходов: один переходит в состояние и читает новый символ, другой переходит в состояние без потребления каких-либо входных данных.
вы также можете автоматизировать обработку EOF с чем-то вроде:
#define STATE(x) s_##x : if ((FSMCHR = fgetc(FSMFILE) == EOF)\ goto sx_endfsm;\ sn_##x : #define ENDFSM sx_endfsm:
хорошая вещь этого подхода заключается в том, что вы можете напрямую перевести диаграмму состояния, которую вы рисуете в рабочий код, и, наоборот, вы можете легко нарисовать диаграмму состояния из кода.
в других методах реализации FSM структура переходов похоронена в структурах управления (while, if, switch ...) и управляется значением переменных (типично a
state
переменная), и это может быть сложной задачей, чтобы связать хорошую диаграмму с запутанным кодом.я узнал эту технику из статьи, появившейся в большом журнале "компьютерный язык", который, к сожалению, больше не публикуется.
Я также использовал табличный подход. Однако, есть накладные расходы. Зачем хранить второй список указателей? Функция в 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 } }
конечно, в зависимости от вашего фактора страха (т. е. безопасности против скорости) вы можете проверить наличие действительных указателей. Для машин состояний, превышающих три или более состояний, приведенный выше подход должен быть меньше инструкций, чем эквивалентный подход коммутатора или таблицы. Вы могли бы даже макро-Изе как:
#define RUN_STATE(state_ptr_) ((state_ptr_)->function(state_ptr_))
кроме того, я чувствую из примера OP, что есть упрощение, которое должно быть сделано при размышлении о / проектировании государственной машины. Я не думаю, что переходное состояние должно использоваться для логики. Каждая государственная функция должна быть в состоянии выполнять свою данную роль без явного знания прошлых состояний. В основном вы разрабатываете, как перейти из состояния, в котором вы находитесь, в другое состояние.
наконец, не начинайте проектирование государственной машины основываясь на" функциональных " границах, используйте для этого подфункции. Вместо этого разделите состояния на основе того, когда вам придется ждать, пока что-то произойдет, прежде чем вы сможете продолжить. Это поможет свести к минимуму количество запусков конечного автомата перед получением результата. Это может быть важно при написании функций ввода-вывода или обработчиков прерываний.
кроме того, несколько плюсов и минусов классического оператора switch:
плюсы:
- это в язык, так он документирован и понятен
- состояния определяются там, где они называются
- может выполнять несколько государств в одном вызове функции
- код, общий для всех состояний, может быть выполнен до и после оператора switch
плюсы:
- может выполнять несколько государств в одном вызове функции
- код, общий для всех состояний, может быть выполнен до и после оператора switch
- переключатель реализация может быть медленной
есть еще логика сетки который является более ремонтопригодным, поскольку государственная машина становится больше
для простого конечного автомата просто используйте оператор switch и тип перечисления для вашего состояния. Выполняйте переходы внутри оператора 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, но он может уменьшить ремонтопригодность вниз, если у вас есть большое количество состояний. Другим распространенным методом является использование указателей функций для хранения следующего состояния. Этот простой пример реализует триггер set/reset:
/* 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, но этот тип подхода очень прост, не требует каких-либо внешних зависимостей и прост в реализации.
Я нашел действительно гладкую реализацию C Moore FSM на edx.org курс встраиваемых систем-Shape The World UTAustinX-UT.6.02 x, Глава 10, Джонатан Вальвано и Рамеш Йеррабалли....
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]; } }
на UML Мартина Фаулера дистиллированный, Он утверждает (без каламбура) в главе 10 диаграммы состояния машины (акцент мой):
диаграмма состояний может быть реализована тремя основными способами:вложенные переключатель на State pattern, и государственный таблиц.
давайте использовать упрощенный пример состояния дисплея мобильного телефона:
вложенные переключатель
Фаулер привел пример кода на 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; } }
State pattern
вот реализация моего примера с шаблоном состояния GoF:
Государственный Таблиц
вдохновляясь Фаулером, Вот таблица для моего примера:
Source State Target State Event Guard Action -------------------------------------------------------------------------------------- ScreenOff ScreenOff pressButton powerLow displayLowPowerMessage ScreenOff ScreenOn pressButton !powerLow ScreenOn ScreenOff pressButton ScreenOff ScreenCharging plugPower ScreenOn ScreenCharging plugPower ScreenCharging ScreenOff unplugPowerсравнение
вложенный переключатель сохраняет всю логику в одном месте, но код может трудно читать, когда есть много состояний и переходов. Возможно, это более безопасно и легче проверить, чем другие подходы (без полиморфизма или интерпретации).
подход к таблицам состояний требует написания какого-то интерпретатора для контента (это может быть проще, если у вас есть отражение на языке, который вы используете), что может быть много работы перед. Как указывает Фаулер, если ваша таблица отделена от вашего кода, Вы можете изменить поведение вашего программного обеспечения без перекомпиляции. Это имеет некоторые последствия для безопасности, однако; программное обеспечение ведет себя на основе содержимого внешнего файла.
редактировать (не совсем для языка C)
существует также свободный интерфейс (он же внутренний доменный язык), который, вероятно, облегчается языками, которые имеют первый-класс функций. Элемент библиотека существует, а блог показывает простой пример с кодом. А реализация Java (pre Java8) есть. Мне показали пример Python на GitHub как хорошо.
возможно, вы захотите заглянуть в либеро программное обеспечение генератора ФСМ. Из языка описания состояний и/или редактора диаграмм состояний (windows) вы можете создавать код для C, C++, java и многих других ... плюс хорошая документация и схемы. Источник и двоичные файлы из iMatix
в этой статье является хорошим для шаблона состояния (хотя это C++, а не конкретно C).
Если вы можете положить руки на книгу"Head First Design Patterns", объяснение и пример очень ясно.
один из моих любимых шаблонов-это шаблон дизайна состояния. Реагировать или вести себя по-разному на один и тот же набор входных данных.
Одна из проблем с использованием операторов switch/case для машин состояний заключается в том, что по мере создания большего количества состояний коммутатор/cases становится сложнее/громоздким для чтения/обслуживания, способствует неорганизованному коду спагетти и все труднее изменять, не нарушая что-то. Я нахожу, что использование шаблонов проектирования помогает мне лучше организовать мои данные, что является целым точка абстракции. Вместо разработки кода состояния вокруг того, из какого состояния вы пришли, вместо этого структурируйте свой код так, чтобы он записывал состояние при вводе нового состояния. Таким образом, вы эффективно получаете запись вашего предыдущего состояния. Мне нравится ответ @JoshPetit, и я принял его решение на один шаг дальше, взятый прямо с Гоф книга: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; }
для большинства государственных машин, esp. Конечные автоматы, каждое состояние будет знать, что его следующее состояние должно быть, и критерии для перехода к следующему состоянию. Для проектов свободных состояний это может быть не так, поэтому можно предоставить API для переходных состояний. Если вы хотите больше абстракции, каждый обработчик состояний может быть разделен на свой собственный файл, который эквивалентен конкретным обработчикам состояний в книге GoF. Если ваш дизайн прост только с несколькими состояниями, то оба stateCtxt.c и государственные служащие.c может быть объединен в один файл для простоты.
по моему опыту использование оператора 'switch' является стандартным способом обработки нескольких возможных состояний. Хотя я удивлен, что вы передаете значение перехода в обработку для каждого состояния. Я думал, что весь смысл государственной машины заключается в том, что каждое государство выполняет одно действие. Затем следующее действие / вход определяет, в какое новое состояние переходить. Поэтому я ожидал бы, что каждая функция обработки состояния немедленно выполнит все, что фиксировано для входа в состояние, а затем затем решите, нужен ли переход в другое состояние.
есть книга под названием практические диаграммы состояния в C / C++. Однако, это путь слишком тяжелый для того, что нам нужно.
для компилятора, который поддерживает
__COUNTER__
, вы можете использовать их для простых (но больших) государственных машин.#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__
, в ограниченном виде его можно использовать с осторожностью__LINE__
ваш вопрос похож на "есть ли типичный шаблон реализации базы данных"? Ответ зависит от того, чего вы хотите достичь? Если вы хотите реализовать более крупный детерминированный конечный автомат, вы можете использовать модель и генератор конечного автомата. Примеры можно посмотреть по адресу www.StateSoft.org -Галерея SM. Януш Добровольский
В C, для простых машин что-то вроде этого я обычно использую.
управляемый событиями FSM описывается объектами состояния (FsmState), связанными с действием (FsmAction), и переходами (FsmEdge), определенными текущим состоянием и событиями.
Он также обеспечивает хранение и передачу как FSM, так и пользовательских данных, чтобы отделить информацию, связанную с FSM и связанную с пользователем, и разрешить несколько экземпляров одного и того же FSM (т. е. используя одно и то же описание, но передавая разные пользовательские данные.)
события представлены целочисленным типом (FsmEvent). Отрицательные значения зарезервированы реализацией, чтобы разрешить специальные общие события (Reset, None, Any, Empty, End). Неотрицательные события определяются пользователем.
для простоты переходы перечислены в массиве, и сопоставление выполняется в порядке массива, по существу обеспечивая приоритеты перехода. Они имеют опционные функции предохранителя. Следующее состояние может быть указано непосредственно в переходе список или с помощью функции перехода, таким образом, обеспечивая большую гибкость, позволяющую динамическое поведение FSM.
в описаниях переходов нулевое текущее состояние будет соответствовать любому состоянию, а подстановочное событие (любое) будет соответствовать любому событию. В любом случае, фактическое значение события, вызвавшего переход, будет передано в функции перехода и защиты.
для сложных FSMs простое решение граничного массива может стать слишком неэффективным. В этом случае правильная функция перехода может быть реализовано с использованием граничного массива и записей событий, преобразованных в матрицу перехода или списки смежности состояний.
действия состояния должны быть реализованы с помощью реентерабельной функции, которая различает операции входа в состояние (Enter), выхода из состояния (Leave) и в состоянии (State). Таким образом, информация о локальном состоянии может быть инкапсулирована и сохранена с помощью статических функциональных переменных.
обычно действия входа и выхода из состояния будут выполняться незаметно и возвращать нет новые события (нет). Если нет, то новое событие будет захвачено и немедленно возвращено. Это позволит эффективно предотвратить переход в случае, если это произойдет при выходе из текущего состояния.
функция FSM step (fsmStep) будет выполнять один шаг FSM, используя новое событие для запуска перехода, или нет события (нет) для выполнения действия в состоянии текущего состояния. Функция step возвращает новое событие, которое может быть обработано или повторно передано в FSM; или нет, пусто и заканчивается в случае нет события, переход не найден или конечное состояние достигнуто, соответственно.
#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, or NULL to match any state 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; switch (e) { case 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 if (cp->action) { us = f->stateData ? f->stateData[0] : NULL; f->type = fsm_Enter, ne = cp->action(cp, fsm_Enter, ps, us); } } break; case fsm_None: // No event, run in-state action if (cp->action) f->type = fsm_State, ne = cp->action(cp, fsm_State, NULL, us); break; default: // Process user transition event ne = fsm_Empty; // Default return in case no transition is found // Search transition in listing order for (size_t i = 0; i < f->edges; ++i) { FsmEdge *ep = &f->edge[i]; // Check for state match (null edge state matches any state) if (ep->state && ep->state != cp) continue; // Not a match // Check for stop processing filter if (ep->event == fsm_End) break; ne = fsm_None; // Default return for a transition // Check for event match 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); 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) // Final state reached if next state is NULL ne = fsm_End; else if (np->action) { us = f->stateData ? f->stateData[np - f->state] : NULL; f->type = fsm_Enter, ne = np->action(np, fsm_Enter, cp, us); } 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_
Ниже приведен очень простой тест, чтобы получить представление о том, как определить и использовать реализацию FSM. Нет никаких пользовательских событий, только данные FSM (строки), одно и то же действие состояния для каждого состояния и общая функция перехода:
#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 имеет библиотеку statechart. http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html
Я не могу говорить об использовании его, Хотя. Не использовал его сам (пока)