Writing the Game Loop

Home | Up | Search | X-Zone News | Services | Book Support | Links | Feedback | Smalltalk MT | The Scrapyard | FAQ | Technical Articles

 

Writing the Game Loop

Written by Robert Dunlop
Microsoft DirectX MVP


Related Articles of Interest:

Implementing Steady Motion
Selecting Timer Functions

What Is It?

At the core of every complex application is a loop - a portion of code that gets executed time and again throughout the life of the program.  This loop, often referred to as the "Main" loop, is responsible for coordinating the programs actions, and establishes the backbone of an application.

The concept of a main loop has been present since the beginning, but their form has changed much as the world has moved from DOS style operating systems into the multi-threaded realm.  These changes have some important ramifications for high performance applications such as games, and since this is the core that your application will be built upon it is important to pick the right path early on.

 

  The Good Old Days

Many game developers, even though they may be happily entrenched in the latest technology, will reminisce fondly over the day when DOS ruled the land.  Even though the hardware had a fraction of today's power, and there was no common interface to access the features of a user's system, one thing it definitely did provide - a sense of control.

The flowchart on the left illustrates a typical game loop that  you would find in a DOS based game.

Unlike the multi-threaded applications of today, where an application must contend for a time slice, the programmer has full control over virtually every operation the processor executes.

 

The Windows Message Loop

Now that we have taken a look at the way things were handled in the DOS days, a quick look that the structure of a typical Windows application will show a different world altogether :

   

After looking at the game loop in DOS, our first glance at the main loop in a Windows application may be a little confusing.

First of all, where did everything go???  Nothing of any real importance seems to be happening here!

Well, this is because Windows is an "Event Driven" operating system.  Rather than being a based upon a constant course of action, a Windows program consists of routines that are responsible for handing various events, such as the mouse moving, the screen being redrawn, etc.

Under this structure, then, the sole job of the main loop is to make sure that these events get handled.  It waits  for Windows to send a message that an even has occurred, and then turns around and send it to the appropriate handler.

 

 

Can This Work for Games??

In its default for, the message loop model of a Windows program is not one that will lend itself to game performance.  The reason is that the design philosophy behind a typical Windows application and a high performance game are totally different:

Application Philosophy :
bulletDon't get in the way
bulletOnly take use of CPU when we are told to
bulletSpend most of our time sleeping till someone wakes us
bulletBe friendly to other applications and step aside
bulletThings will get done when they need too, no hurry

Game Philosophy :
bulletTake all that you can get
bulletWe control all that you see and hear....
bulletDon't let up for a moment
bulletWe're on a deadline here - move it!
bulletOnly let others have control as it benefits us

As you can see, these are two very different ways of thinking.  So, do we just replace our message loop with a game loop akin to the days of old?  The answer is a resounding NO!!! 

Taking complete and utter control of the system in the Windows world will not provide the performance we need.  Rather, it will bring the system to its knees.  The reason for this is simple : we are reliant on other services to provide access to the system, so we must let Windows run on.

So, is there a happy compromise to be found?  A way to insure our performance, while allowing the system to take care of things?  Yes, there is, but it will require a few changes.

Dealing With Messages

A typical message loop in Windows looks something like this :

MSG msg;

while( GetMessage( &msg, NULL, 0, 0 ) ) {
    TranslateMessage( &msg );
    DispatchMessage( &msg );
}

The function of this loop, known as the "message pump", can be broken down by looking at each of its functions:

GetMessage : This function is used to retrieve a message from the Message Queue.  If there is not a message available, calling this function causes the thread to sleep until a message is received.   If the message is WM_QUIT, then GetMessage() will return zero, causing this loop to exit.

TranslateMessage : This function provides translation of keyboard messages, and must be called on all messages before dispatching them.

DispatchMessage : Calls the appropriate handler or sends the message to the appropriate child window.

Keeping Awake

The problem with using such a message pump to drive a game is that the GetMessage function will cause the thread to sleep until a message is available.  This puts us at the mercy of Windows, causing our main loop only to execute when Windows has a message for us.

Below is a revised message loop that is better suited to our needs :

MSG mssg;

// prime the message structure

PeekMessage( &mssg, NULL, 0, 0, PM_NOREMOVE);

// run till completed

