Frage

Wir müssen eine einfache Zustandsmaschine implementieren C.
Ist eine Standard-Switch-Anweisung der beste Weg?
Wir haben einen aktuellen Zustand (State) und einen Auslöser für den Übergang.


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

Gibt es einen besseren Weg? für einfache Zustandsmaschinen

BEARBEITEN:Für C++ denke ich den Boost Zustandsdiagramm Eine Bibliothek könnte der richtige Weg sein.Allerdings tut es das nicht Hilfe bei C.Konzentrieren wir uns auf den C-Anwendungsfall.

War es hilfreich?

Lösung

Ich ziehe einen Tisch orientierten Ansatz für die meisten Zustandsmaschinen zu verwenden:

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

Dies kann natürlich erweitert werden, um mehrere Zustandsmaschinen unterstützen usw. Transition Aktionen als auch untergebracht werden können:

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

Die Tabelle orientierten Ansatz ist leichter zu pflegen und zu erweitern und einfacher zu Zustandsdiagramme abzubilden.

Andere Tipps

Sie haben vielleicht meine Antwort auf eine andere Frage C zu sehen, wo ich FSM erwähnt! Hier ist, wie ich es tun:

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

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

Mit den folgenden Makros definiert

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

Dies kann geändert werden, um den speziellen Fall anzupassen. Zum Beispiel können Sie eine Datei FSMFILE haben, dass Sie Ihre FSM fahren wollen, so könnte man die Aktion des Lesens nächste Zeichen in das das Makro selbst übernehmen:

#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

Jetzt haben Sie zwei Arten von Übergängen: man geht in einen Zustand und liest einen neuen Charakter, der andere geht in einen Zustand ohne jegliche Eingabe raubend

.

Sie können auch die Handhabung von EOF mit so etwas wie automatisieren:

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

#define ENDFSM    sx_endfsm:

Die gute Sache dieses Ansatzes ist, dass Sie direkt ein Zustandsdiagramm übersetzen können Sie in Code arbeiten ziehen und umgekehrt, können Sie leicht ein Zustandsdiagramm aus dem Code ziehen.

In anderen Techniken zur Implementierung FSM, die die Struktur der Übergänge wird in Kontrollstrukturen (während, wenn Schalter ...) vergräbt und durch Variablen Wert gesteuert (tipically a state Variable), und es kann eine komplexe Aufgabe sein, die in Beziehung zu schönes Diagramm zu einem gewundenen Code.

lernte ich diese Technik aus einem Artikel auf der großen „Computersprache“ -Magazin erschien, die leider nicht mehr veröffentlicht.

Ich habe auch die Tabelle Ansatz verwendet. Allerdings gibt es Overhead. Warum eine zweite Liste von Zeigern speichern? Eine Funktion in C, ohne die () ist ein Zeiger const. So können Sie tun:

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

Natürlich abhängig von Ihrer Angst-Faktor (das heißt Sicherheit vs Geschwindigkeit) Sie können für gültige Zeiger überprüfen möchten. Für Zustandsmaschinen größer als drei oder so Staaten, sollte der Ansatz über weniger Anweisungen als einen entsprechenden Schalter oder Tabellen Ansatz. Man könnte sogar Makro ize wie:

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

Auch ich fühle mich von dem Beispiel der OP, dass es eine Vereinfachung, die getan werden sollte, wenn man über das Denken / Gestaltung einer Zustandsmaschine. Ich weiß nicht, was der Übergang Staat sollte für die Logik verwendet werden. Jeder Staat Funktion sollte in der Lage sein, seine gegebene Rolle auszuführen, ohne explizite Kenntnis der Vergangenheit Zustandes (s). Grundsätzlich Sie entwerfen, wie aus dem Zustand, den Sie sind in einem anderen Zustand zu überführen.

Schließlich nicht den Aufbau einer Zustandsmaschine starten, basierend auf „funktional“ Grenzen, verwenden Sie Unterfunktionen dafür. Stattdessen teilen die Staaten auf, wenn Sie auf etwas warten muss geschehen, bevor Sie fortfahren können. Dies wird dazu beitragen, die Anzahl der Male zu minimieren Sie haben die Zustandsmaschine laufen, bevor Sie ein Ergebnis bekommen. Dies kann wichtig sein, wenn I / O-Funktionen zu schreiben, oder Handler unterbrechen.

Auch ein paar Vor-und Nachteile der klassischen Switch-Anweisung:

Vorteile:

  • ist es in der Sprache, so wird dokumentiert und klar
  • Zustände definiert, wo sie genannt werden
  • können mehrere Zustände in einem Funktionsaufruf ausführen
  • Code, die für alle Staaten kann vor und nach der switch-Anweisung ausgeführt werden

