Jump to content
  • Advertisement
Sign in to follow this  
Brain

handling input via windows messages - feedback requested

This topic is 1248 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi all,

 

I was considering writing a short article on how i've handled my input in my game. The reason for this is simply that ive been told by many people that a lot of newbies approaching the problem of input make what could be classed as the "wrong choice" of either using directinput or GetAsyncKeyState to handle keyboard/mouse input.

 

Before i start or even spend any time writing an article however i am seeking feedback on the code i have written to see if anyone else here has any feedback about how they might do this differently and if it makes sense.

 

I am aware there are simpler and easier ways to handle input but after reading up on the subject, many ways shown in tutorials although simple make things such as keyboard remapping and handling of non-keyboard input devices such as gamepads difficult - i believe this approach will simplify such things as i get to them.

 

Each "command" which may be bound to a key press is defined by an object which inherits its class from a class called KeyCommand:

 

class KeyCommand
{
private:
    bool keydown;
protected:
    FireworkFactoryWindow* creator;
public:
    KeyCommand(FireworkFactoryWindow* parent);
    virtual void Action() = 0;
    bool IsPressed();
    virtual void Pressed();
    virtual void Released();
};

 

The "Pressed" method is called whenever the key mapped to the command is pressed, e.g. attached directly to the WM_KEYDOWN event, while the "Released" method is mapped directly to the WM_KEYUP event. The default behaviour of the Pressed and Released methods is simply to set and clear the keydown bool.

 

The Action() method is called repeatedly from the game loop if and only if the key is currently depressed. This allows for either an ordered press/release event on the Pressed/Released method, or a simple edge triggered continually called event on the Action() method, for example for updating movement etc:

 

for (std::map<WPARAM,KeyCommand*>::iterator k = keymap.begin(); k != keymap.end(); ++k)
{
    if (k->second->IsPressed())
        k->second->Action();
}

 

Inside my message loop, just after the PeekMessage() function i check for WM_KEYDOWN and WM_KEYUP messages. As you can see i am also handling mouse events here but am not sure about this as it can introduce numbering conflicts, and might revisit this soon:

 

WPARAM Window::Loop()
{
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));

    while (true)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;
            else if (msg.message == WM_KEYDOWN || msg.message == WM_LBUTTONDOWN)
                this->KeyDown(msg.wParam);
            else if (msg.message == WM_KEYUP || msg.message == WM_LBUTTONUP)
                this->KeyUp(msg.wParam);
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            this->Iteration();
        }
    }

    return msg.wParam;
}

 

You might have noticed that i don't do this inside my wndproc. The main reason for not putting this inside the wndproc is because the Loop function is part of a window class, and the wndproc by its nature must be static, which means that i would be unable to call the KeyUp and KeyDown virtual methods from the static member function.

 

A std::map holds the current mappings between windows virtual keycodes and commands and is set and updated at changes of state so that different states may have different behaviours of keys:

 

keymap[VK_LEFT] = new LeftCommand(this);
keymap[VK_RIGHT] = new RightCommand(this);
keymap[VK_UP] = new UpCommand(this);
keymap[VK_DOWN] = new DownCommand(this);
keymap[VK_SPACE] = new SpaceCommand(this);
keymap[MK_LBUTTON] = new ClickCommand(this);

 

The KeyUp and KeyDown virtual functions handle the keypresses by notifying a class of the change of state by finding them in the map:

 

void FireworkFactoryWindow::KeyDown(WPARAM key)
{
    std::map<WPARAM,KeyCommand*>::iterator k = keymap.find(key);
    if (k != keymap.end())
        k->second->Pressed();
}

 

... The KeyUp method is obviously very similar and i will not repeat it here for sake of brevity.

 

Each of the commands handles the expected behaviour for that key press in a simple and succinct way, for example:

 

void DownCommand::Action()
{
    float speed = 0.08f;
    Camera* c = creator->GetCam();
    c->SetDistAndPitch(c->GetDist() - speed * 3.0f, c->GetPitch() + (speed / 4.0f));
}

 

This about sums it up.

 

I would appreciate firstly any feedback on this code, and how it operates as i am always looking to improve, and secondly if you think this might make useful article material if presented correctly,

 

I await your replies!

Share this post


Link to post
Share on other sites
Advertisement

A question for Spiro, after re-reading some of the linked posts/topics.
In this reply to Hodgman you mention that on the input thread you should be using WaitMessage() (and i assume you can also use GetMessage()). I read that as saying "instead of peeking all the time and running as fast as possible, use a blocking call and let windows manage how fast the thread runs while still having an accurate enough timestamp". My question here is, what if you have different input devices, such as gamepads, that need to be polled?

 

The way i did this in my code is, i PeekMessage() in a while loop, and after the loop i poll the gamepads via XInput. If the state for the gamepad changed, i generate input messages that the game can read (and which is the same struct used for mouse/keyboard input, only different data) and put them into an input buffer that the game polls for that frame/logical tick.

 

Is there a different/better way to handle other input devices that are purely poll based and don't have WM_* events? This question assumes you can't get gamepad/other input device data via WM_* messages, since i'm not sure if WM_INPUT can be used to at least trigger a poll of those devices.

Share this post


Link to post
Share on other sites

You might have noticed that i don't do this inside my wndproc.

 

The solution to this problem it's to use ::GetWindowLongPtr and ::SetWindowLongPtr to change the window procedure after you receive a WM_CREATE message after the window creation:

LRESULT CALLBACK CWindow::OnCreateWndProc(HWND _hWnd, UINT _uiMsg, WPARAM _wParam, LPARAM _lParam) {
	if (_uiMsg == WM_CREATE) {
		::SetWindowLongPtr(_hWnd, GWL_USERDATA, reinterpret_cast<LONG>(reinterpret_cast<LPCREATESTRUCT>(_lParam)->lpCreateParams));
		::SetWindowLongPtr(_hWnd, GWL_WNDPROC, reinterpret_cast<LONG>(WndProc));
		return 0LL;
	}

	return ::DefWindowProc(_hWnd, _uiMsg, _wParam, _lParam);
}

LRESULT CALLBACK CWindow::WndProc(HWND _hWnd, UINT _uiMsg, WPARAM _wParam, LPARAM _lParam) {
	return reinterpret_cast<CWindow*>(::GetWindowLongPtr(_hWnd, GWL_USERDATA))->WindowProc(_hWnd, _uiMsg, _wParam, _lParam);
}

You should use WM_INPUT/raw input for gamepads, touch-screens, etc.

 

You should use WM_INPUT for high-frequency input devices and you should avoid unnecessary dynamic allocation of its buffer when you poll that. At window initialization after you register what are the devices that need to be polled with WM_INPUT, you can create dynamic array of RAWINPUT with its size being something like sizeof( RAWINPUT) * 4. If you see that you have received a WM_INPUT message buffer size bigger than that, you can re-allocate more memory once instead of re-allocating each time (which can be very consuming) and keep that information on the window (or related) class. Example (for mouse):

