QB64.org Forum

Active Forums => QB64 Discussion => Topic started by: johannhowitzer on July 11, 2020, 12:51:26 am

Title: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 11, 2020, 12:51:26 am
Trying to read inputs, and when I press and release left shift, Z, and X together, sometimes Z or X or both will stick, but only in QB64 using the _keydown function.  The press() function below will return true after Z or X is released, until it is pressed and released again.  Notepad does not give the same result, I don't get a long row of Xs or Zs sometimes when I do this, so it doesn't seem like an issue with my laptop.  Is there a workaround for this?

Code: QB64: [Select]
  1.    _limit 60
  2.    cls
  3.    for n = 1 to 12
  4.       color 8
  5.       if press(n) = true then color 7
  6.       print key_name$(n)
  7.    next n
  8.  
  9.  
  10. function press(b)
  11. press = _keydown(keybind(b))
  12.  
Title: Re: Keyboard jamming issue only happening in QB64
Post by: Ashish on July 11, 2020, 01:21:29 am
Hi! What is keybind? Is it an array? or a function? its undefined in your code.

Also, I think you are looking for _KEYHIT...

Code: QB64: [Select]
  1.     _LIMIT 60
  2.     k& = _KEYHIT
  3.     IF k& >= 65 AND k& < 150 THEN
  4.         PRINT CHR$(k&);
  5.     END IF
  6.     _DISPLAY
  7.  

https://qb64.org/wiki/KEYHIT
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 11, 2020, 02:17:34 am
I will try _keyhit and see if it behaves differently from _keydown, but I'm under the impression that _keyhit can't handle modifier keys like shift and ctrl?  Also, I know it's just an example, but that code is almost functionally equivalent to a do-inkey$-loop, which only picks up one keystroke at a time, and won't work for my purposes.  The game I'm coding needs to track a dozen inputs that can happen simultaneously, and even some key combinations.


Here's what keybind looks like:

Code: QB64: [Select]
  1. function keybind(i)
  2. keybind = key_ref(keybind_ref(i))
  3.  

It's just a quick way to type a longer thing.  key_ref contains all the key codes for all bindable keys, and keybind_ref is an array that stores the player's chosen keybinds.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: SMcNeill on July 11, 2020, 02:58:53 am
The game I'm coding needs to track a dozen inputs that can happen simultaneously, and even some key combinations.

If you’re using an USB keyboard, that’ll never happen.  Hardware limitations will cap you out before you get 12+ inputs.  USB specifications generally allow a maximum of 6-key rollover, plus modifier keys.  The only practical way to guarantee n-key input limits is to buy/require a quality PS/2 n-key keyboard for your end users.

You may want to rethink a simpler input scheme.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 11, 2020, 03:12:06 am
Ah, I made it sound more demanding than it is.  There are 12 inputs, but you're not going to be pressing all 12 at once.  Two are enter / esc, which are only used for menus and pausing, and four are directional, of which generally no more than two will be happening at once.

That leaves six, if it was a gamepad, it would be four face buttons and two shoulder buttons - the shoulders are for weapon swapping (on A and S by default), and the four face buttons are attack (default Z), dash (left shift), block (X), and a special combo button (left ctrl).  In the most extreme example of pressing a bunch of stuff at once, you might be holding weapon and combo for a charge attack, while also dashing, holding a diagonal direction, and then make a weapon switch or tap block.  That's a reasonable upper limit of six.

The issue I'm having is that with just three buttons pressed and released at the same time (in this case, it's attack, dash, and block), sometimes one or two of the buttons gets stuck returning true from _keydown - which would stick the weapon in autofire state, for example, and waste the player's ammo.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: SMcNeill on July 11, 2020, 03:44:30 am
See how something like this performs for you, and how many keys your keyboard will display with it:

