From 8f195b39b3cbb99a1c9051dac5f4976c93307f8d Mon Sep 17 00:00:00 2001 From: Loof <223481293+unciv-loof@users.noreply.github.com> Date: Sat, 6 Sep 2025 12:58:22 +0200 Subject: [PATCH 1/3] Refactor AuthPopup.kt --- core/src/com/unciv/ui/popups/AuthPopup.kt | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/core/src/com/unciv/ui/popups/AuthPopup.kt b/core/src/com/unciv/ui/popups/AuthPopup.kt index 7b332917c9..dee4f35a73 100644 --- a/core/src/com/unciv/ui/popups/AuthPopup.kt +++ b/core/src/com/unciv/ui/popups/AuthPopup.kt @@ -8,16 +8,17 @@ import com.unciv.ui.components.input.onClick import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.screens.basescreen.BaseScreen -class AuthPopup(stage: Stage, authSuccessful: ((Boolean) -> Unit)? = null) +class AuthPopup(stage: Stage, private val authSuccessful: ((Boolean) -> Unit)? = null) : Popup(stage) { constructor(screen: BaseScreen, authSuccessful: ((Boolean) -> Unit)? = null) : this(screen.stage, authSuccessful) - init { - val passwordField = UncivTextField("Password") - val button = "Authenticate".toTextButton() - val negativeButtonStyle = BaseScreen.skin.get("negative", TextButton.TextButtonStyle::class.java) + private val passwordField: UncivTextField = UncivTextField("Password") + private val button: TextButton = "Authenticate".toTextButton() + private val negativeButtonStyle: TextButton.TextButtonStyle = + BaseScreen.skin.get("negative", TextButton.TextButtonStyle::class.java) + init { button.onClick { try { UncivGame.Current.onlineMultiplayer.multiplayerServer.authenticate(passwordField.text) @@ -25,17 +26,18 @@ class AuthPopup(stage: Stage, authSuccessful: ((Boolean) -> Unit)? = null) close() } catch (_: Exception) { clear() - addGoodSizedLabel("Authentication failed").colspan(2).row() - add(passwordField).colspan(2).growX().pad(16f, 0f, 16f, 0f).row() - addCloseButton(style = negativeButtonStyle) { authSuccessful?.invoke(false) }.growX().padRight(8f) - add(button).growX().padLeft(8f) - return@onClick + addComponents("Authentication failed") } } - - addGoodSizedLabel("Please enter your server password").colspan(2).row() + addComponents("Please enter your server password") + } + + private fun addComponents(headerLabelText: String) { + addGoodSizedLabel(headerLabelText).colspan(2).row() add(passwordField).colspan(2).growX().pad(16f, 0f, 16f, 0f).row() - addCloseButton(style = negativeButtonStyle) { authSuccessful?.invoke(false) }.growX().padRight(8f) + addCloseButton(style = negativeButtonStyle) { + authSuccessful?.invoke(false) + }.growX().padRight(8f) add(button).growX().padLeft(8f) } } From 41660d240834db2bdd550b2f6c486966bee0cc6f Mon Sep 17 00:00:00 2001 From: Loof <223481293+unciv-loof@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:44:28 +0200 Subject: [PATCH 2/3] Prevent nextTurn() from running more than once if MP authentication fails --- .../ui/screens/worldscreen/WorldScreen.kt | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt index d6a852cd4d..5578826df9 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt @@ -70,6 +70,8 @@ import com.unciv.utils.debug import com.unciv.utils.launchOnGLThread import com.unciv.utils.launchOnThreadPool import com.unciv.utils.withGLContext +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import yairm210.purity.annotations.Readonly @@ -595,17 +597,38 @@ class WorldScreen( gameInfoClone.nextTurn(progressBar) if (originalGameInfo.gameParameters.isOnlineMultiplayer) { + // outer try-catch for non-auth exceptions try { - game.onlineMultiplayer.updateGame(gameInfoClone) - }catch (ex: Exception) { - when (ex) { - is MultiplayerAuthException -> { + // keep retrying if upload fails AND reauthentication succeeds + var retryUpload: Boolean + do { + try { + game.onlineMultiplayer.updateGame(gameInfoClone) + retryUpload = false + } catch (ex: MultiplayerAuthException) { + // true only if authentication succeeds (password retries are permitted) + // false only if user closes the auth popup + // let's hope the GL thread doesn't crash :-) + val authResult = CompletableDeferred() launchOnGLThread { - AuthPopup(this@WorldScreen) { - success -> if (success) nextTurn() - }.open(true) + try { + AuthPopup(this@WorldScreen, authResult::complete).open(true) + } catch (ex: Exception) { + // GL thread about to crash because of AuthPopup init + authResult.cancel() + throw ex + } + } + retryUpload = try { + // wait until authentication has finished + authResult.await() + } catch (ex: CancellationException) { + false } } + } while (retryUpload) + } catch (ex: Exception) { // non-auth exceptions + when (ex) { is FileStorageRateLimitReached -> { val message = "Server limit reached! Please wait for [${ex.limitRemainingSeconds}] seconds" launchOnGLThread { From 555c474fea1a6bdf84ea4e53baa9909e082a2021 Mon Sep 17 00:00:00 2001 From: Loof <223481293+unciv-loof@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:18:06 +0200 Subject: [PATCH 3/3] Minor adjustment to AuthPopup fix --- .../ui/screens/worldscreen/WorldScreen.kt | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt index 5578826df9..dbad4284d0 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt @@ -70,7 +70,6 @@ import com.unciv.utils.debug import com.unciv.utils.launchOnGLThread import com.unciv.utils.launchOnThreadPool import com.unciv.utils.withGLContext -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope @@ -604,27 +603,23 @@ class WorldScreen( do { try { game.onlineMultiplayer.updateGame(gameInfoClone) + // upload succeeded retryUpload = false - } catch (ex: MultiplayerAuthException) { - // true only if authentication succeeds (password retries are permitted) - // false only if user closes the auth popup - // let's hope the GL thread doesn't crash :-) + } catch (_: MultiplayerAuthException) { + // true only if authentication succeeds (the popup permits retries) + // false only if user closes the auth popup or the popup init crashes val authResult = CompletableDeferred() launchOnGLThread { try { AuthPopup(this@WorldScreen, authResult::complete).open(true) } catch (ex: Exception) { - // GL thread about to crash because of AuthPopup init - authResult.cancel() + // GL thread crashed during AuthPopup init, let's wrap up + authResult.complete(false) + // ensure exception is passed to crash handler throw ex } } - retryUpload = try { - // wait until authentication has finished - authResult.await() - } catch (ex: CancellationException) { - false - } + retryUpload = authResult.await() } } while (retryUpload) } catch (ex: Exception) { // non-auth exceptions