Sign in to follow this  
Erius

How to deal with 'lost' input events?

Recommended Posts

Erius    124
Hello everyone :). I'm using raw input to read input events. Language is c++, and need some pointers about how I should handle lost input events. As in, for example: -Keeping both left and right mouse buttons depressed does something as long as you keep them pressed. -You release the keys, but as you did, the program hiccuped for some reason and the events are not picked up by the handler. -The action, obviously, continues. How do I handle such things the best? My thoughts so far: - A separate thread. Probably the most obvious solution, but I have little experience with threading - A timer that assumes that something has gone wrong because a specific time has passed between two frames and thus clears the button states. I can see this working, but I can also see annoying stuff happening, like having to press the combo over and over again ,especially if there is some stutter going on. - Some way of polling relevant button states after a certain time has passed between two frames. Probably a more elegant solution, but I don't know how to pull that off. The thing is, Raw Input does not, to my knowledge, which is limited, seem to register continuous XYZ_DOWN events, which makes this tricky to me... I want to have the option to have timed combos or plain old charging elements with inputs. Like hold backwards for 3 seconds and press a button to unleash doom of all kinds... Which ... isn't too affected by the problem with just that, since if it needs 3 seconds, it should be fine with 10 seconds as well, but what if it's a bow, and keeping it held for 5 seconds makes the wielder grow fatigued, etc. A hacky solution would be pressing the button again, triggering the up event eventually, but I really do like responsive and good controls, so I'd like to avoid that, if possible. (Bad controls are a big turnoff for me, a game should never have bad controls, it can kill immersion, imho if it does.) So yeah. Insight is very welcome and highly appreciated. Doesn't even have to be code (though that would help too), can be anything, even referrals to books or other threads. (I searched around but the only thing that seemed similar to my problem was a thread which never got resolved xP) With hat said, thank you for your time! Cheers!

Share this post


Link to post
Share on other sites
zyrolasting    280
Hey Erius!

Quote:
-You release the keys, but as you did, the program hiccuped for some reason and the events are not picked up by the handler. -The action, obviously, continues.


This should not happen. I can picture getting a delayed notification under heavy workloads, but never just "hiccuping" and not doing work. That kind of behavior is what I expect from the rednecks I work with. When you typed that post, did anything just "hiccup" and the last character you typed repeated several times? Computers don't make mistakes, so I wouldn't try to fight non-existent input demons. [smile]

It is possible to use input devices irresponsibly like in DirectInput, but the only way I can think of for virtual key messages to be garbled is a keyboard with Gatorade in it.

Quote:
A separate thread. Probably the most obvious solution, but I have little experience with threading


If volatile input is a concern, how is another thread going to help? It's not like it executes code any differently.

Quote:
A timer that assumes that something has gone wrong because a specific time has passed between two frames and thus clears the button states.
I can see this working, but I can also see annoying stuff happening, like having to press the combo over and over again ,especially if there is some stutter going on.


You are either being extremely paranoid or it's about time you ran a malware scan. Are you actually observing stuttering input behaviors in several games?
While we're at it, is your PC on fire?

I digress. What I'm trying to say is you at the whim of the OS. Your application has several messages queued that it will receive and process in order. You don't have stutters to worry about, just hangs. You also don't introduce undefined behavior like what you are describing unless you do something stupid like delete 10 keys from HKEY_LOCAL_MACHINE blindfolded or headbutt your monitor with your mouth full of water. Open up Notepad and type in it. While you do this, notepad follows the same rules you do to co-exist with the OS, so if you can type in Notepad fine you should not have problems getting input for your game.

RAW Input is processed in WM_INPUT under Windows, and your application will get that message when the OS is good and ready to give it to you. The mere fact you can get your window to display something is a sign you are getting messages and you have nothing to worry about except Your OWN code. Don't take problems you (may) have and give a suspicious glance to Windows.

Quote:
The thing is, Raw Input does not, to my knowledge, which is limited, seem to register continuous XYZ_DOWN events, which makes this tricky to me...