Code: QB64: [Select]
  1. DIM Keys(10)
  2.  
  3.  
  4.     CLS
  5.     FOR i = 0 TO 10
  6.         IF Keys(i) THEN 'make certain the key is still down
  7.             IF _KEYDOWN(Keys(i)) THEN
  8.                 PRINT Keys(i)
  9.             ELSE
  10.                 PRINT 0
  11.                 Keys(i) = 0
  12.                 FOR j = i TO 9
  13.                     Keys(j) = Keys(j + 1)
  14.                 NEXT
  15.                 Keys(10) = 0
  16.                 keysdown = keysdown - 1
  17.                 i = i - 1
  18.             END IF
  19.         END IF
  20.     NEXT
  21.     k = _KEYHIT
  22.     IF k > 0 THEN
  23.         '        PRINT k
  24.         FOR i = 0 TO keysdown
  25.             IF Keys(i) = k THEN EXIT FOR 'key is already down and reported as such
  26.             IF Keys(i) = 0 THEN keysdown = keysdown + 1: Keys(i) = k: EXIT FOR
  27.         NEXT
  28.     ELSEIF k < 0 THEN
  29.         FOR i = 0 TO keysdown
  30.             IF Keys(i) = -k THEN 'clear the up key
  31.                 FOR j = i TO 9
  32.                     Keys(j) = Keys(j + 1)
  33.                 NEXT
  34.                 Keys(10) = 0
  35.                 keysdown = keysdown - 1
  36.                 EXIT FOR
  37.             END IF
  38.         NEXT
  39.     END IF
  40.     _DISPLAY
  41.     _LIMIT 60
  42.  
  43.  

What I'm doing here is building an array of keys which are pressed down, as they're pressed down.   When we detect up or down presses, I alter my array to reflect those changes.

On the off-chance that keys sometime "ghost" and keep reading false positives, (as you reported was happening to you with your own program), I then compare that array with the current _KEYHIT states, and double check that they're truly down for us, before I process them.  (Simply by printing them in this instance.)

This is the input array handler:

Code: [Select]
    k = _KEYHIT
    IF k > 0 THEN
        '        PRINT k
        FOR i = 0 TO keysdown
            IF Keys(i) = k THEN EXIT FOR 'key is already down and reported as such
            IF Keys(i) = 0 THEN keysdown = keysdown + 1: Keys(i) = k: EXIT FOR
        NEXT
    ELSEIF k < 0 THEN
        FOR i = 0 TO keysdown
            IF Keys(i) = -k THEN 'clear the up key
                FOR j = i TO 9
                    Keys(j) = Keys(j + 1)
                NEXT
                Keys(10) = 0
                keysdown = keysdown - 1
                EXIT FOR
            END IF
        NEXT
    END IF

And this segment is the process handler:
Code: [Select]
    FOR i = 0 TO 10
        IF Keys(i) THEN 'make certain the key is still down
            IF _KEYDOWN(Keys(i)) THEN
                PRINT Keys(i)
            ELSE
                PRINT 0
                Keys(i) = 0
                FOR j = i TO 9
                    Keys(j) = Keys(j + 1)
                NEXT
                Keys(10) = 0
                keysdown = keysdown - 1
                i = i - 1
            END IF
        END IF
    NEXT

I'm thinking something simple like the above should work for your needs.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 11, 2020, 04:07:40 am
Ah, thanks.  I tried the same thing with your code, and it still resulted in sticking the Z or S key, but I was able to see more of what's going on.  The Shift key is the culprit, because of how it's interacting with shift-modifiable keys.  Two examples:

1. Start holding Z > [122]
2. Start holding shift > [122, 100304]
3. Let go of Z > [122, 100304]
4. Let go of shift > [122]

1. Start holding shift > [100304]
2. Start holding Z > [100304, 90]
3. Let go of shift > [90, 122]
4. Let go of Z > [90]

And in the second example, you can't unstick the 90 code by tapping Z by itself - you have to hold shift and tap Z.

My best idea for how to solve this issue would be to manually clear all keyboard codes every frame.  My game engine has the press() function to return current keypress states, but it also has a hold() array, and every frame the current states are dumped into that array, so I can see what was pressed last frame.  Then there's a new_press() function, that returns true only if the button is pressed this frame, and wasn't last frame.

So at the end of each frame, I would clear the keyboard codes right after storing what's pressed in hold().  Not knowing much about hardware, or how _keydown actually interacts with the keyboard in detail, I'm gonna need some more help at this point.  But at least now we know what the problem is!  Thanks a bunch.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: SMcNeill on July 11, 2020, 04:13:41 am
Then make certain to process all keys as either uppercase or lowercase.

k = _KEYHIT
IF k > 64 and k < 91 then k = k + 32
IF k < -64 and k > -91 then k = k - 32

