6. Defining Event Signals and Event Parameters

This QP/C++ Tutorial is adapted from Chapter 1 of Practical UML Statecharts in C/C++, Second Edition
by Miro Samek, the founder and president of Quantum Leaps, LLC.

qp_tutorial.jpg

Prev: 5. Elaborating State Machines of Active Objects
Next: 7. Coding Hierarchical State Machines

The key events in the "Fly 'n' Shoot" game have been identified in the sequence diagram in Figure 4-1. Other events have been invented during the state machine design stage. In any case, you must have noticed that events consist really of two parts. The part of the event called the signal conveys the type of the occurrence (what happened). For example, the TIME_TICK signal conveys the arrival of a time tick, while PLAYER_SHIP_MOVE signal conveys that the player wants to move the Ship. An event can also contain additional quantitative information about the occurrence in form of event parameters. For example, the PLAYER_SHIP_MOVE signal is accompanied by the parameters (x, y) that contain the quantitative information as to where exactly to move the Ship. In QP, events are represented as instances of the QEvent structure provided by the framework. Specifically, the QEvent structure contains the member sig, to represent the signal of that event. Event parameters are added in the process of inheritance, as described in the sidebar Encapsulation and Single Inheritance in C.

6.1 Enumerating Event Signals and Defining Event Parameters

Because events are explicitly shared among most of the application components, it is convenient to declare them in the separate header file game.h shown Listing 6-1. The explanation section immediately following the listing illuminates the interesting points.

Listing 6-1 Signals, event structures, and active object interfaces defined in file game.h.

 (1) enum GameSignals {                                 // signals used in the game
 (2)     TIME_TICK_SIG = Q_USER_SIG,                     // published from tick ISR
         PLAYER_TRIGGER_SIG,    // published by Player (ISR) to trigger the Missile
         PLAYER_QUIT_SIG,             // published by Player (ISR) to quit the game
         GAME_OVER_SIG,             // published by Ship when it finishes exploding

         // insert other published signals here ...
 (3)     MAX_PUB_SIG,                                  // the last published signal

         PLAYER_SHIP_MOVE_SIG,     // posted by Player (ISR) to the Ship to move it


         BLINK_TIMEOUT_SIG,              // signal for Tunnel's blink timeout event
         SCREEN_TIMEOUT_SIG,            // signal for Tunnel's screen timeout event

         TAKE_OFF_SIG,       // from Tunnel to Ship to grant permission to take off
         HIT_WALL_SIG,               // from Tunnel to Ship when Ship hits the wall
         HIT_MINE_SIG,        // from Mine to Ship or Missile when it hits the mine
         SHIP_IMG_SIG,        // from Ship to the Tunnel to draw and check for hits
         MISSILE_IMG_SIG,     // from Missile the Tunnel to draw and check for hits
         MINE_IMG_SIG,               // sent by Mine to the Tunnel to draw the mine
         MISSILE_FIRE_SIG,                   // sent by Ship to the Missile to fire
         DESTROYED_MINE_SIG,    // from Missile to Ship when Missile destroyed Mine
         EXPLOSION_SIG,        // from any exploding object to render the explosion
         MINE_PLANT_SIG,                     // from Tunnel to the Mine to plant it
         MINE_DISABLED_SIG,         // from Mine to Tunnel when it becomes disabled
         MINE_RECYCLE_SIG,            // sent by Tunnel to Mine to recycle the mine
         SCORE_SIG,      // from Ship to Tunnel to adjust game level based on score

 (4)     MAX_SIG                              // the last signal (keep always last)
     };

 (5) struct ObjectPosEvt 
 (6)     : public QEvent                            // derive from the QEvent class
     {          
 (7)     uint8_t x;                                 // the x-position of the object
 (8)     uint8_t y;                                 // new y-position of the object
     };

     struct ObjectImageEvt : public QEvent {        // derive from the QEvent class
         uint8_t x;                                 // the x-position of the object
         int8_t  y;                                 // the y-position of the object
         uint8_t bmp;                      // the bitmap ID representing the object
     };

     struct MineEvt : public QEvent {               // derive from the QEvent class
         uint8_t id;                                          // the ID of the Mine
         MineEvt(QSignal sig, uint8_t id) {
             this->sig      = sig;
             this->dynamic_ = 0;
             this->id       = id;
         }
     };

     struct ScoreEvt : public QEvent {              // derive from the QEvent class
         uint16_t score;                                       // the current score
         ScoreEvt(QSignal sig, uint16_t score) {
             this->sig      = sig;
             this->dynamic_ = 0;
             this->score    = score;
         }
     };

     // opaque pointers to active objects in the application
 (9) extern QActive * const AO_Tunnel;