What on earth are XYZ_DOWN events? I just Googled it and this topic was the first result out of 3. If you mean something like key repeats, I would not worry about using RAW input for that, as I believe you implement behaviors like that yourself. If you were asking for someone's name, etc, then use WM_CHAR. Key repeat will be taken care of, and will be at the speed the user set it on their PC.

Quote:
Like hold backwards for 3 seconds and press a button to unleash doom of all kinds...


Easy enough. Consider this pseudo-code.


switch ( key )
{
/* Pressed means you knew a key was up before, and now you see it's down.*/
case PRESSED:
bCharge = true;
fChargeTime = 0.0f;
break;

case RELEASED:
if ( bCharge )
{
if ( fChargeTime >= 3.0f ) // If key was held for 3 seconds...
{
killBaby();
}
bCharge = false;
fChargeTime = 0.0f; // Charge is released either way.
}
break;

case DOWN:
// Accumulate frame-relative time.
fChargeTime += fDeltaTimeFromTimer;
break;
}





The flow should be clear enough for you to do what you want with.

Hope this helps and cheers!
-Zyro

[Edited by - zyrolasting on November 11, 2009 8:24:13 PM]

Share this post


Link to post
Share on other sites
Codeka    1239
Quote:
Original post by Erius
-Keeping both left and right mouse buttons depressed does something as long as you keep them pressed.
-You release the keys, but as you did, the program hiccuped for some reason and the events are not picked up by the handler.
-The action, obviously, continues.
That is not possible. I assume when you say you're using "raw input" you're talking about WM_INPUT, right?

A quick overview of how raw input (and in fact, all user input) works: When an input event occurs, a message is placed on your application's per-thread message queue so that when you call GetMessage(), it just pulls the first available message off the queue. You'd then call DispatchMessage() and your WndProc would be called to process it. If your program doesn't call GetMessage for a while (say, it "hiccuped") then messages would simply pile up in your thread's queue until your program started calling GetMessage again. No messages are "lost".

Are you trying to solve an actual problem, or is this just theoretical? If you're having an actual problem, can you describe the symptoms that you're seeing and perhaps post a bit of code?

If it's just a theoretical problem you think you might run into, then don't worry - it's not possible :)

Share this post


Link to post
Share on other sites
Erius    124
Well, thanks for the replies, it did give me some direction, and I guess I'm more paranoid than anything.

