Android: Fix rotating EmulationActivity after boot fails

Time for yet another new iteration of working around the
"surface destruction during boot" problem...
This time, the strategy is to use a mutex in MainAndroid.cpp.
This commit is contained in:
JosJuice 2020-11-06 21:22:22 +01:00
parent d06830b274
commit ee52f465b1
7 changed files with 54 additions and 61 deletions

View File

@ -403,15 +403,13 @@ public final class NativeLibrary
*/ */
public static native void StopEmulation(); public static native void StopEmulation();
public static native boolean IsBooting();
public static native void WaitUntilDoneBooting();
/** /**
* Returns true if emulation is running (or is paused). * Returns true if emulation is running (or is paused).
*/ */
public static native boolean IsRunning(); public static native boolean IsRunning();
public static native boolean IsRunningAndStarted();
/** /**
* Enables or disables CPU block profiling * Enables or disables CPU block profiling
* *
@ -487,7 +485,7 @@ public final class NativeLibrary
private static native String GetCurrentTitleDescriptionUnchecked(); private static native String GetCurrentTitleDescriptionUnchecked();
public static boolean displayAlertMsg(final String caption, final String text, 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); Log.error("[NativeLibrary] Alert: " + text);
final EmulationActivity emulationActivity = sEmulationActivity.get(); final EmulationActivity emulationActivity = sEmulationActivity.get();
@ -498,10 +496,9 @@ public final class NativeLibrary
} }
else else
{ {
// AlertMessages while the core is booting will deadlock if WaitUntilDoneBooting is called. // We can't use AlertMessages unless we have a non-null activity reference
// We also can't use AlertMessages unless we have a non-null activity reference. // and are allowed to block. As a fallback, we can use toasts.
// As a fallback, we use toasts instead. if (emulationActivity == null || nonBlocking)
if (emulationActivity == null || IsBooting())
{ {
new Handler(Looper.getMainLooper()).post( new Handler(Looper.getMainLooper()).post(
() -> Toast.makeText(DolphinApplication.getAppContext(), text, Toast.LENGTH_LONG) () -> Toast.makeText(DolphinApplication.getAppContext(), text, Toast.LENGTH_LONG)

View File

@ -330,16 +330,6 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
mSurface = null; mSurface = null;
Log.debug("[EmulationFragment] Surface destroyed."); 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(); NativeLibrary.SurfaceDestroyed();
} }
} }

View File

@ -148,7 +148,7 @@ public final class InputOverlay extends SurfaceView implements OnTouchListener
public void initTouchPointer() public void initTouchPointer()
{ {
// Check if we have all the data we need yet // Check if we have all the data we need yet
boolean aspectRatioAvailable = NativeLibrary.IsRunning() && !NativeLibrary.IsBooting(); boolean aspectRatioAvailable = NativeLibrary.IsRunningAndStarted();
if (!aspectRatioAvailable || mSurfacePosition == null) if (!aspectRatioAvailable || mSurfacePosition == null)
return; return;

View File

@ -222,7 +222,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
const jclass native_library_class = env->FindClass("org/dolphinemu/dolphinemu/NativeLibrary"); const jclass native_library_class = env->FindClass("org/dolphinemu/dolphinemu/NativeLibrary");
s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class)); s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg", 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_do_rumble = env->GetStaticMethodID(s_native_library_class, "rumble", "(ID)V");
s_update_touch_pointer = s_update_touch_pointer =
env->GetStaticMethodID(s_native_library_class, "updateTouchPointer", "()V"); env->GetStaticMethodID(s_native_library_class, "updateTouchPointer", "()V");

View File

@ -77,6 +77,12 @@ ANativeWindow* s_surf;
// sequentially for access. // sequentially for access.
std::mutex s_host_identity_lock; std::mutex s_host_identity_lock;
Common::Event s_update_main_frame_event; 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_have_wm_user_stop = false;
bool s_game_metadata_is_valid = false; bool s_game_metadata_is_valid = false;
} // Anonymous namespace } // Anonymous namespace
@ -159,9 +165,10 @@ static bool MsgAlert(const char* caption, const char* text, bool yes_no, Common:
JNIEnv* env = IDCache::GetEnvForThread(); JNIEnv* env = IDCache::GetEnvForThread();
// Execute the Java method. // Execute the Java method.
jboolean result = env->CallStaticBooleanMethod( jboolean result =
IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertMsg(), ToJString(env, caption), env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertMsg(),
ToJString(env, text), yes_no ? JNI_TRUE : JNI_FALSE, style == Common::MsgType::Warning); ToJString(env, caption), ToJString(env, text), yes_no,
style == Common::MsgType::Warning, s_need_nonblocking_alert_msg);
return result != JNI_FALSE; return result != JNI_FALSE;
} }
@ -216,20 +223,15 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulatio
s_update_main_frame_event.Set(); s_update_main_frame_event.Set();
} }
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsBooting(JNIEnv*, jclass)
{
return static_cast<jboolean>(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) JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunning(JNIEnv*, jclass)
{ {
return Core::IsRunning(); return static_cast<jboolean>(Core::IsRunning());
}
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsRunningAndStarted(JNIEnv*,
jclass)
{
return static_cast<jboolean>(Core::IsRunningAndStarted());
} }
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent( JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadEvent(
@ -391,6 +393,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChang
jclass, jclass,
jobject surf) jobject surf)
{ {
std::lock_guard<std::mutex> guard(s_surface_lock);
s_surf = ANativeWindow_fromSurface(env, surf); s_surf = ANativeWindow_fromSurface(env, surf);
if (s_surf == nullptr) if (s_surf == nullptr)
__android_log_print(ANDROID_LOG_ERROR, DOLPHIN_TAG, "Error: Surface is null."); __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*, JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestroyed(JNIEnv*,
jclass) jclass)
{ {
std::lock_guard<std::mutex> guard(s_surface_lock);
if (g_renderer) if (g_renderer)
g_renderer->ChangeSurface(nullptr); g_renderer->ChangeSurface(nullptr);
@ -480,7 +486,7 @@ static void Run(JNIEnv* env, const std::vector<std::string>& paths,
ASSERT(!paths.empty()); ASSERT(!paths.empty());
__android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", paths[0].c_str()); __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Running : %s", paths[0].c_str());
std::unique_lock<std::mutex> guard(s_host_identity_lock); std::unique_lock<std::mutex> host_identity_guard(s_host_identity_lock);
WiimoteReal::InitAdapterClass(); WiimoteReal::InitAdapterClass();
@ -493,24 +499,41 @@ static void Run(JNIEnv* env, const std::vector<std::string>& paths,
WindowSystemInfo wsi(WindowSystemType::Android, nullptr, s_surf, s_surf); WindowSystemInfo wsi(WindowSystemType::Android, nullptr, s_surf, s_surf);
wsi.render_surface_scale = GetRenderSurfaceScale(env); wsi.render_surface_scale = GetRenderSurfaceScale(env);
// No use running the loop when booting fails s_need_nonblocking_alert_msg = true;
if (BootManager::BootCore(std::move(boot), wsi)) std::unique_lock<std::mutex> surface_guard(s_surface_lock);
bool successful_boot = BootManager::BootCore(std::move(boot), wsi);
if (successful_boot)
{ {
ButtonManager::Init(SConfig::GetInstance().GetGameID()); ButtonManager::Init(SConfig::GetInstance().GetGameID());
static constexpr int TIMEOUT = 10000; static constexpr int TIMEOUT = 10000;
static constexpr int WAIT_STEP = 25; static constexpr int WAIT_STEP = 25;
int time_waited = 0; int time_waited = 0;
// A Core::CORE_ERROR state would be helpful here. // 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)); std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_STEP));
time_waited += WAIT_STEP; time_waited += WAIT_STEP;
} }
while (Core::IsRunning()) }
s_need_nonblocking_alert_msg = false;
surface_guard.unlock();
if (successful_boot)
{ {
guard.unlock(); while (Core::IsRunningAndStarted())
{
host_identity_guard.unlock();
s_update_main_frame_event.Wait(); s_update_main_frame_event.Wait();
guard.lock(); host_identity_guard.lock();
Core::HostDispatchJobs(); Core::HostDispatchJobs();
} }
} }
@ -518,7 +541,7 @@ static void Run(JNIEnv* env, const std::vector<std::string>& paths,
s_game_metadata_is_valid = false; s_game_metadata_is_valid = false;
Core::Shutdown(); Core::Shutdown();
ButtonManager::Shutdown(); ButtonManager::Shutdown();
guard.unlock(); host_identity_guard.unlock();
if (s_surf) if (s_surf)
{ {

View File

@ -102,7 +102,6 @@ static bool s_is_stopping = false;
static bool s_hardware_initialized = false; static bool s_hardware_initialized = false;
static bool s_is_started = false; static bool s_is_started = false;
static Common::Flag s_is_booting; static Common::Flag s_is_booting;
static Common::Event s_done_booting;
static std::thread s_emu_thread; static std::thread s_emu_thread;
static StateChangedCallbackFunc s_on_state_changed_callback; 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); OSD::AddMessage(std::move(message), time_in_ms);
} }
bool IsBooting()
{
return s_is_booting.IsSet() || !s_hardware_initialized;
}
bool IsRunning() bool IsRunning()
{ {
return (GetState() != State::Uninitialized || s_hardware_initialized) && !s_is_stopping; return (GetState() != State::Uninitialized || s_hardware_initialized) && !s_is_stopping;
@ -249,7 +243,6 @@ bool Init(std::unique_ptr<BootParameters> boot, const WindowSystemInfo& wsi)
g_video_backend->PrepareWindow(prepared_wsi); g_video_backend->PrepareWindow(prepared_wsi);
// Start the emu thread // Start the emu thread
s_done_booting.Reset();
s_is_booting.Set(); s_is_booting.Set();
s_emu_thread = std::thread(EmuThread, std::move(boot), prepared_wsi); s_emu_thread = std::thread(EmuThread, std::move(boot), prepared_wsi);
return true; return true;
@ -435,7 +428,6 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
s_on_state_changed_callback(State::Starting); s_on_state_changed_callback(State::Starting);
Common::ScopeGuard flag_guard{[] { Common::ScopeGuard flag_guard{[] {
s_is_booting.Clear(); s_is_booting.Clear();
s_done_booting.Set();
s_is_started = false; s_is_started = false;
s_is_stopping = false; s_is_stopping = false;
s_wants_determinism = false; s_wants_determinism = false;
@ -568,7 +560,6 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
// The hardware is initialized. // The hardware is initialized.
s_hardware_initialized = true; s_hardware_initialized = true;
s_is_booting.Clear(); s_is_booting.Clear();
s_done_booting.Set();
// Set execution state to known values (CPU/FIFO/Audio Paused) // Set execution state to known values (CPU/FIFO/Audio Paused)
CPU::Break(); CPU::Break();
@ -692,12 +683,6 @@ State GetState()
return State::Uninitialized; return State::Uninitialized;
} }
void WaitUntilDoneBooting()
{
if (IsBooting())
s_done_booting.Wait();
}
static std::string GenerateScreenshotFolderPath() static std::string GenerateScreenshotFolderPath()
{ {
const std::string& gameId = SConfig::GetInstance().GetGameID(); const std::string& gameId = SConfig::GetInstance().GetGameID();

View File

@ -99,7 +99,6 @@ void UndeclareAsCPUThread();
std::string StopMessage(bool main_thread, std::string_view message); std::string StopMessage(bool main_thread, std::string_view message);
bool IsBooting();
bool IsRunning(); bool IsRunning();
bool IsRunningAndStarted(); // is running and the CPU loop has been entered bool IsRunningAndStarted(); // is running and the CPU loop has been entered
bool IsRunningInCurrentThread(); // this tells us whether we are running in the CPU thread. 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 // [NOT THREADSAFE] For use by Host only
void SetState(State state); void SetState(State state);
State GetState(); State GetState();
void WaitUntilDoneBooting();
void SaveScreenShot(); void SaveScreenShot();
void SaveScreenShot(std::string_view name); void SaveScreenShot(std::string_view name);