Czy istnieje typowy wzorzec implementacji maszyny stanowej?

Musimy zaimplementować prostą maszynę stanową w C .
Czy standardowa Instrukcja przełączania jest najlepszym rozwiązaniem?
Mamy stan bieżący (stan) i wyzwalacz przejścia.


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;
}

Czy jest lepszy sposób dla prostych maszyn stanowych

Edytuj: Dla C++ myślę, że Biblioteka Boost Statechart może być dobrym rozwiązaniem. Jednak nie pomaga z C. skupmy się na przypadku użycia C.

Author: Emile Cormier, 2008-09-25

19 answers

Preferuję podejście oparte na tabeli dla większości maszyn stanowych:

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
    }
}

Można to oczywiście rozszerzyć o obsługę wielu maszyn stanowych itp. Można również uwzględnić działania przejściowe:

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;
};

Podejście oparte na tabeli jest łatwiejsze do utrzymania i rozszerzenia oraz prostsze do mapowania do diagramów stanu.

 116
Author: Frank Szczerba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-16 00:52:39

Mogliście zobaczyć moją odpowiedź na inne pytanie C, gdzie wspomniałem o FSM! Oto jak to robię:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

Z następującymi makrami zdefiniowanymi

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

Można to zmodyfikować w zależności od konkretnego przypadku. Na przykład możesz mieć plik FSMFILE, którym chcesz sterować FSM, więc możesz włączyć akcję czytania następnego znaku do samego makra:

#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

Teraz masz dwa rodzaje przejść: jeden przechodzi do stanu i czyta nową postać, drugi przechodzi do stan bez zużywania jakichkolwiek danych wejściowych.

Możesz również zautomatyzować obsługę EOF za pomocą czegoś takiego:

#define STATE(x)  s_##x  : if ((FSMCHR = fgetc(FSMFILE) == EOF)\
                             goto sx_endfsm;\
                  sn_##x :

#define ENDFSM    sx_endfsm:

Dobrą rzeczą tego podejścia jest to, że możesz bezpośrednio przetłumaczyć narysowany diagram stanu na kod roboczy i odwrotnie, możesz łatwo narysować diagram stanu z kodu.

W innych technikach implementacji FSM struktura przejść jest zakopana w strukturach sterowania (while, if, switch ...) i sterowane wartościami zmiennych (tipically a state zmienna) i może być skomplikowanym zadaniem powiązanie diagramu nice ' a z zawiłym kodem.

Nauczyłem się tej techniki z artykułu, który ukazał się w wielkim magazynie "Computer Language", który niestety nie jest już publikowany.

 25
Author: Remo.D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 14:07:17

Zastosowałem również podejście tabelaryczne. Jednak jest nad głową. Po co przechowywać drugą listę wskaźników? Funkcja W C bez funkcji () jest wskaźnikiem const. Więc możesz zrobić:

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
    }
}

Oczywiście w zależności od czynnika strachu (tj. bezpieczeństwo vs szybkość) możesz sprawdzić poprawne wskaźniki. Dla maszyn stanowych większych niż trzy lub więcej stanów, powyższe podejście powinno być mniej instrukcji niż równoważne podejście przełącznika lub tabeli. Można nawet makro-ize as:

#define RUN_STATE(state_ptr_) ((state_ptr_)->function(state_ptr_))

Również, na przykładzie OP, czuję, że istnieje uproszczenie, które należy zrobić, myśląc o / projektowaniu maszyny stanowej. Nie sądzę, że stan przejścia powinien być używany do logiki. Każda funkcja stanu powinna być w stanie wykonywać swoją określoną rolę bez wyraźnej wiedzy o przeszłych Stanach. Zasadniczo projektujesz, jak przejść ze stanu, w którym się znajdujesz, do innego stanu.

Wreszcie, nie zaczynaj projektowania maszyny stanowej opartej na granice "funkcjonalne", użyj do tego podfunkcji. Zamiast tego podziel stany na podstawie tego, kiedy będziesz musiał poczekać, aż coś się wydarzy, zanim będziesz mógł kontynuować. Pomoże to zminimalizować liczbę razy, które trzeba uruchomić maszynę stanową przed uzyskaniem wyniku. Może to być ważne podczas pisania funkcji We / Wy lub obsługi przerwań.

Również kilka zalet i wad klasycznej instrukcji switch:

Plusy:

  • jest w języku, więc jest udokumentowane i Wyczyść
  • stany są zdefiniowane gdzie są nazywane
  • może wykonywać wiele stanów w jednym wywołaniu funkcji
  • kod wspólny dla wszystkich stanów może być wykonany przed i po instrukcji switch

Wady:

  • może wykonywać wiele stanów w jednym wywołaniu funkcji
  • kod wspólny dla wszystkich stanów może być wykonany przed i po instrukcji switch
  • implementacja przełączników może być powolna

Zwróć uwagę na dwa atrybuty, które są za i przeciw. myślę, że zmiana daje możliwość zbyt dużego podziału między państwami, a współzależność między państwami może stać się nie do opanowania. Jednak dla niewielkiej liczby stanów może być najbardziej czytelny i możliwy do utrzymania.

 12
Author: Josh Petitt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-14 09:00:02

Istnieje również siatka logiczna , która jest łatwiejsza do utrzymania, gdy maszyna stanowa staje się większa

 8
Author: geocoin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 13:24:00

Dla prostej maszyny stanowej wystarczy użyć instrukcji switch i typu enum dla swojego stanu. Wykonuj przejścia wewnątrz instrukcji switch na podstawie danych wejściowych. W prawdziwym programie można oczywiście zmienić" if(input)", aby sprawdzić swoje punkty przejściowe. Mam nadzieję, że to pomoże.

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;
    }
    ...
}
 8
Author: jsl4980,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 19:36:45

Switch() jest potężnym i standardowym sposobem implementacji maszyn stanowych w C, ale może zmniejszyć konserwowalność, jeśli masz dużą liczbę stanów. Inną popularną metodą jest używanie wskaźników funkcji do przechowywania następnego stanu. W tym prostym przykładzie zaimplementowano przełącznik 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;
}
 4
Author: Commodore Jaeger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 20:58:42

W prostych przypadkach możesz użyć metody Switch style. To, co odkryłem, że działa dobrze w przeszłości, to również radzenie sobie z przejściami: {]}

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
}

Nie wiem nic o bibliotece boost, ale tego typu podejście jest ŚMIERTELNIE PROSTE, nie wymaga żadnych zewnętrznych zależności i jest łatwe do wdrożenia.

 3
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 13:25:30

Znalazłem naprawdę zgrabną implementację C Moore FSM na edx.org kurs Systemy wbudowane-kształtuj świat UTAustinX-UT.6.02 x, Rozdział 10, Jonathan Valvano i Ramesh Yerraballi....

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];  
  }
}
 3
Author: user153222,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-29 02:10:10

W Martin Fowler ' s UML Distilled , stwierdza (Nie zamierzony kalambur) w rozdziale 10 diagramów maszyn stanowych (podkreślenie):

Diagram stanu może być zaimplementowany na trzy główne sposoby: nested switch , State pattern oraz tabele stanu .

Użyjmy uproszczonego przykładu Stanów wyświetlacza telefonu komórkowego:

Tutaj wpisz opis obrazka

Zagnieżdżony przełącznik

Fowler podał przykład kodu C#, ale mam dostosowałem to do mojego przykładu.

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;
    }
}

Wzorzec stanu

Oto implementacja mojego przykładu z wzorcem stanu GoF:

Tutaj wpisz opis obrazka

Tabele Stanu

Czerpiąc inspirację z Fowlera, oto tabela dla mojego przykładu:

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

Porównanie

Zagnieżdżony przełącznik utrzymuje całą logikę w jednym miejscu, ale kod może być trudny do odczytania, gdy istnieje wiele stanów i przejść. Jest prawdopodobnie bezpieczniejszy i łatwiejszy do zweryfikowania niż inne podejścia (brak polimorfizmu i interpretacji).

Implementacja wzorca stanu potencjalnie rozprzestrzenia logikę na kilka oddzielnych klas, co może sprawić, że zrozumienie jej jako całości stanie się problemem. Z drugiej strony, małe klasy są łatwe do zrozumienia oddzielnie. Projekt jest szczególnie delikatny, jeśli zmienisz zachowanie poprzez dodawanie lub usuwanie przejść, ponieważ są to metody w hierarchii i może być wiele zmian w kodzie. Jeśli żyjesz według projektu zasada małych interfejsów, zobaczysz, że ten wzór nie robi tak dobrze. Jeśli jednak maszyna stanowa jest stabilna, to takie zmiany nie będą potrzebne.

