From ea7b1fbc673e40d5e51f2d185ebe8542741164fa Mon Sep 17 00:00:00 2001
From: german77 <juangerman-13@hotmail.com>
Date: Mon, 20 Sep 2021 16:34:05 -0500
Subject: [PATCH] input_common: Create input_engine

---
 src/input_common/input_engine.cpp | 361 ++++++++++++++++++++++++++++++
 src/input_common/input_engine.h   | 224 ++++++++++++++++++
 2 files changed, 585 insertions(+)
 create mode 100644 src/input_common/input_engine.cpp
 create mode 100644 src/input_common/input_engine.h

diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
new file mode 100644
index 0000000000..1534f24b04
--- /dev/null
+++ b/src/input_common/input_engine.cpp
@@ -0,0 +1,361 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include "common/logging/log.h"
+#include "common/param_package.h"
+#include "input_common/input_engine.h"
+
+namespace InputCommon {
+
+void InputEngine::PreSetController(const PadIdentifier& identifier) {
+    std::lock_guard lock{mutex};
+    if (!controller_list.contains(identifier)) {
+        controller_list.insert_or_assign(identifier, ControllerData{});
+    }
+}
+
+void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) {
+    std::lock_guard lock{mutex};
+    ControllerData& controller = controller_list.at(identifier);
+    if (!controller.buttons.contains(button)) {
+        controller.buttons.insert_or_assign(button, false);
+    }
+}
+
+void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) {
+    std::lock_guard lock{mutex};
+    ControllerData& controller = controller_list.at(identifier);
+    if (!controller.hat_buttons.contains(button)) {
+        controller.hat_buttons.insert_or_assign(button, u8{0});
+    }
+}
+
+void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) {
+    std::lock_guard lock{mutex};
+    ControllerData& controller = controller_list.at(identifier);
+    if (!controller.axes.contains(axis)) {
+        controller.axes.insert_or_assign(axis, 0.0f);
+    }
+}
+
+void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) {
+    std::lock_guard lock{mutex};
+    ControllerData& controller = controller_list.at(identifier);
+    if (!controller.motions.contains(motion)) {
+        controller.motions.insert_or_assign(motion, BasicMotion{});
+    }
+}
+
+void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) {
+    {
+        std::lock_guard lock{mutex};
+        ControllerData& controller = controller_list.at(identifier);
+        if (!configuring) {
+            controller.buttons.insert_or_assign(button, value);
+        }
+    }
+    TriggerOnButtonChange(identifier, button, value);
+}
+
+void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) {
+    {
+        std::lock_guard lock{mutex};
+        ControllerData& controller = controller_list.at(identifier);
+        if (!configuring) {
+            controller.hat_buttons.insert_or_assign(button, value);
+        }
+    }
+    TriggerOnHatButtonChange(identifier, button, value);
+}
+
+void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) {
+    {
+        std::lock_guard lock{mutex};
+        ControllerData& controller = controller_list.at(identifier);
+        if (!configuring) {
+            controller.axes.insert_or_assign(axis, value);
+        }
+    }
+    TriggerOnAxisChange(identifier, axis, value);
+}
+
+void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel value) {
+    {
+        std::lock_guard lock{mutex};
+        ControllerData& controller = controller_list.at(identifier);
+        if (!configuring) {
+            controller.battery = value;
+        }
+    }
+    TriggerOnBatteryChange(identifier, value);
+}
+
+void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value) {
+    {
+        std::lock_guard lock{mutex};
+        ControllerData& controller = controller_list.at(identifier);
+        if (!configuring) {
+            controller.motions.insert_or_assign(motion, value);
+        }
+    }
+    TriggerOnMotionChange(identifier, motion, value);
+}
+
+bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
+    std::lock_guard lock{mutex};
+    if (!controller_list.contains(identifier)) {
+        LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
+                  identifier.pad, identifier.port);
+        return false;
+    }
+    ControllerData controller = controller_list.at(identifier);
+    if (!controller.buttons.contains(button)) {
+        LOG_ERROR(Input, "Invalid button {}", button);
+        return false;
+    }
+    return controller.buttons.at(button);
+}
+
+bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const {
+    std::lock_guard lock{mutex};
+    if (!controller_list.contains(identifier)) {
+        LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
+                  identifier.pad, identifier.port);
+        return false;
+    }
+    ControllerData controller = controller_list.at(identifier);
+    if (!controller.hat_buttons.contains(button)) {
+        LOG_ERROR(Input, "Invalid hat button {}", button);
+        return false;
+    }
+    return (controller.hat_buttons.at(button) & direction) != 0;
+}
+
+f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
+    std::lock_guard lock{mutex};
+    if (!controller_list.contains(identifier)) {
+        LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
+                  identifier.pad, identifier.port);
+        return 0.0f;
+    }
+    ControllerData controller = controller_list.at(identifier);
+    if (!controller.axes.contains(axis)) {
+        LOG_ERROR(Input, "Invalid axis {}", axis);
+        return 0.0f;
+    }
+    return controller.axes.at(axis);
+}
+
+BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
+    std::lock_guard lock{mutex};
+    if (!controller_list.contains(identifier)) {
+        LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
+                  identifier.pad, identifier.port);
+        return BatteryLevel::Charging;
+    }
+    ControllerData controller = controller_list.at(identifier);
+    return controller.battery;
+}
+
+BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
+    std::lock_guard lock{mutex};
+    if (!controller_list.contains(identifier)) {
+        LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
+                  identifier.pad, identifier.port);
+        return {};
+    }
+    ControllerData controller = controller_list.at(identifier);
+    return controller.motions.at(motion);
+}
+
+void InputEngine::ResetButtonState() {
+    for (std::pair<PadIdentifier, ControllerData> controller : controller_list) {
+        for (std::pair<int, bool> button : controller.second.buttons) {
+            SetButton(controller.first, button.first, false);
+        }
+        for (std::pair<int, bool> button : controller.second.hat_buttons) {
+            SetHatButton(controller.first, button.first, false);
+        }
+    }
+}
+
+void InputEngine::ResetAnalogState() {
+    for (std::pair<PadIdentifier, ControllerData> controller : controller_list) {
+        for (std::pair<int, float> axis : controller.second.axes) {
+            SetAxis(controller.first, axis.first, 0.0);
+        }
+    }
+}
+
+void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) {
+    std::lock_guard lock{mutex_callback};
+    for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
+        const InputIdentifier& poller = poller_pair.second;
+        if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) {
+            continue;
+        }
+        if (poller.callback.on_change) {
+            poller.callback.on_change();
+        }
+    }
+    if (!configuring || !mapping_callback.on_data) {
+        return;
+    }
+    if (value == GetButton(identifier, button)) {
+        return;
+    }
+    mapping_callback.on_data(MappingData{
+        .engine = GetEngineName(),
+        .pad = identifier,
+        .type = EngineInputType::Button,
+        .index = button,
+        .button_value = value,
+    });
+}
+
+void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) {
+    std::lock_guard lock{mutex_callback};
+    for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
+        const InputIdentifier& poller = poller_pair.second;
+        if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) {
+            continue;
+        }
+        if (poller.callback.on_change) {
+            poller.callback.on_change();
+        }
+    }
+    if (!configuring || !mapping_callback.on_data) {
+        return;
+    }
+    for (std::size_t index = 1; index < 0xff; index <<= 1) {
+        bool button_value = (value & index) != 0;
+        if (button_value == GetHatButton(identifier, button, static_cast<u8>(index))) {
+            continue;
+        }
+        mapping_callback.on_data(MappingData{
+            .engine = GetEngineName(),
+            .pad = identifier,
+            .type = EngineInputType::HatButton,
+            .index = button,
+            .hat_name = GetHatButtonName(static_cast<u8>(index)),
+        });
+    }
+}
+
+void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) {
+    std::lock_guard lock{mutex_callback};
+    for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
+        const InputIdentifier& poller = poller_pair.second;
+        if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) {
+            continue;
+        }
+        if (poller.callback.on_change) {
+            poller.callback.on_change();
+        }
+    }
+    if (!configuring || !mapping_callback.on_data) {
+        return;
+    }
+    if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) {
+        return;
+    }
+    mapping_callback.on_data(MappingData{
+        .engine = GetEngineName(),
+        .pad = identifier,
+        .type = EngineInputType::Analog,
+        .index = axis,
+        .axis_value = value,
+    });
+}
+
+void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
+                                         [[maybe_unused]] BatteryLevel value) {
+    std::lock_guard lock{mutex_callback};
+    for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
+        const InputIdentifier& poller = poller_pair.second;
+        if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Battery, 0)) {
+            continue;
+        }
+        if (poller.callback.on_change) {
+            poller.callback.on_change();
+        }
+    }
+}
+
+void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
+                                        BasicMotion value) {
+    std::lock_guard lock{mutex_callback};
+    for (const std::pair<int, InputIdentifier> poller_pair : callback_list) {
+        const InputIdentifier& poller = poller_pair.second;
+        if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) {
+            continue;
+        }
+        if (poller.callback.on_change) {
+            poller.callback.on_change();
+        }
+    }
+    if (!configuring || !mapping_callback.on_data) {
+        return;
+    }
+    if (std::abs(value.gyro_x) < 1.0f && std::abs(value.gyro_y) < 1.0f &&
+        std::abs(value.gyro_z) < 1.0f) {
+        return;
+    }
+    mapping_callback.on_data(MappingData{
+        .engine = GetEngineName(),
+        .pad = identifier,
+        .type = EngineInputType::Motion,
+        .index = motion,
+        .motion_value = value,
+    });
+}
+
+bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
+                                         const PadIdentifier& identifier, EngineInputType type,
+                                         std::size_t index) const {
+    if (input_identifier.type != type) {
+        return false;
+    }
+    if (input_identifier.index != index) {
+        return false;
+    }
+    if (input_identifier.identifier != identifier) {
+        return false;
+    }
+    return true;
+}
+
+void InputEngine::BeginConfiguration() {
+    configuring = true;
+}
+
+void InputEngine::EndConfiguration() {
+    configuring = false;
+}
+
+const std::string& InputEngine::GetEngineName() const {
+    return input_engine;
+}
+
+int InputEngine::SetCallback(InputIdentifier input_identifier) {
+    std::lock_guard lock{mutex_callback};
+    callback_list.insert_or_assign(last_callback_key, input_identifier);
+    return last_callback_key++;
+}
+
+void InputEngine::SetMappingCallback(MappingCallback callback) {
+    std::lock_guard lock{mutex_callback};
+    mapping_callback = std::move(callback);
+}
+
+void InputEngine::DeleteCallback(int key) {
+    std::lock_guard lock{mutex_callback};
+    if (!callback_list.contains(key)) {
+        LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
+        return;
+    }
+    callback_list.erase(key);
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
new file mode 100644
index 0000000000..86a8e00d8e
--- /dev/null
+++ b/src/input_common/input_engine.h
@@ -0,0 +1,224 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#pragma once
+
+#include <functional>
+#include <mutex>
+#include <unordered_map>
+
+#include "common/common_types.h"
+#include "common/input.h"
+#include "common/param_package.h"
+#include "common/uuid.h"
+#include "input_common/main.h"
+
+// Pad Identifier of data source
+struct PadIdentifier {
+    Common::UUID guid{};
+    std::size_t port{};
+    std::size_t pad{};
+
+    friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default;
+};
+
+// Basic motion data containing data from the sensors and a timestamp in microsecons
+struct BasicMotion {
+    float gyro_x;
+    float gyro_y;
+    float gyro_z;
+    float accel_x;
+    float accel_y;
+    float accel_z;
+    u64 delta_timestamp;
+};
+
+// Stages of a battery charge
+enum class BatteryLevel {
+    Empty,
+    Critical,
+    Low,
+    Medium,
+    Full,
+    Charging,
+};
+
+// Types of input that are stored in the engine
+enum class EngineInputType {
+    None,
+    Button,
+    HatButton,
+    Analog,
+    Motion,
+    Battery,
+};
+
+namespace std {
+// Hash used to create lists from PadIdentifier data
+template <>
+struct hash<PadIdentifier> {
+    size_t operator()(const PadIdentifier& pad_id) const noexcept {
+        u64 hash_value = pad_id.guid.uuid[1] ^ pad_id.guid.uuid[0];
+        hash_value ^= (static_cast<u64>(pad_id.port) << 32);
+        hash_value ^= static_cast<u64>(pad_id.pad);
+        return static_cast<size_t>(hash_value);
+    }
+};
+
+} // namespace std
+
+namespace InputCommon {
+
+// Data from the engine and device needed for creating a ParamPackage
+struct MappingData {
+    std::string engine{};
+    PadIdentifier pad{};
+    EngineInputType type{};
+    int index{};
+    bool button_value{};
+    std::string hat_name{};
+    f32 axis_value{};
+    BasicMotion motion_value{};
+};
+
+// Triggered if data changed on the controller
+struct UpdateCallback {
+    std::function<void()> on_change;
+};
+
+// Triggered if data changed on the controller and the engine is on configuring mode
+struct MappingCallback {
+    std::function<void(MappingData)> on_data;
+};
+
+// Input Identifier of data source
+struct InputIdentifier {
+    PadIdentifier identifier;
+    EngineInputType type;
+    std::size_t index;
+    UpdateCallback callback;
+};
+
+class InputEngine {
+public:
+    explicit InputEngine(const std::string& input_engine_) : input_engine(input_engine_) {
+        callback_list.clear();
+    }
+
+    virtual ~InputEngine() = default;
+
+    // Enable configuring mode for mapping
+    void BeginConfiguration();
+
+    // Disable configuring mode for mapping
+    void EndConfiguration();
+
+    // Sets rumble to a controller
+    virtual bool SetRumble([[maybe_unused]] const PadIdentifier& identifier,
+                           [[maybe_unused]] const Input::VibrationStatus vibration) {
+        return false;
+    }
+
+    // Sets a led pattern for a controller
+    virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier,
+                         [[maybe_unused]] const Input::LedStatus led_status) {
+        return;
+    }
+
+    // Returns the engine name
+    [[nodiscard]] const std::string& GetEngineName() const;
+
+    /// Used for automapping features
+    virtual std::vector<Common::ParamPackage> GetInputDevices() const {
+        return {};
+    };
+
+    /// Retrieves the button mappings for the given device
+    virtual InputCommon::ButtonMapping GetButtonMappingForDevice(
+        [[maybe_unused]] const Common::ParamPackage& params) {
+        return {};
+    };
+
+    /// Retrieves the analog mappings for the given device
+    virtual InputCommon::AnalogMapping GetAnalogMappingForDevice(
+        [[maybe_unused]] const Common::ParamPackage& params) {
+        return {};
+    };
+
+    /// Retrieves the motion mappings for the given device
+    virtual InputCommon::MotionMapping GetMotionMappingForDevice(
+        [[maybe_unused]] const Common::ParamPackage& params) {
+        return {};
+    };
+
+    /// Retrieves the name of the given input.
+    virtual std::string GetUIName([[maybe_unused]] const Common::ParamPackage& params) const {
+        return GetEngineName();
+    };
+
+    /// Retrieves the index number of the given hat button direction
+    virtual u8 GetHatButtonId([[maybe_unused]] const std::string direction_name) const {
+        return 0;
+    };
+
+    void PreSetController(const PadIdentifier& identifier);
+    void PreSetButton(const PadIdentifier& identifier, int button);
+    void PreSetHatButton(const PadIdentifier& identifier, int button);
+    void PreSetAxis(const PadIdentifier& identifier, int axis);
+    void PreSetMotion(const PadIdentifier& identifier, int motion);
+    void ResetButtonState();
+    void ResetAnalogState();
+
+    bool GetButton(const PadIdentifier& identifier, int button) const;
+    bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
+    f32 GetAxis(const PadIdentifier& identifier, int axis) const;
+    BatteryLevel GetBattery(const PadIdentifier& identifier) const;
+    BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
+
+    int SetCallback(InputIdentifier input_identifier);
+    void SetMappingCallback(MappingCallback callback);
+    void DeleteCallback(int key);
+
+protected:
+    void SetButton(const PadIdentifier& identifier, int button, bool value);
+    void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
+    void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
+    void SetBattery(const PadIdentifier& identifier, BatteryLevel value);
+    void SetMotion(const PadIdentifier& identifier, int motion, BasicMotion value);
+
+    virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
+        return "Unknown";
+    }
+
+private:
+    struct ControllerData {
+        std::unordered_map<int, bool> buttons;
+        std::unordered_map<int, u8> hat_buttons;
+        std::unordered_map<int, float> axes;
+        std::unordered_map<int, BasicMotion> motions;
+        BatteryLevel battery;
+    };
+
+    void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
+    void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
+    void TriggerOnAxisChange(const PadIdentifier& identifier, int button, f32 value);
+    void TriggerOnBatteryChange(const PadIdentifier& identifier, BatteryLevel value);
+    void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, BasicMotion value);
+
+    bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
+                                const PadIdentifier& identifier, EngineInputType type,
+                                std::size_t index) const;
+
+    mutable std::mutex mutex;
+    mutable std::mutex mutex_callback;
+    bool configuring{false};
+    bool is_callback_enabled{true};
+    const std::string input_engine;
+    int last_callback_key = 0;
+    std::unordered_map<PadIdentifier, ControllerData> controller_list;
+    std::unordered_map<int, InputIdentifier> callback_list;
+    MappingCallback mapping_callback;
+};
+
+} // namespace InputCommon