case WM_INPUT: {
                /*
                * m_prRawInputBuffer was created at initialization.
                */
		UINT uiRawInputBufferSize;
		::GetRawInputData(reinterpret_cast<HRAWINPUT>(_lParam),
			RID_INPUT,
			NULL,
			&uiRawInputBufferSize,
			sizeof(RAWINPUTHEADER));

		if (uiRawInputBufferSize > m_ui32RawInputBufferSize) {
			delete m_prRawInputBuffer;
			m_ui32RawInputBufferSize = uiRawInputBufferSize / sizeof(RAWINPUT);
			m_prRawInputBuffer = new RAWINPUT[m_ui32RawInputBufferSize];
			::OutputDebugStringA("\nGetRawInputData(...) returned buffer size different of current size. Re-allocating.");
		}

		if (!::GetRawInputData(reinterpret_cast<HRAWINPUT>(_lParam),
			RID_INPUT,
			reinterpret_cast<void*>(m_prRawInputBuffer),
			&m_ui32RawInputBufferSize,
			sizeof(RAWINPUTHEADER))) {
			::OutputDebugStringA("\nGetRawInputData(...) failed.");
		}

		if ( m_prRawInputBuffer->header.dwType == RIM_TYPEMOUSE ) {
			const RAWMOUSE& rmRawMouse = m_prRawInputBuffer->data.mouse;
			if (rmRawMouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN) {
				//Handle it.
			}
			else if (rmRawMouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP) {
				//Handle it.
			}
			else if (rmRawMouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN) {
				//Handle it.
			}
			else if (rmRawMouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) {
				//Handle it.
			}
			else {
				//Mouse move. Handle it.
			}
		}

There should be another way of handling the problem, but that was more intuitive and faster for me, so, feel free if you want to search for a more general solution.

 

 

 


Keys should be enumerated and have specific ID’s. A mapping feature only needs to have an array where there is one integer for each key, and that integer is the value of the remapped key. m_uiKeyMap[KK_ENTER_KEY] = KK_0_KEY;.

 

Complementing what L. Spiro said, a window class shoudn't know about keys/buttons that will be used in your game/application. On Windows®, the only thing that it kowns it's VK_* code, which you map that after pass it into the game/application input buffer.

 

In L. Spiro example the virtual key codes are enumerated (I believe) to KK_ENTER_KEY, KK_SPACEBAR_KEY, etc, and the application/game/engine-side keys are KK_0_KEY, KK_1_KEY, and so on.

 

Here, I've used the _wParam (which it's the VK_* for a WM_KEY* message) directly as an example, but the window-side input buffer has a buffer of keys which its codes are VK_* codes. After you pass that into the application-side, at specific point, you map into your application-side keyboard keys. Then, you have mapped keys and you can start re-mapping these to the application input-contexts or logging that with it's time of duration, etc (which I'd recommend).

 

Independent how you manage input (synchronously or asynchronously), you need to have this kind of separation not only for organization but cross-platform support too. Generally people tend to use synchronously input because they only think as FPSs, but today any type of game requires a lot of complexity in its input system, and doing it synchronized with the game simulation it's the only way of measuring input the fastest we can at any time using time-stamped buffered inputs, that allows we hold a button for 100ms at any lag time and compensate that in logical updates for robust input logging.

Edited by Irlan

Share this post


Link to post
Share on other sites

In L. Spiro example the virtual key codes are enumerated (I believe) to KK_ENTER_KEY, KK_SPACEBAR_KEY, etc, and the application/game/engine-side keys are KK_0_KEY, KK_1_KEY, and so on.

There is only 1 enumeration.
A less-confusing example:
m_uiKeyMap[KK_ENTER_KEY] = KK_SPACE_KEY;

L. Spiro

Share this post


Link to post
Share on other sites

A less-confusing example:

m_uiKeyMap[KK_ENTER_KEY] = KK_SPACE_KEY;
L. Spiro

I am only more confused by this example, since now it appears you're mapping the 'Enter' key to the 'Space' key... unless I'm missing something?

 

In the input system I just finished refactoring, the mapping flow is: platform-specific "virtual" key (i.e. VK_A, VK_B, etc.), to platform-agnostic "virtual" key (VIRTUAL_KEY_A, VIRTUAL_KEY_B, etc.), to game-specific command (COMMAND_JUMP, COMMAND_CROUCH, etc.). This seems to neatly handle every input scenario we support, and that I can think of.

Edited by Zipster

Share this post


Link to post
Share on other sites

In the input system I just finished refactoring, the mapping flow is: platform-specific "virtual" key (i.e. VK_A, VK_B, etc.), to platform-agnostic "virtual" key (VIRTUAL_KEY_A, VIRTUAL_KEY_B, etc.), to game-specific command (COMMAND_JUMP, COMMAND_CROUCH, etc.). This seems to neatly handle every input scenario we support, and that I can think of.

 

I'm using a similar way too. When a input is received I keep its raw key code along with a time-stamp to process it later, then, when it's time to process I map those to a engine-side recognized device key code, that can be mapped later to a game-side key code in the input context or logger.

 

I believe L. Spiro took a shortcut by enumerating the keys once without changing its overall architecture.

Edited by Irlan

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!