diff --git a/Source/Core/Core/HW/GBACore.cpp b/Source/Core/Core/HW/GBACore.cpp index b25414f3b1..fd54f37647 100644 --- a/Source/Core/Core/HW/GBACore.cpp +++ b/Source/Core/Core/HW/GBACore.cpp @@ -254,9 +254,8 @@ bool Core::Start(u64 gc_ticks) if (Config::Get(Config::MAIN_GBA_THREADS)) { - m_idle = true; - m_exit_loop = false; - m_thread = std::make_unique([this] { ThreadLoop(); }); + m_event_thread.Reset(fmt::format("GBA{}", m_device_number + 1), + std::bind_front(&Core::HandleEvent, this)); } return true; @@ -264,17 +263,8 @@ bool Core::Start(u64 gc_ticks) void Core::Stop() { - if (m_thread) - { - Flush(); - m_exit_loop = true; - { - std::lock_guard lock(m_queue_mutex); - m_command_cv.notify_one(); - } - m_thread->join(); - m_thread.reset(); - } + m_event_thread.Shutdown(); + if (m_core) { mCoreConfigDeinit(&m_core->config); @@ -472,103 +462,76 @@ void Core::SetupEvent() m_event.priority = 0x80; } +void Core::SyncJoybus(u64 gc_ticks, u16 keys) +{ + PushEvent({ + .run_until_ticks = gc_ticks, + .keys = keys, + .event_type = JoybusEventType::TimeSync, + }); +} + void Core::SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys) { if (!IsStarted()) return; - Command command{}; - command.ticks = gc_ticks; - command.transfer_time = transfer_time; - command.sync_only = buffer == nullptr; - if (buffer) - std::copy_n(buffer, command.buffer.size(), command.buffer.begin()); - command.keys = keys; + m_joybus_command_transfer_time = transfer_time; + m_joybus_command = GBASIOJOYCommand(buffer[0]); + std::copy_n(buffer + 1, m_joybus_buffer.size(), m_joybus_buffer.data()); - if (m_thread) - { - std::lock_guard lock(m_queue_mutex); - m_command_queue.push(command); - m_idle = false; - m_command_cv.notify_one(); - } - else - { - RunCommand(command); - } + m_command_pending.store(true, std::memory_order_relaxed); + + PushEvent({ + .run_until_ticks = gc_ticks, + .keys = keys, + .event_type = JoybusEventType::RunCommand, + }); } -std::vector Core::GetJoybusResponse() +int Core::GetJoybusResponse(u8* data_out) { - if (!IsStarted()) - return {}; + m_command_pending.wait(true, std::memory_order_acquire); - if (m_thread) - { - std::unique_lock lock(m_response_mutex); - m_response_cv.wait(lock, [&] { return m_response_ready; }); - } - m_response_ready = false; - return m_response; + std::copy_n(m_joybus_buffer.data(), m_response_size, data_out); + return m_response_size; } void Core::Flush() { - if (!IsStarted() || !m_thread) + m_event_thread.WaitForCompletion(); +} + +void Core::PushEvent(JoybusEvent event) +{ + if (m_event_thread.IsRunning()) + m_event_thread.Push(event); + else + HandleEvent(event); +} + +void Core::HandleEvent(JoybusEvent event) +{ + m_keys = event.keys; + RunUntil(event.run_until_ticks); + + if (event.event_type != JoybusEventType::RunCommand) return; - std::unique_lock lock(m_queue_mutex); - m_response_cv.wait(lock, [&] { return m_idle; }); -} -void Core::ThreadLoop() -{ - Common::SetCurrentThreadName(fmt::format("GBA{}", m_device_number + 1).c_str()); - std::unique_lock queue_lock(m_queue_mutex); - while (true) + if (m_link_enabled && !m_force_disconnect) { - m_command_cv.wait(queue_lock, [&] { return !m_command_queue.empty() || m_exit_loop; }); - if (m_exit_loop) - break; - Command command{m_command_queue.front()}; - m_command_queue.pop(); - queue_lock.unlock(); - - RunCommand(command); - - queue_lock.lock(); - if (m_command_queue.empty()) - m_idle = true; - m_response_cv.notify_one(); + m_response_size = + u8(GBASIOJOYSendCommand(&m_sio_driver, m_joybus_command, m_joybus_buffer.data())); } -} - -void Core::RunCommand(Command& command) -{ - m_keys = command.keys; - RunUntil(command.ticks); - if (!command.sync_only) + else { - m_response.clear(); - if (m_link_enabled && !m_force_disconnect) - { - int recvd = GBASIOJOYSendCommand( - &m_sio_driver, static_cast(command.buffer[0]), &command.buffer[1]); - std::copy_n(command.buffer.begin() + 1, recvd, std::back_inserter(m_response)); - } - - if (m_thread && !m_response_ready) - { - std::lock_guard response_lock(m_response_mutex); - m_response_ready = true; - m_response_cv.notify_one(); - } - else - { - m_response_ready = true; - } + m_response_size = 0; } - if (command.transfer_time) - RunFor(command.transfer_time); + + m_command_pending.store(false, std::memory_order_release); + m_command_pending.notify_one(); + + RunFor(m_joybus_command_transfer_time); } void Core::RunUntil(u64 gc_ticks) @@ -691,8 +654,8 @@ void Core::DoState(PointerWrap& p) p.Do(m_gc_ticks_remainder); p.Do(m_keys); p.Do(m_link_enabled); - p.Do(m_response_ready); - p.Do(m_response); + p.Do(m_response_size); + p.Do(m_joybus_buffer); std::vector core_state; core_state.resize(m_core->stateSize(m_core)); diff --git a/Source/Core/Core/HW/GBACore.h b/Source/Core/Core/HW/GBACore.h index 6fd4b86e0c..2c6b7cb068 100644 --- a/Source/Core/Core/HW/GBACore.h +++ b/Source/Core/Core/HW/GBACore.h @@ -6,13 +6,9 @@ #ifdef HAS_LIBMGBA #include -#include #include -#include -#include #include #include -#include #include #define PYCPARSE // Remove static functions from the header @@ -22,6 +18,7 @@ #include #include "Common/CommonTypes.h" +#include "Common/WorkQueueThread.h" class GBAHostInterface; class PointerWrap; @@ -73,8 +70,9 @@ public: void SetForceDisconnect(bool force_disconnect); void EReaderQueueCard(std::string_view card_path); + void SyncJoybus(u64 gc_ticks, u16 keys); void SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys); - std::vector GetJoybusResponse(); + int GetJoybusResponse(u8* data_out); void ImportState(std::string_view state_path); void ExportState(std::string_view state_path); @@ -86,20 +84,23 @@ public: static std::string GetSavePath(std::string_view rom_path, int device_number); private: - void ThreadLoop(); void RunUntil(u64 gc_ticks); void RunFor(u64 gc_ticks); void Flush(); - struct Command + enum class JoybusEventType : u8 { - u64 ticks; - int transfer_time; - bool sync_only; - std::array buffer; - u16 keys; + TimeSync, + RunCommand, }; - void RunCommand(Command& command); + struct JoybusEvent + { + u64 run_until_ticks{}; + u16 keys{}; + JoybusEventType event_type{}; + }; + void PushEvent(JoybusEvent event); + void HandleEvent(JoybusEvent event); bool LoadBIOS(const char* bios_path); bool LoadSave(const char* save_path); @@ -134,17 +135,19 @@ private: std::weak_ptr m_host; - std::unique_ptr m_thread; - bool m_exit_loop = false; - bool m_idle = false; - std::mutex m_queue_mutex; - std::condition_variable m_command_cv; - std::queue m_command_queue; + // Set by the GC thread before issuing a JoybusEventType::RunCommand. + int m_joybus_command_transfer_time{}; + GBASIOJOYCommand m_joybus_command{}; - std::mutex m_response_mutex; - std::condition_variable m_response_cv; - bool m_response_ready = false; - std::vector m_response; + // Commands are synchronous. This buffer is used for the command and the response. + std::array m_joybus_buffer{}; // State saved. + + // Set by the GBA thread after filling in the above buffer. + u8 m_response_size{}; // State saved. + std::atomic_bool m_command_pending{}; + + // The entire threaded GBA runs within events pushed to this queue. + Common::WorkQueueThreadSP m_event_thread; ::Core::System& m_system; }; diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp index b9c22f1d58..ba944cebd8 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp @@ -92,11 +92,9 @@ int CSIDevice_GBAEmu::RunBuffer(u8* buffer, int request_length) case NextAction::ReceiveResponse: { m_next_action = NextAction::SendCommand; - - std::vector response = m_core->GetJoybusResponse(); - if (response.empty()) + const auto response_length = m_core->GetJoybusResponse(buffer); + if (response_length == 0) return -1; - std::ranges::copy(response, buffer); #ifdef _DEBUG const Common::Log::LogLevel log_level = @@ -106,10 +104,10 @@ int CSIDevice_GBAEmu::RunBuffer(u8* buffer, int request_length) GENERIC_LOG_FMT(Common::Log::LogType::SERIALINTERFACE, log_level, "{} [< {:02x}{:02x}{:02x}{:02x}{:02x}] ({})", m_device_number, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], - response.size()); + response_length); #endif - return static_cast(response.size()); + return response_length; } } @@ -170,7 +168,7 @@ void CSIDevice_GBAEmu::DoState(PointerWrap& p) void CSIDevice_GBAEmu::OnEvent(u64 userdata, s64 cycles_late) { - m_core->SendJoybusCommand(m_system.GetCoreTiming().GetTicks() + userdata, 0, nullptr, m_keys); + m_core->SyncJoybus(m_system.GetCoreTiming().GetTicks() + userdata, m_keys); const auto num_cycles = userdata + GetSyncInterval(m_system.GetSystemTimers()); m_system.GetSerialInterface().ScheduleEvent(m_device_number, num_cycles);