Wiring physical devices to abstract inputs

In an earlier post, we discussed different methods of gathering keyboard input in Windows. Today, we will cover how multi-platform and multi-device input can be handled in a straightforward way, showing a very efficient implementation of the underlying parts.

Before we can start wiring physical devices to logical controllers, we need to cover some of the lower level parts first. Molecule’s low-level input API consists of several physical devices available to each platform. For example, on Windows the available devices would be keyboard (using Raw Input), mouse (using Raw Input and Windows Messages), joysticks/gamepads (using DirectInput) and Xbox360 controllers (using XInput).

Physical devices

Each of those devices is exposed by its corresponding class, which can directly be used by clients if they have no need for higher level structures:

Keyboard keyboard;
Mouse mouse;
Joystick joystick(0);
Xbox360Controller controller(0);

if (controller.WentDown(Xbox360Controller::Button::A))
  // action

if (mouse.IsDoubleClicked(Mouse::Button::MIDDLE))
  // other action

if (keyboard.IsPressed(Keyboard::Key::ENTER))
  // yet another action

Some important properties about these classes should be noted:

  • Each of these classes offers IsPressed(), WentDown() and WentUp() methods, all sharing the exact same signature – this becomes important when wiring inputs to higher level structures.
  • None of these implements a virtual interface, or is implemented as a singleton. There can be more than one keyboard/mouse attached, and the same is of course true for joysticks and gamepads.

The latter is especially important, because interfaces (base classes and virtual functions) would just add unneccessary overhead, without solving any real problems. Just because each of the classes offers methods sharing the same name and signature doesn’t mean they have to be squeezed into an interface!

Furthermore, keep in mind that even though the devices (and their corresponding class names) are different, you can already build a multi-platform solution if all you’re interested in are simple buttons (or combinations thereof):

#if _WINDOWS
  typedef Keyboard MenuDevice;
#elif _XBOX360
  typedef Xbox360Controller MenuDevice;
#endif

ConfigSettingInt g_menuBackButton("input_menuBack", "Exits the current menu.", someConfiguredValue);

// initialize the device somewhere
MenuDevice menuDevice;

if (menuDevice.WentDown(g_menuBackButton))
  // action

The above already enables you to use different devices on different platforms, and have configurable input layouts as well – without ever introducing any interfaces or similar.

Of course, as soon as we start dealing with more complicated input configurations, we need something which allows us to tie different devices and their possible inputs into a common, platform-independent structure – enter logical devices.

Logical devices

A logical device serves the following purposes:

  • “Abstract” inputs can be added to a logical device. These inputs can be any of the physical device inputs, or completely arbitrary, user-defined inputs (e.g. gesture detection).
  • The state of each of these inputs can be checked via the logical device, with each of the inputs having an identifier of some kind.
  • The logical device memorizes the state of each input at the beginning of a frame. Checking for toggled inputs should be a simple lookup-and-compare, without having to jump through any additional hoops.

Let us cover each of these points one by one.

Abstract inputs

How can we store inputs from different physical devices in a logical device, and allow user-defined inputs as well? Some user inputs might need to access the keyboard, others need to access the mouse, others need both – same for controllers on any of the consoles.

Of course there is no type in C++ that allows us to achieve the above, but we can use function pointers for that. An abstract input in Molecule is nothing more than any function exhibiting the following signature:

bool MyInputFunction(const LogicalDevice* device);

The function simply has to return true when an input is triggered, and false when it isn’t. Because the LogicalDevice provides access to the underlying physical devices, we can easily use any of the available devices on the platform we’re dealing with. For example, simply checking for a button combination could look something like this:

bool CheckMenuBack(const LogicalDevice* device)
{
#if _WINDOWS
  // configurable key
  if (device->GetKeyboard()->WentDown(g_menuBack) ||
      device->GetKeyboard()->IsPressed(g_menuBack2))
    return true;
#elif _XBOX360
  // non-configurable button
  if (device->GetController(0)->WentDown(Controller::Button::B))
    return true;
#endif
}

