Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key repeat behavior on macOS #127

Closed
JeanMertz opened this issue May 17, 2020 · 4 comments
Closed

Key repeat behavior on macOS #127

JeanMertz opened this issue May 17, 2020 · 4 comments

Comments

@JeanMertz
Copy link

I have a small example that moves a circle across the screen based on WASD input.

From experimenting on macOS, it appears that Coffee respects macOS's key repeat preferences:

image

On the one hand, this is obviously a nice thing to have, as it gives the player control over the behaviour. On the other hand, this is unexpected behaviour for games.

Specifically, even with my preferences set to fast/short, there is still a noticeable delay from the "Delay Until Repeat" option. This means that if I press and hold A to move the circle to the left, it will move to the left once on press down, it will then stop moving for a moment even though A is still held down, and then start moving again as soon as the "repeat" kicks in, until I release the A key.

I've experimented with ggez as well, and that game engine behaves as "expected" on macOS (quotations here because the game behaviour differs from default operating system behaviour, but I believe ggez' behaviour is what people expect in a game).


Given that both engines use winit as a dependency, but ggez is still using 0.19 and Coffee uses 0.22, I suspect it's actually a change in behaviour between winit versions, and I would also suspect that change in behaviour is considered to be a fix, since regular macOS windows are expected to behave this way, but I don't think this is expected for games.


Alternatively, Coffee shouldn't treat key input as "text input". There's some investigation for Piston I found here: PistonDevelopers/piston#1220 (comment).


Another reference is to a long-standing issue for Winit to discuss a better input model, see: rust-windowing/winit#753


And finally, looking at ggez, it appears that they've implemented this behavior themselves by tracking is_key_repeated.

Although I'm not quite sure (yet) how that works, as that still doesn't explain why ggez doesn't have a delay between the first key and the repeat, whereas Coffee does.

@JeanMertz
Copy link
Author

Here's an example output:

Window(Focused) at SystemTime { tv_sec: 1589730587, tv_nsec: 523265000 }
Keyboard(Input { state: Pressed, key_code: A }) at SystemTime { tv_sec: 1589730588, tv_nsec: 837026000 }
Keyboard(TextEntered { character: 'a' }) at SystemTime { tv_sec: 1589730588, tv_nsec: 837055000 }
Keyboard(Input { state: Pressed, key_code: A }) at SystemTime { tv_sec: 1589730589, tv_nsec: 79325000 }
Keyboard(TextEntered { character: 'a' }) at SystemTime { tv_sec: 1589730589, tv_nsec: 79345000 }
Keyboard(Input { state: Pressed, key_code: A }) at SystemTime { tv_sec: 1589730589, tv_nsec: 129870000 }
Keyboard(TextEntered { character: 'a' }) at SystemTime { tv_sec: 1589730589, tv_nsec: 129894000 }
Keyboard(Input { state: Pressed, key_code: A }) at SystemTime { tv_sec: 1589730589, tv_nsec: 163413000 }
Keyboard(TextEntered { character: 'a' }) at SystemTime { tv_sec: 1589730589, tv_nsec: 163438000 }

Using the following Input type:

struct Test;

impl Input for Test {
    fn update(&mut self, event: input::Event) {
        println!("{:?} at {:?}", event, SystemTime::now());
    }

    fn new() -> Self { Self }
    fn clear(&mut self) {}
}

You can see the first Input at sec 1589730588 and nsec 837026000, then, the next input 242 milliseconds later, and then about every 50 milliseconds.

@JeanMertz
Copy link
Author

I guess one approach to solve this problem is to do custom tracking of keyboard state.

It would probably be something like:

  • track key down events in fn interact and keep those events around
  • for every fn update, see what "key" events are active, and act on those
  • on key up events in fn interact, remove them from the event store
  • the next fn update won't see that key as "down" any more

But I wonder if this should "just" be handled by the engine instead, similar to what ggez is doing?

@JeanMertz
Copy link
Author

Another problem I noticed with this (which would be solved by my above suggestion of keeping state around), is that holding down two keys simultaneously does not trigger the repeat event for one of those keys (which, my guess would be, is the one that was pressed first):

Keyboard(Input { state: Pressed, key_code: D })
Keyboard(Input { state: Pressed, key_code: S })
Keyboard(Input { state: Pressed, key_code: S })
Keyboard(Input { state: Pressed, key_code: S })
Keyboard(Input { state: Pressed, key_code: S })
Keyboard(Input { state: Released, key_code: S })
Keyboard(Input { state: Released, key_code: D })

I removed the TextEntered events here, but what can be observed is that I pressed D and S simultaneously, with D being pressed just a bit faster. Because S was pressed last, repeated events are triggered for that key, whereas only the Released event is triggered for D once I release that one, but no repeast Pressed events are triggered.

@JeanMertz
Copy link
Author

Alright, I should have checked the Keyboard implementation more closely. It's already doing exactly what I described above, I just didn't copy it correctly in my implementation.

The reason why I forked the implementation though, is because I wanted to have access to the pressed_keys hash set.

I'll create a PR to see if you are okay exposing those details in the public API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant