Facing the wall with an ARMed cannon
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".

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 :-))

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.
- First let the timer run as fast as possible (1 millisecond time period should be enough) and start measuring the transaction time of the shoot transaction (you will of course measure the draw transactions as well)
- Second disable the draw transaction (just comment out the
start()andstop()methods) and repeat the same test. Have a look at the time needed for one shot. There should be almost no difference depending on the way the ARM collector is configured (i. e. writing transaction instances directly to a file will take longer than writing it to a shared memory). See chapter "How to proceed" for a detailed description of different collecting mechanisms provided with MyARM.