WiimoteReal: Send periodic writes to test for disconnect.

This commit is contained in:
Jordan Woyak 2025-09-27 01:44:40 -05:00
parent 94ecf4df39
commit e8d22923c6
2 changed files with 81 additions and 26 deletions

View File

@ -168,8 +168,6 @@ void Wiimote::Shutdown()
StopThread(); StopThread();
ClearReadQueue(); ClearReadQueue();
NOTICE_LOG_FMT(WIIMOTE, "Disconnected real wiimote.");
} }
// to be called from CPU thread // to be called from CPU thread
@ -209,8 +207,8 @@ void Wiimote::WriteReport(Report rpt)
const auto report_time = const auto report_time =
Core::IsCPUThread() ? core_timing.GetTargetHostTime(core_timing.GetTicks()) : Clock::now(); Core::IsCPUThread() ? core_timing.GetTargetHostTime(core_timing.GetTicks()) : Clock::now();
m_write_thread.EmplaceItem(report_time, std::move(rpt)); m_write_reports.Emplace(report_time, std::move(rpt));
IOWakeup(); m_write_event.Set();
} }
// to be called from CPU thread // 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. // Set reporting mode to non-continuous core buttons and turn on rumble.
Report mode_report = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 1, Report mode_report = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::ReportMode), 1,
u8(InputReportID::ReportCore)}; 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. // Request status and turn off rumble.
Report req_status_report = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::RequestStatus), 0}; 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() void Wiimote::EmuStop()
@ -764,38 +764,54 @@ void WiimoteScanner::ThreadFunc()
bool Wiimote::Connect(int index) bool Wiimote::Connect(int index)
{ {
m_index = index; m_index = index;
StartThread();
if (!m_run_thread.IsSet())
{
m_run_thread.Set();
StartThread();
m_thread_ready_event.Wait();
}
return IsConnected(); return IsConnected();
} }
void Wiimote::StartThread() void Wiimote::StartThread()
{ {
// Note that the read thread starts the writing worker thread. if (m_write_thread.joinable())
m_read_thread = std::thread(&Wiimote::ReadThreadFunc, this); 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() void Wiimote::StopThread()
{ {
if (!m_run_thread.TestAndClear()) if (!m_write_thread.joinable())
return; return;
IOWakeup(); m_run_thread.Clear();
m_write_event.Set();
// Note that the read thread stops the writing worker thread. // Note that the write thread stops the read thread.
m_read_thread.join(); m_write_thread.join();
} }
void Wiimote::ReadThreadFunc() void Wiimote::ReadThreadFunc()
{ {
Common::SetCurrentThreadName("Wiimote Read Thread"); 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(); bool ok = ConnectInternal();
if (!ok) if (!ok)
@ -812,19 +828,55 @@ void Wiimote::ReadThreadFunc()
return; 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()) 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; break;
} }
} }
m_write_thread.StopAndCancel(); IOWakeup();
read_thread.join();
NOTICE_LOG_FMT(WIIMOTE, "Disconnecting Wiimote {}.", m_index + 1);
DisconnectInternal(); DisconnectInternal();
} }

View File

@ -146,6 +146,7 @@ private:
virtual void IOWakeup() = 0; virtual void IOWakeup() = 0;
void ReadThreadFunc(); void ReadThreadFunc();
void WriteThreadFunc();
void RefreshConfig(); void RefreshConfig();
@ -158,14 +159,16 @@ private:
// And we track the rumble state to drop unnecessary rumble reports. // And we track the rumble state to drop unnecessary rumble reports.
bool m_rumble_state = false; bool m_rumble_state = false;
std::thread m_read_thread; std::thread m_write_thread;
// Whether to keep running the thread. // Whether to keep running the thread.
Common::Flag m_run_thread; Common::Flag m_run_thread;
// Triggered when the thread has finished ConnectInternal. // Triggered when the thread has finished ConnectInternal.
Common::Event m_thread_ready_event; Common::Event m_thread_ready_event;
Common::SPSCQueue<Report> m_read_reports; Common::SPSCQueue<Report> m_read_reports;
Common::WorkQueueThreadSP<TimedReport> m_write_thread; Common::SPSCQueue<TimedReport> m_write_reports;
// Kick the write thread.
Common::Event m_write_event;
bool m_speaker_enabled_in_dolphin_config = false; bool m_speaker_enabled_in_dolphin_config = false;
int m_balance_board_dump_port = 0; int m_balance_board_dump_port = 0;