Sign in to follow this  
Brain

handling input via windows messages - feedback requested

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

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

Thanks everyone, there are some absolute gems here and I really appreciate the time you've all taken to critique my code and offer up improvements. Hopefully I will find some time in the coming week to implement a timestamped buffer, and a data driven configuration system based on the suggestions here.

I do have one question though:

 

 

 


events should do nothing but send data to a buffer to be processed later (at a very specific controlled time within your game loop).
 
The 2nd problem is that you have no log of inputs.  Very many games are not purely reactionary to key-downs and key-ups.  Fighting games require a log of inputs to scan for valid key combinations to perform moves, for example.

 

Do I misunderstand here, why do we need a seperate log and buffer? Wouldn't it make sense to just always retain up to n most recent key presses from the buffer to be represented as a log, where n is the longest possible key combination in you game? It seems a bit wasteful to have both log and buffer when one is just a subset of the other yes?

 

Thanks again everyone! 

Edited by braindigitalis

Share this post


Link to post
Share on other sites

The buffer holds input until it is read by the game (which is running on another thread).

The log holds input that has been read by the game and is ready to be scanned for things such as upper-cuts.

 

These things are not running in sync even if you only use 1 thread for both (which is bad), so it is possible to have input in the buffer that hasn’t been read by the game, and if it was the log it would be “input from the future”.

 

 

L. Spiro

Edited by L. Spiro

Share this post


Link to post
Share on other sites

Hi all,

 

Again, many thanks for the feedback.

 

Based on the feedback, i have made the following changes to my code:

 

  • Changed the wndproc to use GWL_USERDATA and SetWindowLong to point to the current class, so that the message handler only exists in one location
  • Moved the game loop out into a seperate thread, so that it does not exist in the same thread as the windows message loop (i have been meaning to do this for some time anyway)
  • Created a buffer of key/mouse presses and releases, with timestamps, which is simply a std::deque of
    enum KeyEvent
    {
    	KE_NOTHING,
    	KE_KEYUP,
    	KE_KEYDOWN,
    	KE_MOUSEUP,
    	KE_MOUSEDOWN
    };
    
    class BufferedKey
    {
    private:
    	double timestamp;
    	WPARAM keycode;
    	KeyEvent keyevent;
    public:
    	BufferedKey(double ts, WPARAM key, KeyEvent ke);
    	double GetTimestamp();
    	WPARAM GetKeycode();
    	KeyEvent GetEvent();
    };
    

    ...and made the message loop just populate this buffer. The buffer is wrapped by a critical section.

  •  

    Read this buffer of pending events from within a controlled point within my game's update loop within the new worker thread. This then dispatches to the various handler classes, which handle the actual donkey work

     

    I haven't implemented a key log, as i don't need this yet. I also haven't yet added a data driven system for configuring the keyboard mappings from an external configuration file, but this is more useful to me and i will probably implement this later next week.

     

    Thanks again everyone!

Share this post


Link to post
Share on other sites

Changed the wndproc to use GWL_USERDATA and SetWindowLong to point to the current class, so that the message handler only exists in one location

 

You can use GWL_WNDPROC to change your main window procedure to a second window procedure at initialization that re-directs to the window class just casting the user pointer to the actual window class. See here.

 

 

 


Created a buffer of key/mouse presses and releases, with timestamps, which is simply a std::deque of

 

The x-device input buffer should be specialized like the one on this article. A keyboard doesn't know what a mouse is.

 

 

 


class BufferedKey
{
private:
double timestamp;
WPARAM keycode;
KeyEvent keyevent;
public:
BufferedKey(double ts, WPARAM key, KeyEvent ke);
double GetTimestamp();
WPARAM GetKeycode();
KeyEvent GetEvent();
};

 

Time-stamps should be unsigned long long for floating/double accumulation errors reasons. The same applies to the time-stamping timer accumulation. The keycode is just an integer. You should know which time the key was pressed and hold that information while the key state exists, in order to calculate its duration. See here.

 

 

 


Read this buffer of pending events from within a controlled point within my game's update loop within the new worker thread. This then dispatches to the various handler classes, which handle the actual donkey work

 

You're over-engineering it. Dispatching inputs it's always optional but it isn't mandatory. Just having the input buffer that can be consumed in the game simulation does the job. I don't know your architecture so I won't say that isn't correct.

 

 

 


I haven't implemented a key log, as i don't need this yet. I also haven't yet added a data driven system for configuring the keyboard mappings from an external configuration file, but this is more useful to me and i will probably implement this later next week.

 

Once you have the correct amount of input in the game class you can do whatever you want with.

 

I'd recommend reading this article (not because I wrote) because simplifies a lot such thing.

Edited by Irlan

Share this post


Link to post
Share on other sites

 

Changed the wndproc to use GWL_USERDATA and SetWindowLong to point to the current class

I think you misspelled SetWindowLongPtr().
 
 
L. Spiro

 

 

Yes.

[ÉDIT]

 

Oh, sorry. I tought you was quoting me.

 

braindigitalis, just follow the link I gave.

Edited by Irlan

Share this post


Link to post
Share on other sites

 

Changed the wndproc to use GWL_USERDATA and SetWindowLong to point to the current class

I think you misspelled SetWindowLongPtr().
 
 
L. Spiro

 

 

