The Message Loop

Introduction

DoEvents

Is There A Better Way?

The Better Way

Being A Good Neighbour

Wrapping Up

Lesson Downloads

Introduction

The Message Loop (also called the Message Pump) is at the core of every Windows program. It handles receiving and dispatching all of the various events that a Windows application can receive. Mouse clicks and key presses are examples of these events. Proper and efficient handling of these events is important if you want to have a robust and efficient application.

DoEvents

In Initializing Direct3D a very simple Message Pump was used. The DoEvents method would process all waiting messages and then return so that the next rendering frame could be processed. While business applications generally wait until there is user input before acting, a game will usually be running at full speed regardless of whether the user has pressed any keys. At first glance DoEvents appears to be exactly what we want. It processes and dispatches the messages without waiting if the message queue is empty. It's also very simple, only requiring a single function call.

Simple and robust, it seems ideal. Where it falls down is performance. If you run the Initializing Direct3D sample in windowed mode and watch the memory consumption in Task Manager you will notice that the memory allocated by the application is steadily growing. Inside DoEvents memory is being allocated. Normally this memory would be freed by the garbage collector but running in a tight loop, the garbage collector never has that opportunity. In addition to wasting large amounts of memory, DoEvents can have a serious impact on the performance of your application.

Is There A Better Way?

Since Managed DirectX was introduced a number of different Message Loops have been presented. These attempts have generally had poor performance and/or relied on ugly hacks. Finally a reasonable Message Pump has emerged. It performs very well and conceptually it is easy to understand and reasonably elegant. A large strike against it in my eyes is that it requires a call into the native Win32 API.

If Managed DirectX (and to a lesser degree, .Net in general) is to be taken seriously as a real solution it should be possible to handle typical situations in a reasonably performant manner without having to call into the Native Win32 API. This is similar to the rise of C as a replacement for Assembler. C was not considered a viable alternative until it was proven repeatedly that it was no longer required to use Assembly to get reasonable performance.

The Better Way

When an application has no messages waiting in the message queue, an Idle event handler is called. If we take control of that event and run our render loop we should get incredible performance. But if we loop within the event then it will never leave that event handler and no other messages will be processed.

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. Unfortunately, .Net does not have anything of the sort, but the native Win32 API does : PeekMessage. Using DllImport we can pull in PeekMessage and make it available to our application.

   // We won't use this maliciously, honest

   [System.Security.SuppressUnmanagedCodeSecurity] 
   [DllImport("User32.dll", CharSet=CharSet.Auto)]
   public static extern bool PeekMessage(
      out peek_message msg,
      IntPtr hWnd,
      uint messageFilterMin,
      uint messageFilterMax,
      uint flags
   );
   <System.Security.SuppressUnmanagedCodeSecurity()> _
   Public 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

PeekMessage also fills in a message structure and returns it in the first parameter. This is not optional so even though we never use the resulting structure we need to define it so we can safely use PeekMessage.

   [StructLayout(LayoutKind.Sequential)]
   public struct peek_message{
      public IntPtr hWnd;
      public Message msg;
      public IntPtr wParam;
      public IntPtr lParam;
      public uint time;
      public System.Drawing.Point p;
   }
   <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

With that out of the way we are free to call PeekMessage to check for waiting messages. Here is a simple method that returns true if there are messages waiting, false if there are not.

   protected bool AppIsIdle(){
      peek_message msg;
      return !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
   }
    Protected Function _AppIsIdle() As Boolean
      Dim msg As MessageStruct

      Return (PeekMessage(msg, IntPtr.Zero, 0, 0, 0) = 0)
    End Function

Building on that we can now write our processing loop which simply calls our Heartbeat function while there are no messages waiting.

   protected void OnApplicationIdle(object sender, EventArgs e){
      while (AppIsIdle()){
         Heartbeat();
      }
   }
   Protected Sub OnApplicationIdle(ByVal sender As Object, ByVal e As EventArgs)
      Do While _AppIsIdle()
         _Heartbeat()
      Loop
   End Sub

Finally, in our Main function we add OnApplicationIdle as a handler for the Idle event. Calling Run launches the app which will churn away rendering as fast as it can.

   System.Windows.Forms.Application.Idle += new EventHandler(app.OnApplicationIdle);
   System.Windows.Forms.Application.Run(app);
   AddHandler System.Windows.Forms.Application.Idle, AddressOf app.OnApplicationIdle
   System.Windows.Forms.Application.Run(app)

Being A Good Neighbour

I'm a fan of fast rendering. In general I believe that Direct3D applications should grab the CPU and run with it. There are exceptions to every rule, however. When running in Windowed mode the application is not guaranteed to always have the users focus, and if the user is focusing elsewhere it doesn't make sense to continue running as fast as possible. All that would accomplish is making the user's machine run poorly and annoy the user. Remember, your application is a guest when running on someone else's computer. A good guest should be considerate.

There are a few different ways to play nicely with other applications. The one I'm going to discuss here assumes:

  • When our application is active we want to run like we own the machine
  • When we are NOT active we want to give other applications plenty of room to run
  • We want a method that is simple to manage
  • The application will still run, just much slower

Every Windows Form has an ActiveForm property which returns the active form. A very simple method is to check if our form is the active form and if so we render as normal. If our form is not the active form, we call Sleep and give up the CPU. Adding this bit to the top of our Heartbeat function takes care of everything.

   //We aren't the active window, so relax a bit and free up the CPU

   if (ActiveForm != this) {
      Thread.Sleep(500);    
   }
   'We aren't the active window, so relax a bit and free up the CPU

   If Not ActiveForm Is Me Then
      Thread.Sleep(500)
   End If

Wrapping Up

While it's unfortunate that we still do not have a viable solution that is 100% .Net, the work-around isn't that painful. This doesn't mean I'm going to stop ranting about making things better; everyone needs a hobby.

Lesson Downloads

Back