Achievements: Replace recursive mutex with lightweight mutex

wip3-rebase
Stenzek 2 days ago
parent 31a610358f
commit 5684d1b005
No known key found for this signature in database

@ -108,7 +108,7 @@ template<typename... T>
static void ReportRCError(int err, fmt::format_string<T...> fmt, T&&... args);
static void ClearGameInfo();
static bool TryLoggingInWithToken();
static void EnableHardcoreMode(bool display_message, bool display_game_summary);
static void SetHardcoreModeState(bool enabled, bool display_message, bool display_game_summary);
static void OnHardcoreModeChanged(bool enabled, bool display_message, bool display_game_summary);
static bool IsRAIntegrationInitializing();
static void FinishInitialize();
@ -117,7 +117,7 @@ static void BeginLoadGame();
static void UpdateGameSummary();
static void UpdateModeSettings(const Settings& old_config);
static DynamicHeapArray<u8> SaveStateToBuffer();
static void LoadStateFromBuffer(std::span<const u8> data, std::unique_lock<std::recursive_mutex>& lock);
static void LoadStateFromBuffer(std::span<const u8> data, std::unique_lock<Threading::Mutex>& lock);
static bool SaveStateToBuffer(std::span<u8> data);
static std::string GetImageURL(const char* image_name, u32 type);
static void PrefetchNextAchievementBadge();
@ -128,14 +128,17 @@ static void UpdatePrefetchAchievementBadgesOSDMessage();
static TinyString DecryptLoginToken(std::string_view encrypted_token, std::string_view username);
static TinyString EncryptLoginToken(std::string_view token, std::string_view username);
static bool CreateClient(std::unique_lock<std::recursive_mutex>& lock, bool is_temporary_client);
static void DestroyClient(std::unique_lock<std::recursive_mutex>& lock);
static bool CreateClient(std::unique_lock<Threading::Mutex>& lock, bool is_temporary_client);
static void DestroyClient(std::unique_lock<Threading::Mutex>& lock);
static void ClientMessageCallback(const char* message, const rc_client_t* client);
static uint32_t ClientReadMemory(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client);
static void ClientServerCall(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data,
rc_client_t* client);
static rc_api_server_response_t MakeRCAPIServerResponse(s32 status_code, const std::vector<u8>& data);
static void WaitForServerCallsWithYield(std::unique_lock<std::recursive_mutex>& lock);
// NOTE: Should be called **without** lock held.
static void WaitForServerCalls();
static void WaitForServerCallsWithYield(std::unique_lock<Threading::Mutex>& lock);
static void ClientEventHandler(const rc_client_event_t* event, rc_client_t* client);
static void HandleResetEvent(const rc_client_event_t* event);
@ -164,7 +167,7 @@ static void ClientLoadGameCallback(int result, const char* error_message, rc_cli
static void DisplayHardcoreDeferredMessage();
static void DisplayAchievementSummary();
static void UpdateRichPresence(std::unique_lock<std::recursive_mutex>& lock);
static void UpdateRichPresence(std::unique_lock<Threading::Mutex>& lock);
static bool EnsureAchievementsDatabaseOpen(Error* error = nullptr);
static void CloseAchievementsDatabase();
@ -200,7 +203,7 @@ static void SetAchievementPinnedInDatabase(u32 achievement_id, bool pinned);
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
static void BeginLoadRAIntegration();
static void UnloadRAIntegration(std::unique_lock<std::recursive_mutex>& lock);
static void UnloadRAIntegration(std::unique_lock<Threading::Mutex>& lock);
// Just to make things even more annoying, RAIntegration callbacks on a worker thread.
static void RAIntegrationClientLoginWithTokenCallback(int result, const char* error_message, rc_client_t* client,
@ -236,7 +239,7 @@ struct State
bool has_saved_credentials : 1 = false;
bool reload_game_on_reset : 1 = false;
std::recursive_mutex mutex; // large
Threading::Mutex mutex;
std::vector<LeaderboardTrackerIndicator> active_leaderboard_trackers;
std::vector<ActiveChallengeIndicator> active_challenge_indicators;
@ -304,7 +307,7 @@ TinyString Achievements::GameHashToString(const std::optional<GameHash>& hash)
return ret;
}
std::unique_lock<std::recursive_mutex> Achievements::GetLock()
std::unique_lock<Threading::Mutex> Achievements::GetLock()
{
return std::unique_lock(s_state.mutex);
}
@ -489,11 +492,10 @@ bool Achievements::IsActive()
bool Achievements::IsHardcoreModeActive()
{
if (!s_state.client)
return false;
const auto lock = GetLock();
return rc_client_get_hardcore_enabled(s_state.client);
// Does not take the lock here because we only call on the core thread.
// This is because it is called in other places like cheats which can have a reload triggered by HC mode.
DebugAssert(Host::IsOnCoreThread());
return (s_state.client && rc_client_get_hardcore_enabled(s_state.client));
}
bool Achievements::HasActiveGame()
@ -564,7 +566,7 @@ void Achievements::Initialize()
CreateClient(lock, false);
}
bool Achievements::CreateClient(std::unique_lock<std::recursive_mutex>& lock, bool is_temporary_client)
bool Achievements::CreateClient(std::unique_lock<Threading::Mutex>& lock, bool is_temporary_client)
{
Assert(!s_state.client);
@ -641,13 +643,16 @@ void Achievements::FinishInitialize()
Host::OnAchievementsActiveChanged(true);
}
void Achievements::DestroyClient(std::unique_lock<std::recursive_mutex>& lock)
void Achievements::DestroyClient(std::unique_lock<Threading::Mutex>& lock)
{
DebugAssert(IsActive());
WaitForServerCallsWithYield(lock);
lock.unlock();
WaitForServerCalls();
lock.lock();
ClearGameInfo();
DisableHardcoreMode(false, false);
SetHardcoreModeState(false, false, false);
CancelFetchGameListRequest();
CancelFetchAllProgressRequest();
@ -712,10 +717,10 @@ bool Achievements::TryLoggingInWithToken()
void Achievements::UpdateSettings(const Settings& old_config)
{
auto lock = GetLock();
if (g_settings.achievements_enabled != old_config.achievements_enabled)
{
auto lock = GetLock();
// we're done here
if (g_settings.achievements_enabled)
{
@ -731,10 +736,14 @@ void Achievements::UpdateSettings(const Settings& old_config)
return;
}
if (!IsActive())
return;
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
if (g_settings.achievements_use_raintegration != old_config.achievements_use_raintegration)
{
// RAIntegration requires a full client reload?
auto lock = GetLock();
if (IsActive())
DestroyClient(lock);
CreateClient(lock, false);
@ -746,22 +755,34 @@ void Achievements::UpdateSettings(const Settings& old_config)
{
// Enables have to wait for reset, disables can go through immediately.
if (!g_settings.achievements_hardcore_mode)
DisableHardcoreMode(true, true);
{
const auto lock = GetLock();
SetHardcoreModeState(false, true, true);
}
}
if (!g_settings.achievements_leaderboard_trackers)
{
const auto lock = GetLock();
s_state.active_leaderboard_trackers.clear();
}
// remove progress indicator because it won't remove normally
if (g_settings.achievements_progress_indicator_mode == AchievementProgressIndicatorMode::Disabled)
{
const auto lock = GetLock();
s_state.active_progress_indicator.reset();
}
// If a game is active and these settings changed, reload the game to apply them.
// Just unload and reload without destroying the client to preserve hardcore mode.
// NOTE: Can't change spectator mode while game is loaded.
if (HasActiveGame() && (g_settings.achievements_encore_mode != old_config.achievements_encore_mode ||
if (g_settings.achievements_encore_mode != old_config.achievements_encore_mode ||
g_settings.achievements_spectator_mode != old_config.achievements_spectator_mode ||
g_settings.achievements_track_unofficial != old_config.achievements_track_unofficial))
g_settings.achievements_track_unofficial != old_config.achievements_track_unofficial)
{
auto lock = GetLock();
if (HasActiveGame())
{
// Save and restore state to preserve progress.
const DynamicHeapArray<u8> state_data = SaveStateToBuffer();
@ -775,6 +796,7 @@ void Achievements::UpdateSettings(const Settings& old_config)
UpdateModeSettings(old_config);
}
}
}
void Achievements::UpdateModeSettings(const Settings& old_config)
{
@ -893,10 +915,17 @@ rc_api_server_response_t Achievements::MakeRCAPIServerResponse(s32 status_code,
}
}
void Achievements::WaitForServerCallsWithYield(std::unique_lock<std::recursive_mutex>& lock)
void Achievements::WaitForServerCalls()
{
HTTPDownloader::WaitForAllRequestsFromOwnerWithYield(
&s_state, [&lock]() { lock.unlock(); }, [&lock]() { lock.lock(); });
HTTPDownloader::WaitForAllRequestsFromOwner(&s_state);
}
void Achievements::WaitForServerCallsWithYield(std::unique_lock<Threading::Mutex>& lock)
{
DebugAssert(lock.owns_lock());
lock.unlock();
WaitForServerCalls();
lock.lock();
}
void Achievements::IdleUpdate()
@ -1010,7 +1039,7 @@ void Achievements::UpdateGameSummary()
rc_client_get_user_game_summary(s_state.client, &s_state.game_summary);
}
void Achievements::UpdateRichPresence(std::unique_lock<std::recursive_mutex>& lock)
void Achievements::UpdateRichPresence(std::unique_lock<Threading::Mutex>& lock)
{
if (!s_state.has_rich_presence)
return;
@ -1061,7 +1090,7 @@ void Achievements::OnSystemStarting(bool disable_hardcore_mode)
{
// only enable hardcore mode if we're logged in, or waiting for a login response
if (!disable_hardcore_mode && g_settings.achievements_hardcore_mode && IsLoggedInOrLoggingIn())
EnableHardcoreMode(false, false);
SetHardcoreModeState(true, false, false);
}
}
@ -1085,7 +1114,7 @@ void Achievements::OnSystemDestroyed()
if (IsActive())
{
ClearGameInfo();
DisableHardcoreMode(false, false);
SetHardcoreModeState(false, false, false);
}
}
@ -1101,7 +1130,7 @@ void Achievements::OnSystemReset()
{
// This will raise the silly reset event, but we can safely ignore that since we're immediately resetting the client
DEV_LOG("Enabling hardcore mode after reset");
EnableHardcoreMode(true, true);
SetHardcoreModeState(true, true, true);
}
DEV_LOG("Reset client");
@ -1129,9 +1158,11 @@ void Achievements::SetGameHash(const std::optional<GameHash>& hash)
INFO_COLOR_LOG(StrongOrange, "RA Hash: {}", GameHashToString(s_state.game_hash));
// just set the hash if inactive
if (!IsActive() || IsRAIntegrationInitializing())
if (!IsActive())
return;
if (IsRAIntegrationInitializing())
{
DisableHardcoreMode(false, false);
SetHardcoreModeState(false, false, false);
return;
}
@ -1156,7 +1187,6 @@ void Achievements::SetGameHash(const std::optional<GameHash>& hash)
std::optional<Achievements::GameHash> Achievements::GetGameHash()
{
const auto lock = GetLock();
return s_state.game_hash;
}
@ -1165,7 +1195,7 @@ void Achievements::BeginLoadGame()
if (!s_state.game_hash.has_value())
{
// no need to go through ClientLoadGameCallback, just bail out straight away
DisableHardcoreMode(false, false);
SetHardcoreModeState(false, false, false);
return;
}
@ -1186,7 +1216,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
if (was_disc_change)
ClearGameInfo();
DisableHardcoreMode(false, false);
SetHardcoreModeState(false, false, false);
return;
}
else if (result == RC_LOGIN_REQUIRED)
@ -1209,7 +1239,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
if (was_disc_change)
ClearGameInfo();
DisableHardcoreMode(false, false);
SetHardcoreModeState(false, false, false);
return;
}
@ -1220,7 +1250,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
if (was_disc_change)
ClearGameInfo();
DisableHardcoreMode(false, false);
SetHardcoreModeState(false, false, false);
return;
}
@ -1236,7 +1266,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
if (!has_achievements && !has_leaderboards)
{
WARNING_LOG("Game '{}' has no achievements or leaderboards, disabling hardcore mode.", info->title);
DisableHardcoreMode(false, false);
SetHardcoreModeState(false, false, false);
}
s_state.game_id = info->id;
@ -1314,6 +1344,8 @@ void Achievements::DisplayAchievementSummary()
{
if (g_settings.achievements_notifications)
{
const bool hardcore_enabled = rc_client_get_hardcore_enabled(s_state.client);
SmallString summary;
if (s_state.game_summary.num_core_achievements > 0)
{
@ -1327,7 +1359,7 @@ void Achievements::DisplayAchievementSummary()
s_state.game_summary.points_unlocked));
summary.append('\n');
if (IsHardcoreModeActive())
if (hardcore_enabled)
{
summary.append(
TRANSLATE_SV("Achievements", "Hardcore mode is enabled. Cheats and save states are unavailable."));
@ -1343,7 +1375,7 @@ void Achievements::DisplayAchievementSummary()
}
FullscreenUI::AddAchievementNotification("AchievementsSummary",
IsHardcoreModeActive() ? ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME_HC :
hardcore_enabled ? ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME_HC :
ACHIEVEMENT_SUMMARY_NOTIFICATION_TIME,
s_state.game_badge_url, s_state.game_title, std::string(summary),
RA_LOGO_ICON_NAME, FullscreenUI::AchievementNotificationNoteType::Image);
@ -1793,14 +1825,15 @@ void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event)
TRANSLATE_STR("Achievements", "All pending unlock requests have completed."));
}
void Achievements::EnableHardcoreMode(bool display_message, bool display_game_summary)
void Achievements::SetHardcoreModeState(bool enabled, bool display_message, bool display_game_summary)
{
DebugAssert(IsActive());
if (rc_client_get_hardcore_enabled(s_state.client))
if ((rc_client_get_hardcore_enabled(s_state.client) != 0) == enabled)
return;
rc_client_set_hardcore_enabled(s_state.client, true);
OnHardcoreModeChanged(true, display_message, display_game_summary);
rc_client_set_hardcore_enabled(s_state.client, enabled);
OnHardcoreModeChanged(enabled, display_message, display_game_summary);
}
void Achievements::DisableHardcoreMode(bool show_message, bool display_game_summary)
@ -1809,11 +1842,7 @@ void Achievements::DisableHardcoreMode(bool show_message, bool display_game_summ
return;
const auto lock = GetLock();
if (!rc_client_get_hardcore_enabled(s_state.client))
return;
rc_client_set_hardcore_enabled(s_state.client, false);
OnHardcoreModeChanged(false, show_message, display_game_summary);
SetHardcoreModeState(false, show_message, display_game_summary);
}
void Achievements::OnHardcoreModeChanged(bool enabled, bool display_message, bool display_game_summary)
@ -1858,21 +1887,26 @@ void Achievements::OnHardcoreModeChanged(bool enabled, bool display_message, boo
Host::OnAchievementsHardcoreModeChanged(enabled);
}
void Achievements::LoadStateFromBuffer(std::span<const u8> data, std::unique_lock<std::recursive_mutex>& lock)
void Achievements::LoadStateFromBuffer(std::span<const u8> data, std::unique_lock<Threading::Mutex>& lock)
{
// if we're active, make sure we've downloaded and activated all the achievements
// before deserializing, otherwise that state's going to get lost.
// need to check for login requests too, because game load won't be created until login finishes
if (s_state.load_game_request || s_state.login_request)
{
// GetGamePath() can call GetGameID(), lock needs to be released first.
lock.unlock();
// Fallback to game icon if we don't have a cover.
std::string image = System::GetImageForLoadingScreen(System::GetGamePath());
FullscreenUI::OpenOrUpdateLoadingScreen(image.empty() ? s_state.game_badge_url : image,
TRANSLATE_SV("Achievements", "Downloading achievements data..."));
WaitForServerCallsWithYield(lock);
WaitForServerCalls();
FullscreenUI::CloseLoadingScreen();
lock.lock();
}
if (data.empty())
@ -2196,7 +2230,7 @@ u32 Achievements::GetPauseThrottleFrames()
return 0;
const auto lock = GetLock();
if (!IsHardcoreModeActive())
if (!rc_client_get_hardcore_enabled(s_state.client))
return 0;
u32 frames_remaining = 0;
@ -2221,7 +2255,7 @@ void Achievements::Logout()
if (HasActiveGame())
{
ClearGameInfo();
DisableHardcoreMode(false, false);
SetHardcoreModeState(false, false, false);
}
CancelFetchGameListRequest();
@ -3899,7 +3933,7 @@ void Achievements::FinishLoadRAIntegrationOnCoreThread()
FinishInitialize();
}
void Achievements::UnloadRAIntegration(std::unique_lock<std::recursive_mutex>& lock)
void Achievements::UnloadRAIntegration(std::unique_lock<Threading::Mutex>& lock)
{
DebugAssert(s_state.using_raintegration && s_state.client);

@ -17,6 +17,10 @@
class Error;
class ProgressCallback;
namespace Threading {
class Mutex;
}
class StateWrapper;
class CDImage;
@ -63,7 +67,8 @@ private:
};
/// Acquires the achievements lock. Must be held when accessing any achievement state from another thread.
std::unique_lock<std::recursive_mutex> GetLock();
/// The core thread is allowed to **read** achievement state without taking the lock first.
std::unique_lock<Threading::Mutex> GetLock();
/// Converts a game hash to a string for display. If the hash is nullopt, returns "[NO HASH]".
TinyString GameHashToString(const std::optional<GameHash>& hash);
@ -119,6 +124,7 @@ void DisableHardcoreMode(bool show_message, bool display_game_summary);
void ConfirmHardcoreModeDisableAsync(std::string_view trigger, std::function<void(bool)> callback);
/// Returns true if hardcore mode is active, and functionality should be restricted.
/// NOTE: Only safe to call on the core thread.
bool IsHardcoreModeActive();
/// RAIntegration only exists for Windows, so no point checking it on other platforms.

