From c8554d218b11b7af081c0b836991ffb94ae003bb Mon Sep 17 00:00:00 2001 From: James Rowe Date: Sat, 22 Sep 2018 14:11:15 -0600 Subject: [PATCH 1/4] Input: Copy current SDL.h/cpp files to impl This should make reviewing much easier as you can then see what changed happened between the old file and the new one --- src/input_common/sdl/sdl_impl.cpp | 629 ++++++++++++++++++++++++++++++ src/input_common/sdl/sdl_impl.h | 51 +++ 2 files changed, 680 insertions(+) create mode 100644 src/input_common/sdl/sdl_impl.cpp create mode 100644 src/input_common/sdl/sdl_impl.h diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp new file mode 100644 index 0000000000..7c1ecc2e00 --- /dev/null +++ b/src/input_common/sdl/sdl_impl.cpp @@ -0,0 +1,629 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/param_package.h" +#include "common/threadsafe_queue.h" +#include "input_common/main.h" +#include "input_common/sdl/sdl.h" + +namespace InputCommon { + +namespace SDL { + +class SDLJoystick; +class SDLButtonFactory; +class SDLAnalogFactory; + +/// Map of GUID of a list of corresponding virtual Joysticks +static std::unordered_map>> joystick_map; +static std::mutex joystick_map_mutex; + +static std::shared_ptr button_factory; +static std::shared_ptr analog_factory; + +/// Used by the Pollers during config +static std::atomic polling; +static Common::SPSCQueue event_queue; + +static std::atomic initialized = false; + +static std::string GetGUID(SDL_Joystick* joystick) { + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + char guid_str[33]; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); + return guid_str; +} + +class SDLJoystick { +public: + SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, + decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) + : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {} + + void SetButton(int button, bool value) { + std::lock_guard lock(mutex); + state.buttons[button] = value; + } + + bool GetButton(int button) const { + std::lock_guard lock(mutex); + return state.buttons.at(button); + } + + void SetAxis(int axis, Sint16 value) { + std::lock_guard lock(mutex); + state.axes[axis] = value; + } + + float GetAxis(int axis) const { + std::lock_guard lock(mutex); + return state.axes.at(axis) / 32767.0f; + } + + std::tuple GetAnalog(int axis_x, int axis_y) const { + float x = GetAxis(axis_x); + float y = GetAxis(axis_y); + y = -y; // 3DS uses an y-axis inverse from SDL + + // Make sure the coordinates are in the unit circle, + // otherwise normalize it. + float r = x * x + y * y; + if (r > 1.0f) { + r = std::sqrt(r); + x /= r; + y /= r; + } + + return std::make_tuple(x, y); + } + + void SetHat(int hat, Uint8 direction) { + std::lock_guard lock(mutex); + state.hats[hat] = direction; + } + + bool GetHatDirection(int hat, Uint8 direction) const { + std::lock_guard lock(mutex); + return (state.hats.at(hat) & direction) != 0; + } + /** + * The guid of the joystick + */ + const std::string& GetGUID() const { + return guid; + } + + /** + * The number of joystick from the same type that were connected before this joystick + */ + int GetPort() const { + return port; + } + + SDL_Joystick* GetSDLJoystick() const { + return sdl_joystick.get(); + } + + void SetSDLJoystick(SDL_Joystick* joystick, + decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) { + sdl_joystick = + std::unique_ptr(joystick, deleter); + } + +private: + struct State { + std::unordered_map buttons; + std::unordered_map axes; + std::unordered_map hats; + } state; + std::string guid; + int port; + std::unique_ptr sdl_joystick; + mutable std::mutex mutex; +}; + +/** + * Get the nth joystick with the corresponding GUID + */ +static std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port) { + std::lock_guard lock(joystick_map_mutex); + const auto it = joystick_map.find(guid); + if (it != joystick_map.end()) { + while (it->second.size() <= port) { + auto joystick = std::make_shared(guid, it->second.size(), nullptr, + [](SDL_Joystick*) {}); + it->second.emplace_back(std::move(joystick)); + } + return it->second[port]; + } + auto joystick = std::make_shared(guid, 0, nullptr, [](SDL_Joystick*) {}); + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +/** + * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie + * it to a SDLJoystick with the same guid and that port + */ +static std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { + std::lock_guard lock(joystick_map_mutex); + auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + const std::string guid = GetGUID(sdl_joystick); + auto map_it = joystick_map.find(guid); + if (map_it != joystick_map.end()) { + auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [&sdl_joystick](const std::shared_ptr& joystick) { + return sdl_joystick == joystick->GetSDLJoystick(); + }); + if (vec_it != map_it->second.end()) { + // This is the common case: There is already an existing SDL_Joystick maped to a + // SDLJoystick. return the SDLJoystick + return *vec_it; + } + // Search for a SDLJoystick without a mapped SDL_Joystick... + auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [](const std::shared_ptr& joystick) { + return !joystick->GetSDLJoystick(); + }); + if (nullptr_it != map_it->second.end()) { + // ... and map it + (*nullptr_it)->SetSDLJoystick(sdl_joystick); + return *nullptr_it; + } + // There is no SDLJoystick without a mapped SDL_Joystick + // Create a new SDLJoystick + auto joystick = std::make_shared(guid, map_it->second.size(), sdl_joystick); + return map_it->second.emplace_back(std::move(joystick)); + } + auto joystick = std::make_shared(guid, 0, sdl_joystick); + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +void InitJoystick(int joystick_index) { + std::lock_guard lock(joystick_map_mutex); + SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); + if (!sdl_joystick) { + LOG_ERROR(Input, "failed to open joystick {}", joystick_index); + return; + } + std::string guid = GetGUID(sdl_joystick); + if (joystick_map.find(guid) == joystick_map.end()) { + auto joystick = std::make_shared(guid, 0, sdl_joystick); + joystick_map[guid].emplace_back(std::move(joystick)); + return; + } + auto& joystick_guid_list = joystick_map[guid]; + const auto it = std::find_if( + joystick_guid_list.begin(), joystick_guid_list.end(), + [](const std::shared_ptr& joystick) { return !joystick->GetSDLJoystick(); }); + if (it != joystick_guid_list.end()) { + (*it)->SetSDLJoystick(sdl_joystick); + return; + } + auto joystick = std::make_shared(guid, joystick_guid_list.size(), sdl_joystick); + joystick_guid_list.emplace_back(std::move(joystick)); +} + +void CloseJoystick(SDL_Joystick* sdl_joystick) { + std::lock_guard lock(joystick_map_mutex); + std::string guid = GetGUID(sdl_joystick); + // This call to guid is save since the joystick is guranteed to be in that map + auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = + std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [&sdl_joystick](const std::shared_ptr& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); +} + +void HandleGameControllerEvent(const SDL_Event& event) { + switch (event.type) { + case SDL_JOYBUTTONUP: { + auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); + if (joystick) { + joystick->SetButton(event.jbutton.button, false); + } + break; + } + case SDL_JOYBUTTONDOWN: { + auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); + if (joystick) { + joystick->SetButton(event.jbutton.button, true); + } + break; + } + case SDL_JOYHATMOTION: { + auto joystick = GetSDLJoystickBySDLID(event.jhat.which); + if (joystick) { + joystick->SetHat(event.jhat.hat, event.jhat.value); + } + break; + } + case SDL_JOYAXISMOTION: { + auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); + if (joystick) { + joystick->SetAxis(event.jaxis.axis, event.jaxis.value); + } + break; + } + case SDL_JOYDEVICEREMOVED: + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); + CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); + break; + case SDL_JOYDEVICEADDED: + LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); + InitJoystick(event.jdevice.which); + break; + } +} + +void CloseSDLJoysticks() { + std::lock_guard lock(joystick_map_mutex); + joystick_map.clear(); +} + +void PollLoop() { + if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { + LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); + return; + } + + SDL_Event event; + while (initialized) { + // Wait for 10 ms or until an event happens + if (SDL_WaitEventTimeout(&event, 10)) { + // Don't handle the event if we are configuring + if (polling) { + event_queue.Push(event); + } else { + HandleGameControllerEvent(event); + } + } + } + CloseSDLJoysticks(); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); +} + +class SDLButton final : public Input::ButtonDevice { +public: + explicit SDLButton(std::shared_ptr joystick_, int button_) + : joystick(std::move(joystick_)), button(button_) {} + + bool GetStatus() const override { + return joystick->GetButton(button); + } + +private: + std::shared_ptr joystick; + int button; +}; + +class SDLDirectionButton final : public Input::ButtonDevice { +public: + explicit SDLDirectionButton(std::shared_ptr joystick_, int hat_, Uint8 direction_) + : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} + + bool GetStatus() const override { + return joystick->GetHatDirection(hat, direction); + } + +private: + std::shared_ptr joystick; + int hat; + Uint8 direction; +}; + +class SDLAxisButton final : public Input::ButtonDevice { +public: + explicit SDLAxisButton(std::shared_ptr joystick_, int axis_, float threshold_, + bool trigger_if_greater_) + : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), + trigger_if_greater(trigger_if_greater_) {} + + bool GetStatus() const override { + float axis_value = joystick->GetAxis(axis); + if (trigger_if_greater) + return axis_value > threshold; + return axis_value < threshold; + } + +private: + std::shared_ptr joystick; + int axis; + float threshold; + bool trigger_if_greater; +}; + +class SDLAnalog final : public Input::AnalogDevice { +public: + SDLAnalog(std::shared_ptr joystick_, int axis_x_, int axis_y_) + : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {} + + std::tuple GetStatus() const override { + return joystick->GetAnalog(axis_x, axis_y); + } + +private: + std::shared_ptr joystick; + int axis_x; + int axis_y; +}; + +/// A button device factory that creates button devices from SDL joystick +class SDLButtonFactory final : public Input::Factory { +public: + /** + * Creates a button device from a joystick button + * @param params contains parameters for creating the device: + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type to bind + * - "button"(optional): the index of the button to bind + * - "hat"(optional): the index of the hat to bind as direction buttons + * - "axis"(optional): the index of the axis to bind + * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", + * "down", "left" or "right" + * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is + * triggered if the axis value crosses + * - "direction"(only used for axis): "+" means the button is triggered when the axis + * value is greater than the threshold; "-" means the button is triggered when the axis + * value is smaller than the threshold + */ + std::unique_ptr Create(const Common::ParamPackage& params) override { + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); + + auto joystick = GetSDLJoystickByGUID(guid, port); + + if (params.Has("hat")) { + const int hat = params.Get("hat", 0); + const std::string direction_name = params.Get("direction", ""); + Uint8 direction; + if (direction_name == "up") { + direction = SDL_HAT_UP; + } else if (direction_name == "down") { + direction = SDL_HAT_DOWN; + } else if (direction_name == "left") { + direction = SDL_HAT_LEFT; + } else if (direction_name == "right") { + direction = SDL_HAT_RIGHT; + } else { + direction = 0; + } + // This is necessary so accessing GetHat with hat won't crash + joystick->SetHat(hat, SDL_HAT_CENTERED); + return std::make_unique(joystick, hat, direction); + } + + if (params.Has("axis")) { + const int axis = params.Get("axis", 0); + const float threshold = params.Get("threshold", 0.5f); + const std::string direction_name = params.Get("direction", ""); + bool trigger_if_greater; + if (direction_name == "+") { + trigger_if_greater = true; + } else if (direction_name == "-") { + trigger_if_greater = false; + } else { + trigger_if_greater = true; + LOG_ERROR(Input, "Unknown direction {}", direction_name); + } + // This is necessary so accessing GetAxis with axis won't crash + joystick->SetAxis(axis, 0); + return std::make_unique(joystick, axis, threshold, trigger_if_greater); + } + + const int button = params.Get("button", 0); + // This is necessary so accessing GetButton with button won't crash + joystick->SetButton(button, false); + return std::make_unique(joystick, button); + } +}; + +/// An analog device factory that creates analog devices from SDL joystick +class SDLAnalogFactory final : public Input::Factory { +public: + /** + * Creates analog device from joystick axes + * @param params contains parameters for creating the device: + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type + * - "axis_x": the index of the axis to be bind as x-axis + * - "axis_y": the index of the axis to be bind as y-axis + */ + std::unique_ptr Create(const Common::ParamPackage& params) override { + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); + const int axis_x = params.Get("axis_x", 0); + const int axis_y = params.Get("axis_y", 1); + + auto joystick = GetSDLJoystickByGUID(guid, port); + + // This is necessary so accessing GetAxis with axis_x and axis_y won't crash + joystick->SetAxis(axis_x, 0); + joystick->SetAxis(axis_y, 0); + return std::make_unique(joystick, axis_x, axis_y); + } +}; + +void Init() { + using namespace Input; + RegisterFactory("sdl", std::make_shared()); + RegisterFactory("sdl", std::make_shared()); + polling = false; + initialized = true; +} + +void Shutdown() { + if (initialized) { + using namespace Input; + UnregisterFactory("sdl"); + UnregisterFactory("sdl"); + initialized = false; + } +} + +Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { + Common::ParamPackage params({{"engine", "sdl"}}); + switch (event.type) { + case SDL_JOYAXISMOTION: { + auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); + params.Set("axis", event.jaxis.axis); + if (event.jaxis.value > 0) { + params.Set("direction", "+"); + params.Set("threshold", "0.5"); + } else { + params.Set("direction", "-"); + params.Set("threshold", "-0.5"); + } + break; + } + case SDL_JOYBUTTONUP: { + auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); + params.Set("button", event.jbutton.button); + break; + } + case SDL_JOYHATMOTION: { + auto joystick = GetSDLJoystickBySDLID(event.jhat.which); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); + params.Set("hat", event.jhat.hat); + switch (event.jhat.value) { + case SDL_HAT_UP: + params.Set("direction", "up"); + break; + case SDL_HAT_DOWN: + params.Set("direction", "down"); + break; + case SDL_HAT_LEFT: + params.Set("direction", "left"); + break; + case SDL_HAT_RIGHT: + params.Set("direction", "right"); + break; + default: + return {}; + } + break; + } + } + return params; +} + +namespace Polling { + +class SDLPoller : public InputCommon::Polling::DevicePoller { +public: + void Start() override { + event_queue.Clear(); + polling = true; + } + + void Stop() override { + polling = false; + } +}; + +class SDLButtonPoller final : public SDLPoller { +public: + Common::ParamPackage GetNextInput() override { + SDL_Event event; + while (event_queue.Pop(event)) { + switch (event.type) { + case SDL_JOYAXISMOTION: + if (std::abs(event.jaxis.value / 32767.0) < 0.5) { + break; + } + case SDL_JOYBUTTONUP: + case SDL_JOYHATMOTION: + return SDLEventToButtonParamPackage(event); + } + } + return {}; + } +}; + +class SDLAnalogPoller final : public SDLPoller { +public: + void Start() override { + SDLPoller::Start(); + + // Reset stored axes + analog_xaxis = -1; + analog_yaxis = -1; + analog_axes_joystick = -1; + } + + Common::ParamPackage GetNextInput() override { + SDL_Event event; + while (event_queue.Pop(event)) { + if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { + continue; + } + // An analog device needs two axes, so we need to store the axis for later and wait for + // a second SDL event. The axes also must be from the same joystick. + int axis = event.jaxis.axis; + if (analog_xaxis == -1) { + analog_xaxis = axis; + analog_axes_joystick = event.jaxis.which; + } else if (analog_yaxis == -1 && analog_xaxis != axis && + analog_axes_joystick == event.jaxis.which) { + analog_yaxis = axis; + } + } + Common::ParamPackage params; + if (analog_xaxis != -1 && analog_yaxis != -1) { + auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); + params.Set("engine", "sdl"); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); + params.Set("axis_x", analog_xaxis); + params.Set("axis_y", analog_yaxis); + analog_xaxis = -1; + analog_yaxis = -1; + analog_axes_joystick = -1; + return params; + } + return params; + } + +private: + int analog_xaxis = -1; + int analog_yaxis = -1; + SDL_JoystickID analog_axes_joystick = -1; +}; + +void GetPollers(InputCommon::Polling::DeviceType type, + std::vector>& pollers) { + switch (type) { + case InputCommon::Polling::DeviceType::Analog: + pollers.emplace_back(std::make_unique()); + break; + case InputCommon::Polling::DeviceType::Button: + pollers.emplace_back(std::make_unique()); + break; + } +} +} // namespace Polling +} // namespace SDL +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h new file mode 100644 index 0000000000..c152fa747d --- /dev/null +++ b/src/input_common/sdl/sdl_impl.h @@ -0,0 +1,51 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "core/frontend/input.h" + +union SDL_Event; +namespace Common { +class ParamPackage; +} +namespace InputCommon { +namespace Polling { +class DevicePoller; +enum class DeviceType; +} // namespace Polling +} // namespace InputCommon + +namespace InputCommon { +namespace SDL { + +/// Initializes and registers SDL device factories +void Init(); + +/// Unresisters SDL device factories and shut them down. +void Shutdown(); + +/// Needs to be called before SDL_QuitSubSystem. +void CloseSDLJoysticks(); + +/// Handle SDL_Events for joysticks from SDL_PollEvent +void HandleGameControllerEvent(const SDL_Event& event); + +/// A Loop that calls HandleGameControllerEvent until Shutdown is called +void PollLoop(); + +/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice +Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event); + +namespace Polling { + +/// Get all DevicePoller that use the SDL backend for a specific device type +void GetPollers(InputCommon::Polling::DeviceType type, + std::vector>& pollers); + +} // namespace Polling +} // namespace SDL +} // namespace InputCommon From 09ac66388c01187ed6a402efcad76984c8af9a2e Mon Sep 17 00:00:00 2001 From: James Rowe Date: Thu, 20 Sep 2018 00:28:05 -0600 Subject: [PATCH 2/4] Input: Remove global variables from SDL Input Changes the interface as well to remove any unique methods that frontends needed to call such as StartJoystickEventHandler by conditionally starting the polling thread only if the frontend hasn't started it already. Additionally, moves all global state into a single SDLState class in order to guarantee that the destructors are called in the proper order --- src/input_common/CMakeLists.txt | 15 +- src/input_common/main.cpp | 23 +- src/input_common/main.h | 2 - src/input_common/sdl/sdl.cpp | 636 +------------------- src/input_common/sdl/sdl.h | 53 +- src/input_common/sdl/sdl_impl.cpp | 197 +++--- src/input_common/sdl/sdl_impl.h | 79 ++- src/yuzu/bootmanager.cpp | 1 - src/yuzu_cmd/emu_window/emu_window_sdl2.cpp | 13 +- 9 files changed, 208 insertions(+), 811 deletions(-) diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 1c7db28c03..5b4e032bd9 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -7,15 +7,18 @@ add_library(input_common STATIC main.h motion_emu.cpp motion_emu.h - - $<$:sdl/sdl.cpp sdl/sdl.h> + sdl/sdl.cpp + sdl/sdl.h ) -create_target_directory_groups(input_common) - -target_link_libraries(input_common PUBLIC core PRIVATE common) - if(SDL2_FOUND) + target_sources(input_common PRIVATE + sdl/sdl_impl.cpp + sdl/sdl_impl.h + ) target_link_libraries(input_common PRIVATE SDL2) target_compile_definitions(input_common PRIVATE HAVE_SDL2) endif() + +create_target_directory_groups(input_common) +target_link_libraries(input_common PUBLIC core PRIVATE common) diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 37f5728532..8e66c1b150 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -17,10 +17,7 @@ namespace InputCommon { static std::shared_ptr keyboard; static std::shared_ptr motion_emu; - -#ifdef HAVE_SDL2 -static std::thread poll_thread; -#endif +static std::unique_ptr sdl; void Init() { keyboard = std::make_shared(); @@ -30,15 +27,7 @@ void Init() { motion_emu = std::make_shared(); Input::RegisterFactory("motion_emu", motion_emu); -#ifdef HAVE_SDL2 - SDL::Init(); -#endif -} - -void StartJoystickEventHandler() { -#ifdef HAVE_SDL2 - poll_thread = std::thread(SDL::PollLoop); -#endif + sdl = SDL::Init(); } void Shutdown() { @@ -47,11 +36,7 @@ void Shutdown() { Input::UnregisterFactory("analog_from_button"); Input::UnregisterFactory("motion_emu"); motion_emu.reset(); - -#ifdef HAVE_SDL2 - SDL::Shutdown(); - poll_thread.join(); -#endif + sdl.reset(); } Keyboard* GetKeyboard() { @@ -88,7 +73,7 @@ namespace Polling { std::vector> GetPollers(DeviceType type) { #ifdef HAVE_SDL2 - return SDL::Polling::GetPollers(type); + return sdl->GetPollers(type); #else return {}; #endif diff --git a/src/input_common/main.h b/src/input_common/main.h index 9eb13106e5..77a0ce90b2 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -20,8 +20,6 @@ void Init(); /// Deregisters all built-in input device factories and shuts them down. void Shutdown(); -void StartJoystickEventHandler(); - class Keyboard; /// Gets the keyboard button device factory. diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp index faf3c1fa30..644db3448f 100644 --- a/src/input_common/sdl/sdl.cpp +++ b/src/input_common/sdl/sdl.cpp @@ -1,631 +1,19 @@ -// Copyright 2017 Citra Emulator Project +// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/math_util.h" -#include "common/param_package.h" -#include "common/threadsafe_queue.h" -#include "input_common/main.h" #include "input_common/sdl/sdl.h" +#ifdef HAVE_SDL2 +#include "input_common/sdl/sdl_impl.h" +#endif -namespace InputCommon { +namespace InputCommon::SDL { -namespace SDL { - -class SDLJoystick; -class SDLButtonFactory; -class SDLAnalogFactory; - -/// Map of GUID of a list of corresponding virtual Joysticks -static std::unordered_map>> joystick_map; -static std::mutex joystick_map_mutex; - -static std::shared_ptr button_factory; -static std::shared_ptr analog_factory; - -/// Used by the Pollers during config -static std::atomic polling; -static Common::SPSCQueue event_queue; - -static std::atomic initialized = false; - -static std::string GetGUID(SDL_Joystick* joystick) { - SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); - char guid_str[33]; - SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); - return guid_str; +std::unique_ptr Init() { +#ifdef HAVE_SDL2 + return std::make_unique(); +#else + return std::make_unique(); +#endif } - -class SDLJoystick { -public: - SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, - decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) - : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {} - - void SetButton(int button, bool value) { - std::lock_guard lock(mutex); - state.buttons[button] = value; - } - - bool GetButton(int button) const { - std::lock_guard lock(mutex); - return state.buttons.at(button); - } - - void SetAxis(int axis, Sint16 value) { - std::lock_guard lock(mutex); - state.axes[axis] = value; - } - - float GetAxis(int axis) const { - std::lock_guard lock(mutex); - return state.axes.at(axis) / 32767.0f; - } - - std::tuple GetAnalog(int axis_x, int axis_y) const { - float x = GetAxis(axis_x); - float y = GetAxis(axis_y); - y = -y; // 3DS uses an y-axis inverse from SDL - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return std::make_tuple(x, y); - } - - void SetHat(int hat, Uint8 direction) { - std::lock_guard lock(mutex); - state.hats[hat] = direction; - } - - bool GetHatDirection(int hat, Uint8 direction) const { - std::lock_guard lock(mutex); - return (state.hats.at(hat) & direction) != 0; - } - /** - * The guid of the joystick - */ - const std::string& GetGUID() const { - return guid; - } - - /** - * The number of joystick from the same type that were connected before this joystick - */ - int GetPort() const { - return port; - } - - SDL_Joystick* GetSDLJoystick() const { - return sdl_joystick.get(); - } - - void SetSDLJoystick(SDL_Joystick* joystick, - decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) { - sdl_joystick = - std::unique_ptr(joystick, deleter); - } - -private: - struct State { - std::unordered_map buttons; - std::unordered_map axes; - std::unordered_map hats; - } state; - std::string guid; - int port; - std::unique_ptr sdl_joystick; - mutable std::mutex mutex; -}; - -/** - * Get the nth joystick with the corresponding GUID - */ -static std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port) { - std::lock_guard lock(joystick_map_mutex); - const auto it = joystick_map.find(guid); - if (it != joystick_map.end()) { - while (it->second.size() <= port) { - auto joystick = std::make_shared(guid, it->second.size(), nullptr, - [](SDL_Joystick*) {}); - it->second.emplace_back(std::move(joystick)); - } - return it->second[port]; - } - auto joystick = std::make_shared(guid, 0, nullptr, [](SDL_Joystick*) {}); - return joystick_map[guid].emplace_back(std::move(joystick)); -} - -/** - * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie - * it to a SDLJoystick with the same guid and that port - */ -static std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { - std::lock_guard lock(joystick_map_mutex); - auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); - const std::string guid = GetGUID(sdl_joystick); - auto map_it = joystick_map.find(guid); - if (map_it != joystick_map.end()) { - auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), - [&sdl_joystick](const std::shared_ptr& joystick) { - return sdl_joystick == joystick->GetSDLJoystick(); - }); - if (vec_it != map_it->second.end()) { - // This is the common case: There is already an existing SDL_Joystick maped to a - // SDLJoystick. return the SDLJoystick - return *vec_it; - } - // Search for a SDLJoystick without a mapped SDL_Joystick... - auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), - [](const std::shared_ptr& joystick) { - return !joystick->GetSDLJoystick(); - }); - if (nullptr_it != map_it->second.end()) { - // ... and map it - (*nullptr_it)->SetSDLJoystick(sdl_joystick); - return *nullptr_it; - } - // There is no SDLJoystick without a mapped SDL_Joystick - // Create a new SDLJoystick - auto joystick = std::make_shared(guid, map_it->second.size(), sdl_joystick); - return map_it->second.emplace_back(std::move(joystick)); - } - auto joystick = std::make_shared(guid, 0, sdl_joystick); - return joystick_map[guid].emplace_back(std::move(joystick)); -} - -void InitJoystick(int joystick_index) { - std::lock_guard lock(joystick_map_mutex); - SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); - if (!sdl_joystick) { - LOG_ERROR(Input, "failed to open joystick {}", joystick_index); - return; - } - std::string guid = GetGUID(sdl_joystick); - if (joystick_map.find(guid) == joystick_map.end()) { - auto joystick = std::make_shared(guid, 0, sdl_joystick); - joystick_map[guid].emplace_back(std::move(joystick)); - return; - } - auto& joystick_guid_list = joystick_map[guid]; - const auto it = std::find_if( - joystick_guid_list.begin(), joystick_guid_list.end(), - [](const std::shared_ptr& joystick) { return !joystick->GetSDLJoystick(); }); - if (it != joystick_guid_list.end()) { - (*it)->SetSDLJoystick(sdl_joystick); - return; - } - auto joystick = std::make_shared(guid, joystick_guid_list.size(), sdl_joystick); - joystick_guid_list.emplace_back(std::move(joystick)); -} - -void CloseJoystick(SDL_Joystick* sdl_joystick) { - std::lock_guard lock(joystick_map_mutex); - std::string guid = GetGUID(sdl_joystick); - // This call to guid is save since the joystick is guranteed to be in that map - auto& joystick_guid_list = joystick_map[guid]; - const auto joystick_it = - std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), - [&sdl_joystick](const std::shared_ptr& joystick) { - return joystick->GetSDLJoystick() == sdl_joystick; - }); - (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); -} - -void HandleGameControllerEvent(const SDL_Event& event) { - switch (event.type) { - case SDL_JOYBUTTONUP: { - auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); - if (joystick) { - joystick->SetButton(event.jbutton.button, false); - } - break; - } - case SDL_JOYBUTTONDOWN: { - auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); - if (joystick) { - joystick->SetButton(event.jbutton.button, true); - } - break; - } - case SDL_JOYHATMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jhat.which); - if (joystick) { - joystick->SetHat(event.jhat.hat, event.jhat.value); - } - break; - } - case SDL_JOYAXISMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); - if (joystick) { - joystick->SetAxis(event.jaxis.axis, event.jaxis.value); - } - break; - } - case SDL_JOYDEVICEREMOVED: - LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); - CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); - break; - case SDL_JOYDEVICEADDED: - LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); - InitJoystick(event.jdevice.which); - break; - } -} - -void CloseSDLJoysticks() { - std::lock_guard lock(joystick_map_mutex); - joystick_map.clear(); -} - -void PollLoop() { - if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { - LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); - return; - } - - SDL_Event event; - while (initialized) { - // Wait for 10 ms or until an event happens - if (SDL_WaitEventTimeout(&event, 10)) { - // Don't handle the event if we are configuring - if (polling) { - event_queue.Push(event); - } else { - HandleGameControllerEvent(event); - } - } - } - CloseSDLJoysticks(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); -} - -class SDLButton final : public Input::ButtonDevice { -public: - explicit SDLButton(std::shared_ptr joystick_, int button_) - : joystick(std::move(joystick_)), button(button_) {} - - bool GetStatus() const override { - return joystick->GetButton(button); - } - -private: - std::shared_ptr joystick; - int button; -}; - -class SDLDirectionButton final : public Input::ButtonDevice { -public: - explicit SDLDirectionButton(std::shared_ptr joystick_, int hat_, Uint8 direction_) - : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} - - bool GetStatus() const override { - return joystick->GetHatDirection(hat, direction); - } - -private: - std::shared_ptr joystick; - int hat; - Uint8 direction; -}; - -class SDLAxisButton final : public Input::ButtonDevice { -public: - explicit SDLAxisButton(std::shared_ptr joystick_, int axis_, float threshold_, - bool trigger_if_greater_) - : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), - trigger_if_greater(trigger_if_greater_) {} - - bool GetStatus() const override { - float axis_value = joystick->GetAxis(axis); - if (trigger_if_greater) - return axis_value > threshold; - return axis_value < threshold; - } - -private: - std::shared_ptr joystick; - int axis; - float threshold; - bool trigger_if_greater; -}; - -class SDLAnalog final : public Input::AnalogDevice { -public: - SDLAnalog(std::shared_ptr joystick_, int axis_x_, int axis_y_) - : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {} - - std::tuple GetStatus() const override { - return joystick->GetAnalog(axis_x, axis_y); - } - -private: - std::shared_ptr joystick; - int axis_x; - int axis_y; -}; - -/// A button device factory that creates button devices from SDL joystick -class SDLButtonFactory final : public Input::Factory { -public: - /** - * Creates a button device from a joystick button - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type to bind - * - "button"(optional): the index of the button to bind - * - "hat"(optional): the index of the hat to bind as direction buttons - * - "axis"(optional): the index of the axis to bind - * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", - * "down", "left" or "right" - * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is - * triggered if the axis value crosses - * - "direction"(only used for axis): "+" means the button is triggered when the axis - * value is greater than the threshold; "-" means the button is triggered when the axis - * value is smaller than the threshold - */ - std::unique_ptr Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - - auto joystick = GetSDLJoystickByGUID(guid, port); - - if (params.Has("hat")) { - const int hat = params.Get("hat", 0); - const std::string direction_name = params.Get("direction", ""); - Uint8 direction; - if (direction_name == "up") { - direction = SDL_HAT_UP; - } else if (direction_name == "down") { - direction = SDL_HAT_DOWN; - } else if (direction_name == "left") { - direction = SDL_HAT_LEFT; - } else if (direction_name == "right") { - direction = SDL_HAT_RIGHT; - } else { - direction = 0; - } - // This is necessary so accessing GetHat with hat won't crash - joystick->SetHat(hat, SDL_HAT_CENTERED); - return std::make_unique(joystick, hat, direction); - } - - if (params.Has("axis")) { - const int axis = params.Get("axis", 0); - const float threshold = params.Get("threshold", 0.5f); - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction '{}'", direction_name); - } - // This is necessary so accessing GetAxis with axis won't crash - joystick->SetAxis(axis, 0); - return std::make_unique(joystick, axis, threshold, trigger_if_greater); - } - - const int button = params.Get("button", 0); - // This is necessary so accessing GetButton with button won't crash - joystick->SetButton(button, false); - return std::make_unique(joystick, button); - } -}; - -/// An analog device factory that creates analog devices from SDL joystick -class SDLAnalogFactory final : public Input::Factory { -public: - /** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ - std::unique_ptr Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - const int axis_x = params.Get("axis_x", 0); - const int axis_y = params.Get("axis_y", 1); - - auto joystick = GetSDLJoystickByGUID(guid, port); - - // This is necessary so accessing GetAxis with axis_x and axis_y won't crash - joystick->SetAxis(axis_x, 0); - joystick->SetAxis(axis_y, 0); - return std::make_unique(joystick, axis_x, axis_y); - } -}; - -void Init() { - using namespace Input; - RegisterFactory("sdl", std::make_shared()); - RegisterFactory("sdl", std::make_shared()); - polling = false; - initialized = true; -} - -void Shutdown() { - if (initialized) { - using namespace Input; - UnregisterFactory("sdl"); - UnregisterFactory("sdl"); - initialized = false; - } -} - -Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { - Common::ParamPackage params({{"engine", "sdl"}}); - switch (event.type) { - case SDL_JOYAXISMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis", event.jaxis.axis); - if (event.jaxis.value > 0) { - params.Set("direction", "+"); - params.Set("threshold", "0.5"); - } else { - params.Set("direction", "-"); - params.Set("threshold", "-0.5"); - } - break; - } - case SDL_JOYBUTTONUP: { - auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("button", event.jbutton.button); - break; - } - case SDL_JOYHATMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jhat.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("hat", event.jhat.hat); - switch (event.jhat.value) { - case SDL_HAT_UP: - params.Set("direction", "up"); - break; - case SDL_HAT_DOWN: - params.Set("direction", "down"); - break; - case SDL_HAT_LEFT: - params.Set("direction", "left"); - break; - case SDL_HAT_RIGHT: - params.Set("direction", "right"); - break; - default: - return {}; - } - break; - } - } - return params; -} - -namespace Polling { - -class SDLPoller : public InputCommon::Polling::DevicePoller { -public: - void Start() override { - event_queue.Clear(); - polling = true; - } - - void Stop() override { - polling = false; - } -}; - -class SDLButtonPoller final : public SDLPoller { -public: - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (event_queue.Pop(event)) { - switch (event.type) { - case SDL_JOYAXISMOTION: - if (std::abs(event.jaxis.value / 32767.0) < 0.5) { - break; - } - case SDL_JOYBUTTONUP: - case SDL_JOYHATMOTION: - return SDLEventToButtonParamPackage(event); - } - } - return {}; - } -}; - -class SDLAnalogPoller final : public SDLPoller { -public: - void Start() override { - SDLPoller::Start(); - - // Reset stored axes - analog_xaxis = -1; - analog_yaxis = -1; - analog_axes_joystick = -1; - } - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (event_queue.Pop(event)) { - if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { - continue; - } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second SDL event. The axes also must be from the same joystick. - int axis = event.jaxis.axis; - if (analog_xaxis == -1) { - analog_xaxis = axis; - analog_axes_joystick = event.jaxis.which; - } else if (analog_yaxis == -1 && analog_xaxis != axis && - analog_axes_joystick == event.jaxis.which) { - analog_yaxis = axis; - } - } - Common::ParamPackage params; - if (analog_xaxis != -1 && analog_yaxis != -1) { - auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("engine", "sdl"); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis_x", analog_xaxis); - params.Set("axis_y", analog_yaxis); - analog_xaxis = -1; - analog_yaxis = -1; - analog_axes_joystick = -1; - return params; - } - return params; - } - -private: - int analog_xaxis = -1; - int analog_yaxis = -1; - SDL_JoystickID analog_axes_joystick = -1; -}; - -std::vector> GetPollers( - InputCommon::Polling::DeviceType type) { - std::vector> pollers; - switch (type) { - case InputCommon::Polling::DeviceType::Analog: - pollers.push_back(std::make_unique()); - break; - case InputCommon::Polling::DeviceType::Button: - pollers.push_back(std::make_unique()); - break; - } - return pollers; -} -} // namespace Polling -} // namespace SDL -} // namespace InputCommon +} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h index 0206860d32..02a8d2e2c9 100644 --- a/src/input_common/sdl/sdl.h +++ b/src/input_common/sdl/sdl.h @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -7,45 +7,36 @@ #include #include #include "core/frontend/input.h" +#include "input_common/main.h" union SDL_Event; + namespace Common { class ParamPackage; -} -namespace InputCommon { -namespace Polling { +} // namespace Common + +namespace InputCommon::Polling { class DevicePoller; enum class DeviceType; -} // namespace Polling -} // namespace InputCommon +} // namespace InputCommon::Polling -namespace InputCommon { -namespace SDL { +namespace InputCommon::SDL { -/// Initializes and registers SDL device factories -void Init(); +class State { +public: + /// Unresisters SDL device factories and shut them down. + virtual ~State() = default; -/// Unresisters SDL device factories and shut them down. -void Shutdown(); + virtual std::vector> GetPollers( + InputCommon::Polling::DeviceType type) = 0; +}; -/// Needs to be called before SDL_QuitSubSystem. -void CloseSDLJoysticks(); +class NullState : public State { +public: + std::vector> GetPollers( + InputCommon::Polling::DeviceType type) override {} +}; -/// Handle SDL_Events for joysticks from SDL_PollEvent -void HandleGameControllerEvent(const SDL_Event& event); +std::unique_ptr Init(); -/// A Loop that calls HandleGameControllerEvent until Shutdown is called -void PollLoop(); - -/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice -Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event); - -namespace Polling { - -/// Get all DevicePoller that use the SDL backend for a specific device type -std::vector> GetPollers( - InputCommon::Polling::DeviceType type); - -} // namespace Polling -} // namespace SDL -} // namespace InputCommon +} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 7c1ecc2e00..83fcf354e7 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -1,4 +1,4 @@ -// Copyright 2017 Citra Emulator Project +// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -20,30 +20,13 @@ #include "common/math_util.h" #include "common/param_package.h" #include "common/threadsafe_queue.h" -#include "input_common/main.h" -#include "input_common/sdl/sdl.h" +#include "core/frontend/input.h" +#include "input_common/sdl/sdl_impl.h" namespace InputCommon { namespace SDL { -class SDLJoystick; -class SDLButtonFactory; -class SDLAnalogFactory; - -/// Map of GUID of a list of corresponding virtual Joysticks -static std::unordered_map>> joystick_map; -static std::mutex joystick_map_mutex; - -static std::shared_ptr button_factory; -static std::shared_ptr analog_factory; - -/// Used by the Pollers during config -static std::atomic polling; -static Common::SPSCQueue event_queue; - -static std::atomic initialized = false; - static std::string GetGUID(SDL_Joystick* joystick) { SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); char guid_str[33]; @@ -51,6 +34,20 @@ static std::string GetGUID(SDL_Joystick* joystick) { return guid_str; } +/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice +static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); + +static int SDLEventWatcher(void* userdata, SDL_Event* event) { + SDLState* sdl_state = reinterpret_cast(userdata); + // Don't handle the event if we are configuring + if (sdl_state->polling) { + sdl_state->event_queue.Push(*event); + } else { + sdl_state->HandleGameControllerEvent(*event); + } + return 0; +} + class SDLJoystick { public: SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, @@ -142,7 +139,7 @@ private: /** * Get the nth joystick with the corresponding GUID */ -static std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port) { +std::shared_ptr SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { std::lock_guard lock(joystick_map_mutex); const auto it = joystick_map.find(guid); if (it != joystick_map.end()) { @@ -161,7 +158,7 @@ static std::shared_ptr GetSDLJoystickByGUID(const std::string& guid * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie * it to a SDLJoystick with the same guid and that port */ -static std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { +std::shared_ptr SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { std::lock_guard lock(joystick_map_mutex); auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); const std::string guid = GetGUID(sdl_joystick); @@ -195,7 +192,7 @@ static std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) return joystick_map[guid].emplace_back(std::move(joystick)); } -void InitJoystick(int joystick_index) { +void SDLState::InitJoystick(int joystick_index) { std::lock_guard lock(joystick_map_mutex); SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); if (!sdl_joystick) { @@ -220,10 +217,10 @@ void InitJoystick(int joystick_index) { joystick_guid_list.emplace_back(std::move(joystick)); } -void CloseJoystick(SDL_Joystick* sdl_joystick) { +void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { std::lock_guard lock(joystick_map_mutex); std::string guid = GetGUID(sdl_joystick); - // This call to guid is save since the joystick is guranteed to be in that map + // This call to guid is safe since the joystick is guaranteed to be in the map auto& joystick_guid_list = joystick_map[guid]; const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), @@ -233,32 +230,28 @@ void CloseJoystick(SDL_Joystick* sdl_joystick) { (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); } -void HandleGameControllerEvent(const SDL_Event& event) { +void SDLState::HandleGameControllerEvent(const SDL_Event& event) { switch (event.type) { case SDL_JOYBUTTONUP: { - auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); - if (joystick) { + if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { joystick->SetButton(event.jbutton.button, false); } break; } case SDL_JOYBUTTONDOWN: { - auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); - if (joystick) { + if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { joystick->SetButton(event.jbutton.button, true); } break; } case SDL_JOYHATMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jhat.which); - if (joystick) { + if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { joystick->SetHat(event.jhat.hat, event.jhat.value); } break; } case SDL_JOYAXISMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); - if (joystick) { + if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { joystick->SetAxis(event.jaxis.axis, event.jaxis.value); } break; @@ -274,33 +267,11 @@ void HandleGameControllerEvent(const SDL_Event& event) { } } -void CloseSDLJoysticks() { +void SDLState::CloseJoysticks() { std::lock_guard lock(joystick_map_mutex); joystick_map.clear(); } -void PollLoop() { - if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { - LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); - return; - } - - SDL_Event event; - while (initialized) { - // Wait for 10 ms or until an event happens - if (SDL_WaitEventTimeout(&event, 10)) { - // Don't handle the event if we are configuring - if (polling) { - event_queue.Push(event); - } else { - HandleGameControllerEvent(event); - } - } - } - CloseSDLJoysticks(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); -} - class SDLButton final : public Input::ButtonDevice { public: explicit SDLButton(std::shared_ptr joystick_, int button_) @@ -369,6 +340,8 @@ private: /// A button device factory that creates button devices from SDL joystick class SDLButtonFactory final : public Input::Factory { public: + explicit SDLButtonFactory(SDLState& state_) : state(state_) {} + /** * Creates a button device from a joystick button * @param params contains parameters for creating the device: @@ -389,7 +362,7 @@ public: const std::string guid = params.Get("guid", "0"); const int port = params.Get("port", 0); - auto joystick = GetSDLJoystickByGUID(guid, port); + auto joystick = state.GetSDLJoystickByGUID(guid, port); if (params.Has("hat")) { const int hat = params.Get("hat", 0); @@ -434,11 +407,15 @@ public: joystick->SetButton(button, false); return std::make_unique(joystick, button); } + +private: + SDLState& state; }; /// An analog device factory that creates analog devices from SDL joystick class SDLAnalogFactory final : public Input::Factory { public: + explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} /** * Creates analog device from joystick axes * @param params contains parameters for creating the device: @@ -453,37 +430,71 @@ public: const int axis_x = params.Get("axis_x", 0); const int axis_y = params.Get("axis_y", 1); - auto joystick = GetSDLJoystickByGUID(guid, port); + auto joystick = state.GetSDLJoystickByGUID(guid, port); // This is necessary so accessing GetAxis with axis_x and axis_y won't crash joystick->SetAxis(axis_x, 0); joystick->SetAxis(axis_y, 0); return std::make_unique(joystick, axis_x, axis_y); } + +private: + SDLState& state; }; -void Init() { +SDLState::SDLState() { using namespace Input; - RegisterFactory("sdl", std::make_shared()); - RegisterFactory("sdl", std::make_shared()); - polling = false; - initialized = true; -} + RegisterFactory("sdl", std::make_shared(*this)); + RegisterFactory("sdl", std::make_shared(*this)); -void Shutdown() { - if (initialized) { - using namespace Input; - UnregisterFactory("sdl"); - UnregisterFactory("sdl"); - initialized = false; + // If the frontend is going to manage the event loop, then we dont start one here + start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); + if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { + LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); + return; + } + + SDL_AddEventWatch(&SDLEventWatcher, this); + + initialized = true; + if (start_thread) { + poll_thread = std::thread([&] { + using namespace std::chrono_literals; + SDL_Event event; + while (initialized) { + SDL_PumpEvents(); + std::this_thread::sleep_for(std::chrono::duration(10ms)); + } + }); + } + // Because the events for joystick connection happens before we have our event watcher added, we + // can just open all the joysticks right here + for (int i = 0; i < SDL_NumJoysticks(); ++i) { + InitJoystick(i); } } -Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { +SDLState::~SDLState() { + using namespace Input; + UnregisterFactory("sdl"); + UnregisterFactory("sdl"); + + CloseJoysticks(); + SDL_DelEventWatch(&SDLEventWatcher, this); + + initialized = false; + if (start_thread) { + poll_thread.join(); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + } +} + +Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { Common::ParamPackage params({{"engine", "sdl"}}); + switch (event.type) { case SDL_JOYAXISMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); + auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); params.Set("port", joystick->GetPort()); params.Set("guid", joystick->GetGUID()); params.Set("axis", event.jaxis.axis); @@ -497,14 +508,14 @@ Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { break; } case SDL_JOYBUTTONUP: { - auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); + auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); params.Set("port", joystick->GetPort()); params.Set("guid", joystick->GetGUID()); params.Set("button", event.jbutton.button); break; } case SDL_JOYHATMOTION: { - auto joystick = GetSDLJoystickBySDLID(event.jhat.which); + auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); params.Set("port", joystick->GetPort()); params.Set("guid", joystick->GetGUID()); params.Set("hat", event.jhat.hat); @@ -534,21 +545,28 @@ namespace Polling { class SDLPoller : public InputCommon::Polling::DevicePoller { public: + explicit SDLPoller(SDLState& state_) : state(state_) {} + void Start() override { - event_queue.Clear(); - polling = true; + state.event_queue.Clear(); + state.polling = true; } void Stop() override { - polling = false; + state.polling = false; } + +protected: + SDLState& state; }; class SDLButtonPoller final : public SDLPoller { public: + explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {} + Common::ParamPackage GetNextInput() override { SDL_Event event; - while (event_queue.Pop(event)) { + while (state.event_queue.Pop(event)) { switch (event.type) { case SDL_JOYAXISMOTION: if (std::abs(event.jaxis.value / 32767.0) < 0.5) { @@ -556,7 +574,7 @@ public: } case SDL_JOYBUTTONUP: case SDL_JOYHATMOTION: - return SDLEventToButtonParamPackage(event); + return SDLEventToButtonParamPackage(state, event); } } return {}; @@ -565,6 +583,8 @@ public: class SDLAnalogPoller final : public SDLPoller { public: + explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} + void Start() override { SDLPoller::Start(); @@ -576,7 +596,7 @@ public: Common::ParamPackage GetNextInput() override { SDL_Event event; - while (event_queue.Pop(event)) { + while (state.event_queue.Pop(event)) { if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { continue; } @@ -593,7 +613,7 @@ public: } Common::ParamPackage params; if (analog_xaxis != -1 && analog_yaxis != -1) { - auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); + auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); params.Set("engine", "sdl"); params.Set("port", joystick->GetPort()); params.Set("guid", joystick->GetGUID()); @@ -612,18 +632,21 @@ private: int analog_yaxis = -1; SDL_JoystickID analog_axes_joystick = -1; }; +} // namespace Polling -void GetPollers(InputCommon::Polling::DeviceType type, - std::vector>& pollers) { +std::vector> SDLState::GetPollers( + InputCommon::Polling::DeviceType type) { + std::vector> pollers; switch (type) { case InputCommon::Polling::DeviceType::Analog: - pollers.emplace_back(std::make_unique()); + pollers.emplace_back(std::make_unique(*this)); break; case InputCommon::Polling::DeviceType::Button: - pollers.emplace_back(std::make_unique()); + pollers.emplace_back(std::make_unique(*this)); break; + return pollers; } } -} // namespace Polling + } // namespace SDL } // namespace InputCommon diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h index c152fa747d..fec82fbe63 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/sdl/sdl_impl.h @@ -1,51 +1,64 @@ -// Copyright 2017 Citra Emulator Project +// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once +#include #include -#include -#include "core/frontend/input.h" +#include +#include "common/threadsafe_queue.h" +#include "input_common/sdl/sdl.h" union SDL_Event; -namespace Common { -class ParamPackage; -} -namespace InputCommon { -namespace Polling { -class DevicePoller; -enum class DeviceType; -} // namespace Polling -} // namespace InputCommon +using SDL_Joystick = struct _SDL_Joystick; +using SDL_JoystickID = s32; -namespace InputCommon { -namespace SDL { +namespace InputCommon::SDL { -/// Initializes and registers SDL device factories -void Init(); +class SDLJoystick; +class SDLButtonFactory; +class SDLAnalogFactory; -/// Unresisters SDL device factories and shut them down. -void Shutdown(); +class SDLState : public State { +public: + /// Initializes and registers SDL device factories + SDLState(); -/// Needs to be called before SDL_QuitSubSystem. -void CloseSDLJoysticks(); + /// Unresisters SDL device factories and shut them down. + ~SDLState() override; -/// Handle SDL_Events for joysticks from SDL_PollEvent -void HandleGameControllerEvent(const SDL_Event& event); + /// Handle SDL_Events for joysticks from SDL_PollEvent + void HandleGameControllerEvent(const SDL_Event& event); -/// A Loop that calls HandleGameControllerEvent until Shutdown is called -void PollLoop(); + std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); + std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port); -/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice -Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event); + /// Get all DevicePoller that use the SDL backend for a specific device type + std::vector> GetPollers( + InputCommon::Polling::DeviceType type) override; -namespace Polling { + /// Used by the Pollers during config + std::atomic polling = false; + Common::SPSCQueue event_queue; -/// Get all DevicePoller that use the SDL backend for a specific device type -void GetPollers(InputCommon::Polling::DeviceType type, - std::vector>& pollers); +private: + void InitJoystick(int joystick_index); + void CloseJoystick(SDL_Joystick* sdl_joystick); -} // namespace Polling -} // namespace SDL -} // namespace InputCommon + /// Needs to be called before SDL_QuitSubSystem. + void CloseJoysticks(); + + /// Map of GUID of a list of corresponding virtual Joysticks + std::unordered_map>> joystick_map; + std::mutex joystick_map_mutex; + + std::shared_ptr button_factory; + std::shared_ptr analog_factory; + + bool start_thread = false; + std::atomic initialized = false; + + std::thread poll_thread; +}; +} // namespace InputCommon::SDL diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 73b04b7492..b2a087aa57 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -121,7 +121,6 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) setAttribute(Qt::WA_AcceptTouchEvents); InputCommon::Init(); - InputCommon::StartJoystickEventHandler(); connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast(parent), &GMainWindow::OnLoadComplete); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 7df8eff539..de7a26e149 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -135,16 +135,16 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() { } EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { - InputCommon::Init(); - - SDL_SetMainReady(); - // Initialize the window if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); exit(1); } + InputCommon::Init(); + + SDL_SetMainReady(); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); @@ -201,11 +201,9 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { } EmuWindow_SDL2::~EmuWindow_SDL2() { - InputCommon::SDL::CloseSDLJoysticks(); + InputCommon::Shutdown(); SDL_GL_DeleteContext(gl_context); SDL_Quit(); - - InputCommon::Shutdown(); } void EmuWindow_SDL2::SwapBuffers() { @@ -262,7 +260,6 @@ void EmuWindow_SDL2::PollEvents() { is_open = false; break; default: - InputCommon::SDL::HandleGameControllerEvent(event); break; } } From 8b98f60e3c4c8a11f9de382fbf694173855f3a7a Mon Sep 17 00:00:00 2001 From: Weiyi Wang Date: Tue, 2 Oct 2018 11:06:42 -0400 Subject: [PATCH 3/4] input/sdl: lock map mutex after SDL call Any SDL invocation can call the even callback on the same thread, which can call GetSDLJoystickBySDLID and eventually cause double lock on joystick_map_mutex. To avoid this, lock guard should be placed as closer as possible to the object accessing code, so that any SDL invocation is with the mutex unlocked --- src/input_common/sdl/sdl_impl.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 83fcf354e7..f5dbd015e9 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -159,9 +159,9 @@ std::shared_ptr SDLState::GetSDLJoystickByGUID(const std::string& g * it to a SDLJoystick with the same guid and that port */ std::shared_ptr SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { - std::lock_guard lock(joystick_map_mutex); auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); const std::string guid = GetGUID(sdl_joystick); + std::lock_guard lock(joystick_map_mutex); auto map_it = joystick_map.find(guid); if (map_it != joystick_map.end()) { auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), @@ -193,13 +193,13 @@ std::shared_ptr SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_ } void SDLState::InitJoystick(int joystick_index) { - std::lock_guard lock(joystick_map_mutex); SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); if (!sdl_joystick) { LOG_ERROR(Input, "failed to open joystick {}", joystick_index); return; } std::string guid = GetGUID(sdl_joystick); + std::lock_guard lock(joystick_map_mutex); if (joystick_map.find(guid) == joystick_map.end()) { auto joystick = std::make_shared(guid, 0, sdl_joystick); joystick_map[guid].emplace_back(std::move(joystick)); @@ -218,16 +218,22 @@ void SDLState::InitJoystick(int joystick_index) { } void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { - std::lock_guard lock(joystick_map_mutex); std::string guid = GetGUID(sdl_joystick); - // This call to guid is safe since the joystick is guaranteed to be in the map - auto& joystick_guid_list = joystick_map[guid]; - const auto joystick_it = - std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), - [&sdl_joystick](const std::shared_ptr& joystick) { - return joystick->GetSDLJoystick() == sdl_joystick; - }); - (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); + std::shared_ptr joystick; + { + std::lock_guard lock(joystick_map_mutex); + // This call to guid is safe since the joystick is guaranteed to be in the map + auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = + std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [&sdl_joystick](const std::shared_ptr& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + joystick = *joystick_it; + } + // Destruct SDL_Joystick outside the lock guard because SDL can internally call event calback + // which locks the mutex again + joystick->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); } void SDLState::HandleGameControllerEvent(const SDL_Event& event) { From 71817afbe9483f7b2258a8891d0a6730c8c7b71e Mon Sep 17 00:00:00 2001 From: B3n30 Date: Mon, 1 Oct 2018 15:10:37 +0200 Subject: [PATCH 4/4] fixup! Joystick: Allow for background events; Add deadzone to SDLAnalog --- src/input_common/sdl/sdl_impl.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index f5dbd015e9..934339d3bc 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -330,17 +330,24 @@ private: class SDLAnalog final : public Input::AnalogDevice { public: - SDLAnalog(std::shared_ptr joystick_, int axis_x_, int axis_y_) - : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {} + SDLAnalog(std::shared_ptr joystick_, int axis_x_, int axis_y_, float deadzone_) + : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {} std::tuple GetStatus() const override { - return joystick->GetAnalog(axis_x, axis_y); + const auto [x, y] = joystick->GetAnalog(axis_x, axis_y); + const float r = std::sqrt((x * x) + (y * y)); + if (r > deadzone) { + return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), + y / r * (r - deadzone) / (1 - deadzone)); + } + return std::make_tuple(0.0f, 0.0f); } private: std::shared_ptr joystick; - int axis_x; - int axis_y; + const int axis_x; + const int axis_y; + const float deadzone; }; /// A button device factory that creates button devices from SDL joystick @@ -435,13 +442,14 @@ public: const int port = params.Get("port", 0); const int axis_x = params.Get("axis_x", 0); const int axis_y = params.Get("axis_y", 1); + float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); auto joystick = state.GetSDLJoystickByGUID(guid, port); // This is necessary so accessing GetAxis with axis_x and axis_y won't crash joystick->SetAxis(axis_x, 0); joystick->SetAxis(axis_y, 0); - return std::make_unique(joystick, axis_x, axis_y); + return std::make_unique(joystick, axis_x, axis_y, deadzone); } private: @@ -459,6 +467,9 @@ SDLState::SDLState() { LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); return; } + if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { + LOG_ERROR(Input, "Failed to set Hint for background events", SDL_GetError()); + } SDL_AddEventWatch(&SDLEventWatcher, this);