Como uma máquina de estado filho pode renunciar ao controle de volta à máquina de estado pai?

9

Minha máquina de estado de nível superior tem alguns estados e arestas. Vou chamar isso de máquina de estado pai.

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

Qualquer estado na máquina de estado pai também pode ser uma máquina de estado. Vou chamar essas crianças de máquinas de estado.

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

Se a máquina de estado pai faz a transição de A para B, a máquina de estado de B assume o controle. Depois que B é executado, como ele deve abrir mão do controle para a máquina de estado pai e fazer a transição para o estado C? Qual padrão de design você usa?

Se você está se perguntando, eu tenho máquinas de estado filhos em máquinas de estado pai porque meu projeto exato é bastante complexo e é natural encapsular o funcionamento interno de um estado filho.

JoJo
fonte
Eu acho que B0, B1 e B2 devem saber que são componentes de algo que o mundo exterior considera ser uma única unidade. Então, talvez você precise ter uma MachineContainerclasse Bque contenha B0, B1 e B2 e, quando B2 termina, passa o controle de volta para o contêiner, que depois passa para C ... Eu nunca tentei algo assim. É um problema interessante!
FrustratedWithFormsDesigner
2
Sua pergunta tem a resposta óbvia ou sua pergunta não é muito clara. Do ponto de vista do pai, você deve implementá-lo exatamente como implementaria uma máquina de estado que não possui máquinas de estado filho. Acontece que os estados são implementados usando máquinas de estado filho, mas isso não afeta o pai. Também não deve afetar as máquinas de estado filho, exceto quando, ao sair, elas geram apenas os eventos no nível pai.
Dunk

Respostas:

5

Toda máquina de estado tem algum tipo de manipulador de eventos e um meio de acionar esses eventos. Esse manipulador toma como entrada o estado e o tipo de evento existentes, escolhe o novo estado e, opcionalmente, executa algum código de efeito colateral.

Essencialmente, enquanto estiver no estado B, seu manipulador de eventos principal encaminha todos os eventos que não reconhece ao Bmanipulador de eventos e permanece no estado B. Quando Bdeseja fazer a transição C, ele lança o evento apropriado no manipulador de eventos principal.

Karl Bielefeldt
fonte
2

Você leu esta seção do Taoup ? Existem várias maneiras diferentes de realizar isso, mas muitas delas dependem de como você dividiu suas máquinas de estado. Eles são processos separados? Tópicos? Objetos?

Descubra como você os construiu e veja se existe uma maneira canônica de se comunicar. Se não existir, você pode estar criando um sistema errado.

Para mim, eu olhava para processos separados, conectando stdin e stdout. A máquina do estado filho torna-se independente, agindo em stdin e produzindo em stdout. Torna-se o trabalho da máquina de estado pai iniciar o processo filho, conectar os tubos e depois despejar dados e aguardar resultados. Todas essas coisas já foram feitas em todas as línguas modernas, por isso deve ser fácil.

Spencer Rathbun
fonte
1

Separe as duas máquinas de estado e use a passagem de mensagens entre elas. Assim, a máquina de estado 1 procederia do ABC, onde no estado B verifica os resultados atuais da máquina de estado 2. Se a saída foi alterada, a máquina de estado 1 pode ser responsável e a máquina de estado 2 não precisa ter nenhum conhecimento. de como a máquina de estado 1 realmente funciona. Algo como:

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
fonte
1

A solução depende de 1) se os sub-estados de A são visíveis aos sub-estados de B. 2) AB e C derivam de um pai comum. Se eles têm um pai comum e a visibilidade é universal, você não deve ter muitos problemas para mudar do subestado de B para o subestado de A.

Se você os isolou por meio de namespaces e / ou A, B e C não possui um pai comum, é melhor ter um driver de alteração de estado externo para máquinas A, B e C. Isso pode ser feito através de um manipulador de eventos. Basta ter um observador em A que possa ouvir eventos gerados em B e fazer a transição para seu próprio sub-estado com base no evento.

DPD
fonte