10

My top level state machine has some states and edges. I will call this the parent state machine.

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

Any state within the parent state machine can be a state machine too. I will call these children state machines.

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

If the parent state machine transitions from A to B, B's state machine takes over. Once B is done running, how should it relinquish control to the parent state machine and transition to state C? Which design pattern do you use?

If you're wondering, I have children state machines within parent state machines because my exact project is quite complex and it is natural to encapsulate the internal workings of a child state.

2
  • I'd guess that B0, B1, and B2 should know that they're components of something that the outside world considers to be a single unit. So maybe you'd have to have a MachineContainer class for B that contains B0, B1, and B2 and when B2 ends, it passes control back to its container which then transitions to C... I've never actually tried anything like this though. It's an interesting problem! Feb 9, 2012 at 19:25
  • 2
    Your question has either the obvious answer or your question is not very clear. From the point of view of the parent, you should implement it exactly like you would implement a state machine that doesn't have child state machines. It just so happens that the states are implemented by using child state machines but that doesn't affect the parent at all. It also shouldn't affect the children state machines other than upon exit they only generate the parent level events.
    – Dunk
    Feb 9, 2012 at 19:50

4 Answers 4

6

Every state machine has some sort of event handler and a means to trigger those events. That handler takes as input the existing state and type of event, chooses the new state, and optionally runs some side effect code.

Essentially, while in state B, your main event handler forwards any events it doesn't recognize to B's event handler and remains in state B. When B wants to transition to C, it posts the appropriate event to the main event handler.

2

Have you read this section of Taoup? There are several different ways to accomplis this but many of them depend on how you have split your state machines. Are they separate processes? Threads? Objects?

Figure out how you have them built, and look to see if there is a canonical way for them to communicate. If one does not exist, you may be designing your system wrong.

For me, I'd look at separate processes, hooking stdin and stdout together. The child state machine then becomes standalone, acting on stdin and outputing on stdout. It becomes the parent state machine's job to start the child process, hook up the pipes, and then dump in data and wait for results. All of those things have already been done in all modern languages, so it should be easy to do.

2

Separate the two state machines and use message passing between them. Thus, state machine 1 would proceed from A B C, where at state B it checks for the current results from state machine 2. If the output has changed, then state machine 1 can account for it and state machine 2 does not need to have any awareness of how state machine 1 actually works. Something like:

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

The solution depends on 1) whether A's sub-states are visble to B's sub-states. 2) Do A B and C derive from a common parent. If they have a common parent and the visibility is universal, you shouldnt have too much trouble moving from B's sub state to A's sub state.

If you have isolated them via namespaces and/or A, B and C dont have a common parent, then your best wayy is tohave an external state change driver for A , B and C machines. THis can be done via an event handler. Simply have an observer in A which can listens to events raised in B and transitions to its own sub state based on the event.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.