Jump to content

  • Log In with Google      Sign In   
  • Create Account


Simultaneous multiple key press release delay


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
16 replies to this topic

#1 Demion   Members   -  Reputation: 126

Like
0Likes
Like

Posted 14 March 2014 - 07:35 AM

I am developing 2D game and faced problem with simultaneous multiple key press / release detection needed, for instance, for character diagonal movement.
 
Problem is when I "simultaneous" (it has nothing to do with my reaction I press / release very quickly) press / release multiple (two) keys delay between messages often is much more than single frame (16ms with VSync), for instance, it can reach 50-100 millisecond, so character starts moving single direction and only after slight delay diagonal (as intended).
 
Is it normal for WinAPI messages or is it hardware limit? Is it possible to detect input faster or how games deal with it?
 
Only solution that kinda helps is process inputs in game logics with periodic delay (for instance every 100 ms), but this sacrifices control responsiveness very much.
 
I am dispatching WinAPI WM_KEYDOWN / WM_KEYUP messages in while loop.
 
I also tried to dispatch input in seperate from render thread with GetMessage and  also tried RawInput approach.
 
Delay measurement test project : pastebin
 
Actual OpenGL diagonal movement test project : pastebin


Sponsor:

#2 Álvaro   Crossbones+   -  Reputation: 11994

Like
0Likes
Like

Posted 14 March 2014 - 07:59 AM

There shouldn't be much of a delay from the key being pressed to the message being sent to your program. I don't have any experience with Windows programming, but I have used SDL and SFML on Linux and Mac OS X, and this has never been a problem.

 

Looking at your code a bit, it looks like you simply register a callback function to process events, so you don't have a whole lot of control of when they get processed. Perhaps you could write your game loop more explicitly, and then you can make sure you process all the events in the queue before you proceed to rendering? [EDIT: Never mind. I see you are dispatching all the events in a loop. I missed that in my first reading.]


Edited by Álvaro, 14 March 2014 - 08:02 AM.


#3 samoth   Crossbones+   -  Reputation: 4532

Like
0Likes
Like

Posted 14 March 2014 - 08:11 AM

