Programming 2D Games

The official forum for "Programming 2D Games" the book by: Charles Kelly

It is currently Tue Oct 15, 2019 12:08 am

All times are UTC




Post new topic Reply to topic  [ 6 posts ] 
Author Message
PostPosted: Sun Oct 05, 2014 1:47 pm 
Offline

Joined: Fri Oct 19, 2012 5:50 pm
Posts: 32
My problem started out as, when I launch in fullscreen, my program doesn't work. After much slow code stepping, I think I've narrowed the issue down quite a bit.

I'm getting in an infinite loop in my message handler.

Before I start pasting code, let me say that I have very, very carefully tried to make sure that I'm initializing my window class, creating my window, and initializing and creating all directx elements with all the same settings and procedures that you use in Spacewar. I'm using 1280x720/60Hz fullscreen in both my game and Spacewar, which I have open side-by-side in concurrent MSVC++ 2010 Express windows. The windows and directx initializations in my engine are virtually identical to yours, and in any differences I could find, I changed my settings to match yours. I'm getting S_OK on all my creation results, and everything seems to go fine up to the point where I enter my message handler, which I will now paste:

Code:
bool GAMEENGINE::WINDOWS::HandleMessages()
{
   while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
   {
      if (msg.message==WM_QUIT)
      {
         return FALSE;
      }

      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

   return TRUE;
}


If you compare my message handler to yours, it's clear that I'm using a loop here and yours does not. And obviously, if I changed my 'while' to an 'if', this problem would go away. But I like the structure of while(PeekMessage) in theory. It seems arbitrary to only take one message and deal with it. Why not take them all?

Well I found what seems to be a good reason why. WM_PAINT. According to MSDN, PeekMessage does not remove WM_PAINT, even if you tell it to. So I could easily see where this would loop endlessly, if the WM_PAINT message were never removed. However, here's where things take another turn:

I modified your message handler in Spacewar to the following:

Code:
while (!done)
        {
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
            {
                // look for quit message
                if (msg.message == WM_QUIT)
                    done = 1;

                // decode and pass messages on to WinProc
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }// else
                game->run(hwnd);    // run the game loop
        }


As you can see, I changed the 'if' to a 'while', and commented away the 'else' condition. Coincidentally, I'm not sure I understand the 'else' condition in the first place. Wouldn't you want to execute game->run() regardless of whether a message was handled? Anyway, I ran Spacewar with those changes, and also modified to run at the same resolution as my game, and to launch in fullscreen. Spacewar handled it with no problems. No infinite loop. Messages were getting dispatched. I even tested for WM_PAINT in there specifically, and not even that message causes an infinite loop in your code. The game runs with no problems.

Here's what I'm doing specifically, running both games in debug alternately. I put a breakpoint somewhere in the message loop, let's say at TranslateMessage(). I run the application and let it hit that breakpoint. At that time, I set a second breakpoint at the first instruction in the actual WinProc function. When I do this with Spacewar, it hits these two breakpoints alternately, sometimes hitting one multiple times consecutively, but never getting stuck in a loop. When I do this with my game, the second breakpoint is never hit, and the application never leaves the message loop. Specifically, once it gets through the first half-dozen or so messages, it just keeps getting 15 (WM_PAINT) over and over. The callback is properly registered, however--before the program hits the message loop, the callback is used a number of times. Also, this infinite loop only happens in fullscreen mode.

So ultimately I guess my question is why does my loop get stuck, and my modified version of your loop doesn't? And for that matter, why mine works windowed but not in fullscreen. I'm sorry, this is very long-winded. I'm just trying to supply all the information I have. As for my WinProc, for testing purposes, I've essentially just trimmed it down to pass the buck on everything to the default winproc. Any ideas or insight you might have will be greatly appreciated.


Top
 Profile  
Reply with quote  
PostPosted: Mon Oct 06, 2014 4:41 pm 
Offline
Site Admin
User avatar

Joined: Sat Jan 28, 2012 4:36 pm
Posts: 553
There is no need to modify the message loop in order to process all of the pending messages. The existing while (!done) loop already takes care of that. If a message is pending it is processed with the calls to TranslateMessage and DispatchMessage. After all of the messages are processed the game->run function will be called. We don't call the game->run function in every loop because we want to process the Windows messages first. The Windows messages may contain information that we want the game loop to use. Things like key press and mouse movements, etc.
Code:
        // main message loop
        int done = 0;
        while (!done)
        {
            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
            {
                // look for quit message
                if (msg.message == WM_QUIT)
                    done = 1;

                // decode and pass messages on to WinProc
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            } else
                game->run(hwnd);    // run the game loop
        }

_________________
Professor Kelly


Top
 Profile  
Reply with quote  
PostPosted: Tue Oct 07, 2014 11:48 am 
Offline

Joined: Fri Oct 19, 2012 5:50 pm
Posts: 32
Ah, yes, you're right, that makes total sense. But I guess the fundamental problem still remains. In fullscreen, I'm never hitting the 'else' clause. PeekMessage keeps finding a WM_PAINT message, and I can't figure out why.


Top
 Profile  
Reply with quote  
PostPosted: Tue Oct 07, 2014 3:21 pm 
Offline
Site Admin
User avatar

Joined: Sat Jan 28, 2012 4:36 pm
Posts: 553
Does the problem exist if you use the message handler from my game engine?

_________________
Professor Kelly


Top
 Profile  
Reply with quote  
PostPosted: Tue Oct 07, 2014 4:54 pm 
Offline

Joined: Fri Oct 19, 2012 5:50 pm
Posts: 32
When you say the message handler, do you mean the part that does Peek/Translate/Dispatch, or the actual WNDPROC function that ultimately handles the messages?

This may shed some more light on the matter. I've determined that my WNDPROC is not being called at all after d3d->CreateDevice(). So that explains the infinite loop concerning the while(Peek). The messages are getting dispatched...essentially out into space, and therefore WM_PAINT never gets removed, because it's not dealt with in any way. So the question becomes, what is happening with CreateDevice() to cause Windows to disassociate my WNDPROC from my application? (If that is indeed what is happening.)

Here's what I've come up with as a test case, copied from within my d3d init stuff:

Code:
SendNotifyMessage(engine.mainWindow.hWnd,WM_CHAR,32,0);

d3d_result = d3d->CreateDevice(D3DADAPTER_DEFAULT,
   D3DDEVTYPE_HAL,
   engine.mainWindow.hWnd,
   D3DCREATE_HARDWARE_VERTEXPROCESSING,
   &d3dpp,
   &d3ddev);

SendNotifyMessage(engine.mainWindow.hWnd,WM_CHAR,32,0);


So essentially I'm wrapping CreateDevice() in two identical, deliberate attempts to trigger the Win Proc. The first call to SendNotifyMessage() triggers my Win Proc, and the second call does not. Very interesting. I feel like I'm close to an answer here.

My presentation parameters are identical to yours. As for the behavior, it's the same as yours for fullscreen, though I have tried SOFTWARE and MIXED with no change. And by the way, d3d_result is S_OK after CreateDevice(). I'm wondering if it's a problem that my HWND is a class member and not a global variable.

If I go windowed, the second call to SendNotifyMessage() also triggers the Win Proc.

A sign that I've been working on this for too long: I just tried to hit Ctrl+Space as I was typing SendNotifyMessage()...


Top
 Profile  
Reply with quote  
PostPosted: Wed Oct 08, 2014 10:06 am 
Offline

Joined: Fri Oct 19, 2012 5:50 pm
Posts: 32
Whenever I solve one of these pesky problems, I feel brilliant and profoundly dumb at the same time. Your instincts were on the money. The problem was in my Win Proc. You see, CreateDevice() itself generates 3 windows messages: WM_NCACTIVATE (134), WM_WINDOWPOSCHANGING (70), and WM_DISPLAYCHANGE (126), in that order. I had a case for WM_DISPLAYCHANGE and it never even dawned on me that it was getting triggered. I put it there as part of the message handling for DirectShow, which I use for video playback. Here's that snippet of the win proc:

Code:
case WM_DISPLAYCHANGE:
   {
      engine.video->DisplayModeChanged();
   } break;


engine.video refers to my video player struct, which houses the necessary DirectShow components. I thought this case should be pretty benign anyway, considering that it still ultimately just proceeds on to the default win proc anyway. But this part of the code happens right before DirectShow is initialized, which means engine.video is NULL. But it executes the code anyway, or at least tries to, which leads to an (alarmingly unreported) access violation, and the handler never reaches the default win proc, but the program marches on like everything is fine.

Anyway, I added "if(engine.video)" in all the relevant places and everything works. How dumb I feel for being felled by a NULL pointer. I think one of these days soon I'm going to do a Ctrl+F and look for everywhere in the entire solution I use -> to dereference a pointer, and see if I need any more idiot checks. Still, I've never seen a null pointer affect a program in such an obfuscated way. If I didn't have two monitors, I don't know that I ever would have figured it out.

Thanks again for your help, Professor. I'm recommending your book on my blog (that no one reads) and in the credits of my game (that'll never get finished).


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group