Merge pull request #12856 from LillyJadeKatrin/retroachievements-pause-v2

Handle Pausing in AchievementManager
This commit is contained in:
Admiral H. Curtiss 2024-07-04 22:53:04 +02:00 committed by GitHub
commit 5ea3d9fca0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 147 additions and 6 deletions

View File

@ -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");

View File

@ -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<Activity>()
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)
}
}

View File

@ -0,0 +1,21 @@
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <jni.h>
#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);
}
}

View File

@ -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

View File

@ -25,6 +25,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"
@ -47,7 +48,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());
@ -159,6 +163,13 @@ bool AchievementManager::IsGameLoaded() const
return game_info && game_info->id != 0;
}
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()
{
FetchBadge(&m_player_badge, RC_IMAGE_TYPE_USER,
@ -243,6 +254,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<float>(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;
@ -441,8 +500,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();
@ -459,6 +518,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;

View File

@ -96,12 +96,16 @@ 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();
void DoFrame();
bool CanPause();
void DoIdle();
std::recursive_mutex& GetLock();
void SetHardcoreMode();
bool IsHardcoreModeActive() const;
@ -193,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;
@ -239,6 +244,8 @@ public:
constexpr void LoadGame(const std::string&, const DiscIO::Volume*) {}
constexpr void SetBackgroundExecutionAllowed(bool allowed) {}
constexpr void DoFrame() {}
constexpr void CloseGame() {}

View File

@ -353,7 +353,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);
@ -693,7 +693,8 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> 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)
@ -702,11 +703,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:
{

View File

@ -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();