Is there a better way to create this game loop? (C++/Windows) - c++

I'm working on a Windows game, and I have this:
bool game_cont;
LRESULT WINAPI WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_QUIT: case WM_CLOSE: case WM_DESTROY: game_cont = false; break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
int WINAPI WinMain(/*lots of parameters*/)
{
//tedious initialization
//game loop
while(game_cont)
{
//give message to WinProc
if(!GameRun()) game_cont = false;
}
return 0;
}
and I am wondering if there is a better way to do this (ignoring timers &c. for right now) than to have game_cont be global. In short, I need to be able to exit the while in WinMain from WinProc, so that if the user presses the closes out of the game in a way other that the game's in game menu, the program wont keep running in memory. (As it did when I tested this without the game_cont.. statement in WinProc.
Oh, and on a side note, GameRun is basically a bool that returns false when the game ends, and true otherwise.

Yes, use PeekMessage, it's the standard in game development.
This is the best approach, I believe:
int Run()
{
MSG msg;
while(true)
{
if(::PeekMessage(&msg,0,0,0 PM_REMOVE))
{
if(msg.message == WM_QUIT ||
msg.message == WM_CLOSE ||
msg.message == WM_DESTROY)
break;
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
//Run game code
if(!GameRun())
break;
}
}
}
Also, look at this (specially the first answer)

You could use exit.
Use atexit to make sure that WM_CLOSE gets to the message que when its time to exit.
I don't know what's the ultimate design here, but it's an idea.

You could make game_cont static to your main file which has WinMain/WinProc, but I don't know of a significantly better structure.

No, don't do that.
WM_QUIT is your flag. GetMessage return value indicates when WM_QUIT is encountered.
Your main window will never receive WM_QUIT, as it isn't sent to a window. WM_CLOSE will call DestroyWindow by default, so you don't need any special handling for that. Handle WM_DESTROY by calling PostQuitMessage, which results in WM_QUIT on your thread, the special return value from GetMessage, and stops your message dispatch loop.

Related

Button to join thread causes win32/winAPI application to freeze