(10) extern QActive * const AO_Ship;
(11) extern QActive * const AO_Missile;

6.2 Generating, Posting, and Publishing Events

The QF framework supports two types of asynchronous event exchange:

  1. The simple mechanism of direct event posting supported through the functions QActive::postFIFO() and QActive::postLIFO(), where the producer of an event directly posts the event to the event queue of the consumer active object.
  2. A more sophisticated publish-subscribe event delivery mechanism supported through the functions QF::publish() and QActive::subscribe(), where the producers of the events "publish" them to the framework, and the framework then delivers the events to all active objects that had "subscribed" to these events.

In QF, any part of the system can produce events, not necessarily only the active objects. For example, interrupt service routines (ISRs) or device drivers can also produce events. On the other hand, only active objects can consume events, because only active objects have event queues.

Note:
QF also provides "raw" thread-safe event queues (struct QEQueue), which can consume events as well. These "raw" thread-safe queues cannot block and are intended to deliver events to ISRs or device drivers. Please refer to Chapter 7 of Practical UML Statecharts in C/C++, Second Edition for more details.

The most important characteristic of event management in QF is that the framework passes around only pointers to events, not the events themselves. QF never copies the events by value ("zero-copy" policy); even in case of publishing events that often involves multicasting the same event to multiple subscribers. The actual event instances are either constant events statically allocated at compile time, or dynamic events allocated at runtime from one of the event pools that the framework manages. Listing 6-2 provides examples of publishing static events and posting dynamic events from the interrupt service routines (ISRs) of the "Fly 'n' Shoot" version for the ARM-Cortex board (file <qpcpp>\examples\arm-cortex\vanilla\iar\game-ev-lm3s811\bsp.cpp). In the upcoming Section 7. Coding Hierarchical State Machines you will see other examples of event posting from active objects in the state machine code.

Listing 6-2 Generating, posting , and publishing events from the ISRs in bsp.cpp for the ARM-Cortex board.

 (1) extern "C" void ISR_SysTick(void) {
 (2)     static QEvent const tickEvt = { TIME_TICK_SIG, 0 };
 (3)     QF::publish(&tickEvt);        // publish the tick event to all subscribers
 (4)     QF::tick();                               // process all armed time events
     }
     //............................................................................
 (5) extern "C" void ISR_ADC(void) {
         static uint32_t adcLPS = 0;               // Low-Pass-Filtered ADC reading
         static uint32_t wheel = 0;                      // the last wheel position

         static uint32_t btn_debounced  = 0;
         static uint8_t  debounce_state = 0;

         unsigned long tmp;

         ADCIntClear(ADC_BASE, 3);                       // clear the ADC interrupt
 (6)     ADCSequenceDataGet(ADC_BASE, 3, &tmp);       // read the data from the ADC

          // 1st order low-pass filter: time constant ~= 2^n samples
          // TF = (1/2^n)/(z-((2^n - 1)/2^n)),
          // eg, n=3, y(k+1) = y(k) - y(k)/8 + x(k)/8 => y += (x - y)/8
 (7)     adcLPS += (((int)tmp - (int)adcLPS + 4) >> 3);          // Low-Pass-Filter

            // compute the next position of the wheel
 (8)     tmp = (((1 << 10) - adcLPS)*(BSP_SCREEN_HEIGHT - 2)) >> 10;

         if (tmp != wheel) {                      // did the wheel position change?
 (9)         ObjectPosEvt *ope = Q_NEW(ObjectPosEvt, PLAYER_SHIP_MOVE_SIG);
(10)         ope->x = (uint8_t)GAME_SHIP_X;                  // x-position is fixed
(11)         ope->y = (uint8_t)tmp;
(12)         AO_Ship->postFIFO(ope);                            // post to the Ship
             wheel = tmp;                    // save the last position of the wheel
         }
         . . .
     }
Note:
The PLAYER_SHIP_MOVE(x, y) event is an example of an event with changing parameters. In general, such an event cannot be allocated statically (like the TIME_TICK event at label (2)) because it can change asynchronously next time the ISR executes. Some active objects in the system might still be referring to the event via a pointer, so the event should not be changing. Dynamic event allocation of QF solves all such concurrency issues, because every time a new event is allocated. QF then recycles the dynamic events, after it determines that all active objects are done with accessing the events.

Prev: 5. Elaborating State Machines of Active Objects
Next: 7. Coding Hierarchical State Machines

logo_ql_TM.jpg

Copyright © 2002-2010 Quantum Leaps, LLC. All Rights Reserved.
http://www.state-machine.com

Generated on Tue Mar 16 19:39:10 2010 for QP/C++ by  doxygen 1.6.3