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 0ac2463217..123cd6c066 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 @@ -393,6 +393,8 @@ public final class NativeLibrary public static native void SurfaceDestroyed(); + public static native boolean HasSurface(); + /** * Unpauses emulation from a paused state. */ @@ -408,6 +410,13 @@ public final class NativeLibrary */ 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). */ @@ -415,6 +424,8 @@ public final class NativeLibrary public static native boolean IsRunningAndStarted(); + public static native boolean IsRunningAndUnpaused(); + /** * Enables or disables CPU block profiling * 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 7d18c47936..4e9e864137 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 @@ -32,7 +32,9 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C private InputOverlay mInputOverlay; - private EmulationState mEmulationState; + private String[] mGamePaths; + private boolean mRunWhenSurfaceIsValid; + private boolean mLoadPreviousTemporaryState; 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. setRetainInstance(true); - String[] gamePaths = getArguments().getStringArray(KEY_GAMEPATHS); - mEmulationState = new EmulationState(gamePaths, getTemporaryStateFilePath()); + mGamePaths = getArguments().getStringArray(KEY_GAMEPATHS); } /** @@ -117,14 +118,18 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C public void onResume() { super.onResume(); - mEmulationState.run(activity.isActivityRecreated()); + run(activity.isActivityRecreated()); } @Override public void onPause() { - if (mEmulationState.isRunning() && !NativeLibrary.IsShowingAlertMessage()) - mEmulationState.pause(); + if (NativeLibrary.IsRunningAndUnpaused() && !NativeLibrary.IsShowingAlertMessage()) + { + Log.debug("[EmulationFragment] Pausing emulation."); + NativeLibrary.PauseEmulation(); + } + 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) { Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height); - mEmulationState.newSurface(holder.getSurface()); + NativeLibrary.SurfaceChanged(holder.getSurface()); + if (mRunWhenSurfaceIsValid) + { + runWithValidSurface(); + } } @Override public void surfaceDestroyed(@NonNull SurfaceHolder holder) { - mEmulationState.clearSurface(); + Log.debug("[EmulationFragment] Surface destroyed."); + NativeLibrary.SurfaceDestroyed(); + mRunWhenSurfaceIsValid = true; } public void stopEmulation() { - mEmulationState.stop(); + Log.debug("[EmulationFragment] Stopping emulation."); + NativeLibrary.StopEmulation(); } public void startConfiguringControls() @@ -210,173 +222,70 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C return mInputOverlay != null && mInputOverlay.isInEditMode(); } - private static class EmulationState + private void run(boolean isActivityRecreated) { - private enum State + if (isActivityRecreated) { - STOPPED, RUNNING, PAUSED - } - - 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) + if (NativeLibrary.IsRunning()) { - Log.debug("[EmulationFragment] Stopping emulation."); - state = State.STOPPED; - NativeLibrary.StopEmulation(); + mLoadPreviousTemporaryState = false; + deleteFile(getTemporaryStateFilePath()); } else { - Log.warning("[EmulationFragment] Stop called while already stopped."); + mLoadPreviousTemporaryState = true; } } - - public synchronized void pause() + else { - if (state != State.PAUSED) - { - state = State.PAUSED; - Log.debug("[EmulationFragment] Pausing emulation."); - - NativeLibrary.PauseEmulation(); - } - else - { - Log.warning("[EmulationFragment] Pause called while already paused."); - } + Log.debug("[EmulationFragment] activity resumed or fresh start"); + mLoadPreviousTemporaryState = false; + // activity resumed without being killed or this is the first run + deleteFile(getTemporaryStateFilePath()); } - 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; - state = State.PAUSED; - deleteFile(temporaryStatePath); + Log.debug("[EmulationFragment] Starting emulation thread from previous state."); + NativeLibrary.Run(mGamePaths, getTemporaryStateFilePath(), true); } else { - loadPreviousTemporaryState = true; + Log.debug("[EmulationFragment] Starting emulation thread."); + NativeLibrary.Run(mGamePaths); } - } - 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) - { - runWithValidSurface(); - } - else - { - mRunWhenSurfaceIsValid = true; - } + EmulationActivity.stopIgnoringLaunchRequests(); + }, "NativeEmulation"); + emulationThread.start(); } - - // Surface callbacks - public synchronized void newSurface(Surface surface) + else { - mSurface = surface; - if (mRunWhenSurfaceIsValid) + if (!EmulationActivity.getHasUserPausedEmulation() && !NativeLibrary.IsShowingAlertMessage()) { - 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() diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 765bab1bcb..53a87da1bc 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -23,6 +23,7 @@ #include "Common/CommonTypes.h" #include "Common/Event.h" #include "Common/FileUtil.h" +#include "Common/Flag.h" #include "Common/IniFile.h" #include "Common/Logging/LogManager.h" #include "Common/MsgHandler.h" @@ -81,6 +82,7 @@ Common::Event s_update_main_frame_event; std::mutex s_surface_lock; bool s_need_nonblocking_alert_msg; +Common::Flag s_is_booting; bool s_have_wm_user_stop = false; bool s_game_metadata_is_valid = false; } // Anonymous namespace @@ -247,9 +249,14 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulatio 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) { - return static_cast(Core::IsRunning()); + return s_is_booting.IsSet() || static_cast(Core::IsRunning()); } 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(Core::IsRunningAndStarted()); } +JNIEXPORT jboolean JNICALL +Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndUnpaused(JNIEnv*, jclass) +{ + return static_cast(Core::GetState() == Core::State::Running); +} + JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent( 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*, jclass) { - std::lock_guard 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) 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*, jclass) { @@ -554,6 +592,7 @@ static void Run(JNIEnv* env, const std::vector& paths, } } + s_is_booting.Clear(); s_need_nonblocking_alert_msg = false; surface_guard.unlock();