WiimoteReal: Send reports with proper timing for theoretically better speaker data.

This commit is contained in:
Jordan Woyak 2025-05-31 19:12:13 -05:00
parent 79614956f3
commit ed93e9481a
2 changed files with 62 additions and 76 deletions

View File

@ -4,22 +4,20 @@
#include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/HW/WiimoteReal/WiimoteReal.h"
#include <algorithm> #include <algorithm>
#include <cstdlib>
#include <mutex> #include <mutex>
#include <queue>
#include <unordered_set> #include <unordered_set>
#include "Common/ChunkFile.h" #include <SFML/Network/UdpSocket.hpp>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Common/Swap.h" #include "Common/Swap.h"
#include "Common/Thread.h" #include "Common/Thread.h"
#include "Core/Config/MainSettings.h" #include "Core/Config/MainSettings.h"
#include "Core/Config/WiimoteSettings.h" #include "Core/Config/WiimoteSettings.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteCommon/DataReport.h" #include "Core/HW/WiimoteCommon/DataReport.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h" #include "Core/HW/WiimoteCommon/WiimoteHid.h"
@ -29,10 +27,8 @@
#include "Core/HW/WiimoteReal/IOhidapi.h" #include "Core/HW/WiimoteReal/IOhidapi.h"
#include "Core/System.h" #include "Core/System.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/Wiimote/WiimoteController.h" #include "InputCommon/ControllerInterface/Wiimote/WiimoteController.h"
#include "InputCommon/InputConfig.h"
#include "SFML/Network.hpp"
namespace WiimoteReal namespace WiimoteReal
{ {
@ -172,7 +168,6 @@ void Wiimote::Shutdown()
StopThread(); StopThread();
ClearReadQueue(); ClearReadQueue();
m_write_reports.Clear();
NOTICE_LOG_FMT(WIIMOTE, "Disconnected real wiimote."); NOTICE_LOG_FMT(WIIMOTE, "Disconnected real wiimote.");
} }
@ -207,7 +202,14 @@ void Wiimote::WriteReport(Report rpt)
m_rumble_state = new_rumble_state; m_rumble_state = new_rumble_state;
} }
m_write_reports.Push(std::move(rpt)); auto& core_timing = Core::System::GetInstance().GetCoreTiming();
// When invoked from the CPU thread, send the report at the proper time.
// From other threads (e.g. on construction/destruction) send the report as soon as possible.
const auto report_time =
Core::IsCPUThread() ? core_timing.GetTargetHostTime(core_timing.GetTicks()) : Clock::now();
m_write_thread.EmplaceItem(report_time, std::move(rpt));
IOWakeup(); IOWakeup();
} }
@ -296,29 +298,18 @@ void Wiimote::InterruptDataOutput(const u8* data, const u32 size)
WriteReport(std::move(rpt)); WriteReport(std::move(rpt));
} }
void Wiimote::Read() bool Wiimote::Read()
{ {
Report rpt(MAX_PAYLOAD); Report rpt(MAX_PAYLOAD);
auto const result = IORead(rpt.data()); auto const result = IORead(rpt.data());
if (0 == result)
{
ERROR_LOG_FMT(WIIMOTE, "Wiimote::IORead failed. Disconnecting Wii Remote {}.", m_index + 1);
DisconnectInternal();
return;
}
// Drop the report if not connected. // Drop the report if not connected.
if (!m_is_linked) if (m_is_linked && result > 0)
return;
if (result > 0)
{ {
if (m_balance_board_dump_port > 0 && m_index == WIIMOTE_BALANCE_BOARD) if (m_balance_board_dump_port > 0 && m_index == WIIMOTE_BALANCE_BOARD)
{ {
static sf::UdpSocket Socket; static sf::UdpSocket Socket;
(void)Socket.send((char*)rpt.data(), rpt.size(), sf::IpAddress::LocalHost, (void)Socket.send(rpt.data(), rpt.size(), sf::IpAddress::LocalHost,
m_balance_board_dump_port); m_balance_board_dump_port);
} }
@ -326,15 +317,13 @@ void Wiimote::Read()
rpt.resize(result); rpt.resize(result);
m_read_reports.Push(std::move(rpt)); m_read_reports.Push(std::move(rpt));
} }
return result != 0;
} }
bool Wiimote::Write() bool Wiimote::Write(const TimedReport& timed_report)
{ {
// nothing written, but this is not an error auto const& rpt = timed_report.report;
if (m_write_reports.Empty())
return true;
Report const& rpt = m_write_reports.Front();
if (m_balance_board_dump_port > 0 && m_index == WIIMOTE_BALANCE_BOARD) if (m_balance_board_dump_port > 0 && m_index == WIIMOTE_BALANCE_BOARD)
{ {
@ -342,13 +331,11 @@ bool Wiimote::Write()
(void)Socket.send((char*)rpt.data(), rpt.size(), sf::IpAddress::LocalHost, (void)Socket.send((char*)rpt.data(), rpt.size(), sf::IpAddress::LocalHost,
m_balance_board_dump_port); m_balance_board_dump_port);
} }
// Write the report at the proper time, mainly for speaker data, not that it will help much.
std::this_thread::sleep_until(timed_report.time);
int ret = IOWrite(rpt.data(), rpt.size()); int ret = IOWrite(rpt.data(), rpt.size());
m_write_reports.Pop();
if (!m_write_reports.Empty())
IOWakeup();
return ret != 0; return ret != 0;
} }
@ -520,22 +507,16 @@ ButtonData Wiimote::GetCurrentlyPressedButtons()
void Wiimote::Prepare() void Wiimote::Prepare()
{ {
m_need_prepare.Set(); const auto now = Clock::now();
IOWakeup();
}
bool Wiimote::PrepareOnThread()
{
// Set reporting mode to non-continuous core buttons and turn on rumble. // Set reporting mode to non-continuous core buttons and turn on rumble.
u8 static const 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));
// Request status and turn off rumble. // Request status and turn off rumble.
u8 static const req_status_report[] = {WR_SET_REPORT | BT_OUTPUT, Report req_status_report = {WR_SET_REPORT | BT_OUTPUT, u8(OutputReportID::RequestStatus), 0};
u8(OutputReportID::RequestStatus), 0}; m_write_thread.EmplaceItem(now + std::chrono::milliseconds{200}, std::move(req_status_report));
return IOWrite(mode_report, sizeof(mode_report)) &&
(Common::SleepCurrentThread(200), IOWrite(req_status_report, sizeof(req_status_report)));
} }
void Wiimote::EmuStop() void Wiimote::EmuStop()
@ -786,7 +767,6 @@ bool Wiimote::Connect(int index)
if (!m_run_thread.IsSet()) if (!m_run_thread.IsSet())
{ {
m_need_prepare.Set();
m_run_thread.Set(); m_run_thread.Set();
StartThread(); StartThread();
m_thread_ready_event.Wait(); m_thread_ready_event.Wait();
@ -797,20 +777,24 @@ bool Wiimote::Connect(int index)
void Wiimote::StartThread() void Wiimote::StartThread()
{ {
m_wiimote_thread = std::thread(&Wiimote::ThreadFunc, this); // Note that the read thread starts the writing worker thread.
m_read_thread = std::thread(&Wiimote::ReadThreadFunc, this);
} }
void Wiimote::StopThread() void Wiimote::StopThread()
{ {
if (!m_run_thread.TestAndClear()) if (!m_run_thread.TestAndClear())
return; return;
IOWakeup(); IOWakeup();
m_wiimote_thread.join();
// Note that the read thread stops the writing worker thread.
m_read_thread.join();
} }
void Wiimote::ThreadFunc() void Wiimote::ReadThreadFunc()
{ {
Common::SetCurrentThreadName("Wiimote Device Thread"); Common::SetCurrentThreadName("Wiimote Read Thread");
bool ok = ConnectInternal(); bool ok = ConnectInternal();
@ -828,23 +812,19 @@ void Wiimote::ThreadFunc()
return; return;
} }
// main loop m_write_thread.Reset("Wiimote Write Thread", std::bind_front(&Wiimote::Write, this));
while (IsConnected() && m_run_thread.IsSet())
while (m_run_thread.IsSet())
{ {
if (m_need_prepare.TestAndClear() && !PrepareOnThread()) if (!Read())
{ {
ERROR_LOG_FMT(WIIMOTE, "Wiimote::PrepareOnThread failed. Disconnecting Wiimote {}.", ERROR_LOG_FMT(WIIMOTE, "Wiimote::Read failed. Disconnecting Wiimote {}.", m_index + 1);
m_index + 1);
break; break;
} }
if (!Write())
{
ERROR_LOG_FMT(WIIMOTE, "Wiimote::Write failed. Disconnecting Wiimote {}.", m_index + 1);
break;
}
Read();
} }
m_write_thread.StopAndCancel();
DisconnectInternal(); DisconnectInternal();
} }