Nachteile:

  • können mehrere Zustände in einem Funktionsaufruf ausführen
  • Code, die für alle Staaten kann vor und nach der switch-Anweisung ausgeführt werden
  • Schalter Implementierung kann langsam sein

Beachten Sie die beiden Attribute, die sowohl pro und contra sind. Ich denke, dass der Schalter die Möglichkeit, zu viel Austausch zwischen den Staaten erlaubt, und die Interdependenz zwischen den Staaten können nicht mehr meistern. Doch für eine kleine Anzahl von Staaten, kann es die meisten lesbar und wartbar sein.

Für eine einfache Zustandsmaschine verwendet nur eine switch-Anweisung und einen Aufzählungstyp für Ihren Zustand. Hat Ihre Übergänge innerhalb der Switch-Anweisung auf der Grundlage Ihrer Eingabe. In einem echten Programm würden Sie offensichtlich den „if (Eingang)“ ändern, um Ihre Übergangspunkte zu überprüfen. Hoffe, das hilft.

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

gibt es auch die Logik Gitter, das ist mehr wartbar wie die Zustandsmaschine wird größer

Martin Fowler UML Destilliertes , sagt er (kein Wortspiel beabsichtigt) in Kapitel 10 State Machine Diagramme (Hervorhebung von mir):

  

Ein Zustandsdiagramm kann auf drei Arten durchgeführt werden: verschachtelte Schalter , Staat Muster , und    Zustandstabellen .

Lassen Sie uns ein vereinfachtes Beispiel für die Zustände eines Mobiltelefons Anzeige verwenden:

 image description hier

Verschachtelte Schalter

Fowler ein Beispiel für C # -Code gab, aber ich habe es mein Beispiel angepasst.

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

Zustandsmuster

Hier ist eine Implementierung von meinem Beispiel mit den GoF Staat Mustern:

 image description hier

Statustabellen

Inspiriert von Fowler, hier ist ein Tisch für mein Beispiel:

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

Vergleich

Verschachtelte Schalter hält die gesamte Logik in einem Punkt, aber der Code kann schwer zu lesen, wenn es viele Zustände und Übergänge sind. Es ist möglicherweise sicherer und einfacher zu validieren als die anderen Ansätze (kein Polymorphismus oder Interpretation).

Der Staat Muster Implementierung breitet sich möglicherweise die Logik über mehrere getrennte Klassen, die es als Ganzes ein Problem machen kann verstehen. Auf der anderen Seite sind die kleinen Klassen leicht getrennt zu verstehen. Das Design ist besonders empfindlich, wenn Sie das Verhalten ändern, indem Sie Übergänge hinzufügen oder entfernen, da sie Methoden in der Hierarchie sind und es könnte viele Änderungen an den Code sein. Wenn Sie durch das Konstruktionsprinzip der kleinen Schnittstellen leben, werden Sie dieses Muster sehen wirklich so gut funktioniert nicht. wenn die Zustandsmaschine stabil ist jedoch, dann solche Änderungen werden nicht benötigt.

Die Zustandstabellen Ansatz erfordert für den Inhalt eine Art Dolmetscher zu schreiben (dies könnte einfacher sein, wenn Sie Reflexion in der Sprache haben Sie verwenden), was eine Menge Arbeit sein könnte vorne zu tun. Wie Fowler weist darauf hin, wenn die Tabelle aus dem Code getrennt ist, können Sie das Verhalten Ihrer Software ohne erneute Kompilierung ändern. Dies hat einige Auswirkungen auf die Sicherheit, jedoch; die Software benimmt sich auf den Inhalt einer externen Datei basiert.

Edit (nicht wirklich für C-Sprache)

Es gibt eine fließend-Schnittstelle (auch bekannt als interne Domain Specific Language) Ansatz auch, die wahrscheinlich von Sprachen erleichtert wird, die First-Class-Funktionen . Die Stateless Bibliothek vorhanden ist und dass Blog zeigt ein einfaches Beispiel mit Code. Ein Java-Implementierung (pre Java8) diskutiert . Ich war auch ein Python Beispiel auf GitHub gezeigt.

Für einfache Fälle, Sie können Sie Ihre Switch-Stil-Methode. Was habe ich festgestellt, dass in der Vergangenheit gut funktioniert, ist, mit Übergängen zu beschäftigen auch:

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
}

Ich weiß nichts über die Boost-Bibliothek, aber diese Art von Ansatz ist tot einfach, erfordert keine externen Abhängigkeiten, und ist einfach zu implementieren.

Schalter () ist ein leistungsfähiges und Standardweg Zustandsmaschinen in C umzusetzen, aber es kann Wartbarkeit absinken, wenn Sie eine große Anzahl von Staaten haben. Eine andere gebräuchliche Methode ist Funktionszeiger zu verwenden, um den nächsten Zustand zu speichern. Dieses einfache Beispiel implementiert ein Set / Reset-Flip-Flop:

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

