Facing the wall with an ARMed cannon

Online section / Tutorials / QArm Tutorial / Facing the wall contact | download | sitemap
 

Now that we have a rough understanding of how QSimpleArmTran works we will jump right into a more complex scenario. We started with Chapter 2 from the original Qt tutorial, now let us jump to chapter 14, "Facing the wall".

The original image from the Qt tutorial, Chapter 14
Figure: The original image from the Qt tutorial, Chapter 14

The aim of this chapter is to see how transaction parent/child relationships are modelled. Moreover this is a good example to show that it is a good idea to differentiate between transaction definitions and transaction instances. How is this obtained?

First we measure the time needed to fire the shot until it either hits the red rectangle or moves beyond the widget border. We will call this transaction "shoot-transaction". This is modelled as one transaction definition so there is no real difference to the first example yet. We create many transaction instances by firing the cannon more than once.

The other transaction definition measures the time needed to move the shot from one position to the next. We will call it "draw-transaction". Remember the original example, every 5 milliseconds the small black rectangle is redrawn at a new position. It might be interesting to see how long it takes to compute that position and draw the rectangle. Having this information it can be decided to draw the rectangle let us say every millisecond to get a smoother move. Or we might find that our computer is too slow to finish that job within 5 milliseconds, resulting in a longer timer period. (Ok, ok, please remember, this is an example :-))

Shoot and draw transaction
Figure: Shoot and draw transaction

However, it is obvious that the "draw-transaction" is somehow related to the "shoot-transaction", i.e. the "shoot-transaction" can be viewed as a summary of many "draws". Therefore the two transactions are modelled as a parent/child relationship.

Source code

Modifications to the original example code by Trolltech are marked. The other source files were not touched.

lcdrange.h contains the LCDRange class definition
llcdrange.cpp contains the LCDRange implementation
autoshoottimer.h contains AutoShootTimer class (added)
cannonfield.h contains the CannonField class definition (modified)
cannonfield.cpp contains the CannonField implementation (modified)
gameboard.h contains the GameBoard class definition
gameboard.cpp contains the GameBoard implementation
main.cpp contains MyWidget and main

Line-by-line walkthrough

The original example is explained in detail in chapter 14 on the Trolltech documentation website. Here we only explain the changes that were necessary to implement ARM.

autoshoottimer.h

The class AutoShootTimer is defined and implemented here. Its aim is to start and stop the "shoot-transaction" when the timer is started and stopped. The implementation is straight forward.

    AutoShootTimer(QObject *parent=0)
            : QTimer(parent)
              shootTransaction("shot airborne") {}

First the AutoShootTimer object is instantiated together with the shootTransaction QSimpleArmTran object. The "shoot-transaction" does not have any parents. The name of the transaction definition is "shot airborne" but can be any string you like.

The start() method from the AutoShootTimer overwrites the start() method from QTimer.

    void start(int msec,bool sshot = false) {
        shootTransaction.start();
        QTimer::setSingleShot(sshot);
        QTimer::start(msec);
    }

Do not confuse the QTime::start() method with the QSimpleArmTran::start() method. The QSimpleArmTran::start() method is called in the overwritten AutoShootTimer::start() method in order to start the transaction measuring. The same is true for the stop() methods, so we do not show the source code here.

cannonfield.h

Two new headers are included

    #include <QSimpleArmTran>
    #include "autoshoottimer.h"

The pointers to these two classes are defined here

    AutoShootTimer *autoShootTimer;
    ...
    QSimpleArmTran *drawTransaction;

The drawTransaction object as well as the autoShootTimer object are instantiated during construction of CannonField.

cannonfield.cpp

The constructor instantiates the two objects mentioned above:

    autoShootTimer = new AutoShootTimer(this);
    drawTransaction = new QSimpleArmTran("move and draw shot",
                                         autoShootTimer->getShootTransaction());

The "draw-transaction" is instantiated here. The name of this transaction definition is "move and draw shot" but can be any descriptive string you like. The parent of the "draw-transaction" is the "shoot-transaction" implicitly offered by the associated AutoShootTimer object.

The "shoot-transaction" is started every time the start() method from AutoShootTimer is called, i.e. every time the timer is activated. This is the case when the user presses the "Shoot" button. The transaction is stopped every time the AutoShootTimer object is stopped. This is the case when the shot hits or misses the target or when the application quits or the "New Game" button is pressed.

The "draw-transaction" is started and stopped in the method moveShot():

    void CannonField::moveShot() 
    {
      drawTransaction->start();
      ...
      if (shotR.intersects(targetRect())) {
        autoShootTimer->stop();
      ...
      } 
      ...
      drawTransaction->stop();
    }

This is a simple call of the start() and stop() methods as described in the first example. One thing should be mentioned here. If the shot intersects with the target then the shot rectangle gets removed from the widget and the AutoShootTimer object is stopped. As we saw before this leads to a stop() of the "shoot-transaction" as well. But in case this happens this would lead to an inconsistent situation since the "draw-transaction" (which is a child of the "shoot-transaction") is stopped later in this method. The solution is simple. Every time a parent transaction is stopped all its children are stopped as well. Please have a look at the description of the QSimpleArmTran class for details. In our example, the call of

drawTransaction.stop() 

at the end of the method simply has no effect in this case.

Compiling

The code can be compiled using the standard Qt qmake mechanism. Just go into the directory tutorial/t2 and say:

    qmake
    make

Behaviour

The program will behave exactly as the program described in the Trolltech tutorial chapter 14. Shoot a few times and analyse the results afterwards as described in chapter "How to proceed" . You may have wondered why there is a differentiation between transaction definitions and transaction instances. For a deeper understanding of the differences between definitions and instances see the next excursus.

E x c u r s u s

As mentioned before ARM differentiate between applications and transactions. Each of these objects has to be created before it can be started. The creation of one of these objects creates an object definition (either of type application or transaction). Object definitions are normally provided with information that does not change as long as the object "lives". Referring to applications this can be the application name. Regarding transactions this can be the name of the function where this transaction will be started.

When an object was created (i.e. the definition exists) it can be started and stopped. This is true for applications as well as for transactions. When an object is started an instance of this object is created. The instance can also be provided with some information which is only valid for that instance. Such information can be the CPU load or the memory usage during execution of a transaction. The information is then saved referring to that instance of the transaction. Of course the information provided to the transaction definition is saved and associated with this transaction instance as well.

Exercise

You might ask how ARM influences the performance of the application at all. You can check this easily by slightly modifying this example.