The first line would turn your A-Z keydown into an a-z keydown, and the second would work with the keyup codes.  :)
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 11, 2020, 05:25:28 am
A couple questions still... I've never used _keyhit and this is a little confusing to me.

1. _keyhit appears to return only one value, so is it (a) the last key that was pressed, (b) a key that is currently pressed when the program reaches the _keyhit, (c) all keys currently pressed, processed into one complex value, like how _rgba works?

2. If (b), does it pick out one key among several pressed in a priority order or something?

3. It seems like there's some mixing of terms, as I read that last line as still involving _keydown.  It took me several minutes to realize by "keydown" you might be meaning lowercase, and "keyup" you mean uppercase, I think?

4. Looking at your code, I can't make heads or tails of what half the stuff is doing.  Marked it up with my own comments about what I think might be going on:

Code: QB64: [Select]
  1. DIM Keys(10)
  2.  
  3.  
  4.     CLS
  5.     FOR i = 0 TO 10
  6.         IF Keys(i) THEN ' If this Keys() index contains a non-false value?
  7.             IF _KEYDOWN(Keys(i)) THEN ' Using _keydown on the stored code in Keys()
  8.                 PRINT Keys(i) ' Print the code, as we already saw
  9.             ELSE
  10.                 PRINT 0 ' Prints a zero on the screen?  Never saw this happen
  11.                 Keys(i) = 0 ' Sees the key is no longer down, clear the value
  12.                 FOR j = i TO 9 ' Collapse the array, eliminating the cleared value
  13.                     Keys(j) = Keys(j + 1)
  14.                 NEXT
  15.                 Keys(10) = 0 ' After collapsing, the last index should always be clear
  16.                 keysdown = keysdown - 1 ' Decrement a counter of how many keys are pressed
  17.                 i = i - 1 ' Decrement the for loop's counter, so next loop will continue at the same counter
  18.             END IF
  19.         END IF
  20.     NEXT
  21.     k = _KEYHIT ' Detect a keystroke?  How are multiple keystrokes on the same frame detected by this?
  22.     IF k > 0 THEN ' A keystroke was detected
  23.         '        PRINT k
  24.         FOR i = 0 TO keysdown
  25.             IF Keys(i) = k THEN EXIT FOR ' Don't add a duplicate keystroke to Keys()
  26.             IF Keys(i) = 0 THEN keysdown = keysdown + 1: Keys(i) = k: EXIT FOR ' Found an empty index, add here and increment number of keys pressed
  27.         NEXT
  28.     ELSEIF k < 0 THEN ' OK, I'm maybe gathering that "keyup" might have meant a key was released?
  29.         FOR i = 0 TO keysdown ' As before, collapse the array since a key was detected released?
  30.             IF Keys(i) = -k THEN 'clear the up key
  31.                 FOR j = i TO 9
  32.                     Keys(j) = Keys(j + 1)
  33.                 NEXT
  34.                 Keys(10) = 0
  35.                 keysdown = keysdown - 1
  36.                 EXIT FOR
  37.             END IF
  38.         NEXT
  39.     END IF
  40.     _DISPLAY
  41.     _LIMIT 60
  42.  
  43.  

This reply has been more or less a brain dump of my reactions to everything, because I'm confused enough to be unsure what questions I should be asking, just trying to wrap my head around how this works.  Hopefully unloading this much about my thoughts will tell you where I might be wrong or unclear.  This looks like a potential solution, but I don't understand it nearly enough to use it, and I've been referring to the wiki a lot during writing this post, it wasn't especially helpful in this case.

My greatest concern here, once I understand how to use this, is detection of multiple new keystrokes on the same frame.  When I ran the program in the first place, it seemed to be detecting them just fine, but _keydown was still involved here, and I wasn't playing the window back frame by frame to see if there was a delay.

Thanks for taking the time to help me with this!
Title: Re: Keyboard jamming issue only happening in QB64
Post by: Cobalt on July 11, 2020, 10:58:31 am
I did not see anyone else mention this on my brief skim so,

Do not forget to add _KEYCLEAR to then end of your loop to remove unwanted buffer data.

