From d9dd21073926b12df1cbabc92d9db564887186a3 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 25 Dec 2017 13:50:29 +0100 Subject: MainAndroid: Allow specifying path for savestates --- .../main/java/org/dolphinemu/dolphinemu/NativeLibrary.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'Source/Android/app/src/main/java') 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 -- cgit v1.2.3 From a81cbf60fb14dc5b7e213f2e847c51fe5026c249 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 25 Dec 2017 13:57:31 +0100 Subject: MainAndroid: Add wrapper for Core::IsRunning --- .../app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'Source/Android/app/src/main/java') 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 -- cgit v1.2.3 From 87957fadddfa95386f46aea6baac8ad65a92c7d5 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Tue, 26 Dec 2017 22:08:44 +0100 Subject: MainAndroid: Allow specifying savestate to load at boot --- .../app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'Source/Android/app/src/main/java') 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(); -- cgit v1.2.3 From f9a05119370dbc3a261cda61d97b17c261db7b0b Mon Sep 17 00:00:00 2001 From: JosJuice Date: Tue, 26 Dec 2017 22:22:42 +0100 Subject: MainAndroid: Optionally wait for savestate to finish writing --- .../main/java/org/dolphinemu/dolphinemu/NativeLibrary.java | 8 ++++++-- .../dolphinemu/activities/EmulationActivity.java | 14 +++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'Source/Android/app/src/main/java') 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 -- cgit v1.2.3 From 82a6701f795a883d80c3b0167b4d3f3e224a5cd8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 28 Dec 2017 10:13:53 +0100 Subject: Optionally delete savestate that gets loaded at boot --- .../app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Source/Android/app/src/main/java') 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); -- cgit v1.2.3 From 136e835fb489f2b0412c760739dd5c668055fb56 Mon Sep 17 00:00:00 2001 From: mahdihijazi Date: Mon, 25 Dec 2017 01:05:37 +0100 Subject: [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. --- .../dolphinemu/activities/EmulationActivity.java | 67 +++++++++++++++---- .../dolphinemu/fragments/EmulationFragment.java | 76 +++++++++++++++++++--- 2 files changed, 121 insertions(+), 22 deletions(-) (limited to 'Source/Android/app/src/main/java') 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 + } + } } -- cgit v1.2.3