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();
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();
}

View File

@ -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<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;
int m_balance_board_dump_port = 0;