I think nothing is inherently wrong with your code (even though the behavior that you get is obviously undesired, I think it is somehow nevertheless exactly doing what you're asking for).

 

What you are doing is peek and drain the message queue until it is empty, then you draw some stuff and flip buffers, which presumably blocks for 16ms.

 

16ms is a very long time for a computer, and everything else that you do happens more or less "instantly" in comparison to that. Therefore most of the time, this will just work fine, as you spend 99% of your time inside SwapBuffers, and so your different key presses all arrive while your application is blocked. When you next drain the message queue, you get all events that are in the queue, and it's correct.

 

However, sometimes, it may just happen that one key event arrives in the message queue while you're draining it. After that, there are none left, so your loop continues (of course, what else!). The next event arrives, but that is now irrelevant since you're already somewhere in your OpenGL calls, and after that you block for 16ms. So you get a huge delay between two keypresses that actually happen simultaneously (or nearly so).

 

Now, why you get figures of 100+ milliseconds, I can't answer. That's truly odd.

 

(I once upon a time, years ago, tried stamping messages with values obtained from timeGetTime by the way, and Windows message queues push events through much faster than the minimum resolution (which is 0.5ms here), so I doubt it's a Windows message problem as such)


Edited by samoth, 14 March 2014 - 08:17 AM.


#4 Demion   Members   -  Reputation: 126

Like
0Likes
Like

Posted 14 March 2014 - 08:16 AM

Actually cause of problem is that there no such thing as "simultaneous" in programming. For instance, I press and hold two keys (down & right) and move diagonally, then I "simultaneous" (as quickly as possible) release these both keys, but window message loop (pretty same with raw input) receives WM_KEYUP Down and then only after slightly delay (it can reach 50-100 ms according to tests!) it receives WM_KEYUP Up. Because game logic and rendering processes much faster (60 fps, vsync, 16 ms) it assumes Up key is still being pressed (changes direction and animation frame to Up instead of keeping Diagonal). You can compile given code and test yourself, it has no dependencies and fully working.

 

Question is - is such behaviour normal? Is there way to process input faster? Could it be hardware (keyboard) limit? Or how to workaround this to make simultaneous multiple key press / release more "smooth" for player (diagonal movement, combos etc) ?
 
More I delay input process in game logics more "smooth" it becomes for player (input messages get in time) but also less responsiveness it gets.

Edited by Demion, 14 March 2014 - 08:25 AM.


#5 samoth   Crossbones+   -  Reputation: 4532

Like
0Likes
Like

Posted 14 March 2014 - 08:23 AM

You got that right, there is no such thing as "simultaneous", since only one message can enter the message queue at the same time (even if the keys are pressed at exactly the same time).

 

That's what I mean... usually this will work just fine, and the simultaneous events will be quasi-simultaneous in your queue (following one after the other). But of course it may happen that you process one, see that the queue is empty, and move on. And then, the event that is being posted immediately after ("simultaneously") will have to wait for a full frame.

 

Except 100ms is more like 8 frames... which I don't understand. That smells like you're lost in a call to Sleep somewhere, but I don't see one anywhere in that code.


Edited by samoth, 14 March 2014 - 08:23 AM.


#6 Demion   Members   -  Reputation: 126

Like
0Likes
Like

Posted 14 March 2014 - 08:42 AM

Log from first program - pastebin

It is rarely 100 milliseconds but often within 25-50 milliseconds, which is 1 or 2 frames (using VSync 60 fps, ~ 16 ms per frame) which already leads to unwanted result (game logics changes direction, animation frame (angle) etc).

 

So only solution I see so far is using raw input (which is subjectively a little bit faster) instead of wm_keydown / up and process input in game with periodic delay (once per 2-5 frames instead of every frame) to avoid unwanted results.

 

According to stackoverflow delays of the order of 50 ms or so are common in processing key presses through the normal Windows message queue.

 

Thanks for effort anyway. I am still looking for any better solutions.


Edited by Demion, 14 March 2014 - 08:43 AM.


#7 mark ds   Members   -  Reputation: 1086

Like
0Likes
Like

Posted 14 March 2014 - 10:18 AM

I tried your code, and nearly always get ~20ms delay between simultaneous key presses. I wasn't aware that the delay was so significant. Incidentally, WM_KEYxxx vs WM_INPUT was the same - I saw no improvement whatsoever.

 

However, my wireless keyboard receiver is attached to my monitor USB connector, which is cabled to the PC, which can't help. I wonder if a wired keyboard fares any better?



#8 wintertime   Members   -  Reputation: 1615

Like
0Likes
Like

Posted 15 March 2014 - 03:17 PM

I saw that you directly add 1 to the velocity each time you get a message, without any checks, and then directly add that to the position. I would not do that, because its dependent on how many messages you get, which can be dependent on hardware or windows.

You also never reset those values, so you have to first accumulate many messages before you have enough to compensate after changing direction.

 

I would instead have acceleration values dependent on keys pressed on last frame and clamp those to some maximum at each change.

Then calculate the velocity from that and the frame time and clamp it to some maximum and possibly dampen it so that it goes back to zero slowly. Only then calculate the position.

Reset the acceleration to zero before next frame.



#9 Demion   Members   -  Reputation: 126

Like
0Likes
Like

Posted 16 March 2014 - 02:39 AM

I saw that you directly add 1 to the velocity each time you get a message, without any checks, and then directly add that to the position.

Sorry, you are very wrong. I add velocity only once on key press and this has nothing to do with my problem.

 

if ((lParam & (1 << 30)) == 0)
  ProcessInput(wParam, 4);
if (!Keys[raw->data.keyboard.MakeCode])
{
  Keys[raw->data.keyboard.MakeCode] = true;


  ProcessInput(MapVirtualKey(raw->data.keyboard.MakeCode, MAPVK_VSC_TO_VK), 4);
}


#10 Adam_42   Crossbones+   -  Reputation: 2381

Like
0Likes
Like

Posted 16 March 2014 - 05:37 AM

Your GetTime() method looks suspicious to me. You could be throwing away lots of precision because of the integer divide.

 

The simplest way to solve that is to make it return a double, and make the program start time come out as zero. Note that float is no good for this - after the program has been running for a while the accuracy will be too low.

// Get current time in seconds
double GetTime()
{
        static LARGE_INTEGER frequency = GetFrequency();
 
        LARGE_INTEGER counter;
         QueryPerformanceCounter(&counter);

        // We want time zero to be when the program starts. I've hacked that in with a static variable here.
        static LARGE_INTEGER programStartTime = counter;

        return (double)(counter.QuadPart - programStartTime.QuadPart) / (double)frequency.QuadPart;
}


#11 wintertime   Members   -  Reputation: 1615

Like
0Likes
Like

Posted 16 March 2014 - 05:39 AM

Yeah sorry, I overlooked that if. Yet the other points still stand, and that you only accept 1 message and ignore the rest will make it still look wrong, for example, if you do push-up, release-up, push-up, push-right, you have added to up-velocity twice and right-velocity once and that will have the effect of not looking diagonal until you press down once.

And you are using an angle there that is not correctly representing the movement you apply:

                x += xvel;
                y += yvel;
 
                if ((xvel != 0) || (yvel != 0))
                {
                        if ((xvel != 0) && (yvel != 0))
                                degrees = 45;
                        else
                                degrees = 0;
                }
 
                glClear(GL_COLOR_BUFFER_BIT);
 
                glLoadIdentity();
 
                glTranslatef(x + quadWidth / 2.f, y + quadHeight / 2.f, 0);
               
                glRotatef(degrees, 0, 0, 1);

And that velocity depends too much on old keypresses so if press the keys a few times and dont count and press the opposite keys exactly the same number of times it will pretty much always show 45 degrees.



#12 Demion   Members   -  Reputation: 126

Like
0Likes
Like

Posted 16 March 2014 - 06:05 AM

 

Your GetTime() method looks suspicious to me.

 

GetTime() multiplies counter x 1 000 000 before division and that gives perfect microsecond accuracy ( frequency = 1 second = 1 000 000 microseconds). Anyway please also take a look at second source code which actually makes drawing without any time involved. Its not time releated problem at all.

 

 

for example, if you do push-up, release-up, push-up, push-right, you have added to up-velocity twice and right-velocity once and that will have the effect of not looking diagonal until you press down once.

 

Not true. Press-Up velocityY -=4, Release-Up velocityY +=4 (resulting velocityY == 0), Press-Up velocityY -=4, Press-Right velocityX += 4 (resulting velocityY == -4, velocityX == 4) etc. Please notice that on key release I call ProcessInput with negative velocity value (on key press positive value accordingly).
 
It would be great if you compile (it has no dependencies) and take a look it works perfectly fine except multiple key "simultaneous" press / release not always detected correctly becouse of delay between message (they are detected one by one, first one key and after slight delay second key which leads to unwanted results).

Edited by Demion, 16 March 2014 - 06:10 AM.


#13 L. Spiro   Crossbones+   -  Reputation: 12369

Like
0Likes
Like

Posted 16 March 2014 - 06:20 AM

According to stackoverflow delays of the order of 50 ms or so are common in processing key presses through the normal Windows message queue.

That has nothing to do with 2 keys being pressed at once, which should be consistently as far apart in microseconds as you are actually pressing them.


The problem is your method of timing. You should be timing only the point at which WM_INPUT is caught, not all the other code that is run inside WM_INPUT.
In other words, good:

                case WM_INPUT:
                {

                        unsigned long long MsgTime = GetTime();
                        // Do stuff and log MsgTime.

Bad:

                case WM_INPUT:
                {

                        // Do a bunch of stuff, allocate some memory (this is likely why your timings vary so much).
                        …
                        …
                                                Records[Count].Time = GetTime();

You’re aren’t currently timing when the key was pressed, you are timing when the key was pressed, plus some logic plus a call to new.

 

 

V-sync isn’t helping either.  It will lag your input thread unless you are rendering from another thread.  This can easily add 16 milliseconds to your timings, but your first test case does not have this problem.

 

 

L. Spiro


Edited by L. Spiro, 16 March 2014 - 06:26 AM.

It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#14 Demion   Members   -  Reputation: 126

Like
0Likes
Like

Posted 16 March 2014 - 06:25 AM


You’re aren’t currently timing when the key was pressed, you are timing when the key was pressed, plus some logic plus a call to new.

Good point. Although WM_KEYDOWN / WM_KEYUP approach (commented out) has no allocations and same results. Anyway in real game I need to determine which event happend (press or release) and then determine key, so I need to do this allocations.

 

Especially for you reupload and fixed - pastebin

 

Last test up to 65 ms delays, Vsync is only 16 ms.


Edited by Demion, 16 March 2014 - 06:31 AM.


#15 tanzanite7   Members   -  Reputation: 1148

Like
0Likes
Like

Posted 16 March 2014 - 12:22 PM

Unlikely to be of help, but i note anyway:

 

You are using QueryPerformanceCounter - what is the frequency you are given for it (btw, there is no need to re-query the frequency as it is strictly not allowed to change at runtime)?

 

That 65 sounds quite close to the 55 resolution limit (8253/8254 timer?). I have never seen GPC using that timer, but it is possible if your hardware can not give anything better (for example, my CPU has dynamic clock frequency and hence can not be used - instead some unknown much-much lower frequency timer is used [~3.5 mil, but high resolution]). I highly doubt you are suffering from bad GPC timer, but for sanity check you could do QPC in a loop and output the times (without frequency division of course) - to see what the resolution for your given frequency actually is.

 

edit: On second though - ignore that. Too bloody unlikely.


Edited by tanzanite7, 16 March 2014 - 12:36 PM.


#16 L. Spiro   Crossbones+   -  Reputation: 12369

Like
2Likes
Like

Posted 16 March 2014 - 01:37 PM

there is no need to re-query the frequency as it is strictly not allowed to change at runtime

He only calls it once; it is a static.

 

Last test up to 65 ms delays, Vsync is only 16 ms.

V-sync and other factors are why input actual in games are not handled in the way shown in your test application, so I disregard those results entirely and would only accept your data for your basic non-OpenGL test.

You are still timing the call to new; it just spans across multiple calls to WindowProc(). That is, you store the current time after WM_INPUT, then do stuff, but all that stuff you do is delaying the next WM_INPUT which could already be in the buffer and waiting.

Eliminate the call to new entirely.
Make a static buffer of an array of 3 RAWINPUT structures and if the first call to GetRawInputData() returns a size greater than (sizeof( RAWINPUT ) * 3) then print an error, dump the message, and increase the size of the static buffer if you want.
While this may not be what you would do in an actual game, the important point now is to find the bottleneck.

If it improves the timings then you know at least one of the main culprits. If not, it won’t be a problem in a real game to do it properly but you need to keep searching for the answer before you add back the allocations (which are leaking, just so you know).


You should also be prepared to accept that your timings are accurate. Maybe you aren’t hitting 2 keys as closely together as you think.


L. Spiro

Edited by L. Spiro, 16 March 2014 - 01:43 PM.

It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#17 mark ds   Members   -  Reputation: 1086

Like
0Likes
Like

Posted 16 March 2014 - 03:07 PM

Using the <time> variable from the MSG structure gives similar results - 15,16,31 or 32 ms between calls. As L. Spiro said, it's quite possible that no one can actually hit two keys at precisely the same time.

 

Incidentally, I'm testing on a Win8.1 machine. Maybe an XP or Win7 system will give different results. Unlikely, but...

 

Edit - here are some actual numbers. The first is the time according to QueryPerformanceCounter, the second as reported by the time variable in the MSG structure. Both are in milliseconds.

 

40.37 - 47
19.97 - 15
19.98 - 16
21.76 - 32
19.87 - 31
19.92 - 16
20.03 - 31
20.74 - 16
18.82 - 15
19.99 - 16
20.21 - 16

 

I've altered your program quite considerably, moving the timing into the GetMessage loop.


Edited by mark ds, 16 March 2014 - 03:29 PM.





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS