State Design Patterns

These patterns enable features of UML statecharts (heavyweight, unoptimized code) in QP (lightweight, optimized code).

We discuss each design pattern, giving the customary points for each pattern: (1)The designer Intent for the pattern, (2) a statement of the Problem the pattern is indended to solve, (3) a description of the Solution guided by the pattern, (4) Sample Code for a solution of an archetypical case, and (5) a discussion of Consequences of use of the pattern.

Ultimate Hook

Intent

  • Provide common facilities and policies for handling events, but allow clients to override and specialize every aspect of behavior

Problem

  • Provide overridable policy consistency
  • Provide default behavior, overridable only when client chooses
  • Provide a common look and feel in system-level software that the client applications can use easily as the default
  • Clients must be able to override every aspect of the default behavior easily if they so choose
    Ultimate Hook
 
 

Solution

Sample Code 1

//...................................................................
enum UltimateHookSignals
{
  A_SIG = Q_USER_SIG, B_SIG, C_SIG, D_SIG
};
//...................................................................
class UltimateHook : public QHsm
// define "default" behavior framework
{
  protected:
    void initial(QEvent const *e);
    QSTATE generic(QEvent const *e);
      QSTATE specific(QEvent const *e);
    QSTATE final(QEvent const *e);
  public:
    UltimateHook() : QHsm((QPseudoState)initial) {}
};
//...................................................................
void UltimateHook::initial(QEvent const *)
{ 
  Q_INIT(&UltimateHook::generic);
}
//...................................................................
QSTATE UltimateHook::final(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:  
    exit(0); 
    return 0;
  }
  return (QSTATE)&UltimateHook::top;   
}
//...................................................................
QSTATE UltimateHook::generic(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_INIT_SIG:
      printf("generic:init;");
      Q_INIT(&UltimateHook::specific);
      return 0;
    case A_SIG:
      printf("generic:A;");
      return 0;
    case B_SIG:
      printf("generic:B;");
      return 0;
    case C_SIG: 
      printf("generic:C(reset);");
      Q_TRAN(&UltimateHook::generic);
      return 0;
    case D_SIG:
      Q_TRAN(&UltimateHook::final);
      return 0;
  }
  return (QSTATE)&UltimateHook::top;
}
//...................................................................
// define "specific" behavior - left to client program
QSTATE UltimateHook::specific(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      printf("specific:entry;");
      return 0;
    case Q_EXIT_SIG:
      printf("specific:exit;");
      return 0;
    case A_SIG:
      printf("specific:A;");
      return 0;
  }
  return (QSTATE)&UltimateHook::generic;
}
//...................................................................

Sample Code 2

//...................................................................
public class HookFrame extends JFrame
{
  public HookFrame()
  {
    super("Hook Example");
    setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    setSize(300,300);
  }

  public static void main(String[] args)
  {
    HookFrame frame = new HookFrame();
    frame.setVisible(true);
  }
}
//...................................................................

Sample Code 3

//...................................................................
LRESULT CALLBACK WndMainProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {
    case WM_GRAPHNOTIFY:
      HandleGraphEvent();
      break;

    case WM_SIZE:
      ResizeVideoWindow();
      break;

    case WM_WINDOWPOSCHANGED:
      ChangePreviewState(! (IsIconic(hwnd)));
      break;

    case WM_CLOSE: 
      // Hide the main window while the graph is destroyed
      ShowWindow(ghApp, SW_HIDE);
      CloseInterfaces();  // Stop capturing and release interfaces
      break;

    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
  }

  // Pass this message to the video window for notification of system changes
  if (g_pVW)
    g_pVW->NotifyOwnerMessage((LONG_PTR) hwnd, message, wParam, lParam);

  return DefWindowProc (hwnd , message, wParam, lParam);
}
//...................................................................

Consequences


Reminder

Intent

  • Make statechart topology more flexible by inventing event and posting to self

Problem

  • Loosely related functions are often strongly coupled by a common event
    • Polling for data, then processing the data
    • Long sequence of events run-to-completion (RTC)
    Reminder

Solution

Examples

Sample Code

