This commit is contained in:
Jordan Woyak 2025-12-15 17:14:34 -06:00 committed by GitHub
commit 31c09b9761
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 90 additions and 126 deletions

View File

@ -98,6 +98,8 @@ public:
m_items.WaitForEmpty(); m_items.WaitForEmpty();
} }
bool IsRunning() { return m_thread.joinable(); }
private: private:
using CommandFunction = std::function<void()>; using CommandFunction = std::function<void()>;
@ -142,8 +144,6 @@ private:
return std::lock_guard{m_mutex}; return std::lock_guard{m_mutex};
} }
bool IsRunning() { return m_thread.joinable(); }
void ThreadLoop(const std::string& thread_name, const FunctionType& function) void ThreadLoop(const std::string& thread_name, const FunctionType& function)
{ {
Common::SetCurrentThreadName(thread_name.c_str()); Common::SetCurrentThreadName(thread_name.c_str());

View File

@ -254,9 +254,8 @@ bool Core::Start(u64 gc_ticks)
if (Config::Get(Config::MAIN_GBA_THREADS)) if (Config::Get(Config::MAIN_GBA_THREADS))
{ {
m_idle = true; m_event_thread.Reset(fmt::format("GBA{}", m_device_number + 1),
m_exit_loop = false; std::bind_front(&Core::HandleEvent, this));
m_thread = std::make_unique<std::thread>([this] { ThreadLoop(); });
} }
return true; return true;
@ -264,17 +263,8 @@ bool Core::Start(u64 gc_ticks)
void Core::Stop() void Core::Stop()
{ {
if (m_thread) m_event_thread.Shutdown();
{
Flush();
m_exit_loop = true;
{
std::lock_guard<std::mutex> lock(m_queue_mutex);
m_command_cv.notify_one();
}
m_thread->join();
m_thread.reset();
}
if (m_core) if (m_core)
{ {
mCoreConfigDeinit(&m_core->config); mCoreConfigDeinit(&m_core->config);
@ -481,103 +471,76 @@ void Core::SetupEvent()
m_event.priority = 0x80; 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) void Core::SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys)
{ {
if (!IsStarted()) if (!IsStarted())
return; return;
Command command{}; m_joybus_command_transfer_time = transfer_time;
command.ticks = gc_ticks; m_joybus_command = GBASIOJOYCommand(buffer[0]);
command.transfer_time = transfer_time; std::copy_n(buffer + 1, m_joybus_buffer.size(), m_joybus_buffer.data());
command.sync_only = buffer == nullptr;
if (buffer)
std::copy_n(buffer, command.buffer.size(), command.buffer.begin());
command.keys = keys;
if (m_thread) m_command_pending.store(true, std::memory_order_relaxed);
{
std::lock_guard<std::mutex> lock(m_queue_mutex); PushEvent({
m_command_queue.push(command); .run_until_ticks = gc_ticks,
m_idle = false; .keys = keys,
m_command_cv.notify_one(); .event_type = JoybusEventType::RunCommand,
} });
else
{
RunCommand(command);
}
} }
std::vector<u8> Core::GetJoybusResponse() int Core::GetJoybusResponse(u8* data_out)
{ {
if (!IsStarted()) m_command_pending.wait(true, std::memory_order_acquire);
return {};
if (m_thread) std::copy_n(m_joybus_buffer.data(), m_response_size, data_out);
{ return m_response_size;
std::unique_lock<std::mutex> lock(m_response_mutex);
m_response_cv.wait(lock, [&] { return m_response_ready; });
}
m_response_ready = false;
return m_response;
} }
void Core::Flush() 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; return;
std::unique_lock<std::mutex> lock(m_queue_mutex);
m_response_cv.wait(lock, [&] { return m_idle; });
}
void Core::ThreadLoop() if (m_link_enabled && !m_force_disconnect)
{
Common::SetCurrentThreadName(fmt::format("GBA{}", m_device_number + 1).c_str());
std::unique_lock<std::mutex> queue_lock(m_queue_mutex);
while (true)
{ {
m_command_cv.wait(queue_lock, [&] { return !m_command_queue.empty() || m_exit_loop; }); m_response_size =
if (m_exit_loop) u8(GBASIOJOYSendCommand(&m_sio_driver, m_joybus_command, m_joybus_buffer.data()));
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();
} }
} else
void Core::RunCommand(Command& command)
{
m_keys = command.keys;
RunUntil(command.ticks);
if (!command.sync_only)
{ {
m_response.clear(); m_response_size = 0;
if (m_link_enabled && !m_force_disconnect)
{
int recvd = GBASIOJOYSendCommand(
&m_sio_driver, static_cast<GBASIOJOYCommand>(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<std::mutex> response_lock(m_response_mutex);
m_response_ready = true;
m_response_cv.notify_one();
}
else
{
m_response_ready = true;
}
} }
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) void Core::RunUntil(u64 gc_ticks)
@ -700,8 +663,8 @@ void Core::DoState(PointerWrap& p)
p.Do(m_gc_ticks_remainder); p.Do(m_gc_ticks_remainder);
p.Do(m_keys); p.Do(m_keys);
p.Do(m_link_enabled); p.Do(m_link_enabled);
p.Do(m_response_ready); p.Do(m_response_size);
p.Do(m_response); p.Do(m_joybus_buffer);
std::vector<u8> core_state; std::vector<u8> core_state;
core_state.resize(m_core->stateSize(m_core)); core_state.resize(m_core->stateSize(m_core));

View File

@ -6,13 +6,9 @@
#ifdef HAS_LIBMGBA #ifdef HAS_LIBMGBA
#include <array> #include <array>
#include <condition_variable>
#include <memory> #include <memory>
#include <mutex>
#include <queue>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <thread>
#include <vector> #include <vector>
#define PYCPARSE // Remove static functions from the header #define PYCPARSE // Remove static functions from the header
@ -23,6 +19,7 @@
#include "Common/Buffer.h" #include "Common/Buffer.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/WorkQueueThread.h"
class GBAHostInterface; class GBAHostInterface;
class PointerWrap; class PointerWrap;
@ -75,8 +72,9 @@ public:
void SetForceDisconnect(bool force_disconnect); void SetForceDisconnect(bool force_disconnect);
void EReaderQueueCard(std::string_view card_path); 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); void SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys);
std::vector<u8> GetJoybusResponse(); int GetJoybusResponse(u8* data_out);
void ImportState(std::string_view state_path); void ImportState(std::string_view state_path);
void ExportState(std::string_view state_path); void ExportState(std::string_view state_path);
@ -88,20 +86,23 @@ public:
static std::string GetSavePath(std::string_view rom_path, int device_number); static std::string GetSavePath(std::string_view rom_path, int device_number);
private: private:
void ThreadLoop();
void RunUntil(u64 gc_ticks); void RunUntil(u64 gc_ticks);
void RunFor(u64 gc_ticks); void RunFor(u64 gc_ticks);
void Flush(); void Flush();
struct Command enum class JoybusEventType : u8
{ {
u64 ticks; TimeSync,
int transfer_time; RunCommand,
bool sync_only;
std::array<u8, 6> buffer;
u16 keys;
}; };
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 LoadBIOS(const char* bios_path);
bool LoadSave(const char* save_path); bool LoadSave(const char* save_path);
@ -136,17 +137,19 @@ private:
std::weak_ptr<GBAHostInterface> m_host; std::weak_ptr<GBAHostInterface> m_host;
std::unique_ptr<std::thread> m_thread; // Set by the GC thread before issuing a JoybusEventType::RunCommand.
bool m_exit_loop = false; int m_joybus_command_transfer_time{};
bool m_idle = false; GBASIOJOYCommand m_joybus_command{};
std::mutex m_queue_mutex;
std::condition_variable m_command_cv;
std::queue<Command> m_command_queue;
std::mutex m_response_mutex; // Commands are synchronous. This buffer is used for the command and the response.
std::condition_variable m_response_cv; std::array<u8, 5> m_joybus_buffer{}; // State saved.
bool m_response_ready = false;
std::vector<u8> m_response; // 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<JoybusEvent> m_event_thread;
::Core::System& m_system; ::Core::System& m_system;
}; };

View File

@ -91,11 +91,9 @@ int CSIDevice_GBAEmu::RunBuffer(u8* buffer, int request_length)
case NextAction::ReceiveResponse: case NextAction::ReceiveResponse:
{ {
m_next_action = NextAction::SendCommand; m_next_action = NextAction::SendCommand;
const auto response_length = m_core->GetJoybusResponse(buffer);
std::vector<u8> response = m_core->GetJoybusResponse(); if (response_length == 0)
if (response.empty())
return -1; return -1;
std::ranges::copy(response, buffer);
#ifdef _DEBUG #ifdef _DEBUG
const Common::Log::LogLevel log_level = const Common::Log::LogLevel log_level =
@ -105,10 +103,10 @@ int CSIDevice_GBAEmu::RunBuffer(u8* buffer, int request_length)
GENERIC_LOG_FMT(Common::Log::LogType::SERIALINTERFACE, log_level, GENERIC_LOG_FMT(Common::Log::LogType::SERIALINTERFACE, log_level,
"{} [< {:02x}{:02x}{:02x}{:02x}{:02x}] ({})", "{} [< {:02x}{:02x}{:02x}{:02x}{:02x}] ({})",
m_device_number, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], m_device_number, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4],
response.size()); response_length);
#endif #endif
return static_cast<int>(response.size()); return response_length;
} }
} }
@ -169,7 +167,7 @@ void CSIDevice_GBAEmu::DoState(PointerWrap& p)
void CSIDevice_GBAEmu::OnEvent(u64 userdata, s64 cycles_late) 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()); const auto num_cycles = userdata + GetSyncInterval(m_system.GetSystemTimers());
m_system.GetSerialInterface().ScheduleEvent(m_device_number, num_cycles); m_system.GetSerialInterface().ScheduleEvent(m_device_number, num_cycles);

View File

@ -95,7 +95,7 @@ static size_t s_state_writes_in_queue;
static std::condition_variable s_state_write_queue_is_empty; static std::condition_variable s_state_write_queue_is_empty;
// Don't forget to increase this after doing changes on the savestate system // Don't forget to increase this after doing changes on the savestate system
constexpr u32 STATE_VERSION = 175; // Last changed in PR 13751 constexpr u32 STATE_VERSION = 176; // Last changed in PR 14110
// Increase this if the StateExtendedHeader definition changes // Increase this if the StateExtendedHeader definition changes
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217 constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217