It seems like the best case then. I'm mostly wondering because of people explaining that polling could cause input to be missed (E.G. if a button was pressed for a shorter time than it takes to get through a single frame, the polling could potentially miss an input).
Thats why some people dont tie their frame time to their polling rate.
As for the input system, myself I also use LWJGL3 and thus GLFW. I've set it up like this:
There are actions that do stuff. These have a "kind". For example, an instance of move left action has ActionKind.MOVE_LEFT. So when an input event mapped to MOVE_LEFT happens, that action handler gets called.
Entities contain a mappings component, which holds mappings from actual device inputs (ie, 'W' key press, X axis movement, etc) to action kinds. So mappings are switchable, and individual to each entity that has them.
So each frame, I poll all the input devices, then for each entity, I iterate over the devices mapped by that entity, calling the actions mapped to the events said devices produced.
Each action receives an InputEvent, which is a context object, containing a reference to the device that raised the event, a reference to the World instance, the entity that the action was mapped into, and a 'value' short. That value is a normalizing factor between input devices. For a key press it could be Short.MAX_VALUE, for a mouse movement event it could be some value depending on how much the mouse was moved in one direction (negative) or the other (positive).
That way you can have an action that assumes analog inputs, say that you're moving with a stick, and can handle being mapped from a keyboard or a game pad. In the case of the keyboard you'd be either moving 100% or not moving at all, whereas if you map the action to a stick axis, you can regulate the movement speed. The action being indifferent to the actual device its being mapped to.
Right now my code doesnt actually does this since I only use a keyboard but the API makes it feasible.
public static final class MoveRight extends Action {
MoveRight () {
super( ActionKind.MOVE_RIGHT );
}
@Override
public final void accept ( final InputEvent event ) {
// Zero is released, 1 is pressed. Using the sign bit of the value.
// So (-0 >>> 31) gives positive zero. (-1 >>> 31) gives one.
final int val = -event.value() >>> 31;
// fetch the velocity component of the entity and update it.
event.world().getHandler( Velocity.class ).get( event.entity() ).x = val;
}
}
I map that MOVE_RIGHT kind to a friendlier action name "actMvRight" which is what I use in my config file:
keyboard :
actMvRight : D
At load time I fetch the event id with the name of 'D' from the keyboard InputDevice and the action mapped to the "actMvRight" name to build the mappings. Getting the event IDs from GLFW is rather easy with reflection, just get all the static fields beginning with "GLFW_KEY" in the GLFW class, and map them to a substring starting at the end of that name. So a "GLFW_KEY_D" field becomes "D" for the config file, and you can map an action instance ot the actual field value at load time (which is just an int).
The nice thing is that its not tied to any "player" entity, you could add those mappings to a random cube and you'd be able to move it with the keyboard. Moreover, you could map wasd for the player entity, the arrow keys for the cube entity, and the input system would handle it just fine.
Its versatile enough that for handling resizing of the window, I made the WindowContext an InputDevice itself, that produces resize events. A couple actions listen for them and call the appropriate method in RenderDevice to resize all the frame buffers.
It isnt perfect. For example, for button combinations, like "ctrl+something", I'd need to expand that a bit. Maybe an action that holds the state of the "ctrl" (or whatever key mapped to it), and a way to query it via the InputEvent. There are probably cleaner ways to handle it.