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 9e7c9bb208..0c35a35da0 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 @@ -403,15 +403,13 @@ public final class NativeLibrary */ public static native void StopEmulation(); - public static native boolean IsBooting(); - - public static native void WaitUntilDoneBooting(); - /** * Returns true if emulation is running (or is paused). */ public static native boolean IsRunning(); + public static native boolean IsRunningAndStarted(); + /** * Enables or disables CPU block profiling * @@ -487,7 +485,7 @@ public final class NativeLibrary private static native String GetCurrentTitleDescriptionUnchecked(); public static boolean displayAlertMsg(final String caption, final String text, - final boolean yesNo, final boolean isWarning) + final boolean yesNo, final boolean isWarning, final boolean nonBlocking) { Log.error("[NativeLibrary] Alert: " + text); final EmulationActivity emulationActivity = sEmulationActivity.get(); @@ -498,10 +496,9 @@ public final class NativeLibrary } else { - // AlertMessages while the core is booting will deadlock if WaitUntilDoneBooting is called. - // We also can't use AlertMessages unless we have a non-null activity reference. - // As a fallback, we use toasts instead. - if (emulationActivity == null || IsBooting()) + // We can't use AlertMessages unless we have a non-null activity reference + // and are allowed to block. As a fallback, we can use toasts. + if (emulationActivity == null || nonBlocking) { new Handler(Looper.getMainLooper()).post( () -> Toast.makeText(DolphinApplication.getAppContext(), text, Toast.LENGTH_LONG) 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 4cdb0051e5..cfcd28ea02 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 @@ -330,16 +330,6 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C mSurface = null; Log.debug("[EmulationFragment] Surface destroyed."); - if (state != State.STOPPED && !NativeLibrary.IsShowingAlertMessage()) - { - // In order to avoid dereferencing nullptr, we must not destroy the surface while booting - // the core, so wait here if necessary. An easy (but not 100% consistent) way to reach - // this method while the core is booting is by having landscape orientation lock enabled - // and starting emulation while the phone is in portrait mode, leading to the activity - // being recreated very soon after NativeLibrary.Run has been called. - NativeLibrary.WaitUntilDoneBooting(); - } - NativeLibrary.SurfaceDestroyed(); } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java index fe37981ab5..e9abd1a05b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.java @@ -148,7 +148,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener public void initTouchPointer() { // Check if we have all the data we need yet - boolean aspectRatioAvailable = NativeLibrary.IsRunning() && !NativeLibrary.IsBooting(); + boolean aspectRatioAvailable = NativeLibrary.IsRunningAndStarted(); if (!aspectRatioAvailable || mSurfacePosition == null) return; diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 70d40da5d8..fcfd4ed177 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -222,7 +222,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass native_library_class = env->FindClass("org/dolphinemu/dolphinemu/NativeLibrary"); s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg", - "(Ljava/lang/String;Ljava/lang/String;ZZ)Z"); + "(Ljava/lang/String;Ljava/lang/String;ZZZ)Z"); s_do_rumble = env->GetStaticMethodID(s_native_library_class, "rumble", "(ID)V"); s_update_touch_pointer = env->GetStaticMethodID(s_native_library_class, "updateTouchPointer", "()V"); diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index f852d61590..9e2950b630 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -77,6 +77,12 @@ ANativeWindow* s_surf; // sequentially for access. std::mutex s_host_identity_lock; Common::Event s_update_main_frame_event; + +// This exists to prevent surfaces from being destroyed during the boot process, +// as that can lead to the boot process dereferencing nullptr. +std::mutex s_surface_lock; +bool s_need_nonblocking_alert_msg; + bool s_have_wm_user_stop = false; bool s_game_metadata_is_valid = false; } // Anonymous namespace @@ -159,9 +165,10 @@ static bool MsgAlert(const char* caption, const char* text, bool yes_no, Common: JNIEnv* env = IDCache::GetEnvForThread(); // Execute the Java method. - jboolean result = env->CallStaticBooleanMethod( - IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertMsg(), ToJString(env, caption), - ToJString(env, text), yes_no ? JNI_TRUE : JNI_FALSE, style == Common::MsgType::Warning); + jboolean result = + env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertMsg(), + ToJString(env, caption), ToJString(env, text), yes_no, + style == Common::MsgType::Warning, s_need_nonblocking_alert_msg); return result != JNI_FALSE; } @@ -216,20 +223,15 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulatio s_update_main_frame_event.Set(); } -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsBooting(JNIEnv*, jclass) -{ - return static_cast(Core::IsBooting()); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WaitUntilDoneBooting(JNIEnv*, - jclass) -{ - Core::WaitUntilDoneBooting(); -} - JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunning(JNIEnv*, jclass) { - return Core::IsRunning(); + return static_cast(Core::IsRunning()); +} + +JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndStarted(JNIEnv*, + jclass) +{ + return static_cast(Core::IsRunningAndStarted()); } JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent( @@ -391,6 +393,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChang jclass, jobject surf) { + std::lock_guard guard(s_surface_lock); + s_surf = ANativeWindow_fromSurface(env, surf); if (s_surf == nullptr) __android_log_print(ANDROID_LOG_ERROR, DOLPHIN_TAG, "Error: Surface is null."); @@ -402,6 +406,8 @@ 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 (g_renderer) g_renderer->ChangeSurface(nullptr); @@ -480,7 +486,7 @@ static void Run(JNIEnv* env, const std::vector& paths, ASSERT(!paths.empty()); __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", paths[0].c_str()); - std::unique_lock guard(s_host_identity_lock); + std::unique_lock host_identity_guard(s_host_identity_lock); WiimoteReal::InitAdapterClass(); @@ -493,24 +499,41 @@ static void Run(JNIEnv* env, const std::vector& paths, WindowSystemInfo wsi(WindowSystemType::Android, nullptr, s_surf, s_surf); wsi.render_surface_scale = GetRenderSurfaceScale(env); - // No use running the loop when booting fails - if (BootManager::BootCore(std::move(boot), wsi)) + s_need_nonblocking_alert_msg = true; + std::unique_lock surface_guard(s_surface_lock); + + bool successful_boot = BootManager::BootCore(std::move(boot), wsi); + if (successful_boot) { ButtonManager::Init(SConfig::GetInstance().GetGameID()); + static constexpr int TIMEOUT = 10000; static constexpr int WAIT_STEP = 25; int time_waited = 0; // A Core::CORE_ERROR state would be helpful here. - while (!Core::IsRunning() && time_waited < TIMEOUT && !s_have_wm_user_stop) + while (!Core::IsRunningAndStarted()) { + if (time_waited >= TIMEOUT || s_have_wm_user_stop) + { + successful_boot = false; + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_STEP)); time_waited += WAIT_STEP; } - while (Core::IsRunning()) + } + + s_need_nonblocking_alert_msg = false; + surface_guard.unlock(); + + if (successful_boot) + { + while (Core::IsRunningAndStarted()) { - guard.unlock(); + host_identity_guard.unlock(); s_update_main_frame_event.Wait(); - guard.lock(); + host_identity_guard.lock(); Core::HostDispatchJobs(); } } @@ -518,7 +541,7 @@ static void Run(JNIEnv* env, const std::vector& paths, s_game_metadata_is_valid = false; Core::Shutdown(); ButtonManager::Shutdown(); - guard.unlock(); + host_identity_guard.unlock(); if (s_surf) { diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 14b7660314..b4e15161f0 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -102,7 +102,6 @@ static bool s_is_stopping = false; static bool s_hardware_initialized = false; static bool s_is_started = false; static Common::Flag s_is_booting; -static Common::Event s_done_booting; static std::thread s_emu_thread; static StateChangedCallbackFunc s_on_state_changed_callback; @@ -175,11 +174,6 @@ void DisplayMessage(std::string message, int time_in_ms) OSD::AddMessage(std::move(message), time_in_ms); } -bool IsBooting() -{ - return s_is_booting.IsSet() || !s_hardware_initialized; -} - bool IsRunning() { return (GetState() != State::Uninitialized || s_hardware_initialized) && !s_is_stopping; @@ -249,7 +243,6 @@ bool Init(std::unique_ptr boot, const WindowSystemInfo& wsi) g_video_backend->PrepareWindow(prepared_wsi); // Start the emu thread - s_done_booting.Reset(); s_is_booting.Set(); s_emu_thread = std::thread(EmuThread, std::move(boot), prepared_wsi); return true; @@ -435,7 +428,6 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi s_on_state_changed_callback(State::Starting); Common::ScopeGuard flag_guard{[] { s_is_booting.Clear(); - s_done_booting.Set(); s_is_started = false; s_is_stopping = false; s_wants_determinism = false; @@ -568,7 +560,6 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi // The hardware is initialized. s_hardware_initialized = true; s_is_booting.Clear(); - s_done_booting.Set(); // Set execution state to known values (CPU/FIFO/Audio Paused) CPU::Break(); @@ -692,12 +683,6 @@ State GetState() return State::Uninitialized; } -void WaitUntilDoneBooting() -{ - if (IsBooting()) - s_done_booting.Wait(); -} - static std::string GenerateScreenshotFolderPath() { const std::string& gameId = SConfig::GetInstance().GetGameID(); diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index 8f220b1a12..5ea1948a9c 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -99,7 +99,6 @@ void UndeclareAsCPUThread(); std::string StopMessage(bool main_thread, std::string_view message); -bool IsBooting(); bool IsRunning(); bool IsRunningAndStarted(); // is running and the CPU loop has been entered bool IsRunningInCurrentThread(); // this tells us whether we are running in the CPU thread. @@ -111,7 +110,6 @@ bool WantsDeterminism(); // [NOT THREADSAFE] For use by Host only void SetState(State state); State GetState(); -void WaitUntilDoneBooting(); void SaveScreenShot(); void SaveScreenShot(std::string_view name);