Keyboard input: poll, push or pull?

Started by
6 comments, last by Zipster 9 years, 7 months ago
Hello everyone!
I'm trying to decide on wether to use polling, pulling or pushing for keyboard input. I tried searching but did not find any good answer containing all three these in a comparison, and I've reached a point where I need to decide for a bigger project. I'm well aware that there's rarely one best solution, but I'm curious as to wether there is any preferred method amongst other game developers.
As far as definitions go (my opinions, of course), polling means that each individual module checks it's own keybinds towards the underlying API (in my case, Windows).

Pros:

  • Easy to implement
  • Keybindings are easy to manage inside the class
  • Multiple keybinds for same button

Cons:

  • Lots and lots of API calls to check key states
  • Will become more difficult to change if performance becomes an issue
  • Will need N polls per frame, N -> number of binding
Pulling is essentially the same, but instead of each individual module polling, we would do one big API-poll in a KeybindManager and then each module polls that one - pulling a keystate variable instead of polling the API.
Pros:
  • It's sort-of Observer-patttern
  • Keybindings are easily visible inside the class
  • Can easily be refactored into either of the others by being a middle ground.
  • Multiple keybinds for same button
Cons:
  • Still need to do lots and lots of checks
  • Still needs 1 API-poll + N pulls per frame
Pushing is the same as pulling, but as the name suggest it tells each class when a button changes state instead of each class checking its buttons.
Pros:
  • It's a correct Observer-pattern
  • An effecient implementation will only notify changes, reducing CPU-cycles needed
Cons:
  • It requires an interface/mediator/semaphore-style variable
  • If we want to reduce notifications, we increase complexity in KeyboardManager
  • Multiple keybinding leads to drastically increased complexity.
If I've done either of the styles injustice in my pros/cons, or if you disagree with the descriptions, please say so. In a code scenario, simplified they'd be like this:
Polling

//Vehicle.cpp
void Vehicle::UpdatePoll(int time){    
    if(GetKeyState(VK_RETURN){         //Do something ?    }
}
Pulling

//Vehicle.cpp
void Vehicle::UpdatePull(int time){
    if (kbManager->IsKey(VK_RETURN){ ... }
}

//Keyboardmanager.cpp
void KBManager::Update(){
    localKeyStates = GetKeyStates();
}
UINT KBManager::IsKey(UINT virtualKey){
    return localKeyStates[virtualKey];
}
Pushing

//Vehicle.cpp inherits KeyObserver
Vehicle::Vehicle(){
    kbManager->Register(VK_RETURN, this);
}
void Vehicle::UpdateKeyState(UINT keyVal, bool state){
    if (keyVal == VK_RETURN)    this->keyReturn = state;
}
void Vehicle::Update(int time){
    if(this->keyReturn) { ... }
}


//Keyboardmanager.cpp
void KBManager::Register(UINT virtualKey, KeyObserver& obj){
     this->BindingList[virtualKey] = obj;
}
void KBManager::Update(int time){
    this->localKeyStates = GetKeyStates();
    for_each (UINT key : BindingList.Keys){
        Notify(key);
    } 
}
void KBManager::Notify(UINT key){
    if (IsChanged(localKeyStates[key])){
        BindingList[key]->Update(key, localKeyStates[key]);
    }
}

Advertisement
Your systems probably should not care about "keyboard" or "mouse" or whatever. You should have some sort of intermediary that receives the input state (via OS events, DirectInput/XInput, or whatever mechanism) and using an input binding system to turn them into game-specific data.

As a simple example, let's say you have a camera in your game that the user can control. With a system like this, your game only needs to know "Camera X coordinate moved by some amount" or "Camera Y coordinate moved by some amount" and your input system handles whether that is tied to direct mouse movement, a key being held down, or a stick on a gamepad being tilted. (And preferably in a way so the user can pick and adjust things like dead zones and sensitivities for analog inputs)

As to your original question - it is really going to come down to the API you're using. Keyboard and mouse on Windows are sent using events, so you should use that. DirectInput/XInput want you to poll them for the current state of the input device. When you get an event from Windows or run an input update call you can then save off the current state of the game's various controls (based on the above bindings) and let your input system decide whether a control changed state and send out events - or have your systems ask your input system if a control is held down.

I concur with SmkViper. On Windows, you want to handle Windows messages for keyboard and mouse input (WM_KEYDOWN/UP, WM_CHAR, WM_MOUSE*, WM_*BUTTONDOWN/UP/DBLCLK, etc.). I supposed this is the "push" option you listed, although I'd just call it "event-driven".

You'd then have a middle layer that translates raw input into game "actions" (i.e. "move camera left", "move camera right", "jump", "shoot", etc.), and dispatches those actions via events to the rest of the game. These can be rebound by the user in a number of different ways for maximum customization. The input system I designed also dispatches raw virtual keys, and in the case of WM_CHAR events, raw character codes. But those are only handled in a few very special places, such as debug modules and chat interfaces. 95% of the game listens for the abstract action codes.


I concur with SmkViper.

^^^ Yes.

The first one, where each object polls the input makes it hard to add keyboard/joystick mapping. Imagine you need an analog stick or the arrow keys to both do the same thing in the game. You don't want game objects worrying about that.

The last one, pushing, seems like a good idea at first, but it doesn't work either. Again, you can't smooth out data, you have no way to fire events when two or more keys are down, or use two buttons as a combo, or anything like that. Imagine a fighting game. If the input was pushed on another thread you would never make sense of it. Plus, you may want some way to record player input and play it back at a slow speed. You can't do replays by simulating input events pushed from the OS.

Do the massive poll, generate any input events, and either make the events available or have objects request push notifications when an event fires. You can still have poll, push, and pull that way, but one level away from the input.

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

Interesting input so far - I've never considered adding action messages. I guess that sort of makes sense. Sort of like XAML/WPF does it.

But then if you did, would you push key states (or rather action states, I guess?) or would you push actions?

F.ex., would you tell it to MOVE_LEFT, or keep it as a bool that you then check in another function?

Hi,

I voted for the second one, because it's easier and takes less time to code.

To be honest, if it is for a complex game, I'd go for an event-driven solution, sending the actions to the objects via an event manager when an input event is detected.

This might be relevant: http://www.gamedev.net/page/resources/_/technical/game-programming/designing-a-robust-input-handling-system-for-games-r2975

Interesting input so far - I've never considered adding action messages. I guess that sort of makes sense. Sort of like XAML/WPF does it.

But then if you did, would you push key states (or rather action states, I guess?) or would you push actions?

F.ex., would you tell it to MOVE_LEFT, or keep it as a bool that you then check in another function?

You push actions (dispatch events), but track states. In this particular example, the movement system would listen for a MOVE_LEFT action, and it would be flagged as a "press". The system then knows to set its own internal state variable indicating that left movement is active. During the system's logical service, it would check that flag and apply the left movement logic if the state is active. If the system later receives a MOVE_LEFT action flagged as "release", it would reset the left movement flag, and the service would no longer apply movement.

This topic is closed to new replies.

Advertisement