diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index c4cd632221b3..959cf3b45d33 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -44,7 +44,10 @@ void AchievementManager::Init() LoadDefaultBadges(); if (!m_client && Config::Get(Config::RA_ENABLED)) { - m_client = rc_client_create(MemoryPeeker, Request); + { + std::lock_guard lg{m_client_lock}; + m_client = rc_client_create(MemoryPeeker, Request); + } std::string host_url = Config::Get(Config::RA_HOST_URL); if (!host_url.empty()) rc_client_set_host(m_client, host_url.c_str()); @@ -239,6 +242,38 @@ void AchievementManager::DoFrame() } } +bool AchievementManager::CanPause() +{ + u32 frames_to_next_pause = 0; + bool can_pause = rc_client_can_pause(m_client, &frames_to_next_pause); + if (!can_pause) + { + OSD::AddMessage("Cannot spam pausing in hardcore mode.", OSD::Duration::VERY_LONG, + OSD::Color::RED); + OSD::AddMessage( + fmt::format("Can pause in {} seconds.", static_cast(frames_to_next_pause) / 60), + OSD::Duration::VERY_LONG, OSD::Color::RED); + } + return can_pause; +} + +void AchievementManager::DoIdle() +{ + m_idle_thread = std::make_unique([]() { + while (Core::GetState(*AchievementManager::GetInstance().m_system) == Core::State::Paused) + { + Common::SleepCurrentThread(1000); + std::lock_guard lg{AchievementManager::GetInstance().m_client_lock}; + auto* client = AchievementManager::GetInstance().m_client; + if (!client || !AchievementManager::GetInstance().IsGameLoaded()) + return; + rc_client_idle(client); + INFO_LOG_FMT(ACHIEVEMENTS, "Beep"); + } + }); + m_idle_thread->detach(); +} + std::recursive_mutex& AchievementManager::GetLock() { return m_lock; @@ -426,8 +461,8 @@ void AchievementManager::CloseGame() void AchievementManager::Logout() { { - std::lock_guard lg{m_lock}; CloseGame(); + std::lock_guard lg{m_lock}; m_player_badge.width = 0; m_player_badge.height = 0; m_player_badge.data.clear(); @@ -443,6 +478,7 @@ void AchievementManager::Shutdown() if (m_client) { CloseGame(); + std::lock_guard lg{m_client_lock}; m_queue.Shutdown(); // DON'T log out - keep those credentials for next run. rc_client_destroy(m_client); diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index a86abeab13c5..54ac1c0ed71f 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -102,6 +102,9 @@ class AchievementManager void DoFrame(); + bool CanPause(); + void DoIdle(); + std::recursive_mutex& GetLock(); void SetHardcoreMode(); bool IsHardcoreModeActive() const; @@ -201,6 +204,7 @@ class AchievementManager std::unordered_map m_locked_badges; RichPresence m_rich_presence; std::chrono::steady_clock::time_point m_last_rp_time = std::chrono::steady_clock::now(); + std::unique_ptr m_idle_thread = nullptr; std::chrono::steady_clock::time_point m_last_progress_message = std::chrono::steady_clock::now(); std::unordered_map m_leaderboard_map; @@ -210,6 +214,7 @@ class AchievementManager Common::WorkQueueThread> m_queue; Common::WorkQueueThread> m_image_queue; mutable std::recursive_mutex m_lock; + mutable std::recursive_mutex m_client_lock; std::recursive_mutex m_filereader_lock; }; // class AchievementManager diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index f28a41f83623..4d0e390c19cd 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -613,7 +613,7 @@ static void EmuThread(Core::System& system, std::unique_ptr boot system.GetPowerPC().SetMode(PowerPC::CoreMode::Interpreter); // Determine the CPU thread function - void (*cpuThreadFunc)(Core::System & system, const std::optional& savestate_path, + void (*cpuThreadFunc)(Core::System& system, const std::optional& savestate_path, bool delete_savestate); if (std::holds_alternative(boot->parameters)) cpuThreadFunc = FifoPlayerThread; @@ -698,7 +698,8 @@ static void EmuThread(Core::System& system, std::unique_ptr boot // Set or get the running state -void SetState(Core::System& system, State state, bool report_state_change) +void SetState(Core::System& system, State state, bool report_state_change, + bool initial_execution_state) { // State cannot be controlled until the CPU Thread is operational if (!IsRunningAndStarted()) @@ -707,11 +708,18 @@ void SetState(Core::System& system, State state, bool report_state_change) switch (state) { case State::Paused: +#ifdef USE_RETRO_ACHIEVEMENTS + if (!initial_execution_state && !AchievementManager::GetInstance().CanPause()) + return; +#endif // USE_RETRO_ACHIEVEMENTS // NOTE: GetState() will return State::Paused immediately, even before anything has // stopped (including the CPU). system.GetCPU().EnableStepping(true); // Break Wiimote::Pause(); ResetRumble(); +#ifdef USE_RETRO_ACHIEVEMENTS + AchievementManager::GetInstance().DoIdle(); +#endif // USE_RETRO_ACHIEVEMENTS break; case State::Running: { diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index 980d336750c5..a9b55dd6a277 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -143,7 +143,8 @@ bool IsHostThread(); bool WantsDeterminism(); // [NOT THREADSAFE] For use by Host only -void SetState(Core::System& system, State state, bool report_state_change = true); +void SetState(Core::System& system, State state, bool report_state_change = true, + bool initial_execution_state = false); State GetState(Core::System& system); void SaveScreenShot();