Compare commits

...

8 Commits

Author SHA1 Message Date
Jordan Woyak
d811baa704
Merge a2591fcb4f into 0fdf1cc386 2025-11-15 16:22:26 +01:00
JosJuice
0fdf1cc386
Merge pull request #14112 from Simonx22/android/remove-unused-bimap-class
Android: Remove unused BiMap class
2025-11-15 16:22:17 +01:00
JosJuice
999e13b3b3
Merge pull request #14109 from OatmealDome/analytics-deadlock
DolphinAnalytics: Only call ReloadConfig in config changed callback when analytics enabled value changes
2025-11-15 14:44:54 +01:00
Jordan Woyak
a2591fcb4f State: Increase STATE_VERSION. 2025-11-12 17:02:27 -06:00
Jordan Woyak
2c9befa728 HW/GBACore: Adjust joybus interthread communication to use WorkQueueThreadSP. 2025-11-12 17:02:27 -06:00
Simonx22
d1526157df Android: Remove unused BiMap class 2025-11-12 17:26:05 -05:00
Jordan Woyak
e5714c350c Common/WorkQueueThread: Make IsRunning function public. 2025-11-12 01:38:57 -06:00
OatmealDome
df5f351add
DolphinAnalytics: Only call ReloadConfig in config changed callback when analytics enabled value changes
Co-authored-by: Simonx22 <simon@oatmealdome.me>
2025-11-12 00:09:41 -05:00
8 changed files with 102 additions and 157 deletions

View File

@ -1,29 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils;
import java.util.HashMap;
import java.util.Map;
public class BiMap<K, V>
{
private Map<K, V> forward = new HashMap<>();
private Map<V, K> backward = new HashMap<>();
public synchronized void add(K key, V value)
{
forward.put(key, value);
backward.put(value, key);
}
public synchronized V getForward(K key)
{
return forward.get(key);
}
public synchronized K getBackward(V key)
{
return backward.get(key);
}
}

View File

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

View File

@ -57,10 +57,19 @@ void DolphinAnalytics::AndroidSetGetValFunc(std::function<std::string(std::strin
DolphinAnalytics::DolphinAnalytics()
{
m_last_analytics_enabled = Config::Get(Config::MAIN_ANALYTICS_ENABLED);
ReloadConfig();
MakeBaseBuilder();
m_config_changed_callback_id = Config::AddConfigChangedCallback([this] { ReloadConfig(); });
m_config_changed_callback_id = Config::AddConfigChangedCallback([this] {
bool current_analytics_enabled = Config::Get(Config::MAIN_ANALYTICS_ENABLED);
if (m_last_analytics_enabled != current_analytics_enabled)
{
m_last_analytics_enabled = current_analytics_enabled;
ReloadConfig();
}
});
}
DolphinAnalytics::~DolphinAnalytics()
@ -80,7 +89,7 @@ void DolphinAnalytics::ReloadConfig()
// Install the HTTP backend if analytics support is enabled.
std::unique_ptr<Common::AnalyticsReportingBackend> new_backend;
if (Config::Get(Config::MAIN_ANALYTICS_ENABLED))
if (m_last_analytics_enabled)
{
new_backend = std::make_unique<Common::HttpAnalyticsBackend>(ANALYTICS_ENDPOINT);
}

View File

@ -203,4 +203,5 @@ private:
std::mutex m_reporter_mutex;
Common::AnalyticsReporter m_reporter;
Config::ConfigChangedCallbackID m_config_changed_callback_id{};
bool m_last_analytics_enabled = false;
};

View File

@ -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<std::thread>([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<std::mutex> 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<std::mutex> 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<u8> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<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;
}
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<u8> core_state;
core_state.resize(m_core->stateSize(m_core));

View File

@ -6,13 +6,9 @@
#ifdef HAS_LIBMGBA
#include <array>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#define PYCPARSE // Remove static functions from the header
@ -22,6 +18,7 @@
#include <mgba/gba/interface.h>
#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<u8> 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<u8, 6> 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<GBAHostInterface> m_host;
std::unique_ptr<std::thread> m_thread;
bool m_exit_loop = false;
bool m_idle = false;
std::mutex m_queue_mutex;
std::condition_variable m_command_cv;
std::queue<Command> 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<u8> m_response;
// Commands are synchronous. This buffer is used for the command and the response.
std::array<u8, 5> 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<JoybusEvent> m_event_thread;
::Core::System& m_system;
};

View File

@ -92,11 +92,9 @@ int CSIDevice_GBAEmu::RunBuffer(u8* buffer, int request_length)
case NextAction::ReceiveResponse:
{
m_next_action = NextAction::SendCommand;
std::vector<u8> 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<int>(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);

View File

@ -95,7 +95,7 @@ static size_t s_state_writes_in_queue;
static std::condition_variable s_state_write_queue_is_empty;
// 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
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217