diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 8227df450b..223b13a209 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -24,6 +24,7 @@ #include "Core/Config/AchievementSettings.h" #include "Core/Core.h" #include "Core/HW/Memmap.h" +#include "Core/HW/VideoInterface.h" #include "Core/PowerPC/MMU.h" #include "Core/System.h" #include "DiscIO/Blob.h" @@ -46,7 +47,10 @@ void AchievementManager::Init() LoadDefaultBadges(); if (!m_client && Config::Get(Config::RA_ENABLED)) { - m_client = rc_client_create(MemoryVerifier, Request); + { + std::lock_guard lg{m_lock}; + m_client = rc_client_create(MemoryVerifier, 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()); @@ -160,6 +164,9 @@ bool AchievementManager::IsGameLoaded() const void AchievementManager::SetBackgroundExecutionAllowed(bool allowed) { + m_background_execution_allowed = allowed; + if (allowed && Core::GetState(*AchievementManager::GetInstance().m_system) == Core::State::Paused) + DoIdle(); } void AchievementManager::FetchPlayerBadge() @@ -246,6 +253,54 @@ 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) / + Core::System::GetInstance().GetVideoInterface().GetTargetRefreshRate()), + OSD::Duration::VERY_LONG, OSD::Color::RED); + } + return can_pause; +} + +void AchievementManager::DoIdle() +{ + std::thread([this]() { + while (true) + { + Common::SleepCurrentThread(1000); + { + std::lock_guard lg{m_lock}; + if (!m_system || Core::GetState(*m_system) != Core::State::Paused) + return; + if (!m_background_execution_allowed) + return; + if (!m_client || !IsGameLoaded()) + return; + } + // rc_client_idle peeks at memory to recalculate rich presence and therefore + // needs to be on host or CPU thread to access memory. + Core::QueueHostJob([this](Core::System& system) { + std::lock_guard lg{m_lock}; + if (!m_system || Core::GetState(*m_system) != Core::State::Paused) + return; + if (!m_background_execution_allowed) + return; + if (!m_client || !IsGameLoaded()) + return; + rc_client_idle(m_client); + }); + } + }).detach(); +} + std::recursive_mutex& AchievementManager::GetLock() { return m_lock; @@ -444,8 +499,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(); @@ -462,6 +517,7 @@ void AchievementManager::Shutdown() { CloseGame(); m_queue.Shutdown(); + std::lock_guard lg{m_lock}; // DON'T log out - keep those credentials for next run. rc_client_destroy(m_client); m_client = nullptr; diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 935b4c0948..f8ce568e62 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -103,6 +103,9 @@ public: void DoFrame(); + bool CanPause(); + void DoIdle(); + std::recursive_mutex& GetLock(); void SetHardcoreMode(); bool IsHardcoreModeActive() const; @@ -194,6 +197,7 @@ private: Badge m_default_game_badge; Badge m_default_unlocked_badge; Badge m_default_locked_badge; + std::atomic_bool m_background_execution_allowed = true; Badge m_player_badge; Hash m_game_hash{}; u32 m_game_id = 0; diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index de791d620c..572aeb0dcd 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -358,7 +358,7 @@ static void CPUSetInitialExecutionState(bool force_paused = false) // SetState must be called on the host thread, so we defer it for later. QueueHostJob([force_paused](Core::System& system) { bool paused = SConfig::GetInstance().bBootToPause || force_paused; - SetState(system, paused ? State::Paused : State::Running); + SetState(system, paused ? State::Paused : State::Running, true, true); Host_UpdateDisasmDialog(); Host_UpdateMainFrame(); Host_Message(HostMessageID::WMUserCreate); @@ -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 (s_state.load() != State::Running) @@ -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 cf30cf3866..0b414534a5 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -148,7 +148,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();