Of course, you can check for arbitrarily complex inputs such as gesture recognition, because you’re allowed to do pretty much anything you want inside your implementation.

Adding abstract inputs to a logical device is as simple as calling AddInput(), handing it a pointer to a function (or a delegate):

logicalDevice.AddInput(&CheckMenuBack);

Input identifiers

After having added several inputs to one of our logical devices, we need to be able to check whether they were toggled or not. But how can we identify both inputs provided by physical devices, as well as user-defined ones?

One solution I have seen used in the past is using strings as an identifier, but this is both inefficient (string compares) and error-prone (typos in identifiers are hard to find), so clearly not something I wanted to use in Molecule.

An alternative, somewhat better solution is to use enums – by putting the enumerators for each possible input inside a common namespace, the user is able to extend the available inputs. However, because the enumerators must be visible to the user, each change in a header triggers a recompile, and adding new inputs is error-prone as well if their values are stored on disk somewhere.

There is a better solution, which is neither error-prone nor subject to recompiles, and is very efficient. Whenever a new input is added to the logical device, an identifier for the given input is returned. Internally, this identifier is nothing more than an integer which points directly into the array of inputs – hence, checking whether an input is toggled is a simple lookup-and-compare operation.

Let me show you an example in order to understand what I am talking about:

Controller::ID chordID = logicalDevice.AddInput(&ComplicatedChord);
Controller::ID gestureID = logicalDevice.AddInput(&CheckGesture);

// at this point, the logical device internally stores an array with two entries.
// chordID = 0, and gestureID = 1.

if (logicalDevice.IsTriggered(chordID))
  // action

if (logicalDevice.IsTriggered(gestureID))
  // action

IsTriggered() is an inline function doing the following:

inline bool LogicalDevice::IsTriggered(ID id) const
{
  return m_inputResults[id];
}

So the lookup is merely an array access (no hash table or map or similar) using the given ID – it doesn’t get any simpler than that.

Chords and aliased/mapped inputs

There are several types of inputs which are very common in games: chords (button combinations) and aliased/mapped inputs (different buttons or combinations which trigger the same action). Because those are so common, having to write a function each time we want to check some simple inputs quickly becomes boring and tiresome.

So let us try to write some helper functions which take care of the work, and can easily be “configured” by the user.

Chord helpers

Any chord can be checked by just &&-ing the result of several input functions together, as in the following examples:

// two-input chord
bool Chord(const LogicalDevice* device)
{
  if (((Function1)(device)) && ((Function2)(device)))
    return true;

  return false;
};

// three-input chords
bool Chord(const LogicalDevice* device)
{
  if (((Function1)(device)) && ((Function2)(device)) && ((Function3)(device)))
    return true;

  return false;
};

// other chords omitted...

As can be seen, the pattern is always the same. But how can the functions actually checking the inputs (Function1, Function2 and Function3 in the code above) be configured? By using non-type template arguments, like we did for our delegates and events:

template <bool (*Function1)(const LogicalDevice*), bool (*Function2)(const LogicalDevice*)>
bool Chord(const LogicalDevice* device)
{
  if (((Function1)(device)) && ((Function2)(device)))
    return true;

  return false;
};

template <bool (*Function1)(const LogicalDevice*), bool (*Function2)(const LogicalDevice*), bool (*Function3)(const LogicalDevice*)>
bool Chord(const LogicalDevice* device)
{
  if (((Function1)(device)) && ((Function2)(device)) && ((Function3)(device)))
    return true;

  return false;
};

Each template argument accepted by the Chord() function has to exhibit the same signature like abstract inputs – this allows us to build so-called forwarding functions, which can in turn be used by the user to configure his chords.

Let us quickly take a look at those forwarding functions, and a later example will make it clear:

namespace Key
{
  template <Keyboard::Key::Enum KEY>
  ME_INLINE bool IsPressed(const LogicalDevice* device)
  {
    return device->GetKeyboard()->IsPressed(KEY);
  }

  template <Keyboard::Key::Enum KEY>
  ME_INLINE bool WentDown(const LogicalDevice* device)
  {
    return device->GetKeyboard()->WentDown(KEY);
  }

