Glorious were the days when we programmed our games in a mostly linear fashion. We didn't have to worry about multitasking, DirectX or message pumps. We just programmed games.
It was a very big jump to port our creations to DirectX, but that was the way that the market was leading. Most importantly, you could achieve a performance that could only have been dreamed without the hardware acceleration and tuning that the DirectX team provided.
But there was this big problem: the Windows Message Pump. It is simply not adequate for game programming. In this article I propose to present an effective way to encapsulate the message pump, provide a linear programming model and, as a very desirable side effect, allow correct ALT-TAB application switching.
[size="5"]The Message Pump
The Windows operating systems demands that our application constantly check for inbound messages for all our members (windows, buttons, listboxes, or whatever). The Windows Messaging System is the heart of the cooperative model that the Microsoft team adopted. We can't just simply forget about the message pump, because it informs us of important things like 'You've been deactivated', and 'You've been reactivated', among many others. A typical Windows application has the following basic layout:
// Message Pump
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
The most used technique to start making games under Windows is to replace the GetMessage() function, which blocks the execution thread, with a couple of functions. PeekMessage() will check if any messages are waiting, and the PM_NOREMOVE parameter tells the function not to remove the message from the queue. If this function returns true, the GetMessage() function can then be used to retrieve the message. Otherwise, if no message is to be processed, we can call our UpdateWorld() function, that will be in charge of updating all the world variables and rendering the new scene. The following code shows this:
// Message Pump modified for games
while( 1 )
{
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
if( !GetMessage( &msg, NULL, 0, 0 ) )
{
return msg.wParam;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
UpdateWorld();
}
[size="5"]Updating the World
Our next step is to code the UpdateWorld() function. This should be very simple; all your application variables, surfaces and interfaces have already been initialized, and now you just have to update and render them. That's an easy task.
Yes indeed, it's an easy task, but only if you plan that your game will have only one screen, no menus, no options, and nothing.
Let's suppose that we wanted to build a simple DirectX application that shows a splash screen, then goes into the game, and then shows the same splash screen again.
In the most traditional linear programming way, we would do something like this:
void GameMain(void)
{
Splash();
PlayTheGame();
Splash();
}
void UpdateWorld(void)
{
static int state=0;
switch( state )
{
case 0: // display a splash screen and return
if( FINISHED(Splash()) )
state=1;
break;
case 1:
if( FINISHED(PlayTheGame())
state=2;
break;
case 2:
if( FINISHED(Splash()) )
TerminateApp();
break;
}
}
int Splash( void )
{
static int state=0;
switch( state )
{
case 0:
// init things
state=1;
break;
case 1:
if( FINISHED(FadeIn()) )
state=2;
break;
case 2:
if( FINISHED(FadeOut()) )
state=0;
break;
}
if( state==0 ) return FINISH_STATE;
else return STILL_WORKING;
}
Well, what we have just done is just a very simple application. Now imagine thirty or forty of these functions, each one with a couple dozen states - as I had to do to port Malvinas 2032 from DOS4GW to DirectX - and you will be facing a very big monster. Try to debug, or even follow this code, and you will get really insane. As you can see, this finite state programming model is far more complicated that the simple one achieved by old linear programs.
[size="5"]The Solution
To solve this, I developed a fairly simple multithreading model, that frees the game's programmer from the message pump and its undesirable finite state programming model.
Fortunately, Windows supports multithreading. This means that our application runs several simultaneous execution threads. The idea is very simple: put the message pump in one thread, put the game in another thread.
The message pump will remain in the initial thread (being the initial thread does not imply that it has more privileges or importance). We can thus remove the UpdateWorld() function from the Message Pump and return it to its simplest form.
Now, we just need to add the code necessary to initiate the game thread to the DoInit() function.
HANDLE hMainThread; // Main Thread handle
static BOOL
doInit( ... )
{
.... // Initialize DirectX and everything you want
DWORD tid;
hMainThread=CreateThread( 0,
0,
&MainThread,
0,
0,
&tid);
return TRUE;
}
DWORD WINAPI
MainThread( LPVOID arg1 )
{
RunGame();
PostMessage(hwnd, WM_CLOSE, 0, 0);
return 0;
}
[size="5"]ALT-TABBING
Making a game truly multitasking under Windows is perhaps one of the most hazardous issues in game programming. A good application must be able to correctly switch to another application. Some games simply won't allow the user to ALT-TAB at all. We will try to do it the right way.
As a first attempt, we can try to use the SuspendThread() and ResumeThread() methods in the WindowProc() function (the one that effectively handles the messages), but, although I've tried and tried, I've never been able to get them to work. Sometimes, the thread was successfully suspended, but it would never get back to life. If someone has been able able to get this approach to work, I'd love to know what you have done.
What I've done to solve this, with very positive results, is to implement waiting code in my FlipSurfaces() function (since it is constantly being called while the game is running). I declared a global bSuspended variable (globals have multithreaded scope) to inform the game thread that the game is in suspended state. Then I've inserted this code in FlipSurfaces():
while( bSuspended )
Sleep( 1000 );
With this simple implementation, when the user ALT-TABs, the game will stop its execution, freeing all the processor's time to enable the user to do other tasks. If your game needs to not be suspended, just suspend the rendering pipeline, and keep on updating the world.
I hope that you liked this article, and that you find the linear programming model as enjoyable and rewarding as I do. You can contact me at [email="javier@sabarasa.com"]javier@sabarasa.com[/email] with any comments, criticism, queries or suggestions about this article. I am really curious if anyone here likes the finite state machine programming model using the message pump.
(C) 2000 by Javier F. Otaegui - email: [email="javier@sabarasa.com"]javier@sabarasa.com[/email]