diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp index 53e8a05a18..36fda0bc8a 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.cpp @@ -168,8 +168,6 @@ void Wiimote::Shutdown() StopThread(); ClearReadQueue(); - - NOTICE_LOG_FMT(WIIMOTE, "Disconnected real wiimote."); } // to be called from CPU thread @@ -209,8 +207,8 @@ void Wiimote::WriteReport(Report rpt) const auto report_time = Core::IsCPUThread() ? core_timing.GetTargetHostTime(core_timing.GetTicks()) : Clock::now(); - m_write_thread.EmplaceItem(report_time, std::move(rpt)); - IOWakeup(); + m_write_reports.Emplace(report_time, std::move(rpt)); + m_write_event.Set(); } // to be called from CPU thread @@ -512,11 +510,13 @@ void Wiimote::Prepare() // Set reporting mode to non-continuous core buttons and turn on rumble. Report mode_report = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 1, u8(InputReportID::ReportCore)}; - m_write_thread.EmplaceItem(now, std::move(mode_report)); + m_write_reports.Emplace(now, std::move(mode_report)); // Request status and turn off rumble. Report req_status_report = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::RequestStatus), 0}; - m_write_thread.EmplaceItem(now + std::chrono::milliseconds{200}, std::move(req_status_report)); + m_write_reports.Emplace(now + std::chrono::milliseconds{200}, std::move(req_status_report)); + + m_write_event.Set(); } void Wiimote::EmuStop() @@ -764,38 +764,54 @@ void WiimoteScanner::ThreadFunc() bool Wiimote::Connect(int index) { m_index = index; - - if (!m_run_thread.IsSet()) - { - m_run_thread.Set(); - StartThread(); - m_thread_ready_event.Wait(); - } - + StartThread(); return IsConnected(); } void Wiimote::StartThread() { - // Note that the read thread starts the writing worker thread. - m_read_thread = std::thread(&Wiimote::ReadThreadFunc, this); + if (m_write_thread.joinable()) + return; + + m_run_thread.Set(); + // Note that the write thread starts the read thread. + m_write_thread = std::thread(&Wiimote::WriteThreadFunc, this); + + m_thread_ready_event.Wait(); } void Wiimote::StopThread() { - if (!m_run_thread.TestAndClear()) + if (!m_write_thread.joinable()) return; - IOWakeup(); + m_run_thread.Clear(); + m_write_event.Set(); - // Note that the read thread stops the writing worker thread. - m_read_thread.join(); + // Note that the write thread stops the read thread. + m_write_thread.join(); } void Wiimote::ReadThreadFunc() { Common::SetCurrentThreadName("Wiimote Read Thread"); + while (m_run_thread.IsSet()) + { + if (!Read()) + { + WARN_LOG_FMT(WIIMOTE, "Wiimote::Read failed on Wiimote {}.", m_index + 1); + m_run_thread.Clear(); + m_write_event.Set(); + break; + } + } +} + +void Wiimote::WriteThreadFunc() +{ + Common::SetCurrentThreadName("Wiimote Write Thread"); + bool ok = ConnectInternal(); if (!ok) @@ -812,19 +828,55 @@ void Wiimote::ReadThreadFunc() return; } - m_write_thread.Reset("Wiimote Write Thread", std::bind_front(&Wiimote::Write, this)); + std::thread read_thread{&Wiimote::ReadThreadFunc, this}; + + // Windows and also the DolphinBar require performing a write + // to detect disconnections in a timely manner + // If we haven't written a report in some time, attempt a rumble-off report. + // This also has a minor benefit of preventing rumble from being stuck on. + constexpr auto WRITE_TEST_INTERVAL = std::chrono::milliseconds{1000}; + + TimePoint last_write_time = Clock::now(); while (m_run_thread.IsSet()) { - if (!Read()) + bool write_success = false; + + if (!m_write_reports.Empty()) { - ERROR_LOG_FMT(WIIMOTE, "Wiimote::Read failed. Disconnecting Wiimote {}.", m_index + 1); + // Send a normal report. + write_success = Write(m_write_reports.Front()); + m_write_reports.Pop(); + } + else if (Clock::now() - last_write_time >= WRITE_TEST_INTERVAL) + { + // We haven't written in a while, test a write so we can check for a disconnect. + DEBUG_LOG_FMT(WIIMOTE, "Sending periodic write test for Wiimote {}.", m_index + 1); + const u8 rumble_off[] = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::Rumble), 0x00}; + + write_success = IOWrite(std::data(rumble_off), std::size(rumble_off)) > 0; + } + else + { + // Nothing to do. Wait a while for a kick. + m_write_event.WaitFor(WRITE_TEST_INTERVAL); + continue; + } + + last_write_time = Clock::now(); + + if (!write_success) + { + WARN_LOG_FMT(WIIMOTE, "Wiimote::Write failed on Wiimote {}.", m_index + 1); + m_run_thread.Clear(); break; } } - m_write_thread.StopAndCancel(); + IOWakeup(); + read_thread.join(); + NOTICE_LOG_FMT(WIIMOTE, "Disconnecting Wiimote {}.", m_index + 1); DisconnectInternal(); } diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h index a54cca3a6c..168d9a6100 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h @@ -146,6 +146,7 @@ private: virtual void IOWakeup() = 0; void ReadThreadFunc(); + void WriteThreadFunc(); void RefreshConfig(); @@ -158,14 +159,16 @@ private: // And we track the rumble state to drop unnecessary rumble reports. bool m_rumble_state = false; - std::thread m_read_thread; + std::thread m_write_thread; // Whether to keep running the thread. Common::Flag m_run_thread; // Triggered when the thread has finished ConnectInternal. Common::Event m_thread_ready_event; Common::SPSCQueue m_read_reports; - Common::WorkQueueThreadSP m_write_thread; + Common::SPSCQueue m_write_reports; + // Kick the write thread. + Common::Event m_write_event; bool m_speaker_enabled_in_dolphin_config = false; int m_balance_board_dump_port = 0;