DirectX:Direct3D:Tutorials:VBNET:DX9:Better Game Loop
A better game loop
The game loop (or message loop/pump) is the loop in which you compute data.
Most game related pumps works as following:
- Initialize game
- Load media
- Game loop: start
- Execute AI
- Get user input
- Calculate physics
- Render graphics
- Game loop: end
- Unload media
- Terminate game
DoEvent is Evil?
Here we will be discussing some options to increase performance in the game loop. For a variety of reasons, using DoEvent's in a loop is totally unacceptable, as DoEvents does some allocations that, due to the nature of a game loop, result in allocations getting promoted and ignored by the GC.
You can observe this by running the following code:
If you run the above code and check the system tray, you'll notice that your application is consuming memory.
To explain it in simple terms, DoEvents allocates resources which is not cleaned up by the garbage collector.
In addition to not using doevents in loops; also never enable visual styles when looping DoEvents!
Depending on the end-user system, enabling visual styles in DoEvent loops can cause memory loss of up to 1MB/5 seconds.
The solution: PeekMessage
What is needed is an inexpensive way of checking if there are messages waiting in the queue so we can bail out of the loop and let them be processed.
The PeekMessage function dispatches incoming sent messages, checks the thread message queue for a posted message, and retrieves the message (if any exist).
PeekMessage is native Win32 API, and not an .NET function. This means that we need to define the functions location and arguments:
<System.Security.SuppressUnmanagedCodeSecurity()> _ Private Declare Function PeekMessage Lib "User32.dll" Alias "PeekMessageA" ( _ ByRef msg As MessageStruct, _ ByVal hWnd As IntPtr, _ ByVal messageFilterMin As Integer, _ ByVal messageFilterMax As Integer, _ ByVal flags As Integer _ ) As Integer
- Warning: By adding the SuppressUnmanagedCodeSecurity attribute to the call, .NET will skip the security-related checks that it would make before allowing your application to make a call out to a native Windows method. This is extremely dangerous and should not be done without carefully considering the security ramifications of making the call. In these cases, you should make sure you declare the function as private.
We also need to create the structure MessageStruct, which is called as the argument "msg" in the function. The structure code is as following:
<StructLayout(LayoutKind.Sequential)> _ Public Structure MessageStruct Public HWnd As IntPtr Public Msg As Message Public WParam As IntPtr Public LParam As IntPtr Public Time As Integer Public P As System.Drawing.Point End Structure
Now that we have set up our definition and structure we can begin using it.
First up we will create a function that returns true if messages are waiting, or false if not.
Protected Function _AppIsIdle() As Boolean Dim msg As MessageStruct
Return (PeekMessage(msg, IntPtr.Zero, 0, 0, 0) = 0) End Function
Using the function: _AppIsIdle
Above we created a function to determine if messages are waiting. To use it, we create a simple sub that should loop a call to your game loop.
Protected Sub OnApplicationIdle(ByVal sender As Object, ByVal e As EventArgs) Do While _AppIsIdle() '<-- call game loop from here - such as Private Sub Render() from the previous tutorials Loop End Sub
We are almost done now. All that's left to do is to make your application to call "OnApplicationIdle", preferably in the sub main before executing the form.
dim app as new form1 AddHandler System.Windows.Forms.Application.Idle, AddressOf app.OnApplicationIdle
This should make sure that OnApplicationIdle is called when all messages have been dealt with.
Using resources right
Having a good game loop is not just about utilizing the system resources to the max. In fact, sometimes this can be a real bother for the end-user.
If your application is running in full-screen, and the user Alt-Tabs to check an IM, or in windowed mode if the user decides to pause and check mail or something - having the application stuck in the game loop can be dead waste of valuable CPU.
One way to minimize dead CPU usage is to have your application know if it currently has focus. If now, we can rather safely assume that resources should be diverted from executing your application to free processing power for use by the overall system.
'We aren't the active window, so relax a bit and free up the CPU If Not ActiveForm Is Me Then Thread.Sleep(200) End If
Having the above code in your game loop can reduce the CPU usage from >90% to almost null, which you can imagine boost system performance a lot when your application is minimized.
What did we learn?
- Hopefully how to create a descent message pump
- Suitable measures to ensure speed and responsiveness from our application in relation with the end-user system
- When we should go easy with system resources