diff --git a/src/gui/wxgui/CemuApp.cpp b/src/gui/wxgui/CemuApp.cpp index a900f10b..f72fea87 100644 --- a/src/gui/wxgui/CemuApp.cpp +++ b/src/gui/wxgui/CemuApp.cpp @@ -391,6 +391,7 @@ int CemuApp::OnExit() { wxApp::OnExit(); wxTheClipboard->Flush(); + InputManager::instance().Shutdown(); #if BOOST_OS_WINDOWS ExitProcess(0); #else diff --git a/src/input/InputManager.cpp b/src/input/InputManager.cpp index 64b238fc..47257d1d 100644 --- a/src/input/InputManager.cpp +++ b/src/input/InputManager.cpp @@ -53,8 +53,7 @@ InputManager::InputManager() InputManager::~InputManager() { - m_update_thread_shutdown.store(true); - m_update_thread.join(); + // destructors will not invoked forever, so we manually release resources in Shutdown(). } void InputManager::load() noexcept @@ -952,3 +951,21 @@ void InputManager::update_thread() std::this_thread::yield(); } } + +void InputManager::Shutdown() +{ + m_update_thread_shutdown = true; + + if (m_update_thread.joinable()) + { + m_update_thread.join(); + } + + for (auto& pad : m_vpad) pad.reset(); + for (auto& pad : m_wpad) pad.reset(); + + for (auto& providers : m_api_available) + { + providers.clear(); + } +} diff --git a/src/input/InputManager.h b/src/input/InputManager.h index 715d8f2e..7957fbf7 100644 --- a/src/input/InputManager.h +++ b/src/input/InputManager.h @@ -46,6 +46,8 @@ public: bool is_gameprofile_set(size_t player_index) const; + void Shutdown(); + EmulatedControllerPtr set_controller(EmulatedControllerPtr controller); EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type); EmulatedControllerPtr set_controller(size_t player_index, EmulatedController::Type type, const std::shared_ptr& controller); diff --git a/src/input/api/DSU/DSUControllerProvider.cpp b/src/input/api/DSU/DSUControllerProvider.cpp index fa00277c..885f3284 100644 --- a/src/input/api/DSU/DSUControllerProvider.cpp +++ b/src/input/api/DSU/DSUControllerProvider.cpp @@ -42,8 +42,25 @@ DSUControllerProvider::~DSUControllerProvider() if (m_running) { m_running = false; - m_writer_thread.join(); - m_reader_thread.join(); + + boost::system::error_code ec; + m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both, ec); + m_socket.close(ec); + + if (m_reader_thread.joinable()) + { + m_reader_thread.join(); + } + + { + std::scoped_lock lock(m_writer_mutex); + m_writer_cond.notify_all(); + } + + if (m_writer_thread.joinable()) + { + m_writer_thread.join(); + } } } @@ -261,6 +278,11 @@ void DSUControllerProvider::reader_thread() const size_t len = m_socket.receive_from(boost::asio::buffer(recv_buf), sender_endpoint, 0, ec); if (ec) { + if (!m_running.load(std::memory_order_relaxed)) + { + break; + } + #ifdef DEBUG_DSU_CLIENT printf(" DSUControllerProvider::ReaderThread: exception %s\n", ec.what()); #endif diff --git a/src/input/api/SDL/SDLControllerProvider.cpp b/src/input/api/SDL/SDLControllerProvider.cpp index ea88fab7..f5f9cfea 100644 --- a/src/input/api/SDL/SDLControllerProvider.cpp +++ b/src/input/api/SDL/SDLControllerProvider.cpp @@ -16,30 +16,6 @@ struct SDL_JoystickGUIDHash SDLControllerProvider::SDLControllerProvider() { - 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"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STADIA, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA, "1"); - - if (!SDL_Init(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC)) - { - throw std::runtime_error(fmt::format("couldn't initialize SDL: {}", SDL_GetError())); - } - - SDL_SetGamepadEventsEnabled(true); - if (!SDL_GamepadEventsEnabled()) - { - cemuLog_log(LogType::Force, "Couldn't enable SDL gamecontroller event polling: {}", SDL_GetError()); - } - m_motion_states.reserve(8); m_running = true; @@ -53,13 +29,15 @@ SDLControllerProvider::~SDLControllerProvider() 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); - // wait until thread exited - m_thread.join(); - } - SDL_QuitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC); + if (m_thread.joinable()) + { + m_thread.join(); + } + } } std::vector> SDLControllerProvider::get_controllers() @@ -143,6 +121,30 @@ MotionSample SDLControllerProvider::motion_sample(SDL_JoystickID diid) void SDLControllerProvider::event_thread() { 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"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STADIA, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_LUNA, "1"); + + if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC)) + { + throw std::runtime_error(fmt::format("couldn't initialize SDL: {}", SDL_GetError())); + } + + SDL_SetGamepadEventsEnabled(true); + if (!SDL_GamepadEventsEnabled()) + { + cemuLog_log(LogType::Force, "Couldn't enable SDL gamecontroller event polling: {}", SDL_GetError()); + } + while (m_running.load(std::memory_order_relaxed)) { SDL_Event event{}; @@ -153,9 +155,8 @@ void SDLControllerProvider::event_thread() case SDL_EVENT_QUIT: { m_running = false; - return; + break; } - case SDL_EVENT_GAMEPAD_AXIS_MOTION: /**< Game controller axis motion */ { break; @@ -275,4 +276,5 @@ void SDLControllerProvider::event_thread() } } } + SDL_QuitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC); } diff --git a/src/input/api/SDL/SDLControllerProvider.h b/src/input/api/SDL/SDLControllerProvider.h index 4763885e..d06712e0 100644 --- a/src/input/api/SDL/SDLControllerProvider.h +++ b/src/input/api/SDL/SDLControllerProvider.h @@ -30,11 +30,11 @@ public: private: void event_thread(); - + std::atomic_bool m_running = false; std::thread m_thread; mutable std::shared_mutex m_mutex; - + struct MotionInfoTracking { uint64 lastTimestampGyro{}; diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index b55c9cd4..76b8f63b 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -25,9 +25,31 @@ WiimoteControllerProvider::~WiimoteControllerProvider() if (m_running) { m_running = false; - m_writer_thread.join(); - m_reader_thread.join(); - m_connectionThread.join(); + + { + std::scoped_lock lock(m_writer_mutex); + m_writer_cond.notify_all(); + } + + { + std::scoped_lock lock(m_connectionMutex); + m_connectionCond.notify_all(); + } + + if (m_writer_thread.joinable()) + { + m_writer_thread.join(); + } + + if (m_reader_thread.joinable()) + { + m_reader_thread.join(); + } + + if (m_connectionThread.joinable()) + { + m_connectionThread.join(); + } } } @@ -169,7 +191,8 @@ void WiimoteControllerProvider::connectionThread() m_connectedDevices.clear(); std::ranges::move(devices, std::back_inserter(m_connectedDevices)); } - std::this_thread::sleep_for(std::chrono::seconds(2)); + std::unique_lock lock(m_connectionMutex); + m_connectionCond.wait_for(lock, std::chrono::seconds(2)); } } diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.h b/src/input/api/Wiimote/WiimoteControllerProvider.h index 90f28d5c..4e813023 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.h +++ b/src/input/api/Wiimote/WiimoteControllerProvider.h @@ -80,6 +80,8 @@ private: std::shared_mutex m_device_mutex; std::thread m_connectionThread; + std::mutex m_connectionMutex; + std::condition_variable m_connectionCond; std::vector m_connectedDevices; std::mutex m_connectedDeviceMutex; struct Wiimote diff --git a/src/util/ScreenSaver/ScreenSaver.h b/src/util/ScreenSaver/ScreenSaver.h index 49625978..30bc5053 100644 --- a/src/util/ScreenSaver/ScreenSaver.h +++ b/src/util/ScreenSaver/ScreenSaver.h @@ -4,37 +4,62 @@ class ScreenSaver { public: - static void SetInhibit(bool inhibit) - { - // temporary workaround because feature crashes on macOS + static void SetInhibit(bool inhibit) + { + // temporary workaround because feature crashes on macOS #if BOOST_OS_MACOS - return; + return; #endif - // Initialize video subsystem if necessary - if (SDL_WasInit(SDL_INIT_VIDEO) == 0) - { - int initErr = SDL_InitSubSystem(SDL_INIT_VIDEO); - if (initErr) - { - cemuLog_log(LogType::Force, "Could not disable screen saver (SDL video subsystem initialization error)"); - } - } - // Toggle SDL's screen saver inhibition - if (inhibit) - { - SDL_DisableScreenSaver(); - if (SDL_ScreenSaverEnabled()) - { - cemuLog_log(LogType::Force, "Could not verify if screen saver was disabled (`SDL_IsScreenSaverEnabled()` returned SDL_TRUE)"); - } - } - else - { - SDL_EnableScreenSaver(); - if (!SDL_ScreenSaverEnabled()) - { - cemuLog_log(LogType::Force, "Could not verify if screen saver was re-enabled (`SDL_IsScreenSaverEnabled()` returned SDL_FALSE)"); - } - } - }; + bool* inhibitArg = new bool(inhibit); + + if (!SDL_RunOnMainThread(SetInhibitCallback, inhibitArg, false)) + { + delete inhibitArg; + cemuLog_log(LogType::Force, "Failed to schedule screen saver logic on main thread: {}", SDL_GetError()); + } + } + +private: + static void SDLCALL SetInhibitCallback(void* userdata) + { + if (!userdata) + { + return; + } + + bool inhibit = *static_cast(userdata); + SDL_free(userdata); + + if (SDL_WasInit(SDL_INIT_VIDEO) == 0) + { + if (!SDL_InitSubSystem(SDL_INIT_VIDEO)) + { + cemuLog_log(LogType::Force, "Could not disable screen saver (SDL video subsystem initialization error: {})", SDL_GetError()); + return; + } + } + + if (inhibit) + { + if (!SDL_DisableScreenSaver()) + { + cemuLog_log(LogType::Force, "Could not disable screen saver: {}", SDL_GetError()); + } + else if (SDL_ScreenSaverEnabled()) + { + cemuLog_log(LogType::Force, "Could not verify if screen saver was disabled (`SDL_IsScreenSaverEnabled()` returned true)"); + } + } + else + { + if (!SDL_EnableScreenSaver()) + { + cemuLog_log(LogType::Force, "Could not enable screen saver: {}", SDL_GetError()); + } + else if (!SDL_ScreenSaverEnabled()) + { + cemuLog_log(LogType::Force, "Could not verify if screen saver was re-enabled (`SDL_IsScreenSaverEnabled()` returned false)"); + } + } + } };