I discovered something this weekend while playing around with the game loop I outlined in my post last week. Seems I completely misread the function of SendNotifyMessage and was using it in place of PostMessage. However, if SendNotifyMessage works similarly to SendMessage when used on the same thread, why was I not getting a stack overflow exception like I would have received with SendMessage?
A reader emailed me today that he'd discovered that SendNotifyMessage was in fact causing some recursion, it was inexplicably breaking after 40 calls. This explained a behavior that had caused me to change my game loop in the first place over the weekend. I had noticed that when I enabled V-Sync in my game, my input handling became very granular -- on the order of 2 updates per second. With my refresh rate locked at 85Hz and a 40 frame updates per recursion loop, that would explain exactly the behavior I had observed. Every 40 frames, recursion would break, and my WndProc could handle all the input events that had stacked up.
After getting some feedback on last week's post, I decided it would be safer to have a WM_USER message to handle my painting rather than posting WM_PAINT, which is not a reccomended action in Win32 world. While I hadn't seen any adverse behavior because of it, I hadn't tested it across lots of different environments. Therefore, I chose to err on the side of safety and implemented a somewhat more robust solution. This solution included a counter that prevents my UserPaint messages from "stacking up" in my message queue, and would avoid any complications from posting my own WM_PAINT messages. However, the allocation profiler shows a that the UserPaint method might be delaying OnMouseMove and similar events. This is problematic for those of you wishing to use those events.
Using PostMessage still didn't work very well with Winform input events, so I'm taking it down. Thanks to feedback from poster Bernhard, he's discovered that this loop seems to work spectacularly, and is totaly compatible with WinForms input handling:
protected override void WndProc(ref Message m)
So it seems that consuming the WM_PAINT message is causing a new WM_PAINT to be thrown by the WinForm despite the styles I've set (ControlStyles.UserPaint and ControlStyles.AllPaintingInWmPaint). Thottled back to an 85Hz refresh rate using VSync, I have verified that input was handled in-time (even better than the first solution). I've got 3 applications I'm working on concurrently to test the solution. The first is a super-lightweight profiling app designed to inject messages and look for allocations. The second is an app with no injections that looks for timing information. The last application is the game engine I'm using for the book. In my game engine and my timing profiler, Bernhard's solution gives similar results to the game loop above. I'm not quite sure why not passing the WM_PAINT message on works so well, but it does. I have to admit, it sounds too good to be true, but it's showing remarkably good behavior in my test applications.
I even tried this new loop in the April SDK and I was pleased with the results. I saw a slight (2 to 5%) improvement in framerates over the existing DoEvents() loop, and a monsterous improvement in number of garbage collections per minute (improved about 90% on a couple samples). So for a gauranteed framerate, it'll be tough to beat this code at the heart of your application. When I get some more time, I'll post a table with statistics on all the techniques I've covered and add new loops as we find them. Thanks again for everyone's feedback!