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 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)

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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<jclass>(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");

View File

@ -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<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)
{
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(
@ -391,6 +393,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChang
jclass,
jobject surf)
{
std::lock_guard<std::mutex> 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<std::mutex> guard(s_surface_lock);
if (g_renderer)
g_renderer->ChangeSurface(nullptr);
@ -480,7 +486,7 @@ static void Run(JNIEnv* env, const std::vector<std::string>& paths,
ASSERT(!paths.empty());
__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();
@ -493,24 +499,41 @@ static void Run(JNIEnv* env, const std::vector<std::string>& 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<std::mutex> 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<std::string>& paths,
s_game_metadata_is_valid = false;
Core::Shutdown();
ButtonManager::Shutdown();
guard.unlock();
host_identity_guard.unlock();
if (s_surf)
{

View File

@ -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<BootParameters> 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<BootParameters> 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<BootParameters> 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();

View File

@ -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);