From d80f9d53fc70e4d4197bea5bfaf1afd63b7f7879 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Wed, 15 Mar 2023 21:53:29 +0100 Subject: [PATCH 1/3] Android: Expose config changed callbacks --- .../settings/model/ConfigChangedCallback.kt | 33 ++++++++++++++ Source/Android/jni/AndroidCommon/IDCache.cpp | 11 +++++ Source/Android/jni/AndroidCommon/IDCache.h | 2 + Source/Android/jni/CMakeLists.txt | 1 + .../jni/Config/ConfigChangedCallback.cpp | 44 +++++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/ConfigChangedCallback.kt create mode 100644 Source/Android/jni/Config/ConfigChangedCallback.cpp diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/ConfigChangedCallback.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/ConfigChangedCallback.kt new file mode 100644 index 0000000000..4415efa9e8 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/ConfigChangedCallback.kt @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.settings.model + +import androidx.annotation.Keep + +/** + * Calls the passed-in Runnable when Dolphin's config changes. + * + * Please note: The Runnable may be called from any thread. + */ +class ConfigChangedCallback(runnable: Runnable) { + @Keep + private var pointer: Long = initialize(runnable) + + /** + * Stops the callback from being called in the future. + */ + fun unregister() { + if (pointer != 0L) { + deinitialize(pointer) + pointer = 0L + } + } + + companion object { + @JvmStatic + private external fun initialize(runnable: Runnable): Long + + @JvmStatic + private external fun deinitialize(pointer: Long) + } +} diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index c3bd435af6..a19423d67c 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -109,6 +109,8 @@ static jclass s_core_device_control_class; static jfieldID s_core_device_control_pointer; static jmethodID s_core_device_control_constructor; +static jmethodID s_runnable_run; + namespace IDCache { JNIEnv* GetEnvForThread() @@ -504,6 +506,11 @@ jmethodID GetCoreDeviceControlConstructor() return s_core_device_control_constructor; } +jmethodID GetRunnableRun() +{ + return s_runnable_run; +} + } // namespace IDCache extern "C" { @@ -709,6 +716,10 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) "(Lorg/dolphinemu/dolphinemu/features/input/model/CoreDevice;J)V"); env->DeleteLocalRef(core_device_control_class); + const jclass runnable_class = env->FindClass("java/lang/Runnable"); + s_runnable_run = env->GetMethodID(runnable_class, "run", "()V"); + env->DeleteLocalRef(runnable_class); + return JNI_VERSION; } diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index eef5a8a085..604b68e38e 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -108,4 +108,6 @@ jclass GetCoreDeviceControlClass(); jfieldID GetCoreDeviceControlPointer(); jmethodID GetCoreDeviceControlConstructor(); +jmethodID GetRunnableRun(); + } // namespace IDCache diff --git a/Source/Android/jni/CMakeLists.txt b/Source/Android/jni/CMakeLists.txt index bb18440b02..ab2acc7063 100644 --- a/Source/Android/jni/CMakeLists.txt +++ b/Source/Android/jni/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(main SHARED Cheats/GraphicsMod.cpp Cheats/GraphicsModGroup.cpp Cheats/PatchCheat.cpp + Config/ConfigChangedCallback.cpp Config/NativeConfig.cpp Config/PostProcessing.cpp GameList/GameFile.cpp diff --git a/Source/Android/jni/Config/ConfigChangedCallback.cpp b/Source/Android/jni/Config/ConfigChangedCallback.cpp new file mode 100644 index 0000000000..2ec8b06541 --- /dev/null +++ b/Source/Android/jni/Config/ConfigChangedCallback.cpp @@ -0,0 +1,44 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "Common/Config/Config.h" +#include "jni/AndroidCommon/AndroidCommon.h" +#include "jni/AndroidCommon/IDCache.h" + +struct ConfigChangedCallbackContext +{ + jobject runnable; + Config::ConfigChangedCallbackID callback_id; +}; + +extern "C" { + +JNIEXPORT jlong JNICALL +Java_org_dolphinemu_dolphinemu_features_settings_model_ConfigChangedCallback_initialize( + JNIEnv* env, jclass, jobject runnable) +{ + auto* context = new ConfigChangedCallbackContext; + + jobject runnable_global = env->NewGlobalRef(runnable); + context->runnable = runnable_global; + context->callback_id = Config::AddConfigChangedCallback([runnable_global] { + IDCache::GetEnvForThread()->CallVoidMethod(runnable_global, IDCache::GetRunnableRun()); + }); + + return reinterpret_cast(context); +} + +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_features_settings_model_ConfigChangedCallback_deinitialize( + JNIEnv* env, jclass, jlong pointer) +{ + auto* context = reinterpret_cast(pointer); + + Config::RemoveConfigChangedCallback(context->callback_id); + env->DeleteGlobalRef(context->runnable); + + delete context; +} +} From 3e7a16f225df1baf94f643e70e6a7ea91dd7ac7a Mon Sep 17 00:00:00 2001 From: JosJuice Date: Wed, 15 Mar 2023 22:12:23 +0100 Subject: [PATCH 2/3] Android: Use config changed callback for tracking recursive scan setting This way the Settings class doesn't contain a hardcoded reference to a specific setting. And Settings.loadSettings no longer calls getBoolean, which is a step towards fixing the crash when recreating EmulationActivity after process death. --- .../features/settings/model/Settings.kt | 9 -------- .../services/GameFileCacheManager.java | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt index 27291718dc..c5c1851e5d 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt @@ -8,7 +8,6 @@ import android.widget.Toast import org.dolphinemu.dolphinemu.NativeLibrary import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.features.input.model.MappingCommon -import org.dolphinemu.dolphinemu.services.GameFileCacheManager import java.io.Closeable class Settings : Closeable { @@ -19,7 +18,6 @@ class Settings : Closeable { private set private var settingsLoaded = false - private var loadedRecursiveIsoPathsValue = false private val isGameSpecific: Boolean get() = !TextUtils.isEmpty(gameId) @@ -41,8 +39,6 @@ class Settings : Closeable { check(!NativeLibrary.IsRunning()) { "Attempted to load game INI while emulating" } NativeConfig.loadGameInis(gameId, revision) } - - loadedRecursiveIsoPathsValue = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean } fun loadSettings(gameId: String, revision: Int, isWii: Boolean) { @@ -65,11 +61,6 @@ class Settings : Closeable { NativeLibrary.ReloadLoggerConfig() NativeLibrary.UpdateGCAdapterScanThread() - - if (loadedRecursiveIsoPathsValue != BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean) { - // Refresh game library - GameFileCacheManager.startRescan() - } } else { // custom game settings if (context != null) { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java index a979ee599f..4c77cfdbc2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java @@ -2,9 +2,14 @@ package org.dolphinemu.dolphinemu.services; +import android.os.Handler; +import android.os.Looper; + import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; +import org.dolphinemu.dolphinemu.features.settings.model.ConfigChangedCallback; import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.model.GameFileCache; import org.dolphinemu.dolphinemu.ui.platform.Platform; @@ -26,6 +31,7 @@ public final class GameFileCacheManager new MutableLiveData<>(new GameFile[]{}); private static boolean sFirstLoadDone = false; private static boolean sRunRescanAfterLoad = false; + private static boolean sRecursiveScanEnabled; private static final ExecutorService sExecutor = Executors.newFixedThreadPool(1); private static final MutableLiveData sLoadInProgress = new MutableLiveData<>(false); @@ -182,6 +188,7 @@ public final class GameFileCacheManager if (!sFirstLoadDone) { sFirstLoadDone = true; + setUpAutomaticRescan(); sGameFileCache.load(); if (sGameFileCache.getSize() != 0) { @@ -258,4 +265,19 @@ public final class GameFileCacheManager sGameFileCache = new GameFileCache(); } } + + private static void setUpAutomaticRescan() + { + sRecursiveScanEnabled = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBoolean(); + new ConfigChangedCallback(() -> + new Handler(Looper.getMainLooper()).post(() -> + { + boolean recursiveScanEnabled = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBoolean(); + if (sRecursiveScanEnabled != recursiveScanEnabled) + { + sRecursiveScanEnabled = recursiveScanEnabled; + startRescan(); + } + })); + } } From 4203632c93f4b73c8fd1159272f08ca022063b2b Mon Sep 17 00:00:00 2001 From: JosJuice Date: Wed, 15 Mar 2023 22:12:39 +0100 Subject: [PATCH 3/3] Android: Improve GameFileCacheManager comments --- .../dolphinemu/services/GameFileCacheManager.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java index 4c77cfdbc2..02b8b15b9f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheManager.java @@ -160,8 +160,8 @@ public final class GameFileCacheManager public static GameFile addOrGet(String gamePath) { - // Common case: The game is in the cache, so just grab it from there. - // (Actually, addOrGet already checks for this case, but we want to avoid calling it if possible + // Common case: The game is in the cache, so just grab it from there. (GameFileCache.addOrGet + // actually already checks for this case, but we want to avoid calling it if possible // because the executor thread may hold a lock on sGameFileCache for extended periods of time.) GameFile[] allGames = sGameFiles.getValue(); for (GameFile game : allGames) @@ -198,6 +198,8 @@ public final class GameFileCacheManager if (sRunRescanAfterLoad) { + // Without this, there will be a short blip where the loading indicator in the GUI disappears + // because neither sLoadInProgress nor sRescanInProgress is true sRescanInProgress.postValue(true); }