Wie kann eine untergeordnete Zustandsmaschine die Kontrolle an die übergeordnete Zustandsmaschine zurückgeben?

9

Meine Zustandsmaschine der obersten Ebene hat einige Zustände und Kanten. Ich werde dies die übergeordnete Zustandsmaschine nennen.

A ----> B ----> C

Jeder Zustand innerhalb der übergeordneten Zustandsmaschine kann auch eine Zustandsmaschine sein. Ich werde diese Kinder als Staatsmaschinen bezeichnen.

           ___________
         /            \
A ----> |  B0->B1->B2  | ----> C
         \____________/

Wenn die übergeordnete Zustandsmaschine von A nach B übergeht, übernimmt die Zustandsmaschine von B. Wie sollte B nach dem Ausführen die Kontrolle an die übergeordnete Zustandsmaschine abgeben und in den Zustand C übergehen? Welches Designmuster verwenden Sie?

Wenn Sie sich fragen, habe ich untergeordnete Zustandsautomaten in übergeordneten Zustandsautomaten, da mein genaues Projekt ziemlich komplex ist und es natürlich ist, die internen Abläufe eines untergeordneten Zustands zu kapseln.

JoJo
quelle
Ich würde vermuten, dass B0, B1 und B2 wissen sollten, dass sie Bestandteile von etwas sind, das die Außenwelt als eine Einheit betrachtet. Vielleicht müssten Sie eine MachineContainerKlasse haben B, die B0, B1 und B2 enthält, und wenn B2 endet, gibt sie die Kontrolle an ihren Container zurück, der dann zu C übergeht ... Ich habe so etwas jedoch noch nie versucht. Es ist ein interessantes Problem!
FrustratedWithFormsDesigner
2
Ihre Frage hat entweder die offensichtliche Antwort oder Ihre Frage ist nicht sehr klar. Aus Sicht des übergeordneten Elements sollten Sie es genau so implementieren, wie Sie eine Zustandsmaschine implementieren würden, die keine untergeordneten Zustandsmaschinen hat. Es kommt einfach so vor, dass die Zustände mithilfe von untergeordneten Zustandsautomaten implementiert werden, dies wirkt sich jedoch überhaupt nicht auf die übergeordneten Zustände aus. Es sollte auch keine Auswirkungen auf die untergeordneten Statusmaschinen haben, außer dass sie beim Beenden nur die Ereignisse auf übergeordneter Ebene generieren.
Dunk

Antworten:

5

Jede Zustandsmaschine verfügt über eine Art Ereignishandler und ein Mittel zum Auslösen dieser Ereignisse. Dieser Handler nimmt den vorhandenen Status und die Art des Ereignisses als Eingabe, wählt den neuen Status aus und führt optional einen Nebenwirkungscode aus.

Im Wesentlichen Bleitet Ihr Hauptereignishandler im Status alle Ereignisse, die er nicht erkennt, an Bden Ereignishandler weiter und bleibt im Status B. Wenn Sie Bzu wechseln Cmöchten, wird das entsprechende Ereignis an den Hauptereignishandler gesendet.

Karl Bielefeldt
quelle
2

Hast du diesen Abschnitt von Taoup gelesen ? Es gibt verschiedene Möglichkeiten, dies zu erreichen, aber viele davon hängen davon ab, wie Sie Ihre Zustandsautomaten aufgeteilt haben. Sind sie getrennte Prozesse? Themen? Objekte?

Finden Sie heraus, wie Sie sie gebaut haben, und prüfen Sie, ob es eine kanonische Art der Kommunikation gibt. Wenn eines nicht vorhanden ist, entwerfen Sie Ihr System möglicherweise falsch.

