[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.
This commit is contained in:
parent
82a6701f79
commit
136e835fb4
|
@ -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);
|
||||
|
||||
if (savedInstanceState == null)
|
||||
{
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue