Input architecture

From CSP

Table of contents

Abstract

CSPSim currently uses SDL to receive user input events from the mouse, keyboard, and joystick. The input interface layer provides a powerful, highly customizable, and simple way to map these raw events to the different actions available in the sim.


Background

Under windows, modern human interface devices (HID) often come with fancy programming interfaces. These typically allow one to map device events to keystrokes. Games generally define a customizable mapping of keystrokes to game actions. In the case of Falcon4 SP3, the number of keystrokes with defined actions is VERY large. When you consider that there could be as many actions for each vehicle type as there are in all of Falcon, the idea of mapping everything through keystrokes gets to be insane. Moreover, those nice graphical HID configuration tools aren't generally available to linux users.


Overview

The input interface layer (IIL) addresses these problems by (1) defining all simulation actions in terms of string identifiers, and (2) providing a powerful configuration and scripting language to map from raw events to simulation actions. To do this, the IIL first translates raw events from input library (SDL) into a library-independent format. These translated events are then used to trigger input configuration scripts, which execute on a "virtual device" (VHID). The output of these scripts is a sequences of action events, which are routed to the corresponding handlers to drive the simulation.

The input configuration scripts are simple text files, which are precompiled into a more terse format that is loaded by the simulation at runtime. Configurations can be associated with one or more vehicles, and will be swapped in and out dynamically as the active vehicle changes. Thus the same button that might trigger the airbrake in an F16 can serve to open the hatch on an M1A1. Eventually this dynamic mapping will be extented to stations within a vehicle, so that for example the driver, gunner, and commander stations of a tank could each have distinct controls. Note that configurations are typically customized to specific input devices (e.g., a Saitek X36), but they can also share common elements.


Scripting

In their most basic form, scripts are simple lists of actions to perform. Each action has an associated time to allow for delayed execution. Only one script can run a a time. If a script is running when another script is triggered, the first script will be cancelled. Scripts support looping, which allows a script to generate a repeated series of output actions for as long as a particular button or key is pressed. Scripts can also yield and resume, so that successive triggering of a script will cycle through a fixed series of actions.

Scripts are executed in the context of a "virtual device" which encapsulates all of the real input devices (keyboard, mouse, and joystick) and defines additional state that modifies the behavior of scripts. The virtual device primarily serves to keep track of the "virtual mode" of each real input device. Up to 16 virtual modes can be defined per real device, as part of the input configuration. Scripts can set the virtual mode for each device, and the modes act to redefine the mapping from raw input events to scripts. For example, an input configuration file for an F16 might define three virtual modes: navigation, air-to-air, and air-to-ground. One button, which is active in all three modes, can be used to cycle between these settings. In air-to-air mode, the joystick trigger might map to the "LAUNCH_MISSILE" action, while in air-to-ground mode it might map to "PICKLE". Some controllers, like the Saitek X36 support this concept of mode-dependent mappings natively, but the virtual device concept provides a simple, uniform way to achieve this functionality, even on less capable hardware. The virtual device also adds the concept of a "virtual shift state" to each joystick device, which acts in the same way as the shift key on a keyboard, to temporarily change the mapping of joystick button events.

Keyboard events, mouse button events, and joystick button events all map to scripts. The mapping depends on various device modifiers (e.g., the SHIFT key), the virtual mode, and the virtual shift state. Joystick axis events and mouse motion events are handled directly with no scripting interface. The various modifiers for each type of event are listed below.

  • Keyboard events depend on the modifier state (combination of SHIFT, CTRL, and ALT) and whether the key is pressed or released.
  • Joystick button events depend on the virtual mode, the virtual shift state, and whether the button is pressed or released.
  • Mouse button events depends on the device, the virtual mode, the keyboard modifier state, and whether the button is pressed or released.
  • Mouse motion events depends on the device, the virtual mode, the keyboard modifier state, and the mouse button state.
  • Joystick axis events do not depend on any state. TODO depending on vmode might be useful

TODO link to scripting language definition


Programming Interface

Declaring HID Interfaces

Here's a sample of how event callbacks can be declared:

class AircraftObject: public DynamicObject {
public:
        AircraftObject();
        virtual ~AircraftObject();

        // provide an interface to the callbacks

        ACTION_INTERFACE(AircraftObject, SmokeOn);
        ACTION_INTERFACE(AircraftObject, SmokeOff);
        ACTION_INTERFACE(AircraftObject, SmokeToggle);
        ACTION_INTERFACE(AircraftObject, GearToggle);

        MOTION_INTERFACE(AircraftObject, MoveRadarCursors);

        AXIS_INTERFACE(AircraftObject, setAileron);
        AXIS_INTERFACE(AircraftObject, setElevator);
        AXIS_INTERFACE(AircraftObject, setRudder);
        AXIS_INTERFACE(AircraftObject, setThrottle);

        ... rest of class declaration ...
};


void AircraftObject::ToggleGear() {
    ...implementation...
}

void AircraftObject::setAileron(double x) {
    // x ranges from -1.0 to 1.0
    ...implementation... 
}

void AircraftObject::MoveRadarCursors(int x, int y, int dx, int dy) {
    // x,y is the mouse position
    // dx, dy is the movement since the last mouse event
    ...implementation....
}

// etc...

The constructor for AircraftObject looks like:

AircraftObject::AircraftObject(): DynamicObject() {
   ... other initialiazation code ...
   BIND_AXIS("AILERON", setAileron);
   BIND_AXIS("ELEVATOR", setElevator);
   BIND_AXIS("RUDDER", setRudder);
   BIND_AXIS("THROTTLE", setThrottle);
   BIND_ACTION("SMOKE_ON", SmokeOn);
   BIND_ACTION("SMOKE_OFF", SmokeOff);
   BIND_ACTION("SMOKE_TOGGLE", SmokeToggle);
   BIND_ACTION("TOGGLE_GEAR", GearToggle);
   BIND_MOTION("MOVE_RADAR_CURSORS", MoveRadarCursors);
}

TODO update the code above to reflect the most recent version of the interface, and expand on the new dynamic mapping facilities. Finish refactoring the rest of this page from the old wiki.