//...................................................................
enum SensorSignals
{
  DATA_READY = Q_USER_SIG, TERMINATE
}; 
//...................................................................
class Sensor : public QHsm
{
public:
  Sensor() : QHsm((QPseudoState)initial) {}
private:
  void initial(QEvent const *e);
  QSTATE polling(QEvent const *e);
    QSTATE processing(QEvent const *e);
      QSTATE idle(QEvent const *e);
      QSTATE busy(QEvent const *e);
  QSTATE final(QEvent const *e);
private:
  int myPollCtr; 
  int myProcCtr; 
  BOOL isHandled;       // flag indicates if last event was handled
  HWND myHwnd;          // the main window handle
  friend BOOL CALLBACK
    reminderDlg(HWND hwnd, UINT iEvt, WPARAM wParam, LPARAM lParam);
};
//...................................................................
static Sensor app;
static HINSTANCE inst;          // this instance
static HWND mainHwnd;           // the main window
static char appName[] = "Sensor";
//...................................................................
void Sensor::initial(QEvent const *)
{
  SendMessage(myHwnd, WM_SETICON, (WPARAM)TRUE,
              (LPARAM)LoadIcon(inst, MAKEINTRESOURCE(IDI_QP)));
  myPollCtr = 0; 
  myProcCtr = 0; 
  Q_INIT(&Sensor::polling);
}
//...................................................................
QSTATE Sensor::final(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      EndDialog(myHwnd, 0);
      return 0;
  }
  return (QSTATE)&Sensor::top;
}
//...................................................................
QSTATE Sensor::polling(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      SetTimer(myHwnd, 1, 500, 0);
      return 0;

    case Q_EXIT_SIG:
      KillTimer(myHwnd, 1);
      return 0;

    case Q_INIT_SIG:
      Q_INIT(&Sensor::processing);
      return 0;

    case WM_TIMER:
      SetDlgItemInt(myHwnd, IDC_POLL, ++myPollCtr, FALSE);
      if ((myPollCtr & 0x3) == 0)
      {
        PostMessage(myHwnd, WM_COMMAND, DATA_READY, 0); // post event to self
      }
      return 0;

    case TERMINATE:
      Q_TRAN(&Sensor::final);  return 0;
  }
  if (e->sig >= Q_USER_SIG)
  {
    isHandled = FALSE;
  }
  return (QSTATE)&Sensor::top;
}
//...................................................................
QSTATE Sensor::processing(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_INIT_SIG:
      Q_INIT(&Sensor::idle);
     return 0;
  }
  return (QSTATE)&Sensor::polling;
}
//...................................................................
QSTATE Sensor::idle(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      SetDlgItemText(myHwnd, IDC_STATE, "idle");
      return 0;

    case DATA_READY: // handle reminder event
      Q_TRAN(&Sensor::busy);
      return 0;
  }
  return (QSTATE)&Sensor::processing;
}
//...................................................................
QSTATE Sensor::busy(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      SetDlgItemText(myHwnd, IDC_STATE, "busy");
      return 0;

    case WM_TIMER:
      SetDlgItemInt(myHwnd, IDC_PROC, ++myProcCtr, FALSE);
      if ((myProcCtr & 0x1) == 0)
      {
        Q_TRAN(&Sensor::idle);
      }
      return 0;
  }
  return (QSTATE)&Sensor::processing;
}
//...................................................................
extern "C" void onAssert__(char const *file, unsigned line)
{
  char str[160];
  wsprintf(str, "Assertion failed in %s, line %d", file, line);      
  MessageBox(mainHwnd, str, appName, MB_ICONEXCLAMATION | MB_OK);
  exit(-1);
}
//...................................................................
BOOL CALLBACK reminderDlg(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
  QEvent e;
  e.sig = iMsg;
  switch (iMsg)
  {
    case WM_INITDIALOG:
      app.myHwnd = mainHwnd = hwnd;
      app.init();                   // take the initial transition
      return TRUE;

    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDCANCEL:
          e.sig = TERMINATE;
          break;
        default:
          e.sig = LOWORD(wParam);
          break;
      }
      // intentionally fall thru

    case WM_TIMER:
      app.isHandled = TRUE;
      app.dispatch(&e);
      return app.isHandled;
  }
  return FALSE;
}
//...................................................................
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, PSTR cmdLine, int iCmdShow)
{
  InitCommonControls();              // load common controls library
  inst = hInst;                             // store instance handle
  DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG), NULL, reminderDlg);                  
  return 0;              // exit application when the dialog returns
}
//...................................................................

