Carbonite Input Plugin

Overview

Carbonite input plugin acts like an input hub, the goal is to make it easy for the input providers to register input devices and events in the input plugin and input users to consume input events or states, abstracting away the underlying mechanisms.

Input users

Any plugin or client that only cares about consuming the inputs are called input users. Input could be consumed either by registering event listeners or directly polling the input state. Input users should use the carb::input::IInput interface.

The main idea behind the user interface design is to unify discrete and analog controls, and assign any type of controls to the user-specified action mapping. More information about the specifics of such unification is presented in the [Values] and [Button flags] sections, and how to set up a user-specified action mapping is described in the [Action mapping] section.

Input states

Alternatively, it is possible to poll the input device state using functions like carb::input::IInput::getKeyboardValue and carb::input::IInput::getKeyboardButtonFlags. Similar functions are present for mouse and gamepad input devices. The difference between get*Value and get*ButtonFlags functions is in how the device button states are treated, and the reasoning behind having two types of interpretations of the same underlying input states is allowing the user to assign any kind of input state, whether it is discrete or analog, to the specific actions. For example, this approach allows to interpret gamepad analog stick as D-pad, assigning discrete actions to analog controls–e.g. analog stick driven up or trigger pressed above specified threshold, can trigger a certain menu to pop up. This works as well if a user wants to move a slider for a determined amount when a discrete keyboard key is pressed.

It is important to note, that there are two input states mapped to any single axis–negative and positive, e.g. carb::input::MouseInput::eMoveLeft / carb::input::MouseInput::eMoveRight corresponding to mouse move on the X axis, or GamepadInput::eLeftStickLeft / GamepadInput::eLeftStickRight corresponding to the same axis of a left gamepad stick–this is done for consistency and finer action mapping control; user can set up one actions on the negative stick/mouse axis move and a different one for the positive stick/mouse axis move, thus effectively turning them into d-pads. Or allow to rebind camera pitch and yaw control to the keyboard keys.

Values

The get*Value functions return floating point numbers representing the current button state–for discrete device inputs (like keyboard keys, mouse or gamepad buttons) it is either 0.0 or 1.0, and for analog inputs (e.g. mouse axes or gamepad sticks) the returned number can take values between 0.0 and 1.0.

Frames

carb.input transfers the current state to a previous state in the update*() functions: carb::input::InputProvider::updateKeyboard, carb::input::InputProvider::updateMouse and carb::input::InputProvider::updateGamepad. When these functions are called it is considered the beginning of the next frame of the device.

Button flags

The get*ButtonFlags functions return interpreted state flags that tell the user whether particular device input can be considered activated or deactivated for the discrete actions. It also allows callers to distinguish between states when the input was just activated/deactivated, or if they were in that particular state for more than one frame. In other words, mouse button could be just pressed or is held for a while. List describing flags combination and their meaning:

  1. carb::input::kButtonFlagStateUp: Button is up and was up on a previous frame–basically a default, “null” state.

  2. kButtonFlagStateUp | kButtonFlagTransitionUp: Button is up, but was just released (i.e. on the previous “frame” it was down)–for the single-time events which typically go on the onMouseButtonPress

  3. carb::input::kButtonFlagStateDown: Button is down, and was down on a previous frame–for the cases when you make the action while the button is pressed (think drawing using pencil in paint or dragging something).

  4. kButtonFlagStateDown | kButtonFlagTransitionDown: Button is down, but was just pressed (i.e. on the previous “frame” it was up)–for the cases when you need to set some state only once per button down event.

Button flags expressed this way decrease user-side code verbosity and improve usability versus other approaches. For example, if button flags were expressed as four distinct states: Held, Up, Pressed, Released–if user only cares about the case when the device input is in the “down” state, the code becomes:

if (input->getMouseButtonFlags(mouse, MouseInput::eLeftButton) == kButtonFlagPressed ||
    input->getKeyboardButtonFlags(keyboard, KeyboardInput::eLeftButton) == kButtonFlagHeld)

i.e. it is now required to check for both Held and Pressed states, because both mean that the actual state is Down. With the button flags approach, it is easy to check for the mouse down event:

if (input->getMouseButtonFlags(mouse, MouseInput::eLeftButton) & kButtonFlagStateDown)

Input events

carb::input::IInput interface provides ways to subscribe and unsubscribe to the input event streams with functions like carb::input::IInput::subscribeToKeyboardEvents / carb::input::IInput::unsubscribeToKeyboardEvents (similar events present for mouse and gamepads).

Action mapping

User interface for the input plugin allows to assign any kind of device input to a named action. Each named action can have several device inputs assigned to it. Actions support both state polling and event subscription, similar to the regular device inputs: carb::input::IInput::getActionValue returns floating point value for the named action, carb::input::IInput::getActionButtonFlags returns discrete button flag, and carb::input::IInput::subscribeToActionEvents / carb::input::IInput::unsubscribeToActionEvents allow to manage named action event stream subscriptions.

In case when several inputs are assigned, a prioritizing logic determines the exact action value or flag. Typically, when queried for the continuous action value, maximum absolute value of the assigned inputs is determined–i.e. if two axes of different sticks are assigned to the same action, the stick with maximum deviation from the initial state is prioritized. Alternatively, when queried for the discrete action flag, the following logic determines the prioritized button flag (in the order of decreasing priority):

  1. The device input is “held” for some time.

  2. The device input was just “pressed”.

  3. The device input was just “released”.

  4. The device input is in idle state.

This list of priorities for the button flag was designed to make sure that different device inputs assigned to the same action do not conflict with each other–for example if two inputs assigned to the same action were “held”, and then one of the inputs gets released, the action is not interrupted.

NOTE: the prioritization logic described is only a recommendation for the input plugin implementation, and is true for the default Carbonite input plugin (carb.input.plugin). Other input plugin implementations may implement different logic to determine action flags or values.

Input providers

Plugins that aim to feed the input events and states use carb::input::InputProvider interface.