AchievementManager: Use HookableEvent instead of std::function callbacks

The callback mechanism AchievementManager had until now only supported
one caller registering a callback, and it didn't have any
synchronization. This isn't a problem for DolphinQt, but the PR to add
Android support for RetroAchievements exposes these problems. Let's
replace it with HookableEvent, which can handle all of this.
This commit is contained in:
JosJuice 2025-08-22 21:02:25 +02:00
parent 0c7fe651bb
commit de98c3b96f
6 changed files with 39 additions and 44 deletions

View File

@ -122,16 +122,6 @@ picojson::value AchievementManager::LoadApprovedList()
return temp;
}
void AchievementManager::SetUpdateCallback(UpdateCallback callback)
{
m_update_callback = std::move(callback);
if (!m_update_callback)
m_update_callback = [](UpdatedItems) {};
m_update_callback(UpdatedItems{.all = true});
}
void AchievementManager::Login(const std::string& password)
{
if (!m_client)
@ -354,7 +344,7 @@ void AchievementManager::DoFrame()
{
m_last_rp_time = current_time;
rc_client_get_rich_presence_message(m_client, m_rich_presence.data(), RP_SIZE);
m_update_callback(UpdatedItems{.rich_presence = true});
UpdateEvent::Trigger(UpdatedItems{.rich_presence = true});
if (Config::Get(Config::RA_DISCORD_PRESENCE_ENABLED))
Discord::UpdateDiscordPresence();
}
@ -753,7 +743,7 @@ void AchievementManager::CloseGame()
INFO_LOG_FMT(ACHIEVEMENTS, "Game closed.");
}
m_update_callback(UpdatedItems{.all = true});
UpdateEvent::Trigger(UpdatedItems{.all = true});
}
void AchievementManager::Logout()
@ -767,7 +757,7 @@ void AchievementManager::Logout()
Config::SetBaseOrCurrent(Config::RA_API_TOKEN, "");
}
m_update_callback(UpdatedItems{.all = true});
UpdateEvent::Trigger(UpdatedItems{.all = true});
INFO_LOG_FMT(ACHIEVEMENTS, "Logged out from server.");
}
@ -910,7 +900,7 @@ void AchievementManager::LoginCallback(int result, const char* error_message, rc
{
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to login {} to RetroAchievements server.",
Config::Get(Config::RA_USERNAME));
AchievementManager::GetInstance().m_update_callback({.failed_login_code = result});
UpdateEvent::Trigger({.failed_login_code = result});
return;
}
@ -922,7 +912,7 @@ void AchievementManager::LoginCallback(int result, const char* error_message, rc
if (!user)
{
WARN_LOG_FMT(ACHIEVEMENTS, "Failed to retrieve user information from client.");
AchievementManager::GetInstance().m_update_callback({.failed_login_code = RC_INVALID_STATE});
UpdateEvent::Trigger({.failed_login_code = RC_INVALID_STATE});
return;
}
@ -941,7 +931,7 @@ void AchievementManager::LoginCallback(int result, const char* error_message, rc
INFO_LOG_FMT(ACHIEVEMENTS, "Attempted to login prior user {}; current user is {}.",
user->username, Config::Get(Config::RA_USERNAME));
rc_client_logout(client);
AchievementManager::GetInstance().m_update_callback({.failed_login_code = RC_INVALID_STATE});
UpdateEvent::Trigger({.failed_login_code = RC_INVALID_STATE});
return;
}
}
@ -987,7 +977,7 @@ void AchievementManager::LeaderboardEntriesCallback(int result, const char* erro
if (static_cast<int32_t>(ix) == list->user_index)
leaderboard.player_index = response_entry.rank;
}
AchievementManager::GetInstance().m_update_callback({.leaderboards = {*leaderboard_id}});
UpdateEvent::Trigger({.leaderboards = {*leaderboard_id}});
}
void AchievementManager::LoadGameCallback(int result, const char* error_message,
@ -1030,7 +1020,7 @@ void AchievementManager::LoadGameCallback(int result, const char* error_message,
rc_client_set_read_memory_function(instance.m_client, MemoryPeeker);
instance.FetchGameBadges();
instance.m_system.store(&Core::System::GetInstance(), std::memory_order_release);
instance.m_update_callback({.all = true});
UpdateEvent::Trigger({.all = true});
// Set this to a value that will immediately trigger RP
instance.m_last_rp_time = std::chrono::steady_clock::now() - std::chrono::minutes{2};
@ -1118,8 +1108,7 @@ void AchievementManager::HandleAchievementTriggeredEvent(const rc_client_event_t
(rc_client_get_hardcore_enabled(instance.m_client)) ? OSD::Color::YELLOW :
OSD::Color::CYAN,
&instance.GetAchievementBadge(client_event->achievement->id, false));
AchievementManager::GetInstance().m_update_callback(
UpdatedItems{.achievements = {client_event->achievement->id}});
UpdateEvent::Trigger(UpdatedItems{.achievements = {client_event->achievement->id}});
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
switch (rc_client_raintegration_get_achievement_state(instance.m_client,
client_event->achievement->id))
@ -1168,8 +1157,7 @@ void AchievementManager::HandleLeaderboardSubmittedEvent(const rc_client_event_t
client_event->leaderboard->title),
OSD::Duration::VERY_LONG, OSD::Color::YELLOW);
AchievementManager::GetInstance().FetchBoardInfo(client_event->leaderboard->id);
AchievementManager::GetInstance().m_update_callback(
UpdatedItems{.leaderboards = {client_event->leaderboard->id}});
UpdateEvent::Trigger(UpdatedItems{.leaderboards = {client_event->leaderboard->id}});
}
void AchievementManager::HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t* client_event)
@ -1206,7 +1194,7 @@ void AchievementManager::HandleAchievementChallengeIndicatorShowEvent(
const auto [iter, inserted] = instance.m_active_challenges.insert(client_event->achievement->id);
if (inserted)
instance.m_challenges_updated = true;
AchievementManager::GetInstance().m_update_callback(UpdatedItems{.rich_presence = true});
UpdateEvent::Trigger(UpdatedItems{.rich_presence = true});
}
void AchievementManager::HandleAchievementChallengeIndicatorHideEvent(
@ -1216,7 +1204,7 @@ void AchievementManager::HandleAchievementChallengeIndicatorHideEvent(
const auto removed = instance.m_active_challenges.erase(client_event->achievement->id);
if (removed > 0)
instance.m_challenges_updated = true;
AchievementManager::GetInstance().m_update_callback(UpdatedItems{.rich_presence = true});
UpdateEvent::Trigger(UpdatedItems{.rich_presence = true});
}
void AchievementManager::HandleAchievementProgressIndicatorShowEvent(
@ -1232,8 +1220,7 @@ void AchievementManager::HandleAchievementProgressIndicatorShowEvent(
OSD::Duration::SHORT, OSD::Color::GREEN,
&instance.GetAchievementBadge(client_event->achievement->id, false));
instance.m_last_progress_message = current_time;
AchievementManager::GetInstance().m_update_callback(
UpdatedItems{.achievements = {client_event->achievement->id}});
UpdateEvent::Trigger(UpdatedItems{.achievements = {client_event->achievement->id}});
}
void AchievementManager::HandleGameCompletedEvent(const rc_client_event_t* client_event,
@ -1372,7 +1359,7 @@ void AchievementManager::FetchBadge(AchievementManager::Badge* badge, u32 badge_
{
if (!m_client || !HasAPIToken())
{
m_update_callback(callback_data);
UpdateEvent::Trigger(callback_data);
if (m_display_welcome_message && badge_type == RC_IMAGE_TYPE_GAME)
DisplayWelcomeMessage();
return;
@ -1418,7 +1405,7 @@ void AchievementManager::FetchBadge(AchievementManager::Badge* badge, u32 badge_
"RetroAchievements connection failed on image request.\n URL: {}",
api_request.url);
rc_api_destroy_request(&api_request);
m_update_callback(callback_data);
UpdateEvent::Trigger(callback_data);
return;
}
@ -1451,7 +1438,7 @@ void AchievementManager::FetchBadge(AchievementManager::Badge* badge, u32 badge_
}
*badge = std::move(tmp_badge);
m_update_callback(callback_data);
UpdateEvent::Trigger(callback_data);
if (badge_type == RC_IMAGE_TYPE_ACHIEVEMENT &&
m_active_challenges.contains(*callback_data.achievements.begin()))
{
@ -1527,7 +1514,7 @@ void AchievementManager::LoadIntegrationCallback(int result, const char* error_m
rc_client_raintegration_set_event_handler(instance.m_client, RAIntegrationEventHandler);
rc_client_raintegration_set_write_memory_function(instance.m_client, MemoryPoker);
rc_client_raintegration_set_get_game_name_function(instance.m_client, GameTitleEstimateHandler);
instance.m_dev_menu_callback();
DevMenuUpdateEvent::Trigger();
// TODO: hook up menu and dll event handlers
break;
@ -1549,12 +1536,11 @@ void AchievementManager::LoadIntegrationCallback(int result, const char* error_m
void AchievementManager::RAIntegrationEventHandler(const rc_client_raintegration_event_t* event,
rc_client_t* client)
{
auto& instance = AchievementManager::GetInstance();
switch (event->type)
{
case RC_CLIENT_RAINTEGRATION_EVENT_MENU_CHANGED:
case RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED:
instance.m_dev_menu_callback();
DevMenuUpdateEvent::Trigger();
break;
case RC_CLIENT_RAINTEGRATION_EVENT_PAUSE:
{

View File

@ -29,6 +29,7 @@
#include "Common/CommonTypes.h"
#include "Common/Config/Config.h"
#include "Common/Event.h"
#include "Common/HookableEvent.h"
#include "Common/HttpRequest.h"
#include "Common/JsonUtil.h"
#include "Common/Lazy.h"
@ -119,11 +120,10 @@ public:
bool rich_presence = false;
int failed_login_code = 0;
};
using UpdateCallback = std::function<void(const UpdatedItems&)>;
using UpdateEvent = Common::HookableEvent<"AchievementManagerUpdate", const UpdatedItems&>;
static AchievementManager& GetInstance();
void Init(void* hwnd);
void SetUpdateCallback(UpdateCallback callback);
void Login(const std::string& password);
bool HasAPIToken() const;
void LoadGame(const DiscIO::Volume* volume);
@ -170,12 +170,9 @@ public:
std::vector<std::string> GetActiveLeaderboards() const;
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
using DevMenuUpdateEvent = Common::HookableEvent<"AchievementManagerDevMenuUpdate">;
const rc_client_raintegration_menu_t* GetDevelopmentMenu();
u32 ActivateDevMenuItem(u32 menu_item_id);
void SetDevMenuUpdateCallback(std::function<void(void)> callback)
{
m_dev_menu_callback = callback;
}
bool CheckForModifications() { return rc_client_raintegration_has_modifications(m_client); }
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
@ -267,7 +264,6 @@ private:
rc_client_t* m_client{};
std::atomic<Core::System*> m_system{};
bool m_is_runtime_initialized = false;
UpdateCallback m_update_callback = [](const UpdatedItems&) {};
std::unique_ptr<DiscIO::Volume> m_loading_volume;
Config::ConfigChangedCallbackID m_config_changed_callback_id;
Badge m_default_player_badge;
@ -294,7 +290,6 @@ private:
bool m_dll_found = false;
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
std::function<void(void)> m_dev_menu_callback;
std::vector<u8> m_cloned_memory;
std::recursive_mutex m_memory_lock;
std::string m_title_estimate;

View File

@ -30,12 +30,16 @@ AchievementsWindow::AchievementsWindow(QWidget* parent) : QDialog(parent)
CreateMainLayout();
ConnectWidgets();
AchievementManager::GetInstance().SetUpdateCallback(
m_event_hook = AchievementManager::UpdateEvent::Register(
[this](AchievementManager::UpdatedItems updated_items) {
QueueOnObject(this, [this, updated_items = std::move(updated_items)] {
AchievementsWindow::UpdateData(std::move(updated_items));
});
});
},
"AchievementsWindow");
UpdateData(AchievementManager::UpdatedItems{.all = true});
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this,
[this] { m_settings_widget->UpdateData(RC_OK); });
}

View File

@ -6,6 +6,7 @@
#ifdef USE_RETRO_ACHIEVEMENTS
#include <QDialog>
#include "Common/HookableEvent.h"
#include "Core/AchievementManager.h"
class AchievementHeaderWidget;
@ -35,6 +36,8 @@ private:
AchievementProgressWidget* m_progress_widget;
AchievementLeaderboardWidget* m_leaderboard_widget;
QDialogButtonBox* m_button_box;
Common::EventHook m_event_hook;
};
#endif // USE_RETRO_ACHIEVEMENTS

View File

@ -294,8 +294,8 @@ void MenuBar::AddToolsMenu()
tools_menu->addAction(tr("Achievements"), this, [this] { emit ShowAchievementsWindow(); });
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
m_achievements_dev_menu = tools_menu->addMenu(tr("RetroAchievements Development"));
AchievementManager::GetInstance().SetDevMenuUpdateCallback(
[this] { QueueOnObject(this, [this] { this->UpdateAchievementDevelopmentMenu(); }); });
m_raintegration_event_hook = AchievementManager::DevMenuUpdateEvent::Register(
[this] { QueueOnObject(this, [this] { UpdateAchievementDevelopmentMenu(); }); }, "MenuBar");
m_achievements_dev_menu->menuAction()->setVisible(false);
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
tools_menu->addSeparator();

View File

@ -12,6 +12,9 @@
#include <QPointer>
#include "Common/CommonTypes.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#include "Common/HookableEvent.h"
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
class QMenu;
class ParallelProgressDialog;
@ -299,4 +302,8 @@ private:
QAction* m_jit_register_cache_off;
bool m_game_selected = false;
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
Common::EventHook m_raintegration_event_hook;
#endif // RC_CLIENT_SUPPORTS_RAINTEGRATION
};