Merge pull request #10008 from JosJuice/android-remove-emulationstate

Android: Remove the EmulationState class
This commit is contained in:
Léo Lam 2021-09-22 11:07:19 +02:00 committed by GitHub
commit 0d8ad5f53a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 155 deletions

View File

@ -393,6 +393,8 @@ public final class NativeLibrary
public static native void SurfaceDestroyed(); public static native void SurfaceDestroyed();
public static native boolean HasSurface();
/** /**
* Unpauses emulation from a paused state. * Unpauses emulation from a paused state.
*/ */
@ -408,6 +410,13 @@ public final class NativeLibrary
*/ */
public static native void StopEmulation(); public static native void StopEmulation();
/**
* Ensures that IsRunning will return true from now on until emulation exits.
* (If this is not called, IsRunning will start returning true at some point
* after calling Run.)
*/
public static native void SetIsBooting();
/** /**
* Returns true if emulation is running (or is paused). * Returns true if emulation is running (or is paused).
*/ */
@ -415,6 +424,8 @@ public final class NativeLibrary
public static native boolean IsRunningAndStarted(); public static native boolean IsRunningAndStarted();
public static native boolean IsRunningAndUnpaused();
/** /**
* Enables or disables CPU block profiling * Enables or disables CPU block profiling
* *

View File

@ -32,7 +32,9 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
private InputOverlay mInputOverlay; private InputOverlay mInputOverlay;
private EmulationState mEmulationState; private String[] mGamePaths;
private boolean mRunWhenSurfaceIsValid;
private boolean mLoadPreviousTemporaryState;
private EmulationActivity activity; private EmulationActivity activity;
@ -73,8 +75,7 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
// So this fragment doesn't restart on configuration changes; i.e. rotation. // So this fragment doesn't restart on configuration changes; i.e. rotation.
setRetainInstance(true); setRetainInstance(true);
String[] gamePaths = getArguments().getStringArray(KEY_GAMEPATHS); mGamePaths = getArguments().getStringArray(KEY_GAMEPATHS);
mEmulationState = new EmulationState(gamePaths, getTemporaryStateFilePath());
} }
/** /**
@ -117,14 +118,18 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
public void onResume() public void onResume()
{ {
super.onResume(); super.onResume();
mEmulationState.run(activity.isActivityRecreated()); run(activity.isActivityRecreated());
} }
@Override @Override
public void onPause() public void onPause()
{ {
if (mEmulationState.isRunning() && !NativeLibrary.IsShowingAlertMessage()) if (NativeLibrary.IsRunningAndUnpaused() && !NativeLibrary.IsShowingAlertMessage())
mEmulationState.pause(); {
Log.debug("[EmulationFragment] Pausing emulation.");
NativeLibrary.PauseEmulation();
}
super.onPause(); super.onPause();
} }
@ -173,18 +178,25 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{ {
Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height); Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height);
mEmulationState.newSurface(holder.getSurface()); NativeLibrary.SurfaceChanged(holder.getSurface());
if (mRunWhenSurfaceIsValid)
{
runWithValidSurface();
}
} }
@Override @Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) public void surfaceDestroyed(@NonNull SurfaceHolder holder)
{ {
mEmulationState.clearSurface(); Log.debug("[EmulationFragment] Surface destroyed.");
NativeLibrary.SurfaceDestroyed();
mRunWhenSurfaceIsValid = true;
} }
public void stopEmulation() public void stopEmulation()
{ {
mEmulationState.stop(); Log.debug("[EmulationFragment] Stopping emulation.");
NativeLibrary.StopEmulation();
} }
public void startConfiguringControls() public void startConfiguringControls()
@ -210,173 +222,70 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
return mInputOverlay != null && mInputOverlay.isInEditMode(); return mInputOverlay != null && mInputOverlay.isInEditMode();
} }
private static class EmulationState private void run(boolean isActivityRecreated)
{ {
private enum State if (isActivityRecreated)
{ {
STOPPED, RUNNING, PAUSED if (NativeLibrary.IsRunning())
}
private final String[] mGamePaths;
private State state;
private Surface mSurface;
private boolean mRunWhenSurfaceIsValid;
private boolean loadPreviousTemporaryState;
private final String temporaryStatePath;
EmulationState(String[] gamePaths, String temporaryStatePath)
{
mGamePaths = gamePaths;
this.temporaryStatePath = temporaryStatePath;
// Starting state is stopped.
state = State.STOPPED;
}
// Getters for the current state
public synchronized boolean isStopped()
{
return state == State.STOPPED;
}
public synchronized boolean isPaused()
{
return state == State.PAUSED;
}
public synchronized boolean isRunning()
{
return state == State.RUNNING;
}
// State changing methods
public synchronized void stop()
{
if (state != State.STOPPED)
{ {
Log.debug("[EmulationFragment] Stopping emulation."); mLoadPreviousTemporaryState = false;
state = State.STOPPED; deleteFile(getTemporaryStateFilePath());
NativeLibrary.StopEmulation();
} }
else else
{ {
Log.warning("[EmulationFragment] Stop called while already stopped."); mLoadPreviousTemporaryState = true;
} }
} }
else
public synchronized void pause()
{ {
if (state != State.PAUSED) Log.debug("[EmulationFragment] activity resumed or fresh start");
{ mLoadPreviousTemporaryState = false;
state = State.PAUSED; // activity resumed without being killed or this is the first run
Log.debug("[EmulationFragment] Pausing emulation."); deleteFile(getTemporaryStateFilePath());
NativeLibrary.PauseEmulation();
}
else
{
Log.warning("[EmulationFragment] Pause called while already paused.");
}
} }
public synchronized void run(boolean isActivityRecreated) // If the surface is set, run now. Otherwise, wait for it to get set.
if (NativeLibrary.HasSurface())
{ {
if (isActivityRecreated) runWithValidSurface();
}
else
{
mRunWhenSurfaceIsValid = true;
}
}
private void runWithValidSurface()
{
mRunWhenSurfaceIsValid = false;
if (!NativeLibrary.IsRunning())
{
NativeLibrary.SetIsBooting();
Thread emulationThread = new Thread(() ->
{ {
if (NativeLibrary.IsRunning()) if (mLoadPreviousTemporaryState)
{ {
loadPreviousTemporaryState = false; Log.debug("[EmulationFragment] Starting emulation thread from previous state.");
state = State.PAUSED; NativeLibrary.Run(mGamePaths, getTemporaryStateFilePath(), true);
deleteFile(temporaryStatePath);
} }
else else
{ {
loadPreviousTemporaryState = true; Log.debug("[EmulationFragment] Starting emulation thread.");
NativeLibrary.Run(mGamePaths);
} }
} EmulationActivity.stopIgnoringLaunchRequests();
else }, "NativeEmulation");
{ emulationThread.start();
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)
{
runWithValidSurface();
}
else
{
mRunWhenSurfaceIsValid = true;
}
} }
else
// Surface callbacks
public synchronized void newSurface(Surface surface)
{ {
mSurface = surface; if (!EmulationActivity.getHasUserPausedEmulation() && !NativeLibrary.IsShowingAlertMessage())
if (mRunWhenSurfaceIsValid)
{ {
runWithValidSurface(); Log.debug("[EmulationFragment] Resuming emulation.");
NativeLibrary.UnPauseEmulation();
} }
} }
public synchronized void clearSurface()
{
if (mSurface == null)
{
Log.warning("[EmulationFragment] clearSurface called, but surface already null.");
}
else
{
mSurface = null;
Log.debug("[EmulationFragment] Surface destroyed.");
NativeLibrary.SurfaceDestroyed();
}
}
private void runWithValidSurface()
{
mRunWhenSurfaceIsValid = false;
if (state == State.STOPPED)
{
Thread emulationThread = new Thread(() ->
{
NativeLibrary.SurfaceChanged(mSurface);
if (loadPreviousTemporaryState)
{
Log.debug("[EmulationFragment] Starting emulation thread from previous state.");
NativeLibrary.Run(mGamePaths, temporaryStatePath, true);
}
else
{
Log.debug("[EmulationFragment] Starting emulation thread.");
NativeLibrary.Run(mGamePaths);
}
EmulationActivity.stopIgnoringLaunchRequests();
}, "NativeEmulation");
emulationThread.start();
}
else if (state == State.PAUSED)
{
NativeLibrary.SurfaceChanged(mSurface);
if (!EmulationActivity.getHasUserPausedEmulation() &&
!NativeLibrary.IsShowingAlertMessage())
{
Log.debug("[EmulationFragment] Resuming emulation.");
NativeLibrary.UnPauseEmulation();
}
}
else
{
Log.debug("[EmulationFragment] Bug, run called while already running.");
}
state = State.RUNNING;
}
} }
public void saveTemporaryState() public void saveTemporaryState()

View File

@ -23,6 +23,7 @@
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Event.h" #include "Common/Event.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/Flag.h"
#include "Common/IniFile.h" #include "Common/IniFile.h"
#include "Common/Logging/LogManager.h" #include "Common/Logging/LogManager.h"
#include "Common/MsgHandler.h" #include "Common/MsgHandler.h"
@ -81,6 +82,7 @@ Common::Event s_update_main_frame_event;
std::mutex s_surface_lock; std::mutex s_surface_lock;
bool s_need_nonblocking_alert_msg; bool s_need_nonblocking_alert_msg;
Common::Flag s_is_booting;
bool s_have_wm_user_stop = false; bool s_have_wm_user_stop = false;
bool s_game_metadata_is_valid = false; bool s_game_metadata_is_valid = false;
} // Anonymous namespace } // Anonymous namespace
@ -247,9 +249,14 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulatio
s_update_main_frame_event.Set(); s_update_main_frame_event.Set();
} }
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetIsBooting(JNIEnv*, jclass)
{
s_is_booting.Set();
}
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunning(JNIEnv*, jclass) JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunning(JNIEnv*, jclass)
{ {
return static_cast<jboolean>(Core::IsRunning()); return s_is_booting.IsSet() || static_cast<jboolean>(Core::IsRunning());
} }
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndStarted(JNIEnv*, JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndStarted(JNIEnv*,
@ -258,6 +265,12 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunnin
return static_cast<jboolean>(Core::IsRunningAndStarted()); return static_cast<jboolean>(Core::IsRunningAndStarted());
} }
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndUnpaused(JNIEnv*, jclass)
{
return static_cast<jboolean>(Core::GetState() == Core::State::Running);
}
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent( JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent(
JNIEnv* env, jclass, jstring jDevice, jint Button, jint Action) JNIEnv* env, jclass, jstring jDevice, jint Button, jint Action)
{ {
@ -430,7 +443,25 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChang
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestroyed(JNIEnv*, JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestroyed(JNIEnv*,
jclass) jclass)
{ {
std::lock_guard<std::mutex> guard(s_surface_lock); {
// If emulation continues running without a valid surface, we will probably crash,
// so pause emulation until we get a valid surface again. EmulationFragment handles resuming.
std::unique_lock host_identity_guard(s_host_identity_lock);
while (s_is_booting.IsSet())
{
// Need to wait for boot to finish before we can pause
host_identity_guard.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
host_identity_guard.lock();
}
if (Core::GetState() == Core::State::Running)
Core::SetState(Core::State::Paused);
}
std::lock_guard surface_guard(s_surface_lock);
if (g_renderer) if (g_renderer)
g_renderer->ChangeSurface(nullptr); g_renderer->ChangeSurface(nullptr);
@ -442,6 +473,13 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr
} }
} }
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_HasSurface(JNIEnv*, jclass)
{
std::lock_guard guard(s_surface_lock);
return s_surf ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jfloat JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGameAspectRatio(JNIEnv*, JNIEXPORT jfloat JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGameAspectRatio(JNIEnv*,
jclass) jclass)
{ {
@ -554,6 +592,7 @@ static void Run(JNIEnv* env, const std::vector<std::string>& paths,
} }
} }
s_is_booting.Clear();
s_need_nonblocking_alert_msg = false; s_need_nonblocking_alert_msg = false;
surface_guard.unlock(); surface_guard.unlock();