  template <Keyboard::Key::Enum KEY>
  ME_INLINE bool WentUp(const LogicalDevice* device)
  {
    return device->GetKeyboard()->WentUp(KEY);
  }
}

namespace MouseButton
{
  template <Mouse::Button::Enum BUTTON>
  ME_INLINE bool IsPressed(const LogicalDevice* device)
  {
    return device->GetMouse()->IsPressed(BUTTON);
  }

  template <Mouse::Button::Enum BUTTON>
  ME_INLINE bool WentDown(const LogicalDevice* device)
  {
    return device->GetMouse()->WentDown(BUTTON);
  }

  template <Mouse::Button::Enum BUTTON>
  ME_INLINE bool WentUp(const LogicalDevice* device)
  {
    return device->GetMouse()->WentUp(BUTTON);
  }
}

Each of the forwarding functions takes either a keyboard key, mouse button, or joystick/gamepad button as template argument. This is important – we need to pass these as template arguments, because if they had been ordinary function arguments, the signature of the forwarding functions would no longer match the ones expected by the Chord() templates!

Another thing to keep in mind is that we need to define a forwarding function for each of the physical devices, because they all need to call different methods. The above just shows the implementation for keyboard and mouse – joysticks and gamepads have a similar implementation.

In case this was hard to follow, here is an example of how the Chord() template helpers can be used:

Controller::ID chordID = logicalDevice.AddInput
(
  &Chord
  <
    &Key::IsPressed<Keyboard::Key::LEFT_CONTROL>,
    &ConfigKey::WentDown<&g_myKey>
  >
);

The abstract input we add is still an ordinary function – the Chord() function template. But the functions used internally are passed as template arguments, making it possible to configure arbitrary chords using a single line of code for each new input.

The above adds a chord as abstract input: in order for the chord to trigger, the left control key must be held down, while an additional configurable key just went down. One thing I didn’t show in the forwarding functions’ implementation is that we can also add additional functions which take a ConfigSettingInt as template argument:

namespace ConfigKey
{
  template <ConfigSettingInt* T>
  ME_INLINE bool IsPressed(const LogicalDevice* device)
  {
    return device->GetKeyboard()->IsPressed(*T);
  }

  template <ConfigSettingInt* T>
  ME_INLINE bool WentDown(const LogicalDevice* device)
  {
    return device->GetKeyboard()->WentDown(*T);
  }

  template <ConfigSettingInt* T>
  ME_INLINE bool WentUp(const LogicalDevice* device)
  {
    return device->GetKeyboard()->WentUp(*T);
  }

  // other implementations omitted
}

This means we can build input functions at compile-time using variables as template arguments which are used during run-time! Quasi-configurable template arguments, think about that for a second.

Aliased buttons helpers

In a similar manner, we can conjure up Map() function templates:

template <bool (*Function1)(const LogicalDevice*), bool (*Function2)(const LogicalDevice*)>
bool Map(const LogicalDevice* device)
{
  if (((Function1)(device)) || ((Function2)(device)))
    return true;

  return false;
};

template <bool (*Function1)(const LogicalDevice*), bool (*Function2)(const LogicalDevice*), bool (*Function3)(const LogicalDevice*)>
bool Map(const LogicalDevice* device)
{
  if (((Function1)(device)) || ((Function2)(device)) || ((Function3)(device)))
    return true;

  return false;
};

All that has changed is that inputs are no longer &&-ed together, but ||-ed instead, because a map triggers as soon as any of the underlying inputs is toggled. Of course, all the forwarding functions can be used as well, allowing us to define completely arbitrary aliases of different inputs using a few lines of code:

Controller::ID mapID = logicalDevice.AddInput
(
  &Map
  <
    &ConfigMouseButton::IsPressed<&g_myMouseButton>,
    &JoystickButton::WentDown<0>,
    &ConfigXbox360ControllerButton::WentDown<&g_controllerButton>,
    &UserDefinedGestureInput
  >
);

Again, inputs from completely different devices can be mixed and matched as desired, and buttons/keys can either be hardcoded or configured at run-time (the above example shows a combination of both).

If you’re still following, you might also have noticed why we bothered giving each input function the same signature in the first place? The reason is that you can also mix and match Chord() and Map() functions:

Controller::ID mapID = defaultController.AddInput
(
  &Map
  <
    &ConfigMouseButton::IsPressed<&g_myMouseButton>,
    &Chord
    <
      &Key::IsPressed<input::Keyboard::Key::LEFT_CONTROL>,
      &ConfigKey::WentDown<&g_myKey>
    >,
    &ConfigJoystickButton::IsPressed<&g_joystickButton>,
    &ConfigXbox360ControllerButton::WentDown<&g_controllerButton>,
    &UserDefinedGestureInput
  >
);

As can be seen in the example, the Chord() function template can also be used as a template argument to the Map() function template, and vice versa. This allows us to alias chords with single buttons, or user-defined inputs, or similar.

One last thing to note: Using the Chord() and Map() function templates is exactly as efficient as if you had written the actual functions yourself (profiled using VS 2010). This is because all arguments are passed in templates, and the forwarding functions are tagged with ME_INLINE (__forceinline).

Let us quickly recap:

  • The above shows a very efficient implementation that is straightforward to use, and easy to configure.
  • Inputs from several different physical devices can be configured at will, supporting both hardcoded constants (where desired), and run-time configurable layouts.
  • Arbitrary user-defined inputs can be mixed and matched with other more simple inputs.
  • The implementation is completely interface and virtual-function free, hence new inputs can be added easily without having to write a ton of code.

And that’s it for today, hope you enjoyed the post.