Podejście do tabel stanu wymaga napisania jakiegoś interpretera dla treści (może to być łatwiejsze, jeśli masz odbicie w języku, którego używasz), co może być dużo pracy do zrobienia z góry. Jak zauważa Fowler, jeśli tabela jest oddzielona od kodu, można zmodyfikować zachowanie oprogramowania bez rekompilacja. Ma to jednak pewne konsekwencje dla bezpieczeństwa; oprogramowanie zachowuje się w oparciu o zawartość zewnętrznego pliku.

Edit (nie do końca dla języka C)

Istnieje również podejście fluent interface (aka internal Domain Specific Language), które jest prawdopodobnie ułatwione przez języki, które mająfunkcje pierwszej klasy . Istnieje biblioteka Stateless i ten blog pokazuje prosty przykład z kodem. A implementacja Java (pre Java8) jest omówione. Pokazano mi również przykład Pythona na Githubie .

 3
Author: Fuhrmanator,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-06 20:32:22

Warto zajrzeć do oprogramowania libero FSM generator. Z języka opisu stanu i / lub edytora diagramów stanu (windows) można wygenerować kod dla języków C, C++, java i wielu innych ... Plus Ładna dokumentacja i schematy. Źródło i binaria z iMatix

 2
Author: pklausner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-26 14:07:19

Ten artykuł jest dobry dla wzorca stanu (choć jest to C++, a nie konkretnie C).

Jeśli możesz położyć ręce na książce "Head First Design Patterns", Wyjaśnienie I przykład są bardzo jasne.

 2
Author: pmlarocque,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-10 06:47:24

Jednym z moich ulubionych wzorców jest Stanowy wzorzec projektowy. Reagują lub zachowują się inaczej na ten sam zestaw danych wejściowych.
Jednym z problemów z używaniem poleceń switch/case dla maszyn stanowych jest to, że w miarę tworzenia kolejnych stanów przełącznik/cases staje się trudniejszy/nieporęczny do odczytu/utrzymania, Promuje niezorganizowany kod spaghetti i coraz trudniejszy do zmiany bez łamania czegoś. Uważam, że użycie wzorców projektowych pomaga mi lepiej zorganizować moje dane, o co chodzi abstrakcja. Zamiast projektować kod stanu wokół tego, z jakiego stanu pochodzisz, zamiast tego ustrukturyzuj kod tak, aby zapisywał stan po wprowadzeniu nowego stanu. W ten sposób skutecznie uzyskasz zapis swojego poprzedniego stanu. Podoba mi się odpowiedź @ JoshPetit, a jego rozwiązanie posunąłem o krok dalej, zaczerpnięte prosto z książki 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;
}

Dla większości państw Maszyny, esp. Skończone maszyny stanowe, każdy stan będzie wiedział, jaki powinien być jego następny stan i kryteria przejścia do następnego stanu. W przypadku projektów Stanów luźnych może tak nie być, stąd opcja ujawnienia API dla stanów przejściowych. Jeśli chcesz uzyskać więcej abstrakcji, każdy moduł obsługi stanu może zostać oddzielony do własnego pliku, który jest odpowiednikiem konkretnych programów obsługi stanu w książce GoF. Jeśli twój projekt jest prosty i zawiera tylko kilka Stanów, to oba stateCtxt.c i statehandlers.c może być łączony w jeden plik dla uproszczenia.

 2
Author: Phileo99,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-19 19:05:55

Z mojego doświadczenia wynika, że używanie instrukcji 'switch' jest standardowym sposobem obsługi wielu możliwych stanów. Chociaż jestem zaskoczony, że przekazujesz wartość przejściową do przetwarzania per-state. Myślałem, że cała maszyna stanowa polega na tym, że każdy stan wykonuje jedną akcję. Następnie następna akcja / wejście określa, w który Nowy stan przejść. Więc oczekiwałbym, że każda funkcja przetwarzania stanu natychmiast wykona to, co jest ustalone dla wprowadzenia stanu, a następnie następnie zdecyduj, czy przejście jest potrzebne do innego państwa.

 1
Author: Phil Wright,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 13:13:05

Jest książka zatytułowana praktyczne podstawy języka C / C++. Jednak jest tosposób zbyt ciężki na to, czego potrzebujemy.

 1
Author: Benoit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 13:34:21

Dla kompilatorów obsługujących __COUNTER__, można ich używać do prostych (ale dużych) maszyn stanowych.

  #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;
     } 
  } 

Zaletą używania __COUNTER__ zamiast mocno zakodowanych numerów jest to, że może dodawać stany w środku innych stanów, bez zmiany numeracji za każdym razem. Jeśli kompilator nie obsługuje __COUNTER__, w ograniczony sposób można go używać z zachowaniem ostrożności __LINE__

 1
