Getting Rid of the Windows Message Pump & Solving the ALT-TAB Problem
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"][b]The Message Pump[/b][/size]
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:
[indent][code]// Message Pump
while (GetMessage(&msg, NULL, 0, 0))
}[/code][/indent] All the application's operativity is defined in the member's methods, and in fact, it's a very comfortable programming model to make simple windows applications. But it's simply not very adequate for games.
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:
[indent][code]// Message Pump modified for games
while( 1 )
if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
if( !GetMessage( &msg, NULL, 0, 0 ) )
}[/code][/indent] This basic Message Pump can be optimized by inserting a flag that indicates whether the game is active (i.e. not minimized), and, if we are not active, instead of updating the world, we call the WaitMessage() function, which will set the application in an idle state until a new message arrives.
[size="5"][b]Updating the World[/b][/size]
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:
}[/code][/indent] Now, with our UpdateWorld() function, we will have use a finite state machine instead:
static int state=0;
switch( state )
case 0: // display a splash screen and return
if( FINISHED(Splash()) )
if( FINISHED(Splash()) )
}[/code][/indent] And not only this, but we will also have to define our Splash() and PlayTheGame() functions to return a finish state when they have finished. Now suppose that the Splash() function performs a FadeIn() and a FadeOut(). Splash() would look like this:
[indent][code]int Splash( void )
static int state=0;
switch( state )
// init things
if( FINISHED(FadeIn()) )
if( FINISHED(FadeOut()) )
if( state==0 ) return FINISH_STATE;
else return STILL_WORKING;
}[/code][/indent] Note that we must set the state to 0 when we finish the last step, because we must be able to invoke this function again later in the code. If we are resetting the state, then we must inform our invoker that we have finished, and that he can continue to his next state.
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.
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.
[indent][code]HANDLE hMainThread; // Main Thread handle
doInit( ... )
.... // Initialize DirectX and everything you want
}[/code][/indent] And MainThread() is defined by:
MainThread( LPVOID arg1 )
PostMessage(hwnd, WM_CLOSE, 0, 0);
}[/code][/indent] Now, here we are. MainThread() will invoke our RunGame() function, and when it is finished, we just post a WM_CLOSE message to tell the other thread to finish execution.
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():
[indent][code]while( bSuspended )
Sleep( 1000 );[/code][/indent] It's quite simple and easy to implement, and, most important, it always works. You just have to set the bSuspended global in the WindowProc() function every time that the application is activated or deactivated. Just remember to restore lost surfaces, and you are ready.
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="email@example.com"]firstname.lastname@example.org[/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="email@example.com"]firstname.lastname@example.org[/email]