Consequences


Deferred Event

Intent

  • Simplify state machines by modifying the sequencing of events

Problem

  • Reactive Systems must handle every event at any time
  • Event may arrive at particularly inconvenient moment
  • Nature of event may allow postponement
    Deferred Event

Solution

Example: ATM

Sample Code: Windows GUI

//...................................................................
class TServer : public QHsm
{
  public:
    TServer() : QHsm((QPseudoState)initial) {}
  private:
    void initial(QEvent const *e);
    QSTATE operational(QEvent const *e);
      QSTATE idle(QEvent const *e);
      QSTATE receiving(QEvent const *e);
      QSTATE authorizing(QEvent const *e);
    QSTATE final(QEvent const *e);
    BOOL defer(QEvent const *e);            // event deferral action
    void recall();                                  // recall action
  private:
    QEvent myDeferredRequest;
    BOOL isHandled;
    HWND myHwnd;                           // the main window handle
    friend BOOL CALLBACK 
      tServerDlg(HWND hwnd, UINT iEvt, WPARAM wParam, LPARAM lParam);
};
//...................................................................
enum TServerSignals
{
   TERMINATE = Q_USER_SIG
}; 
//-------------------------------------------------------------------
static TServer app;
static HINSTANCE inst;                               // this instance
static HWND mainHwnd;                       // the main window handle
static char appName[] = "TServer";

//...................................................................
BOOL TServer::defer(QEvent const *e) // implementation of defer action
{
   if (IsDlgButtonChecked(myHwnd, IDC_DEFERRED))         // deferred?
   {
      return FALSE;                   // cannot defer any more events
   } 
   myDeferredRequest = *e;          // save the event (copy by value)
   CheckDlgButton(myHwnd, IDC_DEFERRED, BST_CHECKED);     // deferred
   return TRUE;
}
//...................................................................
void TServer::recall()           // implementation of recall action
{
  if (IsDlgButtonChecked(myHwnd, IDC_DEFERRED))       // deferred?
  {
    PostMessage(myHwnd, WM_COMMAND, myDeferredRequest.sig, 0);
    CheckDlgButton(myHwnd, IDC_DEFERRED, BST_UNCHECKED);
  }
}
// HSM definition ---------------------------------------------------
void TServer::initial(QEvent const *)
{
  SendMessage(myHwnd, WM_SETICON, (WPARAM)TRUE,
   (LPARAM)LoadIcon(inst, MAKEINTRESOURCE(IDI_QP)));
  Q_INIT(&TServer::operational);
}
//...................................................................
QSTATE TServer::final(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      EndDialog(myHwnd, 0);
      return 0;
  }
  return (QSTATE)&TServer::top;
}
//...................................................................
QSTATE TServer::operational(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_INIT_SIG:
      Q_INIT(&TServer::idle);
      return 0;

    case IDC_NEW:
      if (!defer(e)) // defer at composit level
      {
         Beep(1000, 20);  // if event cannot be deferred, warn the user
      }
      return 0;

    case TERMINATE:
      Q_TRAN(&TServer::final);
      return 0;
  }
  if (e->sig >= Q_USER_SIG)
  {
    isHandled = FALSE;
  }
  return (QSTATE)&TServer::top;
}
//...................................................................
QSTATE TServer::idle(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      SetDlgItemText(myHwnd, IDC_STATE, "idle");
      recall(); // recall at descendant level
      return 0;

    case IDC_NEW:
      Q_TRAN(&TServer::receiving);
      return 0;
  }
  return (QSTATE)&TServer::operational;
}
//...................................................................
QSTATE TServer::receiving(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      SetDlgItemText(myHwnd, IDC_STATE, "receiving");
      SetTimer(myHwnd, 1, 1000, 0);   // arrange for timeout in 1 sec
      return 0;

    case WM_TIMER:
      Q_TRAN(&TServer::authorizing);
      return 0;

    case Q_EXIT_SIG:
      KillTimer(myHwnd, 1);
      return 0;
  }
  return (QSTATE)&TServer::operational;
}
//...................................................................
QSTATE TServer::authorizing(QEvent const *e) {
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      SetDlgItemText(myHwnd, IDC_STATE, "authorizing");
      SetTimer(myHwnd, 1, 2000, 0);   // arrange for timeout in 2 sec
      return 0;

    case WM_TIMER:
      Q_TRAN(&TServer::idle);
      return 0;

    case Q_EXIT_SIG:
      KillTimer(myHwnd, 1);
      return 0;
  }
  return (QSTATE)&TServer::operational;
}
//...................................................................
extern "C" void onAssert__(char const *file, unsigned line)
{
   char str[160];
   sprintf(str, "Assertion failed in %s, line %d", file, line);        
   MessageBox(mainHwnd, str, appName, MB_ICONEXCLAMATION | MB_OK);
   exit(-1);
}
//-------------------------------------------------------------------
BOOL CALLBACK tServerDlg(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
   QEvent e;
   e.sig = iMsg;
   switch (iMsg) {
   case WM_INITDIALOG:
      app.myHwnd = mainHwnd = hwnd;
      app.init();                      // take the initial transition
      return TRUE;
   case WM_COMMAND:
      switch (LOWORD(wParam)) {
      case IDCANCEL:
         e.sig = TERMINATE;
         break;
      default:
          e.sig = LOWORD(wParam);
          break;
      }
      // intentionally fall thru
   case WM_TIMER:
      app.isHandled = TRUE;
      app.dispatch(&e);
      return app.isHandled;
   }
   return FALSE;
}
//...................................................................
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst,
                   PSTR cmdLine, int iCmdShow)
{
   InitCommonControls();              // load common controls library
   inst = hInst;                             // store instance handle
   DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG), NULL, tServerDlg);                  
   return 0;              // exit application when the dialog returns
}

