From 01b44837f4f970246e800d46e57e4382eb17d065 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 15 Jun 2024 18:15:45 +0200 Subject: [PATCH 1/2] Android: Track whether app is in foreground --- .../dolphinemu/DolphinApplication.java | 2 + .../dolphinemu/utils/ActivityTracker.kt | 41 +++++++++++++++++++ Source/Android/jni/ActivityTracker.cpp | 21 ++++++++++ Source/Android/jni/CMakeLists.txt | 3 +- Source/Core/Core/AchievementManager.cpp | 4 ++ Source/Core/Core/AchievementManager.h | 3 ++ 6 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ActivityTracker.kt create mode 100644 Source/Android/jni/ActivityTracker.cpp diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java index c197c002b0..e0816e408b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java @@ -6,6 +6,7 @@ import android.app.Application; import android.content.Context; import android.hardware.usb.UsbManager; +import org.dolphinemu.dolphinemu.utils.ActivityTracker; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.Java_GCAdapter; import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter; @@ -20,6 +21,7 @@ public class DolphinApplication extends Application { super.onCreate(); application = this; + registerActivityLifecycleCallbacks(new ActivityTracker()); VolleyUtil.init(getApplicationContext()); System.loadLibrary("main"); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ActivityTracker.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ActivityTracker.kt new file mode 100644 index 0000000000..b3a6a5d91a --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ActivityTracker.kt @@ -0,0 +1,41 @@ +package org.dolphinemu.dolphinemu.utils + +import android.app.Activity +import android.app.Application.ActivityLifecycleCallbacks +import android.os.Bundle + +class ActivityTracker : ActivityLifecycleCallbacks { + val resumedActivities = HashSet() + var backgroundExecutionAllowed = false + + override fun onActivityCreated(activity: Activity, bundle: Bundle?) {} + + override fun onActivityStarted(activity: Activity) {} + + override fun onActivityResumed(activity: Activity) { + resumedActivities.add(activity) + if (!backgroundExecutionAllowed && !resumedActivities.isEmpty()) { + backgroundExecutionAllowed = true + setBackgroundExecutionAllowedNative(true) + } + } + + override fun onActivityPaused(activity: Activity) { + resumedActivities.remove(activity) + if (backgroundExecutionAllowed && resumedActivities.isEmpty()) { + backgroundExecutionAllowed = false + setBackgroundExecutionAllowedNative(false) + } + } + + override fun onActivityStopped(activity: Activity) {} + + override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {} + + override fun onActivityDestroyed(activity: Activity) {} + + companion object { + @JvmStatic + external fun setBackgroundExecutionAllowedNative(allowed: Boolean) + } +} diff --git a/Source/Android/jni/ActivityTracker.cpp b/Source/Android/jni/ActivityTracker.cpp new file mode 100644 index 0000000000..b871890576 --- /dev/null +++ b/Source/Android/jni/ActivityTracker.cpp @@ -0,0 +1,21 @@ +// Copyright 2024 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "Common/Logging/Log.h" +#include "Core/AchievementManager.h" + +extern "C" { + +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_utils_ActivityTracker_setBackgroundExecutionAllowedNative( + JNIEnv*, jclass, jboolean allowed) +{ + // This is called with allowed == false when the app goes into the background. + // We use this to stop continuously running background threads so we don't waste battery. + + INFO_LOG_FMT(CORE, "SetBackgroundExecutionAllowed {}", allowed); + AchievementManager::GetInstance().SetBackgroundExecutionAllowed(allowed); +} +} diff --git a/Source/Android/jni/CMakeLists.txt b/Source/Android/jni/CMakeLists.txt index ab2acc7063..7ddae94c07 100644 --- a/Source/Android/jni/CMakeLists.txt +++ b/Source/Android/jni/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(main SHARED + ActivityTracker.cpp Cheats/ARCheat.cpp Cheats/Cheats.h Cheats/GeckoCheat.cpp @@ -11,6 +12,7 @@ add_library(main SHARED GameList/GameFile.cpp GameList/GameFile.h GameList/GameFileCache.cpp + GpuDriver.cpp Host.cpp Host.h InfinityConfig.cpp @@ -32,7 +34,6 @@ add_library(main SHARED RiivolutionPatches.cpp SkylanderConfig.cpp WiiUtils.cpp - GpuDriver.cpp ) target_link_libraries(main diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index c85f6f0f59..8227df450b 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -158,6 +158,10 @@ bool AchievementManager::IsGameLoaded() const return game_info && game_info->id != 0; } +void AchievementManager::SetBackgroundExecutionAllowed(bool allowed) +{ +} + void AchievementManager::FetchPlayerBadge() { FetchBadge(&m_player_badge, RC_IMAGE_TYPE_USER, diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index c26d3bcf96..935b4c0948 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -96,6 +96,7 @@ public: bool HasAPIToken() const; void LoadGame(const std::string& file_path, const DiscIO::Volume* volume); bool IsGameLoaded() const; + void SetBackgroundExecutionAllowed(bool allowed); void FetchPlayerBadge(); void FetchGameBadges(); @@ -239,6 +240,8 @@ public: constexpr void LoadGame(const std::string&, const DiscIO::Volume*) {} + constexpr void SetBackgroundExecutionAllowed(bool allowed) {} + constexpr void DoFrame() {} constexpr void CloseGame() {} From 33081184bbfbd43390b16d23ef630ac8a5648851 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Thu, 13 Jun 2024 19:58:18 -0400 Subject: [PATCH 2/2] Handle Pausing in AchievementManager There are two pieces of functionality to be added here. One, we want to disallow pausing too frequently, as it may be used as an artificial slowdown. This is handled within the client, which can tell us if a pause is allowed. Two, we want to call rc_client_idle on a periodic basis so the connection with the server can be maintained even while the emulator is paused. --- Source/Core/Core/AchievementManager.cpp | 60 ++++++++++++++++++++++++- Source/Core/Core/AchievementManager.h | 4 ++ Source/Core/Core/Core.cpp | 12 ++++- Source/Core/Core/Core.h | 3 +- 4 files changed, 74 insertions(+), 5 deletions(-) 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();