Also, I should have called hiccups freezes or hangs or something else, but that's irrelevant.
(As well as XYZ_down events, 'twas intended as a general wildcard for anything that can be pressed, but oh well)

Anyway. It's not about holes or headbutting computers while spitting soft drinks into their tubes.
One of those freezes, for example, can easily be produced by setting up an listener that executes a 50ms Sleep when one clicks or presses a key, if the key is released during the sleep-period, it will repeat the event until one manages to somehow hammer a keyup event in there.

Granted, that is an exaggerated event which will hopefully never manifest as a real issue, but I still wanna be prepared for such a thing, even if it's just once in a blue moon.

If somethign silly like sleep can screw it up, how about a hang because the user decided to use a console command to spawn a truckload of something at once.

Should be his fault, but that's no excuse not to think about how to make something like that as painless as possible.

Plus, I played Aion, a very recent game, and it manages to screw up the mouse controls on a regular basis.
I know, that's NC softs problem, not mine, but man such things are sooo unnessesary.

Anyway, after thinking this through, I do realize that I'm just nitpicky, but controls ARE the interface for the user, it should be as robust as possible.
Plus, it's not like an input sheme requires millions of lines of code like other components, so why slack?

But yeah, I guess I'm going to figure this out myself, GetAsyncKeyState looks promising, I might scratch it all and go for a simple sheme in the end, but the exercise is helpful.

Cheers.

Edit:
@Codeka:
Well, I haven't run into a real-not-induced-bysleep-etc problem yet, but as I said above, it did screw up everything.
Also, I'm doing buffered raw input reads, which seems to live outside of the standard messages.
I can extract input events into the buffer before the actual message loop/pump begins.
My single state sheme uses WM_INPUT, though, yes.
(Buffered input must have some sort of upside, though ,right? Just looks more thorough to me)

Share this post


Link to post
Share on other sites
bitshifter    113
Messages in the queue are sorted internally by their priority.
You might want to play around with GetAsyncKeyState or GetKeyboardState.
In my experience they provide a more solid connection than message based.

Share this post


Link to post
Share on other sites
Codeka    1239
Quote:
Original post by Erius
One of those freezes, for example, can easily be produced by setting up an listener that executes a 50ms Sleep when one clicks or presses a key, if the key is released during the sleep-period, it will repeat the event until one manages to somehow hammer a keyup event in there.
What is "it" in this case?

Your program should be able to call Sleep(300000) if you wanted it to. Once Sleep() returns, your app will call GetRawInputBuffer as normal and all the events that occurred in between should be there.

If that's not what you're seeing, then there is a bug in your code.

Share this post


Link to post
Share on other sites
Erius    124
Hm, that is odd then.
Time for some code extracts (includes pseudocode)

Main loop:



while (forever)
{



input_manager.scan_input();
screw_stuff_up(& input_manager);
main_window.msgpump ();
timestep(1);
render();
}






scan input:

UINT cbSize;

GetRawInputBuffer(NULL, &cbSize, sizeof(RAWINPUTHEADER));
cbSize *= 16; // this is a wild guess by the people who wrote the MSDN docs.
PRAWINPUT pRawInput = (PRAWINPUT)malloc(cbSize);
if (pRawInput == NULL)
{
return;
}

for (;;)
{
UINT cbSizeT = cbSize;
UINT nInput = GetRawInputBuffer(pRawInput, &cbSizeT, /*0,
*/
sizeof(RAWINPUTHEADER));

if (nInput == 0)
{

break;
}


if (nInput == -1) { //sometimes returns -1 and throws error 0x0000007A, The data area passed to a system call is too small.
DWORD error = GetLastError ();
break;}
assert(nInput != -1);
PRAWINPUT* paRawInput = (PRAWINPUT*)malloc(sizeof(PRAWINPUT) *
nInput);
if (paRawInput == NULL)
{

break;
}
PRAWINPUT pri = pRawInput;
for (UINT i = 0; i < nInput; ++i)
{

paRawInput[i] = pri;
if (paRawInput[i]->header.dwType == 0)
{

if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN)
{ this->mouse_buffer.button[0] = 1;}
else if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP)
{ this->mouse_buffer.button[0] = 0;}

// Middle mouse button
if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN)
this->mouse_buffer.button[2] = 1;
else if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP)
this->mouse_buffer.button[2] = 0;

// Right mouse button
if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN)
{ this->mouse_buffer.button[1] = 1;}
else if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP)
{ this->mouse_buffer.button[1] = 0;}

// Mouse button 4
if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN)
this->mouse_buffer.button[3] = 1;
else if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP)
this->mouse_buffer.button[3] = 0;
// Mouse button 5
if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_DOWN)
this->mouse_buffer.button[4] = 1;
else if(paRawInput[i]->data.mouse.usButtonFlags & RI_MOUSE_BUTTON_5_UP)
this->mouse_buffer.button[4] = 0;
}
pri = NEXTRAWINPUTBLOCK(pri);
}
// to clean the buffer
LRESULT h = DefRawInputProc(paRawInput, nInput, sizeof(RAWINPUTHEADER));
assert (h == S_OK);
free(paRawInput);
}
free(pRawInput);






screw_stuff_up:

void screw_stuff_up( inputmanager * man)
{
if ( man->mouse_buffer.button[0] == 1)
{
if ( man->mouse_buffer.button[1] == 1)
Sleep (100); //both left and right are pressed, thus sleep
//release the buttons during sleep, and it will call it forever
}
}






Well, and that's basically it.
If there is a bug...then I'm not seeing it, though the error thrown in
scan_input does make me wonder, since I don't see a pattern to it.

Edit:
Whoops, forgot a bit of code in the scan_input section. Sorry.
Fixed now.

[Edited by - Erius on November 11, 2009 10:12:08 PM]

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