From d9dd21073926b12df1cbabc92d9db564887186a3 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 25 Dec 2017 13:50:29 +0100 Subject: [PATCH 1/6] MainAndroid: Allow specifying path for savestates --- .../dolphinemu/dolphinemu/NativeLibrary.java | 14 ++++++++++++ Source/Android/jni/MainAndroid.cpp | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 8d193e75ec..d4f6d4cdf6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -297,6 +297,13 @@ public final class NativeLibrary */ public static native void SaveState(int slot); + /** + * Saves a game state to the specified path. + * + * @param path The path to save state to. + */ + public static native void SaveStateAs(String path); + /** * Loads a game state from the slot number. * @@ -304,6 +311,13 @@ public final class NativeLibrary */ public static native void LoadState(int slot); + /** + * Loads a game state from the specified path. + * + * @param path The path to load state from. + */ + public static native void LoadStateAs(String path); + /** * Sets the current working user directory * If not set, it auto-detects a location diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 9fc8781cfa..39b9cfcac3 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -449,9 +449,15 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetFilename( JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveState(JNIEnv* env, jobject obj, jint slot); +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveStateAs(JNIEnv* env, + jobject obj, + jstring path); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JNIEnv* env, jobject obj, jint slot); +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadStateAs(JNIEnv* env, + jobject obj, + jstring path); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_services_DirectoryInitializationService_CreateUserDirectories( JNIEnv* env, jobject obj); @@ -650,6 +656,14 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveState(JN State::Save(slot); } +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveStateAs(JNIEnv* env, + jobject obj, + jstring path) +{ + std::lock_guard guard(s_host_identity_lock); + State::SaveAs(GetJString(env, path)); +} + JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JNIEnv* env, jobject obj, jint slot) @@ -658,6 +672,14 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JN State::Load(slot); } +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadStateAs(JNIEnv* env, + jobject obj, + jstring path) +{ + std::lock_guard guard(s_host_identity_lock); + State::LoadAs(GetJString(env, path)); +} + JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_services_DirectoryInitializationService_SetSysDirectory( JNIEnv* env, jobject obj, jstring jPath) From a81cbf60fb14dc5b7e213f2e847c51fe5026c249 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 25 Dec 2017 13:57:31 +0100 Subject: [PATCH 2/6] MainAndroid: Add wrapper for Core::IsRunning --- .../java/org/dolphinemu/dolphinemu/NativeLibrary.java | 3 +++ Source/Android/jni/MainAndroid.cpp | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index d4f6d4cdf6..b2f4fc96c2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -349,6 +349,9 @@ public final class NativeLibrary /** Stops emulation. */ public static native void StopEmulation(); + /** Returns true if emulation is running (or is paused). */ + public static native boolean IsRunning(); + /** * Enables or disables CPU block profiling * @param enable diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 39b9cfcac3..457b773acb 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -404,6 +404,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_PauseEmulati jobject obj); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulation(JNIEnv* env, jobject obj); +JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunning(JNIEnv* env, + jobject obj); JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent( JNIEnv* env, jobject obj, jstring jDevice, jint Button, jint Action); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadMoveEvent( @@ -503,11 +505,19 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulatio Core::Stop(); s_update_main_frame_event.Set(); // Kick the waiting event } + +JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunning(JNIEnv* env, + jobject obj) +{ + return Core::IsRunning(); +} + JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent( JNIEnv* env, jobject obj, jstring jDevice, jint Button, jint Action) { return ButtonManager::GamepadEvent(GetJString(env, jDevice), Button, Action); } + JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadMoveEvent( JNIEnv* env, jobject obj, jstring jDevice, jint Axis, jfloat Value) { From 87957fadddfa95386f46aea6baac8ad65a92c7d5 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Tue, 26 Dec 2017 22:08:44 +0100 Subject: [PATCH 3/6] MainAndroid: Allow specifying savestate to load at boot --- .../dolphinemu/dolphinemu/NativeLibrary.java | 5 ++++ Source/Android/jni/MainAndroid.cpp | 27 ++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index b2f4fc96c2..35548a4930 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -336,6 +336,11 @@ public final class NativeLibrary */ public static native void Run(String path); + /** + * Begins emulation from the specified savestate. + */ + public static native void Run(String path, String savestatePath); + // Surface Handling public static native void SurfaceChanged(Surface surf); public static native void SurfaceDestroyed(); diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 457b773acb..ab2458c782 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -476,8 +477,11 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteProfileResults(JNIEnv* env, jobject obj); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_CacheClassesAndMethods(JNIEnv* env, jobject obj); -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv* env, jobject obj, - jstring jFile); +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2( + JNIEnv* env, jobject obj, jstring jFile); +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2( + JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChanged(JNIEnv* env, jobject obj, jobject surf); @@ -797,10 +801,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimo WiimoteReal::Refresh(); } -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv* env, jobject obj, - jstring jFile) +static void Run(const std::string& path, std::optional savestate_path = {}) { - const std::string path = GetJString(env, jFile); __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", path.c_str()); // Install our callbacks @@ -817,7 +819,7 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv* // No use running the loop when booting fails s_have_wm_user_stop = false; - if (BootManager::BootCore(BootParameters::GenerateFromFile(path))) + if (BootManager::BootCore(BootParameters::GenerateFromFile(path, savestate_path))) { static constexpr int TIMEOUT = 10000; static constexpr int WAIT_STEP = 25; @@ -848,6 +850,19 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv* } } +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2( + JNIEnv* env, jobject obj, jstring jFile) +{ + Run(GetJString(env, jFile)); +} + +JNIEXPORT void JNICALL +Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2( + JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate) +{ + Run(GetJString(env, jFile), GetJString(env, jSavestate)); +} + #ifdef __cplusplus } #endif From f9a05119370dbc3a261cda61d97b17c261db7b0b Mon Sep 17 00:00:00 2001 From: JosJuice Date: Tue, 26 Dec 2017 22:22:42 +0100 Subject: [PATCH 4/6] MainAndroid: Optionally wait for savestate to finish writing --- .../org/dolphinemu/dolphinemu/NativeLibrary.java | 8 ++++++-- .../dolphinemu/activities/EmulationActivity.java | 14 +++++++------- Source/Android/jni/MainAndroid.cpp | 16 ++++++++++------ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 35548a4930..0d55f9bb12 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -294,15 +294,19 @@ public final class NativeLibrary * Saves a game state to the slot number. * * @param slot The slot location to save state to. + * @param wait If false, returns as early as possible. + * If true, returns once the savestate has been written to disk. */ - public static native void SaveState(int slot); + public static native void SaveState(int slot, boolean wait); /** * Saves a game state to the specified path. * * @param path The path to save state to. + * @param wait If false, returns as early as possible. + * If true, returns once the savestate has been written to disk. */ - public static native void SaveStateAs(String path); + public static native void SaveStateAs(String path, boolean wait); /** * Loads a game state from the slot number. diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index 56873cab76..de9e864914 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -412,7 +412,7 @@ public final class EmulationActivity extends AppCompatActivity // Quick save / load case MENU_ACTION_QUICK_SAVE: - NativeLibrary.SaveState(9); + NativeLibrary.SaveState(9, false); return; case MENU_ACTION_QUICK_LOAD: @@ -436,27 +436,27 @@ public final class EmulationActivity extends AppCompatActivity // Save state slots case MENU_ACTION_SAVE_SLOT1: - NativeLibrary.SaveState(0); + NativeLibrary.SaveState(0, false); return; case MENU_ACTION_SAVE_SLOT2: - NativeLibrary.SaveState(1); + NativeLibrary.SaveState(1, false); return; case MENU_ACTION_SAVE_SLOT3: - NativeLibrary.SaveState(2); + NativeLibrary.SaveState(2, false); return; case MENU_ACTION_SAVE_SLOT4: - NativeLibrary.SaveState(3); + NativeLibrary.SaveState(3, false); return; case MENU_ACTION_SAVE_SLOT5: - NativeLibrary.SaveState(4); + NativeLibrary.SaveState(4, false); return; case MENU_ACTION_SAVE_SLOT6: - NativeLibrary.SaveState(5); + NativeLibrary.SaveState(5, false); return; // Load state slots diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index ab2458c782..c4ab13f59c 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -451,10 +451,12 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetFilename( jstring jFile); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveState(JNIEnv* env, jobject obj, - jint slot); + jint slot, + jboolean wait); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveStateAs(JNIEnv* env, jobject obj, - jstring path); + jstring path, + jboolean wait); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JNIEnv* env, jobject obj, jint slot); @@ -664,18 +666,20 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetConfig( JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveState(JNIEnv* env, jobject obj, - jint slot) + jint slot, + jboolean wait) { std::lock_guard guard(s_host_identity_lock); - State::Save(slot); + State::Save(slot, wait); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveStateAs(JNIEnv* env, jobject obj, - jstring path) + jstring path, + jboolean wait) { std::lock_guard guard(s_host_identity_lock); - State::SaveAs(GetJString(env, path)); + State::SaveAs(GetJString(env, path), wait); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_LoadState(JNIEnv* env, From 82a6701f795a883d80c3b0167b4d3f3e224a5cd8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 28 Dec 2017 10:13:53 +0100 Subject: [PATCH 5/6] Optionally delete savestate that gets loaded at boot --- .../dolphinemu/dolphinemu/NativeLibrary.java | 2 +- Source/Android/jni/MainAndroid.cpp | 18 ++++++++++------- Source/Core/Core/Boot/Boot.h | 1 + Source/Core/Core/Core.cpp | 20 +++++++++++++------ 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 0d55f9bb12..e82de7fa01 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -343,7 +343,7 @@ public final class NativeLibrary /** * Begins emulation from the specified savestate. */ - public static native void Run(String path, String savestatePath); + public static native void Run(String path, String savestatePath, boolean deleteSavestate); // Surface Handling public static native void SurfaceChanged(Surface surf); diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index c4ab13f59c..b1bd33093c 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "ButtonManager.h" @@ -482,8 +483,8 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_CacheClassesAndMethods(JNIEnv* env, JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2( JNIEnv* env, jobject obj, jstring jFile); JNIEXPORT void JNICALL -Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2( - JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate); +Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z( + JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate, jboolean jDeleteSavestate); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChanged(JNIEnv* env, jobject obj, jobject surf); @@ -805,7 +806,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimo WiimoteReal::Refresh(); } -static void Run(const std::string& path, std::optional savestate_path = {}) +static void Run(const std::string& path, std::optional savestate_path = {}, + bool delete_savestate = false) { __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", path.c_str()); @@ -823,7 +825,9 @@ static void Run(const std::string& path, std::optional savestate_pa // No use running the loop when booting fails s_have_wm_user_stop = false; - if (BootManager::BootCore(BootParameters::GenerateFromFile(path, savestate_path))) + std::unique_ptr boot = BootParameters::GenerateFromFile(path, savestate_path); + boot->delete_savestate = delete_savestate; + if (BootManager::BootCore(std::move(boot))) { static constexpr int TIMEOUT = 10000; static constexpr int WAIT_STEP = 25; @@ -861,10 +865,10 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_l } JNIEXPORT void JNICALL -Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2( - JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate) +Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2Ljava_lang_String_2Z( + JNIEnv* env, jobject obj, jstring jFile, jstring jSavestate, jboolean jDeleteSavestate) { - Run(GetJString(env, jFile), GetJString(env, jSavestate)); + Run(GetJString(env, jFile), GetJString(env, jSavestate), jDeleteSavestate); } #ifdef __cplusplus diff --git a/Source/Core/Core/Boot/Boot.h b/Source/Core/Core/Boot/Boot.h index 7581b2abb8..a6b7391b44 100644 --- a/Source/Core/Core/Boot/Boot.h +++ b/Source/Core/Core/Boot/Boot.h @@ -76,6 +76,7 @@ struct BootParameters Parameters parameters; std::optional savestate_path; + bool delete_savestate = false; }; class CBoot diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 1d77f4e2fc..500885ffbb 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -318,7 +318,7 @@ static void CPUSetInitialExecutionState() } // Create the CPU thread, which is a CPU + Video thread in Single Core mode. -static void CpuThread(const std::optional& savestate_path) +static void CpuThread(const std::optional& savestate_path, bool delete_savestate) { DeclareAsCPUThread(); @@ -342,7 +342,13 @@ static void CpuThread(const std::optional& savestate_path) EMM::InstallExceptionHandler(); // Let's run under memory watch if (savestate_path) - QueueHostJob([&savestate_path] { ::State::LoadAs(*savestate_path); }); + { + QueueHostJob([&savestate_path, delete_savestate] { + ::State::LoadAs(*savestate_path); + if (delete_savestate) + File::Delete(*savestate_path); + }); + } s_is_started = true; CPUSetInitialExecutionState(); @@ -380,7 +386,8 @@ static void CpuThread(const std::optional& savestate_path) EMM::UninstallExceptionHandler(); } -static void FifoPlayerThread(const std::optional& savestate_path) +static void FifoPlayerThread(const std::optional& savestate_path, + bool delete_savestate) { DeclareAsCPUThread(); const SConfig& _CoreParameter = SConfig::GetInstance(); @@ -517,6 +524,7 @@ static void EmuThread(std::unique_ptr boot) } const std::optional savestate_path = boot->savestate_path; + const bool delete_savestate = boot->delete_savestate; // Load and Init Wiimotes - only if we are booting in Wii mode if (core_parameter.bWii && !SConfig::GetInstance().m_bt_passthrough_enabled) @@ -556,7 +564,7 @@ static void EmuThread(std::unique_ptr boot) PowerPC::SetMode(PowerPC::CoreMode::Interpreter); // Determine the CPU thread function - void (*cpuThreadFunc)(const std::optional& savestate_path); + void (*cpuThreadFunc)(const std::optional& savestate_path, bool delete_savestate); if (std::holds_alternative(boot->parameters)) cpuThreadFunc = FifoPlayerThread; else @@ -597,7 +605,7 @@ static void EmuThread(std::unique_ptr boot) Host_Message(WM_USER_CREATE); // Spawn the CPU thread - s_cpu_thread = std::thread(cpuThreadFunc, savestate_path); + s_cpu_thread = std::thread(cpuThreadFunc, savestate_path, delete_savestate); // become the GPU thread Fifo::RunGpuLoop(); @@ -615,7 +623,7 @@ static void EmuThread(std::unique_ptr boot) Common::SetCurrentThreadName("Emuthread - Idle"); // Spawn the CPU+GPU thread - s_cpu_thread = std::thread(cpuThreadFunc, savestate_path); + s_cpu_thread = std::thread(cpuThreadFunc, savestate_path, delete_savestate); while (CPU::GetState() != CPU::State::PowerDown) { From 136e835fb489f2b0412c760739dd5c668055fb56 Mon Sep 17 00:00:00 2001 From: mahdihijazi Date: Mon, 25 Dec 2017 01:05:37 +0100 Subject: [PATCH 6/6] [Android] Support restore emulator state after the emulation screen is killed 1) Most of the times the native heap is kept around even after the activity is killed, we can ask the native code if it is still running and resume the emulation if that is the case. 2) In case the native heap is freed and the emulation can't resume we used a temporary state to load on the game boot. I couldn't find a way to test this, if you want to test this schnario, add this block to EmulationFragment. public void onDestroy() { stopEmulation(); super.onDestroy(); } onDestroy is only called if the acivity killed by the OS and not be rotation change whihch in this case will make sure to kill the emulation and start again when the activiy is re-created. --- .../activities/EmulationActivity.java | 67 ++++++++++++---- .../fragments/EmulationFragment.java | 76 +++++++++++++++++-- 2 files changed, 121 insertions(+), 22 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index de9e864914..80972f0c7f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -2,7 +2,6 @@ package org.dolphinemu.dolphinemu.activities; import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.hardware.usb.UsbManager; @@ -42,7 +41,6 @@ import org.dolphinemu.dolphinemu.utils.Animations; import org.dolphinemu.dolphinemu.utils.ControllerMappingHelper; import org.dolphinemu.dolphinemu.utils.Java_GCAdapter; import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter; -import org.dolphinemu.dolphinemu.utils.Log; import java.lang.annotation.Retention; import java.util.List; @@ -68,8 +66,15 @@ public final class EmulationActivity extends AppCompatActivity private static boolean sIsGameCubeGame; + private boolean activityRecreated; private String mScreenPath; private String mSelectedTitle; + private String mPath; + + public static final String EXTRA_SELECTED_GAME = "SelectedGame"; + public static final String EXTRA_SELECTED_TITLE = "SelectedTitle"; + public static final String EXTRA_SCREEN_PATH = "ScreenPath"; + public static final String EXTRA_GRID_POSITION = "GridPosition"; @Retention(SOURCE) @IntDef({MENU_ACTION_EDIT_CONTROLS_PLACEMENT, MENU_ACTION_TOGGLE_CONTROLS, MENU_ACTION_ADJUST_SCALE, @@ -138,10 +143,10 @@ public final class EmulationActivity extends AppCompatActivity { Intent launcher = new Intent(activity, EmulationActivity.class); - launcher.putExtra("SelectedGame", path); - launcher.putExtra("SelectedTitle", title); - launcher.putExtra("ScreenPath", screenshotPath); - launcher.putExtra("GridPosition", position); + launcher.putExtra(EXTRA_SELECTED_GAME, path); + launcher.putExtra(EXTRA_SELECTED_TITLE, title); + launcher.putExtra(EXTRA_SCREEN_PATH, screenshotPath); + launcher.putExtra(EXTRA_GRID_POSITION, position); ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation( activity, @@ -158,13 +163,23 @@ public final class EmulationActivity extends AppCompatActivity { super.onCreate(savedInstanceState); - // Get params we were passed - Intent gameToEmulate = getIntent(); - String path = gameToEmulate.getStringExtra("SelectedGame"); - sIsGameCubeGame = Platform.fromNativeInt(NativeLibrary.GetPlatform(path)) == Platform.GAMECUBE; - mSelectedTitle = gameToEmulate.getStringExtra("SelectedTitle"); - mScreenPath = gameToEmulate.getStringExtra("ScreenPath"); - mPosition = gameToEmulate.getIntExtra("GridPosition", -1); + if (savedInstanceState == null) + { + // Get params we were passed + Intent gameToEmulate = getIntent(); + mPath = gameToEmulate.getStringExtra(EXTRA_SELECTED_GAME); + mSelectedTitle = gameToEmulate.getStringExtra(EXTRA_SELECTED_TITLE); + mScreenPath = gameToEmulate.getStringExtra(EXTRA_SCREEN_PATH); + mPosition = gameToEmulate.getIntExtra(EXTRA_GRID_POSITION, -1); + activityRecreated = false; + } + else + { + activityRecreated = true; + restoreState(savedInstanceState); + } + + sIsGameCubeGame = Platform.fromNativeInt(NativeLibrary.GetPlatform(mPath)) == Platform.GAMECUBE; mDeviceHasTouchScreen = getPackageManager().hasSystemFeature("android.hardware.touchscreen"); mControllerMappingHelper = new ControllerMappingHelper(); @@ -206,7 +221,7 @@ public final class EmulationActivity extends AppCompatActivity .findFragmentById(R.id.frame_emulation_fragment); if (mEmulationFragment == null) { - mEmulationFragment = EmulationFragment.newInstance(path); + mEmulationFragment = EmulationFragment.newInstance(mPath); getSupportFragmentManager().beginTransaction() .add(R.id.frame_emulation_fragment, mEmulationFragment) .commit(); @@ -256,6 +271,25 @@ public final class EmulationActivity extends AppCompatActivity } + @Override + protected void onSaveInstanceState(Bundle outState) + { + mEmulationFragment.saveTemporaryState(); + outState.putString(EXTRA_SELECTED_GAME, mPath); + outState.putString(EXTRA_SELECTED_TITLE, mSelectedTitle); + outState.putString(EXTRA_SCREEN_PATH, mScreenPath); + outState.putInt(EXTRA_GRID_POSITION, mPosition); + super.onSaveInstanceState(outState); + } + + protected void restoreState(Bundle savedInstanceState) + { + mPath = savedInstanceState.getString(EXTRA_SELECTED_GAME); + mSelectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE); + mScreenPath = savedInstanceState.getString(EXTRA_SCREEN_PATH); + mPosition = savedInstanceState.getInt(EXTRA_GRID_POSITION); + } + @Override public void onBackPressed() { @@ -716,4 +750,9 @@ public final class EmulationActivity extends AppCompatActivity { return sIsGameCubeGame; } + + public boolean isActivityRecreated() + { + return activityRecreated; + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java index 27d32db0d5..1d6a67e6c4 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java @@ -25,6 +25,8 @@ import org.dolphinemu.dolphinemu.services.DirectoryInitializationService.Directo import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver; import org.dolphinemu.dolphinemu.utils.Log; +import java.io.File; + import rx.functions.Action1; public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback @@ -39,6 +41,8 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C private DirectoryStateReceiver directoryStateReceiver; + private EmulationActivity activity; + public static EmulationFragment newInstance(String gamePath) { @@ -57,6 +61,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C if (context instanceof EmulationActivity) { + activity = (EmulationActivity)context; NativeLibrary.setEmulationActivity((EmulationActivity) context); } else @@ -79,7 +84,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); String gamePath = getArguments().getString(KEY_GAMEPATH); - mEmulationState = new EmulationState(gamePath); + mEmulationState = new EmulationState(gamePath, getTemporaryStateFilePath()); } /** @@ -120,7 +125,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C super.onResume(); if (DirectoryInitializationService.areDolphinDirectoriesReady()) { - mEmulationState.run(); + mEmulationState.run(activity.isActivityRecreated()); } else { @@ -155,7 +160,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C directoryStateReceiver = new DirectoryStateReceiver(directoryInitializationState -> { if (directoryInitializationState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) { - mEmulationState.run(); + mEmulationState.run(activity.isActivityRecreated()); } else if (directoryInitializationState == DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) { Toast.makeText(getContext(), R.string.write_permission_needed, Toast.LENGTH_SHORT) .show(); @@ -249,10 +254,13 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C private State state; private Surface mSurface; private boolean mRunWhenSurfaceIsValid; + private boolean loadPreviousTemporaryState; + private final String temporaryStatePath; - EmulationState(String gamePath) + EmulationState(String gamePath, String temporaryStatePath) { mGamePath = gamePath; + this.temporaryStatePath = temporaryStatePath; // Starting state is stopped. state = State.STOPPED; } @@ -280,6 +288,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C { if (state != State.STOPPED) { + Log.debug("[EmulationFragment] Stopping emulation."); state = State.STOPPED; NativeLibrary.StopEmulation(); } @@ -307,8 +316,29 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C } } - public synchronized void run() + public synchronized void run(boolean isActivityRecreated) { + if (isActivityRecreated) + { + if (NativeLibrary.IsRunning()) + { + loadPreviousTemporaryState = false; + state = State.PAUSED; + deleteFile(temporaryStatePath); + } + else + { + loadPreviousTemporaryState = true; + } + } + else + { + Log.debug("[EmulationFragment] activity resumed or fresh start"); + loadPreviousTemporaryState = false; + // activity resumed without being killed or this is the first run + deleteFile(temporaryStatePath); + } + // If the surface is set, run now. Otherwise, wait for it to get set. if (mSurface != null) { @@ -362,12 +392,19 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C mRunWhenSurfaceIsValid = false; if (state == State.STOPPED) { - Log.debug("[EmulationFragment] Starting emulation thread."); - mEmulationThread = new Thread(() -> { NativeLibrary.SurfaceChanged(mSurface); - NativeLibrary.Run(mGamePath); + if (loadPreviousTemporaryState) + { + Log.debug("[EmulationFragment] Starting emulation thread from previous state."); + NativeLibrary.Run(mGamePath, temporaryStatePath, true); + } + else + { + Log.debug("[EmulationFragment] Starting emulation thread."); + NativeLibrary.Run(mGamePath); + } }, "NativeEmulation"); mEmulationThread.start(); @@ -385,4 +422,27 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C state = State.RUNNING; } } + + public void saveTemporaryState() + { + NativeLibrary.SaveStateAs(getTemporaryStateFilePath(), true); + } + + private String getTemporaryStateFilePath() + { + return getContext().getFilesDir() + File.separator + "temp.sav"; + } + + private static void deleteFile(String path) + { + try + { + File file = new File(path); + file.delete(); + } + catch (Exception ex) + { + // fail safely + } + } }