Prev: 9. Using Software Tracing for Testing and Debugging
Next: 11. Summary
The "Fly 'n' Shoot" game behaves intentionally almost identically to the "Quickstart" application provided in source code with the Luminary Micro Cortex-M3 LM3S811 evaluation kit (http://www.luminarymicro.com). In this section I'd like to compare the traditional approach represented by the "Quickstart" application with the state machine-based solution exemplified in the "Fly 'n' Shoot" game.
Figure 10-1(a) shows schematically the flowchart of the "Quickstart" application, while Figure 10-1(b) shows the flowchart of the "Fly 'n' Shoot" game running on top of the cooperative "vanilla" kernel. At the highest level, the flowcharts are similar in that they both consist of an endless loop surrounding the entire processing. But the internal structure of the main loop is very different in the two cases. As indicated by the heavy lines in the flowcharts, the "Quickstart" application spends most of its time in the tight "event loops" designed to busy-wait for certain events, such as the screen update event. In contrast, the "Fly 'n' Shoot" application spends most of its time right in the main loop. The QP framework dispatches any available event to the appropriate state machine that handles the event and returns quickly to the main loop without ever waiting for events internally.
Figure 10-1 The control flow in the
The "Quickstart" application has much more convoluted flow of control than the "Fly 'n' Shoot" example, because the traditional solution is very specific to the problem at hand while the state-machine approach is generic. The "Quickstart" application is structured very much like a traditional sequential program that tries to stay in control from the beginning to the end. From time to time, the application pauses to busy-wait for a certain event, whereas the code is generally not ready to handle any other events than the one it chooses to wait for. All this contributes to the inflexibility of the design. Adding new events is hard because the whole structure of the intervening code is designed to accept only very specific events and would need to change dramatically to accommodate new events. Also, while busy-waiting for the screen update event (equivalent to the TIME_TICK event in "Fly 'n' Shoot" example) the application is really not responsive to any other events. The task-level response is hard to characterize and generally depends on the event type. The timing established by the hard-coded waiting for the existing events might not work well for new events.
In contrast, the "Fly 'n' Shoot" application has a much simpler control flow that is purely event-driven and completely generic (see Figure 10-1(b)). The context of each active object component is represented as the current state of a state machine, rather than as a certain place in the code. That way, hanging in tight "event loops" around certain locations in the code corresponding to the current context is unnecessary. Instead, a state machine remembers the context very efficiently as a small data item (the state-variable, see Chapter 3 of Practical UML Statecharts in C/C++, Second Edition). After processing of each event the state machine can return to the common event loop that is designed generically to handle all kinds of events. For every event, the state machine naturally picks up where it left off and moves on to the next state, if necessary. Adding new events is easy in this design, because a state machine is responsive to any event at any time. An event-driven, state-machine-based application is incomparably more flexible and resilient to change than the traditional one.
1.5.4