Consequences


Orthogonal Component

Intent

  • Use state machines as independent components
    1. State machine = stand-alone system
    2. Re-use

Problem

  • Independent machine behavior best modeled by independent state machine
  • UML Statechart Orthogonal Region is heavy and not comprised of independent (re-usable) components
    Orthoganl Component
 
 

Solution

Example: Alarm Clock

  • TimeKeeping machine - stand-alone HSM
  • Alarm
  • machine - stand-alone FSM
  • AlarmClock a modified TimeKeeping machine that has an Alarm data member
  • AlarmClock has responsibility:
    - for initializing Alarm
    - for dispatching events to Alarm
    Alarm Clock

Sample Code

(see text)

Consequences


Transition to History

Intent

  • Transition out of a composite state, but remember the most recent active substate so it can be returned to at a later time

Problem

  • State transitions defined in high level composite states often deal with events that require immediate attention
  • Event may force transition out of a composit state
  • After handling the event, the system should return to the most recent substate of the given composite state
  • A mechanism is needed to remember the last state
    Transition to History
 
 

Solution

Examples

Sample Code

class ToasterOven : public QHsm
{
  public:
    ToasterOven() : QHsm((QPseudoState)initial) {}
  private:
    QState myDoorClosedHistory;
};

QSTATE ToasterOven::doorClosed(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      printf("door-Closed;");
      return 0;
    case Q_EXIT_SIG:
      myDoorClosedHistory = getState();
      return 0;
   }
   return (QSTATE)&ToasterOven::top;
}

QSTATE ToasterOven::doorOpen(QEvent const *e)
{
  switch (e->sig)
  {
    case Q_ENTRY_SIG:
      printf("door-Open,lamp-On;");
      return 0;
    case Q_EXIT_SIG:
      printf("lamp-Off;");
      return 0;
    case CLOSE_SIG: 
      Q_TRAN_DYN(myDoorClosedHistory);
      return 0;
  }
  return (QSTATE)&ToasterOven::top;
}

Consequences