ich eine wirklich glatte C Implementierung von Moore FSM auf den edx.org Kurs Embedded Systems gefunden - Gestalten Sie die Welt UTAustinX - UT.6.02x, Kapitel 10, von Jonathan Valvano und 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];  
  }
}

Sie können in den libero aussehen wollen FSM-Generator-Software. Von einem Zustand Beschreibungssprache und / oder ein (Fenster) Zustandsdiagramm-Editor Sie Code für C, C ++, Java und viele andere ... plus schöne Dokumentation und Diagramme erzeugen. Quelle und Binärdateien von iMatix

Dieser Artikel ein guter für den Staat Muster ist (obwohl es C ++ ist, nicht speziell C).

Wenn Sie Ihre Hände auf dem Buch setzen können „ Head First Design Patterns “, ist die Erklärung und Beispiel sehr klar.

Eines meiner Lieblingsmuster ist das State-Design-Muster.Reagieren oder verhalten Sie sich unterschiedlich auf denselben vorgegebenen Satz von Eingaben.
Eines der Probleme bei der Verwendung von Switch/Case-Anweisungen für Zustandsmaschinen besteht darin, dass, je mehr Zustände Sie erstellen, die Switch/Cases schwieriger/umständlicher zu lesen/aufrechtzuerhalten sind, unorganisierten Spaghetti-Code fördern und es immer schwieriger wird, sie zu ändern, ohne etwas kaputt zu machen.Ich finde, dass die Verwendung von Entwurfsmustern mir hilft, meine Daten besser zu organisieren, und das ist der Sinn der Abstraktion.Anstatt Ihren Bundesstaatscode nach dem Bundesstaat zu gestalten, aus dem Sie stammen, strukturieren Sie Ihren Code stattdessen so, dass er den Bundesstaat aufzeichnet, wenn Sie in einen neuen Bundesstaat eintreten.Auf diese Weise erhalten Sie effektiv eine Aufzeichnung Ihres vorherigen Zustands.Mir gefällt die Antwort von @JoshPetit und ich habe seine Lösung noch einen Schritt weitergeführt, direkt aus dem GoF-Buch übernommen:

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

Für die meisten Zustandsmaschinen, insb.Bei Finite-State-Maschinen weiß jeder Zustand, was sein nächster Zustand sein sollte und welche Kriterien für den Übergang in seinen nächsten Zustand gelten.Bei losen Zustandsdesigns ist dies möglicherweise nicht der Fall, daher besteht die Option, die API für Übergangszustände verfügbar zu machen.Wenn Sie mehr Abstraktion wünschen, kann jeder Statushandler in eine eigene Datei aufgeteilt werden, die den konkreten Statushandlern im GoF-Buch entspricht.Wenn Ihr Entwurf einfach ist und nur wenige Zustände aufweist, können der Einfachheit halber sowohl stateCtxt.c als auch statehandlers.c in einer einzigen Datei kombiniert werden.

Nach meiner Erfahrung der ‚Schalter‘ Anweisung ist der normale Weg, mehr möglichen Zustände zu behandeln. Obwohl ich Surpirsed bin, dass Sie in einem Übergangswert auf die pro-Zustand Verarbeitung übergeben. Ich dachte, der ganze Sinn einer Zustandsmaschine war, dass jeder Staat eine einzige Aktion ausgeführt hat. Dann wird die nächste Aktion / Eingabe bestimmt, welche neuen Zustand in den Übergang. So würde ich jede Zustand Verarbeitungsfunktion zu erwarten, sofort durchführt, was für die Eingabe Zustand fixiert wird und dann später entscheiden, ob Übergang in einem anderen Zustand benötigt wird.

Es gibt ein Buch mit dem Titel Praktische Statechart in C / C ++ . Es ist jedoch Weg zu Schwergewicht für das, was wir brauchen.

Für die Compiler __COUNTER__ unterstützen, können Sie sie für eine einfache Verwendung (aber groß) Zustand mashines.

  #define START 0      
  #define END 1000

  int run = 1;
  state = START;    
  while(run)
  {
    switch (state)
    {
        case __COUNTER__:
            //do something
            state++;
            break;
        case __COUNTER__:
            //do something
            if (input)
               state = END;
            else
               state++;
            break;
            .
            .
            .
        case __COUNTER__:
            //do something
            if (input)
               state = START;
            else
               state++;
            break;
        case __COUNTER__:
            //do something
            state++;
            break;
        case END:
            //do something
            run = 0;
            state = START;
            break;
        default:
            state++;
            break;
     } 
  } 

