3. The main() Function

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: 2. Let's Play
Next: 4. Designing an Event-Driven Application

Perhaps the best place to start the explanation of the "Fly 'n' Shoot" application code is the main() function, located in the file main.cpp. Unless indicated otherwise in this Tutorial, you can browse the code either in the DOS version, or the ARM-Cortex version, because the application source code is identical in both. The complete main.cpp file is shown in Listing 3-1.

Note:
To explain code listings, I place numbers in parentheses at the interesting lines in the left margin of the listing. I then use these labels in the left margin of the explanation section that immediately follows the listing. Occasionally, to unambiguously refer to a line of a particular listing from sections of text other than the explanation section, I use the full reference consisting of the listing number followed by the label. For example, Listing 3-1(18) refers to the label (18) in Listing 3-1

Listing 3-1 The file main.cpp of the "Fly 'n' Shoot" game application.

 (1) #include "qp_port.h"
 (2) #include "bsp.h"
 (3) #include "game.h"

     // Local-scope objects -------------------------------------------------------
 (4) static QEvent const * l_missileQueueSto[2];
 (5) static QEvent const * l_shipQueueSto[3];
 (6) static QEvent const * l_tunnelQueueSto[GAME_MINES_MAX + 5];
 (7) static ObjectPosEvt   l_smlPoolSto[GAME_MINES_MAX + 5];    // small event pool
 (8) static ObjectImageEvt l_medPoolSto[GAME_MINES_MAX + 5];   // medium event pool
 (9) static QSubscrList    l_subscrSto[MAX_PUB_SIG];

     //............................................................................
     void main(int argc, char *argv[]) {

(10)     BSP_init(argc, argv);              // initialize the Board Support Package

(11)     QF::init();       // initialize the framework and the underlying RT kernel

                                                   // initialize the event pools...
(12)     QF::poolInit(l_smlPoolSto, sizeof(l_smlPoolSto), sizeof(l_smlPoolSto[0]));
(13)     QF::poolInit(l_medPoolSto, sizeof(l_medPoolSto), sizeof(l_medPoolSto[0]));

(14)     QF::psInit(l_subscrSto, Q_DIM(l_subscrSto));     // init publish-subscribe

                                                     // start the active objects...
(15)     AO_Missile->start(1,                                           // priority
                           l_missileQueueSto, Q_DIM(l_missileQueueSto),// evt queue
                           (void *)0, 0,                     // no per-thread stack
                           (QEvent *)0);                 // no initialization event
(16)     AO_Ship   ->start(2,                                           // priority
                           l_shipQueueSto, Q_DIM(l_shipQueueSto),      // evt queue
                           (void *)0, 0,                     // no per-thread stack
                           (QEvent *)0);                 // no initialization event
(17)     AO_Tunnel ->start(3,                                           // priority
                           l_tunnelQueueSto, Q_DIM(l_tunnelQueueSto),  // evt queue
                           (void *)0, 0,                     // no per-thread stack
                           (QEvent *)0);                 // no initialization event

(18)     QF::run();                                       // run the QF application
     }

The QP event-driven platform is a collection of components, such as the QEP event processor that executes state machines according to the UML semantics and the QF real-time framework that implements the active object computing model. Active objects in QF are encapsulated state machines (each with an event queue, a separate task context, and a unique priority) that communicate with one another asynchronously by sending and receiving events, while QF handles all the details of thread-safe event exchange and queuing. Within an active object, the events are processed by the QEP event processor sequentially in a run-to-completion (RTC) fashion, meaning that processing of one event must necessarily complete before processing the next event.

The QF real-time framework supports two event delivery mechanisms: the simple direct event posting to active objects, and the more advanced mechanism called publish-subscribe that decouples event producers from the consumers. In the publish-subscribe mechanism, active objects subscribe to events by the framework. Event producers publish the events to the framework. Upon each publication request, the framework delivers the event to all active objects that had subscribed to that event type. One obvious implication of publish-subscribe is that the framework must store the subscriber information, whereas it must be possible to handle multiple subscribers to any give event type. The event delivery mechanisms are described in Chapters 6 and 7 of Practical UML Statecharts in C/C++, Second Edition.

The utility macro Q_DIM(a) provides the dimension of a one-dimensional array a[] computed as sizeof(a)/sizeof(a[0]), which is a compile-time constant. The use of this macro simplifies the code because it allows me to eliminate many define constants that otherwise I would need to provide for the dimensions of various arrays. I can simply hard-code the dimension right in the definition of an array, which is the only place that I specify it. I then use the macro Q_DIM() whenever I need this dimension in the code.

I like to keep the code and data of every active object strictly encapsulated within its own C-file. For example, all code and data for the active object Ship are encapsulated in the file ship.cpp, with the external interface consisting of the pointer AO_Ship.

After the call to QF::run() the framework is in full control. The framework executes the application by calling your code, not the other way around. The function QF::run() never returns the control back to main(). In the DOS version of the "Fly 'n' Shoot" game, you can terminate the application by pressing the ESC key, in which case QF::run() exits to DOS, but not to main(). In an embedded system, such as the ARM-Cortex board, QF::run() runs forever or till the power is removed, whichever comes first.

Note:
For best cross-platform portability, the source code uses consistently the UNIX end-of-line convention (lines are terminated with LF only, 0xA character). This convention seems to be working for all C/C++ compilers and cross-compilers, including legacy DOS-era tools. In contrast, the DOS/Windows end-of-line convention (lines terminated with the CR,LF, or 0xD,0xA pair of characters), is known to cause problems on UNIX-like platforms, especially in the multi-line preprocessor macros.

Prev: 2. Let's Play
Next: 4. Designing an Event-Driven Application

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:09 2010 for QP/C++ by  doxygen 1.6.3