Author: Seb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-20 09:16:16

W C++ rozważ wzorzec stanu.

 0
Author: Bruno De Fraine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 13:23:27

Twoje pytanie jest podobne do "czy istnieje typowy wzór implementacji bazy danych"? Odpowiedź zależy od tego, co chcesz osiągnąć? Jeśli chcesz zaimplementować większą deterministyczną maszynę stanową, możesz użyć modelu i generatora maszyn stanowych. Przykłady można obejrzeć na www.StateSoft.org -Galeria SM. Janusz Dobrowolski

 0
Author: Janusz Dobrowolski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-10 19:36:23

W C, w przypadku prostych maszyn, zwykle używam czegoś takiego.

FSM sterowany zdarzeniami jest opisywany przez obiekty stanu (fsmstate) powiązane z akcją (FsmAction), a przejścia (fsmedge) zdefiniowane przez bieżący stan i zdarzenia.

Zapewnia również przechowywanie i przekazywanie zarówno danych FSM, jak i danych użytkownika, aby oddzielić informacje związane z FSM i związane z użytkownikiem oraz umożliwić wiele wystąpień tego samego FSM (tj. używanie tego samego opisu, ale przekazywanie różnych użytkowników danych).

Zdarzenia są reprezentowane przez typ integer (FsmEvent). Wartości ujemne są zarezerwowane przez implementację, aby umożliwić specjalne wspólne zdarzenia (Reset, None, Any, Empty, End). Zdarzenia nieujemne są definiowane przez użytkownika.

Dla uproszczenia, przejścia są wymienione w tablicy, a dopasowanie jest próbowane w porządku tablicy, zasadniczo zapewniając priorytety przejścia. Posiadają opcjonalne funkcje ochronne. Następny stan może być wskazywany bezpośrednio na liście przejść lub przez skok funkcji, zapewniając w ten sposób większą elastyczność umożliwiającą dynamiczne zachowanie FSM.

W opisach przejść, bieżący stan NULL będzie pasował do dowolnego stanu, a Zdarzenie wieloznaczne (dowolne) będzie pasował do każdego zdarzenia. W każdym razie rzeczywista wartość zdarzenia, które wywołało przejście, zostanie przekazana do funkcji skoku i straży.

W przypadku złożonych systemów FSMs rozwiązanie simple edge array może stać się zbyt nieefektywne. W takim przypadku można zaimplementować odpowiednią funkcję skoku przy użyciu tablicy krawędzi i wpisy zdarzeń przekształcone w macierz przejściową lub listy stanu.

Działania Stanowe mają być zaimplementowane za pomocą funkcji reentrant, która rozróżnia operacje wejścia stanu( Enter), wyjścia stanu (Leave) i in-state (State). W ten sposób informacje o stanie lokalnym mogą być hermetyzowane i przechowywane za pomocą statycznych zmiennych funkcji.

Normalnie, działania wejścia i wyjścia stanu będą wykonywane bez zmian i nie zwrócą żadnych nowych zdarzeń (None). Jeśli nie, nowe wydarzenie jest uwięziony i natychmiast powrócił. To skutecznie zapobiegnie przejściu w przypadku, gdy nastąpi to po wyjściu ze stanu bieżącego.

Funkcja FSM step (fsmStep) wykona pojedynczy krok FSM, używając nowego zdarzenia do wywołania przejścia lub żadnego zdarzenia (None) do wykonania akcji stanu bieżącego. Funkcja step zwraca nowe wyemitowane zdarzenie, które można obsłużyć lub ponownie przesłać do FSM; lub None, Empty I End w przypadku braku zdarzenia, nie znaleziono przejścia lub stanu końcowego / align = "left" /

#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_

Poniżej znajduje się bardzo prosty test, aby dowiedzieć się, jak zdefiniować i wykorzystać implementację FSM. Nie ma zdarzeń zdefiniowanych przez użytkownika, tylko dane FSM (ciągi), ta sama akcja stanu dla każdego stanu i wspólna funkcja skoku:

#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;
}
 0
Author: pbk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-17 11:19:00

Boost ma bibliotekę statechart. http://www.boost.org/doc/libs/1_36_0/libs/statechart/doc/index.html

Nie mogę się jednak wypowiedzieć na jego temat. Not used it myself (yet)

 -1
Author: jdt141,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-25 13:11:48