Der Vorteil der Verwendung __COUNTER__ statt hart codierten Zahlen ist, dass Sie können die Staaten in der Mitte anderer Staaten hinzuzufügen, ohne jedes Mal alles Umnummerierung. Wenn der Compiler tut Unterstützung __COUNTER__, in begrenztem Umfang seine posible mit Vorsicht __LINE__ verwenden

In C ++, betrachtet die Staat Muster rel="nofollow.

Ihre Frage ist ähnlich wie „ist es ein typisches Data Base Implementierung Muster“? Die Antwort hängt davon ab, was wollen Sie erreichen? Wenn Sie eine größere determinisZustandsMaschine implementieren wollen, können Sie ein Modell und eine Zustandsmaschine Generator verwenden. SM Gallery - Beispiele können www.StateSoft.org betrachtet werden. Janusz Dobrowolski

In C, für einfache Maschinen so etwas wie das ist, was ich in der Regel am Ende mit.

Die ereignisgesteuerte FSM wird von Zustandsobjekte beschrieben (FsmState) mit einer Aktion (FsmAction) zugeordnet ist, und Übergänge (FsmEdge) von aktuellen Status und Ereignisse definiert sind.

Sie stellt auch Speicherung und sowohl FSM und Benutzerdaten Geben, FSM-gebundene und benutzer gebundene Informationen zu trennen, und mehrere Instanzen des gleichen FSM (d.h. unter Verwendung der gleiche Beschreibung aber unterschiedliche Benutzerdatenweitergabe) zu ermöglichen.

Die Ereignisse werden durch einen Integer-Typ (FsmEvent) dargestellt. Negative Werte werden durch die Implementierung reserviert besondere gemeinsame Veranstaltungen zu ermöglichen (Reset, Keine, Alle, Leer, End). Nicht-negative Ereignisse werden Benutzer definiert werden.

Zur Vereinfachung sind Übergänge enthalten ist in einem Array und Anpassen in dem Array um versucht wird, im Wesentlichen Übergang Prioritäten bereitstellt. Sie haben optional Wache Funktionen. Der nächste Zustand kann entweder direkt in der Übergangsliste oder durch eine Sprungfunktion angezeigt werden, auf diese Weise eine größere Flexibilität ermöglicht dynamische Verhalten FSM bereitgestellt wird.

Übergänge Beschreibungen Ein NULL aktueller Zustand wird jeden Zustand entsprechen und ein Wildcard Ereignis (alle) wird jedes Ereignis entsprechen. Wie auch immer, der tatsächliche Wert des Ereignisses, das den Übergang ausgelöst wird die Sprung und die Schutzfunktionen übergeben werden.

Für komplexe FSMs, die einfache Kantenfeld Lösung kann zu ineffizient werden. In diesem Fall könnte eine geeignete Sprungfunktion des Kantenfeld und Ereigniseinträge in eine List bergangsmatrix oder Zustandes adjacency umgewandelt implementiert werden.

Staatliche Maßnahmen sollen mit einer Reentry-Funktion implementiert werden, die zwischen Statuseintrag unterscheidet (Enter), Zustand Ausfahrt (Leave) und in-Zustand (State) Operationen. Auf diese Weise können lokale Zustandsinformationen gekapselt und mit statischer Funktion Variablen erhalten bleiben.

Normalerweise Zustand Eintritts- und Austrittsaktionen ausführen wird unremarkably und zurück keine neuen Ereignisse (Keine). Wenn nicht, wird das neue Ereignis gefangen und kehrte sofort. Dies wird wirksam einen Übergang in Fall verhindern, dass es passiert, wenn der aktuelle Zustand verlassen wird.

Die FSM Stufenfunktion (fsmStep) einen einzigen Schritt des FSM durchzuführen, ein neues Ereignis mit einem Übergang zu triggern, oder kein Ereignis (None), um die in-state Wirkung des aktuellen Zustands auszuführen. Die Stufenfunktion gibt ein neues Ereignis ausgesendet, die gehandhabt werden kann, oder an die FSM wieder zugeführt; oder None, Leer und endet im Fall von keinem Fall, Übergang nicht gefunden oder Endzustand erreicht ist.

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

Im Folgenden gibt es einen sehr einfachen Test ist die Idee auf, wie zu definieren und verwenden, um die FSM Implementierung zu erhalten. Es gibt keine benutzerdefinierten Ereignisse, nur FSM Daten (Strings), die gleiche Zustand Aktion für jeden Staat und eine gemeinsame Sprungfunktion:

#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 hat die Zustands Bibliothek. http://www.boost.org/doc/ libs / 1_36_0 / libs / Statechart / doc / index.html

Ich kann nicht auf die Verwendung davon sprechen, though. Es selbst nicht verwendet wird (noch)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top