Für mich würde ich getrennte Prozesse betrachten und stdin und stdout miteinander verbinden. Die untergeordnete Zustandsmaschine wird dann eigenständig, wirkt auf stdin und gibt auf stdout aus. Es wird zur Aufgabe der übergeordneten Zustandsmaschine, den untergeordneten Prozess zu starten, die Pipes anzuschließen, dann Daten einzugeben und auf Ergebnisse zu warten. All diese Dinge wurden bereits in allen modernen Sprachen erledigt, daher sollte es einfach sein, dies zu tun.

Spencer Rathbun
quelle
1

Trennen Sie die beiden Zustandsautomaten und verwenden Sie die Nachrichtenübertragung zwischen ihnen. Somit würde die Zustandsmaschine 1 von ABC ausgehen, wo sie im Zustand B nach den aktuellen Ergebnissen von der Zustandsmaschine 2 sucht. Wenn sich die Ausgabe geändert hat, kann die Zustandsmaschine 1 dies berücksichtigen und die Zustandsmaschine 2 muss keine Kenntnis haben wie Zustandsmaschine 1 tatsächlich funktioniert. So etwas wie:

typedef struct StateMachine {
  void(*Update)(); // function to update the state machine
  int Data;        // generic temp holder to survive state contexts
  int State;       // current state of our state machine
  int *Message;    // pointer to a shared integer for message passing
};

int main(void) {
  int Message = 0;
  /* NewStateMachine would malloc the struct, pass in the int reference
   * and function pointer as well as add it to a circularly linked list */
  NewStateMachine(&Message, MainLoop);
  NewStateMachine(&Message, MinorLoop);
  StateMachine *Current = StateMachine_CLL.First;

  for(;;) {
    Current->Update(Current); /* Update the current state machine */
    Current = Current->Next;  /* And the advance to the next one */
  }
}

void MainLoop(StateMachine *this) {
  switch(this.State) {
  case 0:
    CloseCoolantTank(1); /* safe to call if valve already closed */
    CloseCoolantTank(2); /* safe to call if valve already closed */
    this.State = 1;
    break;
  case 1:
    /* we have a message, do something */
    if(*this.Message) this.State = 2;          
    /* otherwise stall at this state until we get a message */
    else this.State = 1;          
    break;
  case 2:
    if(*this.Message == 1) this.State = 3;      /* warm */
    else if(*this.Message == 2) this.State = 4; /* hot! */
    else this.State = 0;                        /* cooled down, shut off valves */
    this.Message = 0;                           /* clear the message */
    break;
  case 3:
    OpenCoolantTank(1); /* opens the valve, safe to call if already open */
    this.State = 2;     /* recheck for new message */
    break;
  case 4:
    OpenCoolantTank(2); /* opens the valve, safe to call if already open */
    this.State = 3;     /* also open coolant tank 1 for extra cooling */
    break;
  }
}

/* Monitor temperature and send messages on overheat */
void MinorLoop(StateMachine *this) {
  switch(this.State) {
  case 0:
    this.Data = ReadADCValue();
    this.State = 1;
    break;
  case 1:
    if(this.Data > 150) *this.Message = 2;
    else if(this.Data > 100) *this.Message = 1;
    this.State = 0;
    break;
  }
}
Renee Cousins
quelle
1

Die Lösung hängt davon ab, 1) ob die Unterzustände von A für die Unterzustände von B sichtbar sind. 2) Leiten AB und C von einem gemeinsamen Elternteil ab? Wenn sie ein gemeinsames Elternteil haben und die Sichtbarkeit universell ist, sollten Sie nicht zu viel Mühe haben, vom Unterzustand von B in den Unterzustand von A zu wechseln.

Wenn Sie sie über Namespaces isoliert haben und / oder A, B und C keine gemeinsamen Eltern haben, ist es am besten, einen externen Treiber für Statusänderungen für A-, B- und C-Maschinen zu haben. Dies kann über einen Event-Handler erfolgen. Haben Sie einfach einen Beobachter in A, der Ereignisse in B abhören und basierend auf dem Ereignis in einen eigenen Unterzustand übergehen kann.

DPD
quelle