0
\$\begingroup\$

I am trying to multi-thread my game so that one thread updates and renders the player, while the other listens for inputs. I used a while loop to keep my detached thread running in the background, which is working, but my player isn't moving even though the code tells it to do exactly that. If I put MessageBox(NULL, "Testing", "Alert", NULL) in Player::update or Player::draw in Player.cpp, the window spams message boxes, which means the code IS running, but the player is NOT moving. Is it a problem with one of the Player functions or with the thread itself?

Window.cpp

#include <windows.h>
void quit();
void setup(HDC, PAINTSTRUCT, HWND);

const char g_szClassName[] = "myWindowClass";

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CLOSE:
        DestroyWindow(hwnd);
        quit();
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_PAINT:
        PAINTSTRUCT ps;
        HDC hdc;
        hdc = BeginPaint(hwnd, &ps);

        setup(hdc, ps, hwnd);

        EndPaint(hwnd, &ps);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
        break;
    }
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, "Window Registration Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        "Testing!!!",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
        NULL, NULL, hInstance, NULL);

    if (hwnd == NULL)
    {
        MessageBox(NULL, "Window Creation Failed!", "Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

Main.cpp

#include <iostream>
#include <thread>
#include <windows.h>
#include "Functions.h"
#include "Player.h"
using namespace std;

void draw(HDC);
void startThreads();
void checkForInput();
bool Quit = false;
HDC hdc;
PAINTSTRUCT ps;
HWND hwnd;
Player player(50, 50);

void setup(HDC hDC, PAINTSTRUCT pS, HWND hWND)
{
    hdc = hDC;
    ps = pS;
    hwnd = hWND;
    startThreads();
}

void checkForInput()
{
    while (!Quit)
    {
        if (GetKeyState(27) & 0x8000)
        {
            Quit = true;
        }
    }
}

void quit()
{
    Quit = true;
}

void draw(HDC hdc)
{
    while (!Quit)
    {
        player.draw(hdc);
        player.update();
        Sleep(16);
    }
}

void startThreads()
{
    thread mm(draw, hdc);
    thread input(checkForInput);
    input.detach();
    mm.detach();
}

Player.cpp

#include <windows.h>
#include "Player.h"
#include "Functions.h"

Player::Player(int xx, int yy)
{
    x = xx;
    y = yy;
}

void Player::update()
{
    x++;
}

void Player::draw(HDC hdc)
{
    rect(hdc, x, y, 50, 50);
}

Player.h

#pragma once
#include <Windows.h>

struct Player {
public:
    int x;
    int y;

    Player(int, int);
    void update();
    void draw(HDC hdc);
};

Functions.cpp

#include "Functions.h"

void rect(HDC hdc, int x, int y, int w, int h)
{
    Rectangle(hdc, x, y, x + w, y + h);
}

void ellipse(HDC hdc, int x, int y, int w, int h)
{
    Ellipse(hdc, x, y, x + w, y + h);
}

Functions.h

#pragma once
#include <windows.h>

void rect(HDC, int, int, int, int);
void ellipse(HDC, int, int, int, int);
\$\endgroup\$
  • 1
    \$\begingroup\$ You're only allowed to draw when handling WM_PAINT. \$\endgroup\$ – user253751 Sep 26 '18 at 1:23
  • \$\begingroup\$ @immibis Can you post that as an answer so I can mark it? Using RedrawWindow to recall WM_PAINT made it work. \$\endgroup\$ – Priswall Sep 26 '18 at 13:49
1
\$\begingroup\$

In Windows, you're only supposed to draw, or at least you're only allowed to use the HDC from BeginPaint, in the WM_PAINT handler. Drawing anywhere else usually won't work properly.

To trigger a repaint, call RedrawWindow or InvalidateRect.

Then do all the painting inside the WM_PAINT handler.

\$\endgroup\$
1
\$\begingroup\$

The while loop you have above is what is called a 'busy-wait' which chews through CPU resources but does no useful work. You can do that for very short periods, but you've effectively tied up 100% of a CPU core doing nothing. A better way to do this kind of input-checking is to use a timer to regularly check for input but that yields the CPU the rest of the time. You should also make your 'quit' flag a event so that other threads can check to see if you are quitting since you need to ensure all your threads can shutdown properly to avoid crashes on exit.

On Windows, the best option is using WaitForSingleObject or WaitForMultipleObjects with a timeout. You can of course use older Win32 message-loop style timers with WM_TIMER, but using events, semaphores, mutexes, etc. are much more efficient.

HANDLE g_quitEvent;

// Should be created before you start up your threads
// We use ``CREATE_EVENT_MANUAL_RESET`` because once signaled, all threads
// that check for the quit event should get the event once it's fired.
g_quitEvent = CreateEventEx(
    nullptr, nullptr,
    CREATE_EVENT_MANUAL_RESET,
    EVENT_MODIFY_STATE | SYNCHRONIZE);
if (!g_quitEvent)
    // Error

while (!Quit)
{
   switch (WaitForSingleObject(m_quitEvent, 16 /* check at around 60 fps */))
   {
   case WAIT_OBJECT_0:
       Quit = true;
       break;

   case WAIT_TIMEOUT:
       if (GetKeyState(27) & 0x8000)
       {
           SetEvent(g_quitEvent);
       }
       break;

    case WAIT_FAILED:
       //  GetLastError() will tell you more about the failure
       throw std::exception("WaitForSingleObject");
    }
}

You should read Coding For Multiple Cores on Xbox 360 and Microsoft Windows. It's a little old, but still useful information.

You may also want to look at Lockless Programming Considerations for Xbox 360 and Microsoft Windows, but you really need a good grounding on basic multi-threading before attempting to use lockless models.

Accurate game-time can also be a challenge with multi-threaded designs, so be sure to read Game Timing and Multicore Processors.

\$\endgroup\$
  • \$\begingroup\$ Thank you! I had no idea that this would cause any problems in the long-term, and I appreciate the links. I will be sure to give the links a read \$\endgroup\$ – Priswall Sep 25 '18 at 16:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.