Aquarium is a C++-library specially designed for use with realtime applications. It’s based on an idea that might seem object-oriented, but I’ve experienced that it has certain advantages compared to ordinary OO-design. In it’s current version, Aquarium is mainly developed for use with realtime graphical presentations, but the general structure and design of the system should be useable for all kinds of realtime applications.
1. The main idea
In a realtime system like Aquarium, you need a mainloop, the part of your program that runs over and over again. Organizing such a loop isn’t just easy, and here’s where the main idea of Aquarium steps in.
A straight forward mainloop for a game
could look like this:
FIG. 1.1
while ( !QuitFlag )
{
CheckKeyStates();
CalculateAllVectorStuff();
RenderFrame();
}//while
Inside the CheckKeyStates() you check if Esc has been pressed, and set the QuitFlag to true if so. If the user also should be able to shoot in the game, CheckKeyStates() could set a flag DrawBullet to true, and the mainloop could look like this:
FIG. 1.2
while ( !QuitFlag )
{
CheckKeyStates();
CalculateVectors();
if
( DrawBullet )
CalculateBulletStuff();
RenderFrame();
}//while
The bullet travels through space until it hits something and we set the DrawBullet-flag to false to avoid further rendering. When the game gets bigger, your mainloop will contain a whole lot of ”if”s that decide what should be done at the current frame. The problem with this is that the program gets nasty and all the if-sentences takes cpu-time.
If we set up the mainloop like this:
//Pointer to function taking void, returning
void
typedef void (*Function)(void);
//A table of pointers to functions
Function Mainloop[] = {
CheckKeyStates,
CalculateVectors,
RenderFrame,
NULL};
//The mainloop
while ( !QuitFlag )
{
int
C = 0;
while
( Mainloop[C] != NULL )
{
Mainloop[C];
C++;
}//while
}//while
Here we set up an array called Mainloop, that contains a list of pointers to functions that are to be run. By setting up this list of functions as an array with a dynamic size (maybe a linked list), we may easily insert and remove functions in the mainloop. We don’t have to insert any if-sentences and the code doesn’t change, no matter how many functions we stuff into the mainloop.
Aquarium works almost like this. Say that you change the mainloop into this:
//Pointer to function taking void, returning
void
typedef void (*Function)(void);
//A list of functionpointers
Function FunctionList[] = {
NULL,
CheckKeyStates,
CalculateVectors,
RenderFrame,
};
//The mainloop-array
int Mainloop[] = {1, 2, 3,
0};
//The mainloop
while ( !QuitFlag )
{
int
C = 0;
while
( Mainloop[C] != 0 )
{
FunctionList[MainLoop[C]];
C++;
}//while
}//while
Now we set up Mainloop as an array of indexes. These indexes point into an array of functionpointers (FunctionList). Up to now, we’ve assumed that the user plays the game by using the arrowkeys. The CheckKeyStates-function checks the current keystate, and updates some flags here and there. If we suddenly want the user to utilize the mouse instead, we could change the table FunctionList[1] to CheckMouseStates and this function would be run instead. Now you probably think ”Hey, stupid! Why didn’t you just change the Mainloop-array in FIG 1.3 then? We don’t need this lookup-table-thingy?!?” Well, I’ll come to that.
Ok, so let’s change our program once again. (”Jippi! This is fun!”). I divide the example into a header-file and sourcefile.
HEADERFILE:
//Pointer to function taking void, returning
void
typedef void (*Function)(void);
class Fish
{
public:
int
Type;
}//Fish
class Keypressed: public Fish
{
public:
static
void Check();
}//Keystate
class Vector: public Fish
{
public:
static
void CalculateWireframe();
static
void CalculateSolid();
int NrVertices;
Vertex *Vertices;
int NrFaces;
Face *Faces;
...etc
}//Vector
class Canvas: public Fish
{
public:
static
void ViewFrame();
}//Render
SOURCEFILE:
//A list of functionpointers
Function FunctionList[] = {
NULL,
Keypressed::Check,
Vector::CalculateWireframe,
Canvas::ViewFrame,
Vector::CalculateSolid,
};
Fish *Current = NULL;
void main(void)
{
Keypressed K;
Vector V1, V2;
V1.Type = 2; //Wireframe
V2.Type = 4; //Solid
Canvas R;
//The mainloop-array
Fish *Mainloop[] = {&K,
&V1, &V2, &R, NULL};
//The mainloop
while ( !QuitFlag )
{
int
C = 0;
while
( Mainloop[C] != 0 )
{
Current = Mainloop[C];
FunctionList[Mainloop[C]->Type];
C++;
}//while
}//while
}//main
This is how the mainloop in Aquarium works. There are several things to note here.
- The array Mainloop is now built up of pointers to objects. These objects are all derived from classes that inherit from the class Fish.
- The field Type in Fish are the same index as the one we used for the Mainloop in FIG. 1.4. This index is (as in FIG. 1.4) used to select wich function to run.
- Before the function FunctionList[Mainloop[C]->Type] is run, the pointer Current is set to point to the current object in the mainloop. This is where the functions get their input-data. When you’re inside Vector::CalculateWireframe(), you know that Current points to a object of the class Vector.
- In Aquarium, the array FunctionList and the pointer Current are global variables.
- Even if this system works fine, it’s totally up to the programmer to make certain that the Fish have appropriate Type-values. If you set up a Keypressed-fish with Type=2, the Vector::CalculateSolid will be called, and you’ll probably get a nice crash. Just make sure this doesn’t happen.
This system end with in the following advantages:
- You may change the ”type” of a Fish at any time. If a vector is rendered as a solid model, you may change it into wireframe just by changing the type from 2 to 4.
- You may change the type of all Fish of the same type. You have a collection of 20 Vectors (solid rendered, that means Type=2), but this is simply too much for your poor cpu. Then you want to change all of them into wireframe-rendered vectors that requires less cputime. Instead of changing the type of all the Vectors, you can simply change the FunctionList.
- Functions may easily be inserted and removed from the mainloop, simply by adding/removing elements in the Mainloop-array.
Torbjørn Vik
Last changed 15th of June 2000