A sanity check for the input API is available.
Much like the thread component, the idea of running two separate input drivers makes little sense. There is only one keyboard or mouse. And these all have the same functionality, so different driver types aren't particularly useful. Except for the cases where one driver might support more game controllers or more mouse features than another, there needs to only be one driver type for input. But, even with multiple driver types, the need to use two separate drivers simultaneously is virtually non-existent. As such, the driver should not be exposed directly to the user.
The input design is broken up into 3 layers. At the lowest layer is the driver and other AllegroPro driver functionality. The next layer are devices, as defined later. Above that is the virtual game controller model, as defined below.
In the input design model, the driver can expose three kinds of "devices". A device is a conceptual piece of hardware, such as the mouse or a game controller. The three available kinds of devices are:
The keyboard represents an arbitrary keyboard. The mouse represents a mouse that can have a mouse wheel and a number of buttons, along with the regular pointer functionality. A game controller represents an arbitrary controller, such as a joystick or a gamepad.
Each device offers data as a delta since the last time the data was updated. However, devices may not be updated independently; to do so would unbalance the virtual game controller system. As such, the driver is updated, and it updates all of the devices associated with it.
Once the devices are updated, appropriate functions can be called to iterate over the input data in the device. The device should provide the delta information in the order in which it occurred, though games are advised to try not to go for a long time without updating their input, as internal driver buffers may overflow with data if they are left for too long.
Delta data in a device is transformed into a sequence of events. Each event pertains to a single, independent input member of an input device. Input members are buttons (or button-like controls, like keys on a keyboard), analog stick axes, digital stick axes, and analog pointer axes (mouse-type axes that provide offset directions of motion rather than joystick-like directions). This means that if the user moved the stick along 2 axes simultaneously, the API would return 2 separate events for the motion, one for each axis.
After initializing the input driver, one can gain access to a keyboard device. If the device is not available, highly unlikely but possible, a critical error will be given to the error mechanism, and this function will not return a device.
The data that is fed in is a list of which keys were pressed/released since the last update. The keys are enumerated into various possibilities. If the keyboard device allows it, the user may request which key enumerants are available from this keyboard device.
Note that there is no functionality, directly through the device, to get "character" values. That is, to convert from key press/release signals into what character should be entered. Such functionality does exist, but it exists at a higher level of the API, as defined below.
Note: usually, when making a native OS GUI application, using windowed-tool graphics drivers, the user wants to use the regular GUI input system(s).
After initializing the input driver, one can gain access to a mouse device. If the device is not available, then an error will be given, and this function will not return a device.
The mouse's input consists of two pointer axes, x and y, that offer delta values of motion along their respective axes. There may be a third axis, the z or mouse-wheel, that also offers delta values. Lastly, there may be one or more pressed/released button inputs.
Unlike Allegro 4, there is no support for "hardware" mouse cursors. If the user wishes to display a mouse pointer, then the user is free to blit one at the mouse's position.
Note: usually, when making a native OS GUI application, using windowed-tool graphics drivers, the user wants to use the regular GUI input system(s).
Issue 4: Should multiple mice be allowed? If so, should they be named, as per Game Controllers?
An input driver can support one or more game controller devices. Each game controller has a separate device associated with it. The driver will tell the user how many game controllers there are, and the user can access each controller in turn.
A game controller can have quite a few different types of individual controls. A game controller may have one or more axes of sticks controllers. These controllers may be analog or digital. Analog stick axes return a value between the maximum and minimum 32-bit signed integer value. Digital stick axes, such as hat devices or d-pads, return either 0, max_32_signed_int, or min_32_signed_int, representing which axis is being pressed. Lastly, buttons are represented as pressed/released inputs. All such input is represented as a delta since the last update.
The user may query information about the number and nature of the controls on the controller. In terms of nature, the user may query whether the axis in question is analog or digital, and whether it is an "x", "y", or "z" axis. The precise nature of the "z" axis is device-dependent. In general, it is best for the game to let the user decide which axis corresponds to which other functionality.
Game controller devices are named. The user can query a controller name. There are two names. The diaplay name is a human readable name for the device; the user can display this name to the user, so the name should be unique. The id name is the internal data that uniquely represents the actual device. It should not be binary data, but it need not be human readable either. The user can request a game device via this name. An input driver implementation is required to make certain that this device gets the same id name, as long as it is the same device.
On some systems, manual calibration of game controllers is necessary. By manual, this means that the game controller device itself must be calibrated for each program that uses it. Some operating systems maintain an interface that alleviates the user from such needs. But some operating systems do not. For driver implementations that require applications to perform this operation, there will be an API to calibrate a game controller device. The calibration procedure itself will likely require the user to display messages to the screen instructing the user on what to do.
Once calibration is complete, the user can query a char* string containing the calibration data. If the user saves this string, the data can be sent to the device. If the user gives calibration data for the wrong device (the device that created the calibration data is not the same as the one that is now being given the calibration data), then the system should throw an error and fail to calibrate the device. Drivers that do not require calibration should not return a calibration string.
Using a game controller device that requires calibration without manual calibration or using a calibration string can result in malformed data.
Frequently in games, the user wants the ability to reconfigure controls. In order to make it so that this is possible, the game programmer typically virtualizes input. This allows the mapping of specific input device controls to a virtual control interface. For example, the user might bind the 'a' key and button 0 on a game pad to give the same input value of 3. When 'a' or button 0 is pressed, then control 3 is active. When they are not pressed, control 3 is inactive.
As such, there is a system that allows the user to create virtual controllers. These are, effectively, a list of mappings from real input devices into a virtual one. The user can then query the virtual device as they see fit. This is the goal of the system.
This system is implemented entirely in AllegroPro. There is little or no driver interaction when using the virtual game controller system.
Virtual controllers are updated automatically when the input driver is updated. As such, they will always have up-to-date state data. A virtual controller contains full state information about the status of its controls. The user can query the state information about any particular control on the controller.
An input control is a single, independent component of a controller. The joystick x-axis is a control of the joystick controller. A button or key is a control of their respective controller.
There are 3 types of input controls:
Issue 2: Does there need to be a wheel axis control type separate from pointer axis controls? A wheel would provide only deltas, while the pointer is an absolute value.
Solution: Allow mapping pointer axes to stick axes. Mouse wheel-type controls can be stick axes.
The controls of input devices, such as the mouse device's x, y axes, joystick positions, or keyboard keys, fall into these same classifications. The keyboard device is a number of buttons. The mouse has 2 pointer axes, possibly a third referring to the wheel, and some number of buttons. A joystick has some number of stick axes, pointer axes, and buttons, depending on the device.
Virtual controllers work as follows. The user creates a virtual controller using the API. Once it is created, the user may create input members, or controls, on that controller. These members, when created, are given a name, an integer name. The user uses this name when the user needs to access a specific control.
Merely creating the member, however, is not enough. The user must specify where the source data for a control comes from. This is known as mapping.
Each input control of the virtual controller can be mapped to controls from an actual device. There are limitations as to which kinds of controls on the virtual controller can be mapped to which controls on an input device.
A button on the virtual controller may be mapped to button controls of a particular device. These can be keys on the keyboard, mouse click buttons, or buttons on a particular game controller. The mapping is direct, so no conversion is necessary.
Buttons on the virtual control can also be mapped to stick axis controls. This requires some conversion. Upon mapping, the user gives an integer value. If this value is positive, then the virtual button will be considered pressed if the stick returns a positive value greater than the given integer. If the value is negative, then the button is pressed if the stick returns a negative value that is smaller than the given integer. If the user wishes to modify this value, he can simply respecify the mapping; this will overwrite the old mapping.
A pointer axis on the virtual controller may only be mapped to a pointer axis on a device. When this mapping occurs, the user may specify a scale factor, in floating point, to apply to the delta input values obtained from the device. This allows for mouse sensitivity-like controls. Modifying this value after mapping requires respecifying the mapping.
A virtual stick axis may be mapped to a stick axis on a device. This mapping is direct.
A virtual stick axis may also be mapped to 2 buttons, which must be on the same device. This functions as a digital stick. When one button is pressed, it returns the maximum value; when the other is pressed, it returns the minimum value. If both are pressed, it returns the maximum value. The user may use this to map only one button, to one direction on the axis.
A virtual stick axis can, lastly, be mapped to a pointer axis. The value of the virtual axis is the delta value of the control on that update. As such, one can build an axis that gives larger absolute values with faster motion. This data can have a floating-point scale applied to them.
A virtual control can be mapped to up to 4 separate device controls. A device control may be mapped to any number of virtual controls on any number of virtual controllers.
A conflict in a virtual control can occur if the user activates two separate device controls that are mapped to a single virtual control. How this is resolved depends on the virtual device:
The user may access the current state of a controller using the controller's name. The current state of controls is defined as follows:
The input pointer axis control data is only a delta of moment. However, virtual pointer controls keep the absolute position of the pointer along that axis. A pointer axis can be clamped to a range, and the current position can be set by the user.
If the user tries to access a control in an improper fashion, accessing a button as though it were a stick axis, then an error will be thrown.
The user has another action that they can take. The user can send events to a virtual pad. These events are formatted as standard input data events , and will have the same function as if one of the devices associated with the control had emitted that event. The user must specify a specific device that should be considered the originator of the event. It must be a event of the type and format that the device can send, and from a control on the device that can send that type of event. The API for passing these kinds of events will validate the message to the best of its ability before sending it.
Each device can create a virtual control replica of itself. This is useful for getting at device data via a state-interface. However, because each virtual control takes up some processing time during the input driver update function, these should only be created if they are going to be used.
The virtual controls returned by these functions may not have their mappings modified by the user. This means that additional mappings are not possible for default controllers. However, the user may modify mapping parameters, such as the pointer axis scale values.
The names of the controls on these virtual pads are standardized. For keyboards, the controls are named the same as the key scancodes to which they are bound.
The keyboard device has the ability to interface with the keyboard virtual controller in a special way. If this device has been created, the user may query the keyboard device for "characters". These can be either ASCII characters or UNICODE characters. During an update, the keyboard device will build a sequence of characters that have been entered since the last update. Using the API, the user can gain access to these characters.
A user-created virtual controller will have the ability to be converted into a string. This string can then be used to build the controller exactly as it was again. This is useful for saving and loading controller configurations. If the data cannot be converted from the string back into a controller, because the user has removed one of the specified devices or the properties of that device have changed significantly, then the API should throw an error and return an invalid controller.
Issue 3: This seems to require the functionality from Issue 1. Is this functionality possible on many platforms?
Issue 1 was resolved to require the functionality. So this issue is moot.
On most GUI OS's, input is bound to a window. To allow this, the current main window, when the input driver is created, is the window that supports input. As long as this window has input focus, as defined by the OS, then the input system should work. Otherwise, the results are undefined.
This means that an input driver can only be created after the graphics driver, or after the user sets a main window.
Issue 4: How should we deal with the mouse when running in a windowed mode?
Details: Some operating systems deactivate a window when the mouse moves outside of the window. This cuts off that window's access to input, among other things.
Potential Solution: If the user expresses an interest in mouse input, by requesting a mouse device, then the mouse should be captured. Most OS's have a mechanism for preventing the mouse from moving outside of the confines of the window, or if they allow this motion, they prevent the window from losing both mouse input and overall input focus. If the user gets the mouse device, then the mouse should be captured; the user should only be interested in giving input to the AllegroPro application.
Part 2: In general, if the user wants the mouse to play fair with the OS, then the user should interface with the OS. This would be the typical interface for Windowed-Tool applications.
Note: I'm not entirely satisfied with this solution.
Many functions in the input system interface directly with the driver/devices, but some of them do not. For example, once the virtual controllers are set up, the only input system function that interfaces with any driver/devices, with a few minor exceptions, is the one to update drivers.
Any function that interfaces with an actual hardware device/driver must be called from the primary thread of AllegroPro; this is the thread that initialized AllegroPro to begin with. Also note that, while you can in fact use virtual controllers outside of the original thread, they are not technically thread-safe. Fortunately, this isn't very important, as all the virtual control functions that do not access the driver/device are querying, not modification, functions (getting the current state of the gamepad, etc).