View File

@ -10,11 +10,12 @@
#include <thread> #include <thread>
#include <vector> #include <vector>
#include "Common/Common.h"
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
#include "Common/Event.h" #include "Common/Event.h"
#include "Common/Flag.h" #include "Common/Flag.h"
#include "Common/SPSCQueue.h" #include "Common/SPSCQueue.h"
#include "Common/WorkQueueThread.h"
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
#include "Core/HW/WiimoteCommon/WiimoteConstants.h" #include "Core/HW/WiimoteCommon/WiimoteConstants.h"
#include "Core/HW/WiimoteCommon/WiimoteHid.h" #include "Core/HW/WiimoteCommon/WiimoteHid.h"
@ -53,8 +54,6 @@ public:
Wiimote& operator=(Wiimote&&) = delete; Wiimote& operator=(Wiimote&&) = delete;
~Wiimote() override; ~Wiimote() override;
// This needs to be called in derived destructors!
void Shutdown();
virtual std::string GetId() const = 0; virtual std::string GetId() const = 0;
@ -95,6 +94,9 @@ public:
int GetIndex() const; int GetIndex() const;
protected: protected:
// This needs to be called in derived destructors!
void Shutdown();
Wiimote(); Wiimote();
int m_index = 0; int m_index = 0;
@ -108,14 +110,18 @@ protected:
u8 m_bt_device_index = 0; u8 m_bt_device_index = 0;
private: private:
void Read(); struct TimedReport
bool Write(); {
TimePoint time;
Report report;
};
bool Read();
bool Write(const TimedReport& timed_report);
void StartThread(); void StartThread();
void StopThread(); void StopThread();
bool PrepareOnThread();
void ResetDataReporting(); void ResetDataReporting();
virtual void EnablePowerAssertionInternal() {} virtual void EnablePowerAssertionInternal() {}
@ -130,13 +136,15 @@ private:
virtual int IORead(u8* buf) = 0; virtual int IORead(u8* buf) = 0;
virtual int IOWrite(u8 const* buf, size_t len) = 0; virtual int IOWrite(u8 const* buf, size_t len) = 0;
// Make a blocking IORead call immediately return.
virtual void IOWakeup() = 0; virtual void IOWakeup() = 0;
void ThreadFunc(); void ReadThreadFunc();
void RefreshConfig(); void RefreshConfig();
bool m_is_linked = false; std::atomic<bool> m_is_linked = false;
// We track the speaker state to convert unnecessary speaker data into rumble reports. // We track the speaker state to convert unnecessary speaker data into rumble reports.
bool m_speaker_enable = false; bool m_speaker_enable = false;
@ -145,16 +153,14 @@ 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_wiimote_thread; std::thread m_read_thread;
// Whether to keep running the thread. // Whether to keep running the thread.
Common::Flag m_run_thread; Common::Flag m_run_thread;
// Whether to call PrepareOnThread.
Common::Flag m_need_prepare;
// 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::SPSCQueue<Report> m_write_reports; Common::WorkQueueThreadSP<TimedReport> m_write_thread;
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;