Depending on how fast your computer is and how big your keyboard buffer is you can get large amounts of  extra  characters printed and or read.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 11, 2020, 08:28:57 pm
Hmm.  That seemed hopeful, but adding _keyclear to the end of my loop changed nothing, and adding it to the end of SMcNeill's loop completely eliminated all keypresses, as far as I could tell.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 12, 2020, 02:50:05 am
As a last resort, if it comes to it, I can just disallow shift in keybinds, not a big deal.  It's part of the default controls, and I think it's kind of natural to use, but I'm not married to it.  Modifier keys in general have always made me a little bit concerned, being able to bind alt for example would generate a game input if the player uses alt+tab, and ctrl would generate an input if the player uses ctrl+printscreen.  The most generally reliable solution may just be to lock out all modifier keys.  I'm already disallowing binding to F1-F12 and num/caps/scroll lock.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: SMcNeill on July 12, 2020, 03:16:19 am
Wouldn’t the easiest solution just be _KEYDOWN, without any loops or all, since you only have a total of 12 interaction keys?

IF  _KEYDOWN(90) OR _KEYDOWN(122) THEN ‘it’s a “Z” or “z” key down.
    ‘Do Z stuff
END IF

....and so on...
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 12, 2020, 03:42:57 am
That's what I'm already doing, and that's the problem.  _keydown is returning true for keys that aren't being pressed.  When I run my press() function on, say, the Z key, it's checking both _keydown(90) and _keydown(122) via the key_ref() array of codes - but either the uppercase or lowercase is returning true from _keydown when the key isn't pressed.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: FellippeHeitor on July 12, 2020, 05:33:50 am
Try Steve's simpler solution above, without jumping though the hoops of functions and arrays. See if that works please.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 12, 2020, 05:59:12 am
Fellipe, it sounds like you're talking about reply #5... please go back and read reply #6, where I describe in detail what happened when I tried Steve's "simpler solution."

In fact, here.  I'll make it as simple as possible.  Go compile this program, and follow these steps when it's running:

1. Start holding Z
2. Start holding Shift
3. Stop holding Z
4. Stop holding Shift

See what happens?  No "jumping through the hoops of functions and arrays," same result.

Code: QB64: [Select]
  1.    _limit 60
  2.    cls
  3.    if _keydown(90) then print "uppercase"
  4.    if _keydown(122) then print "lowercase"
  5.  
Title: Re: Keyboard jamming issue only happening in QB64
Post by: FellippeHeitor on July 12, 2020, 08:59:24 am
I was talking about reply #12, but now that you've provided a minimal reproducible example I can confirm it's actually a bug.

Thanks for reporting, we'll begin investigating the issue.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: FellippeHeitor on July 12, 2020, 09:11:29 am
In the meantime, here's a workaround for your game:

Code: QB64: [Select]
  1.     _LIMIT 60
  2.     CLS
  3.     k& = _KEYHIT
  4.     SELECT CASE k&
  5.         CASE 122, 90: zIsDown = -1
  6.         CASE -122, -90: zIsDown = 0
  7.     END SELECT
  8.  
  9.     PRINT "Left shift: "; _KEYDOWN(100304)
  10.     PRINT "Right shift: "; _KEYDOWN(100303)
  11.     PRINT "Z/z: "; zIsDown
  12.     _DISPLAY
  13.  
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 12, 2020, 09:19:49 am
Nice!  Ran that immediately and it works perfectly.  Seems _keyhit indeed lacks the issue in _keydown; I still don't have my mind around how _keyhit is supposed to capture multiple keys pressed at the same time, but that's the first code I've seen in which the issue vanished.  I'll have to do some messing around and testing to educate myself on how _keyhit works under various circumstances.

This did have a good side-effect, too.  In experimenting with other default keybinding setups, I found that Lctrl-Z-X-C is in fact a bit more comfortable than Lctrl-Lshift-Z-X.  In the latter, whenever hitting shift, I realized I felt a bit like my ring finger was having to reach over my pinky finger a bit.  Which is not good, as Lctrl+Lshift is the combination that would produce a blink-teleport.

Thanks for the help.
Title: Re: Keyboard jamming issue only happening in QB64
Post by: johannhowitzer on July 12, 2020, 09:35:54 am
As an aside, I found Steve's reply 12 to be very strange, didn't seem like it was even coming from the same person as reply 5, since all it did was take the false positives and slap an OR between them - which does nothing to remove a false positive.  That's why I had assumed you couldn't be talking about reply 12, reply 12 doesn't make any sense after 5.