Существует ли типичный шаблон реализации государственного аппарата?
Нам нужно реализовать простой конечный автомат в 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;
}
Есть ли лучший способ для простых состояний машин
EDIT: Для С++ я думаю, что библиотека Boost Statechart может быть способом. Тем не менее, он помогает не с C. Давайте сосредоточимся на случае использования C.
Ответы
Ответ 1
Я предпочитаю использовать подход, основанный на таблицах, для большинства состояний машин:
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;
};
Подход, основанный на таблицах, проще поддерживать, расширять и упрощать сопоставление диаграмм состояний.
Ответ 2
Возможно, вы видели мой ответ на другой вопрос 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, чтобы вы могли включить действие следующего char в сам макрос:
#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 структура переходов кладется в управляющие структуры (в то время как if, switch...) и управляется значением переменных (типично a state
variable), и это может быть сложной задачей для связать красивую диаграмму с запутанным кодом.
Я узнал эту технику из статьи, появившейся в большом журнале "Компьютерный язык", который, к сожалению, больше не публикуется.
Ответ 3
Я также использовал табличный подход. Однако есть накладные расходы. Зачем хранить второй список указателей? Функция в 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
- реализация коммутатора может быть медленной
Обратите внимание на два атрибута, которые являются как pro, так и con. Я думаю, что коммутатор дает возможность слишком большого обмена между государствами, и взаимозависимость между государствами может стать неуправляемой. Однако для небольшого числа состояний он может быть наиболее читаемым и поддерживаемым.
Ответ 4
Для простого конечного компьютера просто используйте оператор 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;
}
...
}
Ответ 5
существует также логическая сетка , которая более удобна в обслуживании, когда машина состояния становится больше
Ответ 6
В Martin Fowler UML Distilled он говорит (не каламбур) в главе 10 "Диаграммы конечных автоматов" (выделено мое):
Диаграмма состояний может быть реализована тремя основными способами: вложенный переключатель, шаблон состояний и таблицы состояний.
Давайте использовать упрощенный пример состояний дисплея мобильного телефона:
![enter image description here]()
Вложенный переключатель
Фаулер привел пример кода на С#, но я адаптировал его к своему примеру.
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 State:
![enter image description here]()
Таблицы состояний
Вдохновленный Фаулером, вот таблица для моего примера:
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
Сравнение
Вложенный переключатель хранит всю логику в одном месте, но код может быть трудночитаемым при большом количестве состояний и переходов. Это, возможно, более безопасно и проще для проверки, чем другие подходы (без полиморфизма или интерпретации).
Реализация шаблона State может распространить логику на несколько отдельных классов, что может затруднить понимание ее в целом. С другой стороны, маленькие классы легко понять отдельно. Дизайн особенно хрупок, если вы изменяете поведение, добавляя или удаляя переходы, так как они являются методами в иерархии и в коде может быть много изменений. Если вы живете по принципу дизайна небольших интерфейсов, вы увидите, что этот шаблон не очень хорошо работает. Однако, если конечный автомат стабилен, такие изменения не потребуются.
Подход таблиц состояний требует написания некоторого интерпретатора для содержимого (это может быть проще, если у вас есть отражение в языке, который вы используете), что может потребовать много работы заранее. Как указывает Фаулер, если ваша таблица отделена от вашего кода, вы можете изменить поведение своего программного обеспечения без перекомпиляции. Это имеет некоторые последствия для безопасности, однако; программное обеспечение ведет себя на основе содержимого внешнего файла.
Изменить (не совсем для языка Си)
Существует также свободный подход к интерфейсу (он же внутренний предметно-ориентированный язык), которому, вероятно, способствуют языки с первоклассными функциями. Библиотека без сохранения состояния существует, и этот блог показывает простой пример с кодом. Java реализация (до Java8) обсуждается. Мне также показали пример Python на GitHub.
Ответ 7
Для простых случаев вы можете использовать свой метод стиля переключения. То, что я нашел, что хорошо работает в прошлом, также связано с переходами:
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
}
Я ничего не знаю об ускорительной библиотеке, но этот тип подхода прост, не требует каких-либо внешних зависимостей и его легко реализовать.
Ответ 8
switch() является мощным и стандартным способом реализации состояний машин на C, но он может уменьшить поддерживаемость вниз, если у вас есть большое количество состояний. Другим распространенным методом является использование указателей функций для хранения следующего состояния. Этот простой пример реализует набор / 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;
}
Ответ 9
Я нашел действительно гладкую реализацию C Moore FSM на курсе edx.org. Встраиваемые системы - сформируйте мир UTAustinX - UT.6.02x, глава 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];
}
}
Ответ 10
Эта статья является хорошей для паттерна состояния (хотя это C++, а не конкретно C).
Если вы можете приложить руки к книге "Head First Design Patterns", объяснение и пример очень понятны.
Ответ 11
Возможно, вы захотите изучить программное обеспечение генератора FSM libo. На языке описания состояний и/или (графическом редакторе состояний окна) вы можете генерировать код для C, С++, java и многих других... плюс хорошая документация и диаграммы.
Источник и двоичные файлы из iMatix
Ответ 12
Один из моих любимых шаблонов - шаблон состояния. Отвечайте или ведите себя по-разному с тем же заданным набором входов.
Одна из проблем с использованием операторов switch/case для конечных автоматов заключается в том, что при создании большего количества состояний коммутатор/случаи становятся более трудными/громоздкими для чтения/обслуживания, способствует неорганизованному коду спагетти и все труднее изменить, не нарушая что-либо. Я нахожу, что использование шаблонов проектирования помогает мне лучше упорядочить мои данные, что является целым смыслом абстракции.
Вместо того, чтобы создавать свой код состояния, в каком состоянии вы пришли, вместо этого структурируйте свой код, чтобы он записывал состояние при вводе нового состояния. Таким образом, вы фактически получаете отчет о своем предыдущем состоянии. Мне нравится @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 exit event
pFsm->signal = eExit;
STATE dummyState = (*UsbState)( pFsm, pvEvent);
// Update the State Machine structure
UsbState = targetState ;
// Call the new state 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;
}
Для большинства State Machines, особенно. Конечные государственные машины, каждое государство будет знать, каково должно быть следующее состояние, и критерии перехода к следующему состоянию. Для конструкций свободного состояния это может быть не так, следовательно, возможность раскрывать API для состояний перехода. Если вы хотите больше абстракции, каждый обработчик состояния может быть отделен от собственного файла, который эквивалентен конкретным обработчикам состояний в книге GoF. Если ваш проект прост с несколькими состояниями, то как stateCtxt.c, так и statehandlers.c могут быть объединены в один файл для простоты.
Ответ 13
В моем опыте использование оператора switch является стандартным способом обработки нескольких возможных состояний. Хотя я уверен, что вы передаете значение перехода для обработки в одном состоянии. Я думал, что все точки государственной машины состоят в том, что каждое государство совершает одно действие. Затем следующее действие/ввод определяет, в какое новое состояние перейти. Поэтому я ожидал, что каждая функция обработки состояния немедленно выполнит все, что будет исправлено для ввода состояния, а затем решит, требуется ли переход в другое состояние.
Ответ 14
Есть книга под названием Практические диаграммы состояний в C/C++.
Однако это способ слишком тяжелый для того, что нам нужно.
Ответ 15
Для компилятора, поддерживающего __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__
Ответ 16
В С++ рассмотрим шаблон состояния.
Ответ 17
Ваш вопрос похож на "существует ли типичная модель реализации базы данных"?
Ответ зависит от того, чего вы хотите достичь? Если вы хотите реализовать больший детерминированный конечный автомат, вы можете использовать модель и генератор конечных автоматов.
Примеры можно посмотреть на сайте www.StateSoft.org - SM Gallery. Януш Добровольский
Ответ 18
В C для простых машин это то, что я обычно использую.
Управляемый событиями FSM описывается объектами состояния (FsmState), связанными с действием (FsmAction), и переходами (FsmEdge), определяемыми текущим состоянием и событиями.
Он также обеспечивает хранение и передачу как FSM, так и пользовательских данных для разделения связанной с FSM и связанной с пользователем информации и позволяет нескольким экземплярам одного и того же FSM (т.е. использовать одно и то же описание, но передавать разные пользовательские данные).
События представлены целым типом (FsmEvent). Отрицательные значения зарезервированы для реализации, чтобы разрешить специальные общие события (Reset, None, Any, Empty, End). Неотрицательные события определяются пользователем.
Для простоты переходы перечисляются в массиве, и в порядке массива выполняется попытка сопоставления, в основном обеспечивающая приоритеты перехода. У них есть дополнительные функции защиты. Следующее состояние может быть указано непосредственно в списке переходов или функцией перехода, таким образом обеспечивая большую гибкость, обеспечивающую динамическое поведение FSM.
В описаниях переходов текущее состояние NULL будет соответствовать любому состоянию, а событие подстановки (Any) будет соответствовать любому событию. Во всяком случае, фактическое значение события, вызвавшего переход, будет передано в функции перехода и защиты.
Для сложных FSM решение простой решетки может стать слишком неэффективным. В этом случае правильная функция перехода может быть реализована с использованием массива ребер и записей событий, преобразованных в списки перехода или списки смежности состояний.
Действия государства должны быть реализованы с помощью функции повторного входа, которая различает операции ввода состояния (Enter), state exit (Leave) и in-state (State). Таким образом, локальная информация о состоянии может быть инкапсулирована и сохранена с помощью статических функциональных переменных.
Обычно действия входа и выхода состояния выполняются незаметно и не возвращают никаких новых событий (None). Если это не так, новое событие захватывается и возвращается немедленно. Это эффективно предотвратит переход в случае, если это произойдет при выходе из текущего состояния.
Функция шага FSM (fsmStep) выполнит один шаг FSM, используя новое событие для запуска перехода или событие (None), чтобы выполнить действие in-state текущего состояния. Функция шага возвращает новое испущенное событие, которое может быть обработано или перенаправлено в 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, 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;
}
Ответ 19
Вы можете использовать минималистский фреймворк UML в c. https://github.com/kiishor/UML-State-Machine-in-C
Он поддерживает как конечный, так и иерархический конечный автомат. Он имеет только 3 API, 2 структуры и 1 перечисление.
Конечный автомат представлен структурой state_machine_t
. Это абстрактная структура, которая может быть унаследована для создания конечного автомата.
//! Abstract state machine structure
struct state_machine_t
{
uint32_t Event; //!< Pending Event for state machine
const state_t* State; //!< State of state machine.
};
Состояние представлено указателем на структуру state_t
в структуре.
Если структура настроена для конечного автомата, то state_t
содержит,
typedef struct finite_state_t state_t;
// finite state structure
typedef struct finite_state_t{
state_handler Handler; //!< State handler function (function pointer)
state_handler Entry; //!< Entry action for state (function pointer)
state_handler Exit; //!< Exit action for state (function pointer)
}finite_state_t;
Инфраструктура предоставляет API dispatch_event
для отправки события на конечный автомат и два API для обхода состояния.
state_machine_result_t dispatch_event(state_machine_t* const pState_Machine[], uint32_t quantity);
state_machine_result_t switch_state(state_machine_t* const, const state_t*);
state_machine_result_t traverse_state(state_machine_t* const, const state_t*);
Подробнее о том, как реализовать иерархический конечный автомат, см. в репозитории GitHub.
примеры кода
https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine/readme.md
https://github.com/kiishor/UML-State-Machine-in-C/blob/master/demo/simple_state_machine_enhanced/readme.md
Ответ 20
У Boost есть библиотека statechart. http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html
Я не могу говорить об использовании этого. Не использовал его сам (пока)