🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Basic Windows Message loop

Started by
15 comments, last by Funkymunky 4 years, 5 months ago

Common wisdom is to construct a game's message pump as some flavor of this:

	MSG msg = { 0 };

	while(msg.message != WM_QUIT)
	{
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			Update();
		}
	}

Does this still make sense with modern multi-threaded engine design? Might it not be better to use GetMessage instead of PeekMessage, which blocks until a message is received, and let a separate thread(s) handle all the game logic/rendering? Then the actual window handling can't get hung up by the rendering, and the rendering doesn't get potentially hiccuped by windows events…

Advertisement

Funkymunky said:
PeekMessage, which blocks until a message is received

Who told you about?

PeekMessage from MSDN

Dispatches incoming sent messages, checks the thread message queue for a posted message, and retrieves the message (if any exist)

doesn't say anything about blocking the thread until a message receives, this is the purpose of the game loop to run until some important OS messages arrive. The issue is that you have to capture those messages to handle something like input, window resize and application exit events send from the OS to your process. If you ignore those messages then there will be a message stall and Windows will detect your application to hang (while displaying the message box to the user) so you want to prevent this.

But you are free to change this behavior as long as you are publishing OS messages to your engine! Modern game engines are multithreaded (at least the AAA ones) and split up the game loop in several ways. However, code may need to optain the exact frametime so you need to track that in your message thread because the message handling is part of the delay between two frames.

One possible path is that the engine will start to prepare the update loop in another thread while render whatever objects is to display when there is space to do so. It then awaits while still processing messages until the update thread is finished and start rendering again. You can use events, a render queue or any other appropirate method to update your game state while also receiving input events from the render thread (where your message pump lives), for example user input that has to be propagated to the update thread. You still should handle rendering (or at least display logic) in your main thread or else there could occure ugly artifacts while Windows is doing something with it's own renderer that handles the GDI drawing for example.

