diff --git a/src/gui/wxgui/CemuApp.cpp b/src/gui/wxgui/CemuApp.cpp index f72fea87..9b6d48ff 100644 --- a/src/gui/wxgui/CemuApp.cpp +++ b/src/gui/wxgui/CemuApp.cpp @@ -9,6 +9,7 @@ #include "config/LaunchSettings.h" #include "wxgui/GettingStartedDialog.h" #include "input/InputManager.h" +#include "input/api/SDL/SDLControllerProvider.h" #include "wxgui/helpers/wxHelpers.h" #include "Cemu/ncrypto/ncrypto.h" #include "wxgui/input/HotkeySettings.h" @@ -26,6 +27,7 @@ #include #include #include +#include #include "wxHelper.h" #include "Cafe/TitleList/TitleList.h" @@ -332,8 +334,18 @@ bool CemuApp::OnInit() #ifdef CEMU_DEBUG_ASSERT UnitTests(); #endif + +#if BOOST_OS_MACOS + SDLControllerProvider::InitSDL(); +#endif CemuCommonInit(); +#if BOOST_OS_MACOS + m_sdlEventPumpTimer = new wxTimer(this); + Bind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this); + m_sdlEventPumpTimer->Start(5, wxTIMER_CONTINUOUS); +#endif + wxInitAllImageHandlers(); // fill colour db @@ -389,9 +401,22 @@ bool CemuApp::OnInit() int CemuApp::OnExit() { +#if BOOST_OS_MACOS + if (m_sdlEventPumpTimer) + { + m_sdlEventPumpTimer->Stop(); + Unbind(wxEVT_TIMER, &CemuApp::OnSDLEventPumpTimer, this); + delete m_sdlEventPumpTimer; + m_sdlEventPumpTimer = nullptr; + } +#endif + wxApp::OnExit(); wxTheClipboard->Flush(); InputManager::instance().Shutdown(); +#if BOOST_OS_MACOS + SDLControllerProvider::ShutdownSDL(); +#endif #if BOOST_OS_WINDOWS ExitProcess(0); #else @@ -399,6 +424,15 @@ int CemuApp::OnExit() #endif } +#if BOOST_OS_MACOS +void CemuApp::OnSDLEventPumpTimer(wxTimerEvent& event) +{ + // this callback is only used on macOS where SDL event functions need to be called on the main thread + // on other platforms SDLControllerProvider creates a separate thread for SDL event polling + SDLControllerProvider::PumpSDLEvents(); +} +#endif + #if BOOST_OS_WINDOWS void DumpThreadStackTrace(); #endif diff --git a/src/gui/wxgui/CemuApp.h b/src/gui/wxgui/CemuApp.h index b73d627d..7735ef74 100644 --- a/src/gui/wxgui/CemuApp.h +++ b/src/gui/wxgui/CemuApp.h @@ -3,6 +3,8 @@ #include class MainWindow; +class wxTimer; +class wxTimerEvent; class CemuApp : public wxApp { @@ -30,6 +32,10 @@ private: static std::vector GetAvailableTranslationLanguages(wxTranslations* translationsMgr); MainWindow* m_mainFrame = nullptr; +#if BOOST_OS_MACOS + void OnSDLEventPumpTimer(wxTimerEvent& event); + wxTimer* m_sdlEventPumpTimer = nullptr; +#endif wxLocale m_locale; std::vector m_availableTranslations; diff --git a/src/input/api/SDL/SDLControllerProvider.cpp b/src/input/api/SDL/SDLControllerProvider.cpp index f5f9cfea..3aa146a4 100644 --- a/src/input/api/SDL/SDLControllerProvider.cpp +++ b/src/input/api/SDL/SDLControllerProvider.cpp @@ -16,28 +16,43 @@ struct SDL_JoystickGUIDHash SDLControllerProvider::SDLControllerProvider() { - m_motion_states.reserve(8); - - m_running = true; - m_thread = std::thread(&SDLControllerProvider::event_thread, this); +#if !BOOST_OS_MACOS + std::scoped_lock _l(s_mutex); + if (s_initCount.fetch_add(1) == 0) + { + s_running = true; + s_thread = std::thread(&SDLControllerProvider::event_thread, this); + } +#endif } SDLControllerProvider::~SDLControllerProvider() { - if (m_running) +#if !BOOST_OS_MACOS + bool shutdownSDL = false; + { + std::scoped_lock _l(s_mutex); + if (s_initCount.fetch_sub(1) == 1) + { + cemu_assert_debug(s_running); + s_running = false; + shutdownSDL = true; + } + } + + if (shutdownSDL) { - m_running = false; // wake the thread with a quit event if it's currently waiting for events SDL_Event evt; SDL_zero(evt); evt.type = SDL_EVENT_QUIT; SDL_PushEvent(&evt); - - if (m_thread.joinable()) + if (s_thread.joinable()) { - m_thread.join(); + s_thread.join(); } } +#endif } std::vector> SDLControllerProvider::get_controllers() @@ -47,34 +62,22 @@ std::vector> SDLControllerProvider::get_controll std::unordered_map guid_counter; TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks); - int gamepad_count = 0; - SDL_JoystickID *gamepad_ids = SDL_GetGamepads(&gamepad_count); - if (gamepad_ids) { for (size_t i = 0; i < gamepad_count; ++i) { const auto guid = SDL_GetGamepadGUIDForID(gamepad_ids[i]); - const auto it = guid_counter.try_emplace(guid, 0); - if (const char* name = SDL_GetGamepadNameForID(gamepad_ids[i])) - { result.emplace_back(std::make_shared(guid, it.first->second, name)); - } else - { result.emplace_back(std::make_shared(guid, it.first->second)); - } - ++it.first->second; } - SDL_free(gamepad_ids); } - return result; } @@ -82,11 +85,8 @@ int SDLControllerProvider::get_index(size_t guid_index, const SDL_GUID& guid) co { size_t index = 0; int gamepad_count = 0; - TempState lock(SDL_LockJoysticks, SDL_UnlockJoysticks); - SDL_JoystickID *gamepad_ids = SDL_GetGamepads(&gamepad_count); - if (gamepad_ids) { for (size_t i = 0; i < gamepad_count; ++i) @@ -98,30 +98,23 @@ int SDLControllerProvider::get_index(size_t guid_index, const SDL_GUID& guid) co SDL_free(gamepad_ids); return i; } - ++index; } } - SDL_free(gamepad_ids); } - return -1; } MotionSample SDLControllerProvider::motion_sample(SDL_JoystickID diid) { - std::shared_lock lock(m_mutex); - - auto it = m_motion_states.find(diid); - - return (it != m_motion_states.end()) ? it->second.data : MotionSample{}; + std::shared_lock lock(s_mutex); + auto it = s_motion_states.find(diid); + return (it != s_motion_states.end()) ? it->second.data : MotionSample{}; } -void SDLControllerProvider::event_thread() +void SDLControllerProvider::InitSDL() { - SetThreadName("SDL_events"); - SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, "1"); @@ -144,17 +137,30 @@ void SDLControllerProvider::event_thread() { cemuLog_log(LogType::Force, "Couldn't enable SDL gamecontroller event polling: {}", SDL_GetError()); } +} - while (m_running.load(std::memory_order_relaxed)) +void SDLControllerProvider::ShutdownSDL() +{ + SDL_QuitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC); +} + +#if BOOST_OS_MACOS +void SDLControllerProvider::PumpSDLEvents() +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + HandleSDLEvent(event); +} +#endif + +void SDLControllerProvider::HandleSDLEvent(SDL_Event& event) +{ + switch (event.type) { - SDL_Event event{}; - SDL_WaitEvent(&event); - - switch (event.type) - { case SDL_EVENT_QUIT: { - m_running = false; + std::scoped_lock _l(s_mutex); + s_running = false; break; } case SDL_EVENT_GAMEPAD_AXIS_MOTION: /**< Game controller axis motion */ @@ -171,13 +177,15 @@ void SDLControllerProvider::event_thread() } case SDL_EVENT_GAMEPAD_ADDED: /**< A new Game controller has been inserted into the system */ { + std::scoped_lock _l(s_mutex); InputManager::instance().on_device_changed(); break; } case SDL_EVENT_GAMEPAD_REMOVED: /**< An opened Game controller has been removed */ { + std::scoped_lock _l(s_mutex); InputManager::instance().on_device_changed(); - m_motion_states.erase(event.gdevice.which); + s_motion_states.erase(event.gdevice.which); break; } case SDL_EVENT_GAMEPAD_REMAPPED: /**< The controller mapping was updated */ @@ -200,10 +208,8 @@ void SDLControllerProvider::event_thread() { SDL_JoystickID id = event.gsensor.which; uint64_t ts = event.gsensor.timestamp; - - std::scoped_lock lock(m_mutex); - - auto& state = m_motion_states[id]; + std::scoped_lock _l(s_mutex); + auto& state = s_motion_states[id]; auto& tracking = state.tracking; if (event.gsensor.sensor == SDL_SENSOR_ACCEL) @@ -274,7 +280,21 @@ void SDLControllerProvider::event_thread() } break; } - } } - SDL_QuitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC); +} + +void SDLControllerProvider::event_thread() +{ +#if BOOST_OS_MACOS + cemu_assert(false); +#endif + SetThreadName("SDL_events"); + InitSDL(); + while (s_running.load(std::memory_order_relaxed)) + { + SDL_Event event{}; + SDL_WaitEvent(&event); + HandleSDLEvent(event); + } + ShutdownSDL(); } diff --git a/src/input/api/SDL/SDLControllerProvider.h b/src/input/api/SDL/SDLControllerProvider.h index d06712e0..53864234 100644 --- a/src/input/api/SDL/SDLControllerProvider.h +++ b/src/input/api/SDL/SDLControllerProvider.h @@ -28,12 +28,26 @@ public: MotionSample motion_sample(SDL_JoystickID diid); + // exposed for manual event handling on macOS +#if BOOST_OS_MACOS + static void InitSDL(); + static void ShutdownSDL(); + static void PumpSDLEvents(); +#endif + private: void event_thread(); + static void HandleSDLEvent(union SDL_Event& event); +#if !BOOST_OS_MACOS + static void InitSDL(); + static void ShutdownSDL(); +#endif - std::atomic_bool m_running = false; - std::thread m_thread; - mutable std::shared_mutex m_mutex; + // there is only one SDL instance, for this reason all of our state can be static + inline static std::atomic_int s_initCount{0}; + inline static std::shared_mutex s_mutex; + inline static std::atomic_bool s_running = false; + inline static std::thread s_thread; struct MotionInfoTracking { @@ -55,5 +69,5 @@ private: MotionState() = default; }; - std::unordered_map m_motion_states{}; + inline static std::unordered_map s_motion_states{}; };