The title pretty much summarizes what my problem is. I have this function shown below and when I press the button to join the thread the application stops responding. I'm fairly new to multithreading in c++ and I don't really know what I am doing or why this is occurring.
HWND hwnd;
//this stops the declaration of the thread from saying that hwnd doesn't exist
bool stopRaidAction;
void JoinLoop(HWND hwnd)
{
for (std::string tokenString; std::getline(std::cin,tFileContents,'\n');) {
if(stopRaidAction==false)
{
if(tokenString.length()==0){break;}
if(tokenString.length()!=0){
//send post request to join the server using token through proxy
}
}
if(stopRaidAction){break;}
}
}
std::thread joinThread(JoinLoop, hwnd);
The issue is with the "JOIN_RAID" thing
LRESULT CALLBACK WindowProcessMessages(HWND hwnd, UINT msg, WPARAM param, LPARAM lparam) {
switch (msg) {
case WM_CREATE:
Controls(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_COMMAND:
{
if ((HIWORD(param) == BN_CLICKED) && (lparam != 0))
{
switch (LOWORD(param))
{
case JOIN_RAID:
stopRaidAction=false;
MessageBeep(MB_OK);
joinThread.join();
break;
case STOP_RAID:
stopRaidAction=true;
MessageBeep(MB_OK);
break;
}
}
break;
}
default:
return DefWindowProc(hwnd, msg, param, lparam);
}
return 0;
}
joinThread.join() blocks the calling thread until JoinLoop() exits. If you do that call in your main UI thread, you will be blocking your message loop from being able to process new messages. That is why your UI freezes.
JoinLoop() is waiting on 2 conditions - user input being entered, and stopRaidAction being true while processing that input. Even if the user enters input, your main thread is setting stopRaidAction to false before calling join(), so unless JoinLoop() itself, or a 3rd thread, sets stopRaidAction to true then JoinLoop() won't exit, and so join() won't unblock.
Even if stopRaidAction were able to be set to true while join() is waiting, JoinLoop() still won't exit until the next time that user input is entered, so it can see the new stopRaidAction value. There is no way to unlocking a pending std::getline() call. So you should rewrite the way you are handling console input so you can stop waiting for input in a timely manner. See Win32 - read from stdin with timeout, for instance.
std::thread::join() is not something you should be calling until you are ready to terminate the worker thread. Which this code is not doing.
Given the code shown, it may make more sense to not create the thread at all until JOIN_RAID is clicked, and then terminate the thread when `STOP_RAID' is clicked, eg:
bool stopRaidAction = false;
std::thread joinThread;
void JoinLoop(HWND hwnd)
{
std::string tokenString;
while (!stopRaidAction) {
// read input with timeout...
if(tokenString.empty()){ break; }
//send post request to join the server using token through proxy
}
}
LRESULT CALLBACK WindowProcessMessages(HWND hwnd, UINT msg, WPARAM param, LPARAM lparam) {
switch (msg) {
case WM_CREATE:
Controls(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_COMMAND:
{
if ((HIWORD(param) == BN_CLICKED) && (lparam != 0))
{
switch (LOWORD(param))
{
case JOIN_RAID:
stopRaidAction=false;
MessageBeep(MB_OK);
joinThread = std::thread(JoinLoop, hwnd);
break;
case STOP_RAID:
stopRaidAction=true;
MessageBeep(MB_OK);
if (joinThread.joinable()) joinThread.join();
break;
}
}
break;
}
default:
return DefWindowProc(hwnd, msg, param, lparam);
}
return 0;
}
But this is just speculation since you did not explain what you are actually trying to achieve in the first place.
Update:
IT WAS THE FOR LOOP. I am literally dying. I don't even know how many hours I spent debugging only to find out that the for loop was messed up and causing the hangs.

Handling window messages in wndproc vs MSG, what's the difference?

I noticed that MSG from winuser.h (which is typedef'd to tagMSG) contains all the parameters from wndproc callback.
I was wondering if there's any real difference handling messages in wndproc vs handling the outside of wndproc by using MSG, along with the different use cases between the two
Basically something like the following
LRESULT WinApp::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
DestroyWindow(hwnd);
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
vs
MSG msg;
if ((PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
switch (msg.message)
{
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
DestroyWindow(hwnd);
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
Each window is associated with a WindowProc function. Both queued and non-queued messages that are dispatched to a window will arrive in its WindowProc. This is the preferred place to process messages that are specific to each window.
MSG is used when retrieving queued messages from a thread's message queue, before dispatching the messages to their target windows (see DispatchMessage()) . Processing messages directly in the message loop is the preferred way to handle messages that are specific to a thread but not necessarily to a specific window.
Basically it's the same, in terms of code processing and execution.
But, the main difference goes from the SendMessage function's family:
The SendMessage function calls the window procedure for the specified window...
Generally speaking, in Windows world each window has its own window's procedure, that can be called outside of the thread's message loop this window belongs to. As opposed to the PostMessage function, that:
Places (posts) a message in the message queue associated with the thread that created the specified window
So now it's clear, that messages might be handled in the main message loop, but in this case you are able to handle only the thread's messages. But you lose the ability to handle messages that was sent directly to your WndProc.
The difference is that using the normal message loop will handle cases involving multiple window classes, each with its own WndProc. What you show will only work if there is only going to be one set of message handlers for all windows (including popup menus).

How can I repeat my Code in Windows Mousehook while mouse1 is held down

I need to repeat my function in my mousehook while the left mouse button is held down. But with my current code, it only gets called once. I assumed that when I hold down the left mouse button the code gets called over and over again and I am not sure if this actually works what I wanna try. I need the code to run in the hook for timing purposes.
LRESULT __stdcall hk_mouse( int nCode, WPARAM wParam, LPARAM lParam )
{
if (nCode >= 0)
{
switch (wParam) {
case WM_LBUTTONDOWN:
{
Beep( 1000, 100 );
break;
}
case WM_LBUTTONUP:
{
break;
}
default:
{
break;
}
}
}
return CallNextHookEx( mouse_hook, nCode, wParam, lParam );
}
Mouse button messages don't repeat in Windows.
When you receive WM_LBUTTONDOWN you should create a timer with the repeat delay you require, and then handle WM_TIMER messages in your hook proc and look for the timer ID you specified when creating the timer.
When the mouse button is released and you receive WM_LBUTTONUP you should delete the timer.
You should note that any code you execute in response to the WM_TIMER message should also be executed in the WM_LBUTTONDOWN event (unless you want a delay when the button is first pressed), so it would be best to put that code in a function that can then be called from both places.

Is it possible to capture the window messages/events inside the Translate/Dispatch Message loop

Is it possible to capture the window messages/events inside the Translate/Dispatch Message loop?
this is the Message process method for the window
LRESULT CALLBACK MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
PressedKey[wParam] = true;
break;
case WM_KEYUP:
PressedKey[wParam] = false;
break;
case WM_SIZE:
break;
default:
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
and here is Translate/Dispatch Message loop
void MyScreen::RunScreenMainLoop()
{
while (WM_QUIT != msg.message)
{
//I wanna handle the messages here as well!
switch (msg.message)
{
case WM_SIZE:
show("size event called");
break;
default:
break;
}
if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//do other stuff
}
}
}
but it never goes inside the switch statement , is there a work a round to do this?
One obvious problem appears to be that you are reading msg.message before it has been initialized. Note that you are reading msg.message before calling PeekMessage. Of course, since we cannot see where msg has been declared, perhaps the real code doesn't suffer this problem.
Now, the main issue is that WM_SIZE is not a queued message and does not arrive via the message queue. There is an important distinction between asynchronous queued messages like input messages, timer messages, paint messages, and synchronous messages that are delivered direct to the window procedure. And WM_SIZE is non-queued, synchronous.
You can learn that this is so from the documentation which says:
A window receives this message through its WindowProc function.
You won't catch it with GetMessage, PeekMessage etc. The way to intercept this message is from code inside the window proc.
To learn more about queued and non-queued messages, you can start with this topic from MSDN: About Messages and Message Queues.
That is not how your message loop should look. Try this instead:
void MyScreen::RunScreenMainLoop()
{
MSG msg;
while (1)
{
if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//do other stuff
}
}
}
Then intercept messages for specific windows by subclassing those windows:
Subclassing Controls
LRESULT CALLBACK MySubWndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
switch (msg)
{
case WM_SIZE:
show("size event called");
break;
case WM_NCDESTROY:
RemoveWindowSubclass(hWnd, MySubWndProc, uIdSubclass);
break;
// other case statements as needed...
}
return DefSubclassProc(hWnd, msg, wParam, lParam); // will dispatch the message to MsgProc()
}
hWnd = CreateWindowEx(...);
SetWindowSubclass(hWnd, MySubWndProc, 0, 0);
You wrote the message pump, so you can freely add code to process messages in you rmessage loop. For instance, if you don't want WM_CHAR conversion, don't call TranslateMessage(). If you want normal windows to have dialog-style tab navigation, call IsDialogMessage(). You can literally go crazy here.
Thread messages work in the same way. Thread messages don't have a window, so DispatchMessage() does nothing to them. You have to handle those in your message pump.
However, keep in mind that if you enter a dialog box or something else that uses its own message pump, your customizations will be lost. Here's an explanation that uses thread messages.
MFC, ATL, Qt, WPF, etc. may also have their own message pump setups that you have to watch for (or their own message pumps, period, in which case you'll have to see if the API provides a hooking mechanism).
if you just wanna handle your messagens in loop, is just access the MSG for get your messages information, like this:
void MyScreen::RunScreenMainLoop()
{
while (WM_QUIT != msg.message)
{
// Handle here your messages
if (msg.message == WM_LBUTTONDOWN) // Mouse Left Button Event
{
MessageBox(NULL, L"You Clicked!", L"Info", MB_OK);
}
//
if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//do other stuff
}
}
}

How to use enter key on a Login/Password window in c++?

I am trying to add the possibility to use the RETURN key in an already made Login/Password form.
The thing is, I didn't make the form, and it is the first time I've used the Window API for Windows. So there is a lot I don't know...
So basically, a window is created, and inside of it, three child "windows" are created.
One editable: Login
A second one editable: Password
And a button: OK
Basically, I added a handle for the WM_CHAR in the message callback method:
else if (msg == WM_CHAR)
{
if (wParam == VK_RETURN)
{
char BufLogin[128];
char BufPasswd[128];
GetWindowText(hwndEditLogin, BufLogin, sizeof(BufLogin));
GetWindowText(hwndEditPasswd, BufPasswd, sizeof(BufPasswd));
print_msg(BufLogin);
print_msg(BufPasswd);
return (0);
}
else
return (DefWindowProcW(hwnd, msg, wParam, lParam));
}
(I also tried by replacing WM_CHAR with WM_KEYDOWN to no success.)
I checked on stack overflow, and the only solutions I've found so far talk of things I don't understand, or simply don't work...
I added a check in the message loog like so:
while(GetMessage(&msg, NULL, 0, 0))
{
if (!IsDialogMessage(hWnd, &msg) || msg.wParam == VK_RETURN)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
And it did work, unless the user had clicked on any child window... Basically, it worked if the user hadn't clicked on anything or modified anything, which makes the Login/password part of the window pretty useless! ^^'
So what I'm guessing is that the (!IsDialogMessage) check rules out the WM_CHAR and the WM_KEYDOWN messages (still allowing the user to type for some reason), and if I add the check for the wParam, it works but only for the parent window.
If you guys had any idea on how to work aroun this conundrum of mine, I would be so grateful! Thanks in advance! :)
(P.S.: I edited this question, before I asked on how to make the TAB key work, because the IsDialogMessage check would make each character appear four times. I simply forgot to put the ! before the IsDialogMessage! ^^'
I make this note in case anyone found my question and had the same problem I had before editing! ;) )
Ok, so I've finally figured it out! Here's my solution for it if anyone needs to do the same! ;)
I made two global variables, one for each window that needs to handle the ENTER key:
WNDPROC wpOldEditProcPass;
WNDPROC wpOldEditProcLogin;
Then I created two CALLBACK methods, again, one for each window:
(Placed them before my main CALLBACK)
LRESULT CALLBACK CustomEditProcLogin(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CHAR:
{
if (wParam == VK_RETURN)
{
SendMessage(GetParent(hWnd), WM_COMMAND, MAKEWPARAM(ID_BUTTON, BN_CLICKED), 0);
return(0);
}
}
}
CallWindowProc(wpOldEditProcLogin, hWnd, msg, wParam, lParam);
}
LRESULT CALLBACK CustomEditProcPass(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CHAR:
{
if (wParam == VK_RETURN)
{
SendMessage(GetParent(hWnd), WM_COMMAND, MAKEWPARAM(ID_BUTTON, BN_CLICKED), 0);
return(0);
}
}
}
CallWindowProc(wpOldEditProcPass, hWnd, msg, wParam, lParam);
}
In the main CALLBACK, in the WM_CREATE handle, after I created the child windows, I subclassed them:
wpOldEditProcPass = (WNDPROC)SetWindowLongPtr(hwndEditPasswd, GWLP_WNDPROC, (LONG_PTR)CustomEditProcPass);
wpOldEditProcLogin = (WNDPROC)SetWindowLongPtr(hwndEditLogin, GWLP_WNDPROC, (LONG_PTR)CustomEditProcLogin);
And then in WM_DESTROY, I gave them back their original process:
(Before PostQuitMessage(0); )
SetWindowLongPtr(hwndEditPasswd, GWLP_WNDPROC, (LONG_PTR)wpOldEditProcPass);
SetWindowLongPtr(hwndEditLogin, GWLP_WNDPROC, (LONG_PTR)wpOldEditProcLogin);
And then I replaced the:
if (!IsDialogMessage(hWnd, &msg))
With:
if (!IsDialogMessage(hWnd, &msg) ||(msg.message == WM_KEYDOWN && msg.wParam == VK_RETURN))
And it works like a charm! :D
(I still can't figure out why I have to allow a WM_KEYDOWN message, but handle a WM_CHAR message... If I allow a WM_CHAR message, it doesn't work, and if I handle a WM_KEYDOWN message, I get the obnoxious "Ding" sound when I press enter! ^^' )