Multithreading is difficult and even experts (that I don't count myself to) are struggling when it comes to designing a multithreaded architecture so it is easier for most games/ game engines to have a single threaded game loop. Unity is such an example, even while they are moving more and more into threaded environment, user code is still and has to be executed single threaded in the main thread

PeekMessage never blocks, which means you are always running at 100% CPU usage, even when your Update function got nothing to do, because it just did an update. That is, except when you rely on vsync to block you.

With GetMessage you get no updating when there is no messages, although you can try having Windows generate timer messages for you (this probably will not look smoothly).

In both cases, your Update function will not be called when moving/resizing the window. Thats why you want a separate thread for everything besides Windows messages. Just translate and forward all messages on your own threadsafe queue and make sure quitting waits for the other thread. Additionally, this prevents stalling the Windows messaging when your Update function has a hickup, making your program always seem responsive to Windows.

Ah, my bad, when I said:

Might it not be better to use GetMessage instead of PeekMessage, which blocks until a message is received, and let a separate thread(s) handle all the game logic/rendering?

I meant that GetMessage blocks until a message is received. Not PeekMessage.

What I'm saying is that I would leave the thread handling windows messages by itself. I would launch other threads that manage the game logic, rendering, etc. When a message comes in and wakes up GetMessage that the game logic / rendering needs to do something about (WM_QUIT, WM_SIZE, etc.), I'd interop with the threads at that point. That way my updating/rendering is independent of the windows messages.

Is there a reason not to do this?

wintertime, when you say:

In both cases, your Update function will not be called when moving/resizing the window. Thats why you want a separate thread for everything besides Windows messages. Just translate and forward all messages on your own threadsafe queue and make sure quitting waits for the other thread. Additionally, this prevents stalling the Windows messaging when your Update function has a hickup, making your program always seem responsive to Windows.

That's exactly what I'm talking about. A “separate thread for everything besides Windows messages” ultimately means I should call GetMessage instead of PeekMessage. Since there's no reason to spin in a thread that's only handling windows messages if there aren't any new messages to handle.

Like so:

	bool alive = true;
	std::thread thread = std::thread(Update, &alive);

	while(GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	alive = false;
	thread.join();

Where “Update” is a function that has a while(*alive) loop that doles out work to a threadpool. I guess the main concern is over-saturating the CPU cores, but with the windows thread sleeping most of the time and the Update function waiting for the previous frame before kicking off a new round of work, it seems like a better solution than constantly calling Update at a potentially un-even rate. Plus now the window renders as I drag it around, which is nice.

Doing this you always have a performance hit when something happens and it will definitely happen that there is something send from the OS. At least player input messages.

What will happen here in your implementation is Windows interrupting your game with the message thread because lets assume your thread pool is balanced (what I highly recommend) to the CPU core count, then unless you leafe one open for catching interruptions, one of your pool threads will always be scheduled into wait mode when the message thread returns.

Doing something like

while(running)
{
    if(PeekMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else ThreadPool::ExecuteSingleTask();
}

turned out like a better solution because you can spend the message thread doing something instead of waiting until the OS decides to return and with a work stealing approach (the thread pool balances runtime performance by allow idle threads to ask other workers for tasks) this will also not impact your game too much

@funkymunky You should not have such a thread-unsafe alive variable. If it stops the other thread it would be at wrong time anyway (too early when your queue sending the other messages could still contain messages or too late when the message thread gets WM_QUIT).

Just send a stop message over the message queue you use to send all other interesting messages over when you get WM_DESTROY in your WindowProc. Additionally, you could forward WM_CLOSE as a different message if you want to ask in your GUI if the user really wants to quit, then depending on user decision call DestroyWindow.

Btw., Don't forget ValidateRect on WM_PAINT.

I would try having only a second thread at first, which got an inner loop for working on all queued messages and an outer loop doing the updates. When everything works correctly there, you can think about if it would be worth complicating it further by introducing a thread pool.

@shaarigan Switching to the message thread for a message and back will probably run more smoothly than completing a random workitem, unless you can guarantee all your workitems are tiny (which you'd normally try to avoid, because that generates more overhead). Thats why I would not mix up both things in one thread.

@shaarigan I'm not sure why you think there will be a performance hit, or that Windows will interrupt my asynchronous threadpool. I'm not updating Windows controls in those threads, I have a DirectX12 renderer that is generating command lists. Again, like I said, barring CPU core saturation, I think this is a better way for a multi-threaded program to operate.

@wintertime My “alive” variable is thread-safe, I'm not using it recklessly. I also have no clue why you would want to forward over the windows messages and process them in a different thread.

I've measured my frametimes and they are indeed smoother when using GetMessage and having all update/rendering logic asynchronously active.

Another option is to always keep a repaint event in the queue. Whenever you get WM_PAINT, post another repaint event with InvalidateRect(). That way, it doesn't matter which particular event queue polling method you use. It's also not dependent on timer or such.

enum Bool { True, False, FileNotFound };

Funkymunky said:

@wintertime My “alive” variable is thread-safe, I'm not using it recklessly. I also have no clue why you would want to forward over the windows messages and process them in a different thread.

A normal bool variable like in your code fragment above is not thread-safe. You'd need at least an atomic flag, but as you have to communicate other data to the other thread anyway and communicating that data would go into a threadsafe queue, the bool is superfluous. Additionally, there is a race condition in that the atomic flag could be set, the other thread would see that and could quit without emptying possibly important other messages from the queue.

I did not say you should directly forward all windows messages to the other thread. You need to translate the interesting ones for your game into some useful messages for your game though, to let the other thread know about keys, chars, mouse messages for a user interface or window size changes for the renderer to adapt to them or the destroy message for when to stop or character movement for gameplay or other things. The best way to communicate these is through a threadsafe queue like mentioned above, because using shared state is what makes multithreading difficult and messaging is easy to reason about.

This topic is closed to new replies.

Advertisement