17 thoughts on “Wiring physical devices to abstract inputs

    • Hi Riley, a valid question.

      To be honest, I find polling easier and simpler to use. I can poll from free functions, from inside classes, and hence can carry state around.

      Event-based input adds overhead because each event listener needs to be registered/stored somewhere, resulting in memory allocations (and deallocations whenever a listener unregisters itself from an event). It’s nothing too bad, and it’s nothing I would worry about performance-wise, but I somehow find polling easier/simpler to use, but that may very well be personal preference.

      It would be simple to just add events on top of my implementation, but I’ve not had any reason to do so yet.

      What’s your take on that? I would very much like to hear your opinion on polling vs. event-based inputs.

  1. Hi Stefan,

    first of all thank for sharing this blog post.

    I think a downside is, that you have to write a Chord or Map function for each amount of test functions. Well, it is a very small downside and I would live with it, but nevertheless I thought about implementing this for the general case.

    So, with C++11 and variadic templates I came up with:

    typedef bool (*InputFunc)(Input const*);

    template
    struct Logic {
    static inline bool conjunction(Input const* input) {
    return Function1(input) && Logic::conjunction(input);
    }
    static inline bool disjunction(Input const* input) {
    return Function1(input) || Logic::disjunction(input);
    }
    };

    template
    struct Logic {
    static inline bool conjunction(Input const* input) {
    return Function1(input);
    }
    static inline bool disjunction(Input const* input) {
    return Function1(input);
    }
    };

    Usage example:
    if (
    Logic<
    Logic<
    Key::pressed,
    Key::wentDown
    >::conjunction,
    Mouse::wentDown
    >::disjunction(&input)
    ) {
    printf(“LCtrl + C or right mouse pressed.\n”);
    }

    • Thanks for the input, Carsten!

      I changed the implementation a bit in the meantime, so you don’t have to write different Chord() or Map() functions. In fact, they are no longer needed at all. The new implementation works with the following syntax:

      Controller copyController;
      copyController.AddInput(&keyboard, &Keyboard::IsPressed, Keyboard::Key::LEFT_CONTROL);
      copyController.AddInput(&keyboard, &Keyboard::WentDown, Keyboard::Key::C);

      Controller enterMenuController;
      enterMenuController.AddInput(&keyboard, &Keyboard::IsPressed, Keyboard::Key::ENTER);
      enterMenuController.AddInput(&mouse, &Mouse::WentUp, Mouse::Button::LEFT);

      As can be seen, you can now freely mix different functions of completely different devices/classes. It even allows hooking up functions coming from your own classes (without having to derive from a common base class):

      GestureInput gesture(&mouse);
      Controller cancelController;
      cancelController.AddInput(&keyboard, &Keyboard::WentDown, Keyboard::Key::ESCAPE);
      cancelController.AddInput(&gesture, &GestureInput::Evaluate);

      The controller implementation now offers both a IsChordTriggered() and IsAnyTriggered() function.

      Of course, all of the above is handled in a completely type-safe manner, no void* or anything like that involved. There’s documentation and sample code in the SDK if you want to check it out.

      • How does this work without inheriting from a common base class and without using void*?

        Controller::AddInput must store the parameter values in some kind of container, but I can’t imagine how to do this, if the device types could be completely different.

      • The implementation uses type erasure in conjunction with a common base class and function pointers internally, but this is completely transparent to the user.

  2. Hi Stefan,

    I’m trying to understand the controller, which you mentioned above. Thinking about that I have some questions.

    As in the example below:

    class InputController
    {
    class InputValidator
    {
    public:
    virtual bool Verify() const = 0;
    };

    template
    class InputValidatorWrapper : public InputValidator
    {
    public:
    InputValidatorWrapper(Device * device, Function function, Button button)
    : m_device(device)
    , m_function(function)
    , m_button(button)
    {}

    BOOL Verify() const { return (m_device->*m_function)(m_button); };

    private:
    Device * m_device;
    Function m_function;
    Button m_button;
    };
    public:
    InputController();

    template
    void AddInput(Device * device, Function funct, Button button)
    {
    m_validators[m_index] = new InputValidatorWrapper(device, funct, button);
    m_index++;
    }

    bool IsAnyTriggered()
    {
    bool result = false;

    for (int i = 0; i Verify();
    }

    return result;
    }
    private:
    InputValidator * m_validators[10];
    int m_index;
    };

    Usage:

    InputController exitController;
    exitController.AddInput(&keyboard, &Keyboard::WentDown, Keyboard::Key::ESCAPE);

    Using type erasure I can’t avoid dynamic memory allocation. In your example you declare the controller on the stack and do not see any allocator in the constructor parameter.

    Do you have any tips for avoiding dynamic memory allocation in this case?

    • Do you have any tips for avoiding dynamic memory allocation in this case?

      Yes, there are two solutions to this:

      1. Use a custom allocator. Strictly speaking, this does not get rid of the dynamic memory allocation, but is much, much better than just allocating with keyword new.
      2. Use a perfectly-sized buffer as a member inside your InputValidatorWrapper and create new instances using placement new. Of course this increases the class’ sizeof(), but gets rid of any dynamic memory allocation, is completely transparent to the user, and the fastest you can get.

      I often use the placement-new technique when working with type-erased types and a base class.

      • The perfect size buffer sounds like a good idea. But I don’t see the reason to use inside the InputValidatorWrapper. In different way I declare the storage inside InputController, follows below:

        class InputController
        {
        /**/
        template<typename Device, typename Function, typename Button>
        void AddInput(Device * device, Function funct, Button button)
        {
        typedef InputValidatorWrapper<Device, Function, Button> InputValidatorType;

        void * memory = m_allocator.Allocate(sizeof(InputValidatorType), alignof(InputValidatorType),
        0);

        InputValidator * validator = new (memory) InputValidatorType(device, funct, button);

        m_validators.Push(validator);
        }

        BOOL IsAnyTriggered();
        private:
        static NS_CONSTEXPR SIZE STORAGE_SIZE = 512;
        static NS_CONSTEXPR SIZE MAX_INPUTS = 16;

        FixedStack<InputValidator *, MAX_INPUTS> m_validators;
        CHAR m_storage[STORAGE_SIZE];
        StackAllocator m_allocator;
        };

        Inside the InputController constructor I initialize the allocator with the storage buffer and in the destructor I free the memory.

        That working fine but I thinking if it is the best solution for the problem.

      • I don’t fully understand your question.
        If you want to avoid dynamic memory allocation, you can always store a perfectly-sized buffer as member in your derived class and use placement new to construct your instance in there. The size of the buffer needs to be large enough to hold only one instance of a class.
        This means you neither need dynamic memory allocation, nor an allocator.

    • That doesn’t look right.
      You store an array of GenericInputValidators by value, rather than pointers to it, hence you won’t get dynamic binding for the Invoke() call.

      I will whip up an example for you.

      • Said example can be found here:


        class InputController
        {
        class InputValidator
        {
        public:
        virtual bool Invoke() const = 0;
        virtual ~InputValidator(void) {}
        };
        template <typename Device, typename Function, typename Button>
        class InputValidatorWrapper : public InputValidator
        {
        public:
        InputValidatorWrapper(Device * device, Function function, Button button)
        : m_device(device)
        , m_function(function)
        , m_button(button)
        {}
        virtual bool Invoke() const { return (m_device->*m_function)(m_button); };
        private:
        Device* m_device;
        Function m_function;
        Button m_button;
        };
        struct InputStorage
        {
        // NOTE: this needs to be large enough to hold any InputValidatorWrapper of any type.
        // pointers-to-member-functions can have different sizes depending on where they point to (compiler-dependent).
        static const size_t MAX_SIZE = 16u;
        InputValidator* validator;
        char memory[MAX_SIZE];
        };
        public:
        InputController(void)
        : m_index(0u)
        {
        }
        ~InputController(void)
        {
        for (size_t i = 0u; i < m_index; ++i)
        {
        InputValidator* validator = m_storage[i].validator;
        validator->~InputValidator();
        }
        }
        template <typename Device, typename Function, typename Button>
        void AddInput(Device* device, Function funct, Button button)
        {
        typedef InputValidatorWrapper<Device, Function, Button> Wrapper;
        // make sure that this type of wrapper really fits into the storage buffer
        static_assert(sizeof(Wrapper) <= InputStorage::MAX_SIZE, "Buffer is too small.");
        void* memory = m_storage[m_index].memory;
        m_storage[m_index].validator = new (memory) Wrapper(device, funct, button);
        ++m_index;
        }
        bool IsAnyTriggered()
        {
        bool result = false;
        for (size_t i = 0u; i < m_index; ++i)
        {
        result |= m_storage[i].validator->Invoke();
        }
        return result;
        }
        private:
        static const size_t MAX_INPUTS = 16;
        InputStorage m_storage[MAX_INPUTS];
        size_t m_index;
        };

        view raw

        buffer.cpp

        hosted with ❤ by GitHub

        Note that you forgot to add a virtual destructor to the base class. You need this if you want to delete (or destruct) instances using a pointer-to-base, otherwise you invoke UB.
        I added this along with proper destruction of InputValidator objects that were created using placement new.

        Regarding the size of the InputStorage buffer, please see the corresponding note. I also added a check that statically asserts in case the buffer is too small.

        I hope this clears things up!

  3. Thank you very much for the blog post.

    If i see correctly you use something like event polling, but where exactly do you validate the inputs? In my code, I got sth. like `while (PollInputEvent(&event))`, which gives me all the current input states but checking all maps on every occurred event feels overkill.
    Do you cache inputs per frame? If yes, how do you store the cache since a bitmap would be huge for all input events.

    Greetings
    Cyres

    • Hi Cyres,

      All physical devices poll their inputs (keyboard keys, mouse buttons, touchpad gestures, etc.) once per frame and cache them.
      Then all InputControllers poll the underlying inputs (GetKey, IsButtonDown, etc.) once per frame, and also cache the result (see the comments section for an example).
      When the game code wants to check a certain input, it is a simple array-lookup into a controller.

      In my projects, I always use one controller per game/application input, which isn’t *that* many and shouldn’t be of any performance concern on any platform.
      I used it to ship The Lion’s Song on 5 platforms, and input handling in general shouldn’t be a concern.

      • Thanks for the reply!

        And congratulation to the overwhelmingly positive review rating at Steam for The Lion’s Song!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.