while (mssg.message!=WM_QUIT) {

// is there a message to process?

if (PeekMessage( &mssg, NULL, 0, 0, PM_REMOVE)) {

// dispatch the message

TranslateMessage(&mssg);
DispatchMessage(&mssg);

} else {

             // our stuff will go here!!
     }
}

Looking at the revised code, you should note several major differences :

bulletPeekMessage is being used in place of GetMessage, to avoid being put to sleep
bulletThere is now a test for the WM_QUIT message.  This is because PeekMessage does not test for this itself, but rather returns whether it found a message to process.
bulletThere is now an outer loop encompassing the message pump.  This is so that we can process the game loop, and allow for processing even when there is no message available.

Alternatives we have Avoided

Before we proceed to fill out our loop, let's take a brief look at some of the other possible methods we have avoided :

bulletOnIdle : This method is intended for handling of low overhead background tasks that are not time-critical.  Obviously this does not describe a game!  Some of the pitfalls that prevent OnIdle from being useful to us:
bulletOnIdle will only be called when there are no messages available.
bulletOnce OnIdle has been started, no more messages can be processed until OnIdle returns.
bulletOnce OnIdle lets go of its time slice, the thread will sleep.  OnIdle will not be called again until the thread has received new messages and finished processing them
bulletOnTimer : OnTimer and other event based timing techniques are reliant upon the message loop to activate an event handler in the application.  The messages used for timer completion have a low priority, and may be preempted by other messages - resulting in delayed notification and inconsistent timing.

Applying a Throttle

Now that we have a tight inner loop to control our game, let's take a look at how to control the rate of the game play.  First you may want to review the timer functions available, which can be found in our article Selecting Timer Functions

The loop below provides an example of how to create a throttled game loop, which is limited to 25 frames per second.   To keep things simple, we will limit our game loop to 2 functions :

bulletMoveObjects(), which will be called once per frame and can be started any time after the render function is called.
bulletRenderFrame(), which is called each frame to draw the scene.  This function will be the pacing function, that we will call every 40 ms.

MSG mssg;                // message from queue
LONGLONG cur_time;       // current time
DWORD  time_count=40;    // ms per frame, default if no performance counter
LONGLONG perf_cnt;       // performance timer frequency
BOOL perf_flag=FALSE;    // flag determining which timer to use
LONGLONG next_time=0;    // time to render next frame
BOOL move_flag=TRUE;     // flag noting if we have moved yet

// is there a performance counter available?

if (QueryPerformanceFrequency((LARGE_INTEGER *) &perf_cnt)) {

// yes, set time_count and timer choice flag

perf_flag=TRUE;
time_count=perf_cnt/25;        // calculate time per frame based on frequency
QueryPerformanceCounter((LARGE_INTEGER *) &next_time);

} else {

// no performance counter, read in using timeGetTime

next_time=timeGetTime();

}

// prime the message structure

PeekMessage( &mssg, NULL, 0, 0, PM_NOREMOVE);

// run till completed

while (mssg.message!=WM_QUIT) {

// is there a message to process?

if (PeekMessage( &mssg, NULL, 0, 0, PM_REMOVE)) {

// dispatch the message

TranslateMessage(&mssg);
DispatchMessage(&mssg);

} else {

// do we need to move?

if (move_flag) {

// yes, move and clear flag

MoveObjects();
move_flag=FALSE;

}

// use the appropriate method to get time

if (perf_flag)

QueryPerformanceCounter((LARGE_INTEGER *) &cur_time);

else

cur_time=timeGetTime();

// is it time to render the frame?

if (cur_time>next_time) {

// yes, render the frame

RenderFrame();

// set time for next frame

next_time += time_count;

// If we get more than a frame ahead, allow us to drop one
// Otherwise, we will never catch up if we let the error
// accumulate, and message handling will suffer

if (next_time < cur_time)
    next_time = cur_time + time_count;

// flag that we need to move objects again

move_flag=TRUE;

}

}

}

Related Articles of Interest:

Implementing Steady Motion
Selecting Timer Functions

This site, created by DirectX MVP Robert Dunlop and aided by the work of other volunteers, provides a free on-line resource for DirectX programmers.

Special thanks to WWW.MVPS.ORG, for providing a permanent home for this site.

Visitors Since 1/1/2000: Hit Counter
Last updated: 07/26/05.