@ -138,6 +138,7 @@ void AddAchievementNotification(std::string key, float duration, std::string ima
u16 min_width = 0, bool small_font = false);
/// Clears all cached state used to render the UI.
/// NOTE: Assumes achievements lock is held.
void ClearAchievementsState();
/// Updates cached data for the last progress update.

@ -11,6 +11,7 @@
#include "common/error.h"
#include "common/log.h"
#include "common/string_util.h"
#include "common/threading.h"
#include "discord_rpc.h"

@ -644,7 +644,11 @@ void FullscreenUI::Shutdown()
s_locals.was_paused_on_quick_menu_open = false;
s_locals.has_pending_window_switch = false;
{
const auto lock = Achievements::GetLock();
ClearAchievementsState();
}
ClearSettingsState();
ClearGameListState();
s_locals.current_time_string = {};

@ -226,8 +226,6 @@ void FullscreenUI::ClearAchievementsState()
// NOTE: can be called on the CPU thread. don't mess with any GPU thread state
// will already be held if we're clearing as a result of achievements shutting down
const auto lock = Achievements::GetLock();
CloseLeaderboard();
s_achievements_locals.notifications = {};
@ -2849,7 +2847,7 @@ void FullscreenUI::DrawLeaderboardsWindow()
RenderShadowedTextClipped(UIStyle.Font, UIStyle.MediumFontSize, UIStyle.NormalFontWeight, summary_bb.Min,
summary_bb.Max, summary_color, text, nullptr, ImVec2(0.0f, 0.0f), 0.0f, &summary_bb);
if (!is_leaderboard_open && !Achievements::IsHardcoreModeActive())
if (!is_leaderboard_open && !rc_client_get_hardcore_enabled(Achievements::GetClient()))
{
const ImRect hardcore_warning_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.MediumFontSize));
top += UIStyle.MediumFontSize + spacing_small;

@ -15,6 +15,7 @@
#include "common/file_system.h"
#include "common/path.h"
#include "common/string_util.h"
#include "common/threading.h"
#include "IconsEmoji.h"
#include "IconsPromptFont.h"

@ -4300,9 +4300,12 @@ bool System::PopulateGameListEntryFromCurrentGame(GameList::Entry* entry, Error*
((s_state.region == ConsoleRegion::NTSC_J) ? DiscRegion::NTSC_J : DiscRegion::PAL));
}
{
const auto lock = Achievements::GetLock();
entry->achievements_game_id = Achievements::GetGameID();
if (const std::optional<Achievements::GameHash> achievements_hash = Achievements::GetGameHash())
entry->achievements_hash = achievements_hash.value();
}
entry->is_runtime_populated = true;
return true;

Loading…
Cancel
Save