Yeah thanks, this was a typo in the post, i am using the *Ptr versions which continue to work properly with 64 bit pointers :)

Share this post


Link to post
Share on other sites


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.

Is there a reason you buffer raw input and postpone the translation to when the event is handled? It seems to me that you'd want to translate the input at the same time it's received, so in the case that the raw input doesn't actually map to anything in the game, it's not taking up space in the buffer just to be thrown out later.

Share this post


Link to post
Share on other sites

Is there a reason you buffer raw input and postpone the translation to when the event is handled? It seems to me that you'd want to translate the input at the same time it's received

Define “translate”.
Are you talking about triggering in-game actions such as jumping?
See the first link in my first post.

 

 

If you are talking about remapping keys, this should not happen until the button-press is ready to be consumed/processed unless there is no chance the mappings could change (and even then there is no reason to do it sooner rather than later).

The moment you remap keys in the options menu and hit the “OK” button, everything in the original buffer would need to be remapped.

 

 

L. Spiro

Share this post


Link to post
Share on other sites


Is there a reason you buffer raw input and postpone the translation to when the event is handled? It seems to me that you'd want to translate the input at the same time it's received, so in the case that the raw input doesn't actually map to anything in the game, it's not taking up space in the buffer just to be thrown out later.

 

In that article I've simplified by passing directly the WM_PARAM (the actual VK) to the keyboard buffer to clarify just the part of the synchronization with the game simulation, but this is how I do:

 

The keyboard input buffer it's a temporary input buffer. Since  the class it's specialized (related to the keyboard) there are two options of how you can handle its inputs:

 

Map when the key it's received and just copy the virtual code along with its time-stamp to the temporary input event structure;

Map when the key it's passed to the actual input buffer device manager (thus, the keyboard), that is, consuming from the temporary game-side input buffer; 

 

If you treat your keyboard buffer as an general abstract input buffer, you can pass the actual virtual code when you receive the input event and its time-stamp, and call it InputEventBuffer instead of KeyboardBuffer, but remember if you want to follow the SRP you should transfer the raw input events to a second class that manages the final handled inputs (in that case the keyboard).

 

Abstracting a input system it's never a good idea. You may want to follow the Single Responsability Principle and treat the device input buffer as a specialized input buffer. So, having keyboard, gamepad, mouse, touch, motion controller, etc. it's always a good to take.

 

So, having a specialized class and mapping directly when the key event it's received it's a good solution since you have a specialized class.

Share this post


Link to post
Share on other sites

Is there a reason you buffer raw input and postpone the translation to when the event is handled?

 

"Translate" can be divided in two parts:

 

First:

 

Translated raw inputs scan codes to virtual key codes;

Translate virtual key codes to temporary specialized device input buffer key IDs;

 

Second:

 

Translate game-side temporary input to a specialized input device manager to be used by the game or a input logger.

Translate recognized engine key IDs to the input logger;

Translate input logger key events into a game-side recognized input events, such jump, fire, etc.

 

After the second you can do whathever you want such creating game-side input contexts, input logging, etc. 

 

Once you have specialized input information for each type of device received and handled at the correct time you can start doing your high-level input system.

Edited by Irlan

Share this post


Link to post
Share on other sites


Define “translate”.

By "translation" I meant mapping the raw input to actions, but not actually handling or processing them (which is done via a buffer as described above).

 


If you are talking about remapping keys, this should not happen until the button-press is ready to be consumed/processed unless there is no chance the mappings could change (and even then there is no reason to do it sooner rather than later).
The moment you remap keys in the options menu and hit the “OK” button, everything in the original buffer would need to be remapped.

I was under the impression that there wouldn't be very many frames between when an event is put into the buffer and when it's processed, otherwise the responsiveness of your game would suffer. Even if the mapping did change though, as a user I would expect the original mapping at the time the key was pressed to take effect. It would just be weird if I pressed a key expecting one action, then at some point in the future a different action took place... even if I somehow remapped the key in that time window. So that's probably an even better reason you'd want to map ahead of time, other than just taking up space in a buffer.

Share this post


Link to post
Share on other sites

By "translation" I meant mapping the raw input to actions, but not actually handling or processing them (which is done via a buffer as described above).

 

You don't map raw input events to actions. You map raw input events to recognized input events that can be handled at specific time on the game loop. After having recognized keyboard events that can be consumed in one logical update, that is, inputs that ocurred up to the actual game time, you can start mapping these to input context actions or logging that to be used later.

Edited by Irlan

Share this post


Link to post
Share on other sites


You don't map raw input events to actions. You map raw input events to recognized input events that can be handled at specific time on the game loop. After having recognized keyboard events that can be consumed in one logical update, that is, inputs that ocurred up to the actual game time, you can start mapping these to input context actions or logging that to be used later.

That's arguing semantics a bit. You don't directly map raw input to actions, but you start with raw input and end up with an action (potentially), via some amount of translation/mapping processes. I'm just curious as to was why the final mapping to an action was postponed until the event is about to be consumed, as opposed to when all the previous mappings occur.

Share this post


Link to post
Share on other sites

I also question why the input system needs to maintain a log of events, if it doesn't serve any purpose in the system itself. If you need the log for game-specific functionality like combos, it seems better to me to put it into a separate system that just processes combos and can better handle the log using knowledge of its specific needs.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this