Fix multiplayer turn check worker exception (#6915)

This commit is contained in:
Timo T 2022-05-23 09:48:08 +02:00 committed by GitHub
parent 6836038252
commit b59b890ced
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 75 additions and 70 deletions

View File

@ -27,14 +27,14 @@ class CustomSaveLocationHelperAndroid(private val activity: Activity) : CustomSa
@GuardedBy("this") @GuardedBy("this")
private val callbacks = ArrayList<IndexedCallback>() private val callbacks = ArrayList<IndexedCallback>()
override fun saveGame(gameSaver: GameSaver, gameInfo: GameInfo, gameName: String, forcePrompt: Boolean, saveCompleteCallback: ((Exception?) -> Unit)?) { override fun saveGame(gameInfo: GameInfo, gameName: String, forcePrompt: Boolean, saveCompleteCallback: ((Exception?) -> Unit)?) {
val callbackIndex = synchronized(this) { val callbackIndex = synchronized(this) {
val index = callbackIndex++ val index = callbackIndex++
callbacks.add(IndexedCallback( callbacks.add(IndexedCallback(
index, index,
{ uri -> { uri ->
if (uri != null) { if (uri != null) {
saveGame(gameSaver, gameInfo, uri) saveGame(gameInfo, uri)
saveCompleteCallback?.invoke(null) saveCompleteCallback?.invoke(null)
} else { } else {
saveCompleteCallback?.invoke(RuntimeException("Uri was null")) saveCompleteCallback?.invoke(RuntimeException("Uri was null"))
@ -68,16 +68,16 @@ class CustomSaveLocationHelperAndroid(private val activity: Activity) : CustomSa
} }
} }
private fun saveGame(gameSaver: GameSaver, gameInfo: GameInfo, uri: Uri) { private fun saveGame(gameInfo: GameInfo, uri: Uri) {
gameInfo.customSaveLocation = uri.toString() gameInfo.customSaveLocation = uri.toString()
activity.contentResolver.openOutputStream(uri, "rwt") activity.contentResolver.openOutputStream(uri, "rwt")
?.writer() ?.writer()
?.use { ?.use {
it.write(gameSaver.gameInfoToString(gameInfo)) it.write(GameSaver.gameInfoToString(gameInfo))
} }
} }
override fun loadGame(gameSaver: GameSaver, loadCompleteCallback: (GameInfo?, Exception?) -> Unit) { override fun loadGame(loadCompleteCallback: (GameInfo?, Exception?) -> Unit) {
val callbackIndex = synchronized(this) { val callbackIndex = synchronized(this) {
val index = callbackIndex++ val index = callbackIndex++
callbacks.add(IndexedCallback( callbacks.add(IndexedCallback(
@ -90,7 +90,7 @@ class CustomSaveLocationHelperAndroid(private val activity: Activity) : CustomSa
?.reader() ?.reader()
?.readText() ?.readText()
?.run { ?.run {
gameSaver.gameInfoFromString(this) GameSaver.gameInfoFromString(this)
} }
} catch (e: Exception) { } catch (e: Exception) {
exception = e exception = e

View File

@ -20,11 +20,10 @@ interface CustomSaveLocationHelper {
* @param saveCompleteCallback Action to call upon completion (success _and_ failure) * @param saveCompleteCallback Action to call upon completion (success _and_ failure)
*/ */
fun saveGame( fun saveGame(
gameSaver: GameSaver, gameInfo: GameInfo,
gameInfo: GameInfo, gameName: String,
gameName: String, forcePrompt: Boolean = false,
forcePrompt: Boolean = false, saveCompleteCallback: ((Exception?) -> Unit)? = null
saveCompleteCallback: ((Exception?) -> Unit)? = null
) )
/**### Load from custom location /**### Load from custom location
@ -34,5 +33,5 @@ interface CustomSaveLocationHelper {
* *
* @param loadCompleteCallback Action to call upon completion (success _and_ failure) * @param loadCompleteCallback Action to call upon completion (success _and_ failure)
*/ */
fun loadGame(gameSaver: GameSaver, loadCompleteCallback: (GameInfo?, Exception?) -> Unit) fun loadGame(loadCompleteCallback: (GameInfo?, Exception?) -> Unit)
} }

View File

@ -31,8 +31,6 @@ class GameSaver(
) { ) {
//region Data //region Data
var saveZipped = false
var autoSaveJob: Job? = null var autoSaveJob: Job? = null
//endregion //endregion
@ -107,17 +105,6 @@ class GameSaver(
} }
} }
/** Returns gzipped serialization of [game], optionally gzipped ([forceZip] overrides [saveZipped]) */
fun gameInfoToString(game: GameInfo, forceZip: Boolean? = null): String {
val plainJson = json().toJson(game)
return if (forceZip ?: saveZipped) Gzip.zip(plainJson) else plainJson
}
/** Returns gzipped serialization of preview [game] */
fun gameInfoToString(game: GameInfoPreview): String {
return Gzip.zip(json().toJson(game))
}
/** /**
* Overload of function saveGame to save a GameInfoPreview in the MultiplayerGames folder * Overload of function saveGame to save a GameInfoPreview in the MultiplayerGames folder
*/ */
@ -140,7 +127,7 @@ class GameSaver(
} }
fun saveGameToCustomLocation(game: GameInfo, GameName: String, saveCompletionCallback: (Exception?) -> Unit) { fun saveGameToCustomLocation(game: GameInfo, GameName: String, saveCompletionCallback: (Exception?) -> Unit) {
customSaveLocationHelper!!.saveGame(this, game, GameName, forcePrompt = true, saveCompleteCallback = saveCompletionCallback) customSaveLocationHelper!!.saveGame(game, GameName, forcePrompt = true, saveCompleteCallback = saveCompletionCallback)
} }
//endregion //endregion
@ -161,40 +148,11 @@ class GameSaver(
} }
fun loadGameFromCustomLocation(loadCompletionCallback: (GameInfo?, Exception?) -> Unit) { fun loadGameFromCustomLocation(loadCompletionCallback: (GameInfo?, Exception?) -> Unit) {
customSaveLocationHelper!!.loadGame(this) { game, e -> customSaveLocationHelper!!.loadGame { game, e ->
loadCompletionCallback(game?.apply { setTransients() }, e) loadCompletionCallback(game?.apply { setTransients() }, e)
} }
} }
fun gameInfoFromString(gameData: String): GameInfo {
return gameInfoFromStringWithoutTransients(gameData).apply {
setTransients()
}
}
/**
* Parses [gameData] as gzipped serialization of a [GameInfoPreview]
* @throws SerializationException
*/
fun gameInfoPreviewFromString(gameData: String): GameInfoPreview {
return json().fromJson(GameInfoPreview::class.java, Gzip.unzip(gameData))
}
/**
* WARNING! transitive GameInfo data not initialized
* The returned GameInfo can not be used for most circumstances because its not initialized!
* It is therefore stateless and save to call for Multiplayer Turn Notifier, unlike gameInfoFromString().
*
* @throws SerializationException
*/
private fun gameInfoFromStringWithoutTransients(gameData: String): GameInfo {
val unzippedJson = try {
Gzip.unzip(gameData)
} catch (ex: Exception) {
gameData
}
return json().fromJson(GameInfo::class.java, unzippedJson)
}
//endregion //endregion
//region Settings //region Settings
@ -229,6 +187,9 @@ class GameSaver(
} }
companion object { companion object {
var saveZipped = false
/** Specialized function to access settings before Gdx is initialized. /** Specialized function to access settings before Gdx is initialized.
* *
* @param base Path to the directory where the file should be - if not set, the OS current directory is used (which is "/" on Android) * @param base Path to the directory where the file should be - if not set, the OS current directory is used (which is "/" on Android)
@ -244,6 +205,48 @@ class GameSaver(
) )
else GameSettings().apply { isFreshlyCreated = true } else GameSettings().apply { isFreshlyCreated = true }
} }
fun gameInfoFromString(gameData: String): GameInfo {
return gameInfoFromStringWithoutTransients(gameData).apply {
setTransients()
}
}
/**
* Parses [gameData] as gzipped serialization of a [GameInfoPreview]
* @throws SerializationException
*/
fun gameInfoPreviewFromString(gameData: String): GameInfoPreview {
return json().fromJson(GameInfoPreview::class.java, Gzip.unzip(gameData))
}
/**
* WARNING! transitive GameInfo data not initialized
* The returned GameInfo can not be used for most circumstances because its not initialized!
* It is therefore stateless and save to call for Multiplayer Turn Notifier, unlike gameInfoFromString().
*
* @throws SerializationException
*/
private fun gameInfoFromStringWithoutTransients(gameData: String): GameInfo {
val unzippedJson = try {
Gzip.unzip(gameData)
} catch (ex: Exception) {
gameData
}
return json().fromJson(GameInfo::class.java, unzippedJson)
}
/** Returns gzipped serialization of [game], optionally gzipped ([forceZip] overrides [saveZipped]) */
fun gameInfoToString(game: GameInfo, forceZip: Boolean? = null): String {
val plainJson = json().toJson(game)
return if (forceZip ?: saveZipped) Gzip.zip(plainJson) else plainJson
}
/** Returns gzipped serialization of preview [game] */
fun gameInfoToString(game: GameInfoPreview): String {
return Gzip.zip(json().toJson(game))
}
} }
//endregion //endregion

View File

@ -4,6 +4,7 @@ import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameInfoPreview import com.unciv.logic.GameInfoPreview
import com.unciv.logic.GameSaver
/** /**
* Allows access to games stored on a server for multiplayer purposes. * Allows access to games stored on a server for multiplayer purposes.
@ -19,7 +20,6 @@ import com.unciv.logic.GameInfoPreview
class OnlineMultiplayerGameSaver( class OnlineMultiplayerGameSaver(
private var fileStorageIdentifier: String? = null private var fileStorageIdentifier: String? = null
) { ) {
private val gameSaver = UncivGame.Current.gameSaver
fun fileStorage(): FileStorage { fun fileStorage(): FileStorage {
val identifier = if (fileStorageIdentifier == null) UncivGame.Current.settings.multiplayerServer else fileStorageIdentifier val identifier = if (fileStorageIdentifier == null) UncivGame.Current.settings.multiplayerServer else fileStorageIdentifier
@ -34,7 +34,7 @@ class OnlineMultiplayerGameSaver(
tryUploadGamePreview(gameInfo.asPreview()) tryUploadGamePreview(gameInfo.asPreview())
} }
val zippedGameInfo = gameSaver.gameInfoToString(gameInfo, forceZip = true) val zippedGameInfo = GameSaver.gameInfoToString(gameInfo, forceZip = true)
fileStorage().saveFileData(gameInfo.gameId, zippedGameInfo, true) fileStorage().saveFileData(gameInfo.gameId, zippedGameInfo, true)
} }
@ -49,7 +49,7 @@ class OnlineMultiplayerGameSaver(
* @see GameInfo.asPreview * @see GameInfo.asPreview
*/ */
suspend fun tryUploadGamePreview(gameInfo: GameInfoPreview) { suspend fun tryUploadGamePreview(gameInfo: GameInfoPreview) {
val zippedGameInfo = gameSaver.gameInfoToString(gameInfo) val zippedGameInfo = GameSaver.gameInfoToString(gameInfo)
fileStorage().saveFileData("${gameInfo.gameId}_Preview", zippedGameInfo, true) fileStorage().saveFileData("${gameInfo.gameId}_Preview", zippedGameInfo, true)
} }
@ -59,7 +59,7 @@ class OnlineMultiplayerGameSaver(
*/ */
suspend fun tryDownloadGame(gameId: String): GameInfo { suspend fun tryDownloadGame(gameId: String): GameInfo {
val zippedGameInfo = fileStorage().loadFileData(gameId) val zippedGameInfo = fileStorage().loadFileData(gameId)
return gameSaver.gameInfoFromString(zippedGameInfo) return GameSaver.gameInfoFromString(zippedGameInfo)
} }
/** /**
@ -68,6 +68,6 @@ class OnlineMultiplayerGameSaver(
*/ */
suspend fun tryDownloadGamePreview(gameId: String): GameInfoPreview { suspend fun tryDownloadGamePreview(gameId: String): GameInfoPreview {
val zippedGameInfo = fileStorage().loadFileData("${gameId}_Preview") val zippedGameInfo = fileStorage().loadFileData("${gameId}_Preview")
return gameSaver.gameInfoPreviewFromString(zippedGameInfo) return GameSaver.gameInfoPreviewFromString(zippedGameInfo)
} }
} }

View File

@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Align
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameSaver
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.ui.images.IconTextButton import com.unciv.ui.images.IconTextButton
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
@ -55,7 +56,7 @@ class CrashScreen(val exception: Throwable): BaseScreen() {
return "" return ""
return "\n**Save Data:**\n<details><summary>Show Saved Game</summary>\n\n```" + return "\n**Save Data:**\n<details><summary>Show Saved Game</summary>\n\n```" +
try { try {
game.gameSaver.gameInfoToString(UncivGame.Current.gameInfo, forceZip = true) GameSaver.gameInfoToString(UncivGame.Current.gameInfo, forceZip = true)
} catch (e: Throwable) { } catch (e: Throwable) {
"No save data: $e" // In theory .toString() could still error here. "No save data: $e" // In theory .toString() could still error here.
} + "\n```\n</details>\n" } + "\n```\n</details>\n"

View File

@ -42,8 +42,8 @@ fun debugTab() = Table(BaseScreen.skin).apply {
game.gameInfo.gameParameters.godMode = it game.gameInfo.gameParameters.godMode = it
}).colspan(2).row() }).colspan(2).row()
} }
add("Save games compressed".toCheckBox(game.gameSaver.saveZipped) { add("Save games compressed".toCheckBox(GameSaver.saveZipped) {
game.gameSaver.saveZipped = it GameSaver.saveZipped = it
}).colspan(2).row() }).colspan(2).row()
add("Save maps compressed".toCheckBox(MapSaver.saveZipped) { add("Save maps compressed".toCheckBox(MapSaver.saveZipped) {
MapSaver.saveZipped = it MapSaver.saveZipped = it

View File

@ -9,6 +9,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.Align import com.badlogic.gdx.utils.Align
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameSaver
import com.unciv.logic.MissingModsException import com.unciv.logic.MissingModsException
import com.unciv.logic.UncivShowableException import com.unciv.logic.UncivShowableException
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
@ -88,7 +89,7 @@ class LoadGameScreen(previousScreen:BaseScreen) : PickerScreen(disableScroll = t
loadFromClipboardButton.onClick { loadFromClipboardButton.onClick {
try { try {
val clipboardContentsString = Gdx.app.clipboard.contents.trim() val clipboardContentsString = Gdx.app.clipboard.contents.trim()
val loadedGame = game.gameSaver.gameInfoFromString(clipboardContentsString) val loadedGame = GameSaver.gameInfoFromString(clipboardContentsString)
UncivGame.Current.loadGame(loadedGame) UncivGame.Current.loadGame(loadedGame)
} catch (ex: Exception) { } catch (ex: Exception) {
handleLoadGameException("Could not load game from clipboard!", ex) handleLoadGameException("Could not load game from clipboard!", ex)

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.crashhandling.launchCrashHandling import com.unciv.ui.crashhandling.launchCrashHandling
import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.crashhandling.postCrashHandlingRunnable
@ -43,7 +44,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : PickerScreen(disableScroll = true
copyJsonButton.onClick { copyJsonButton.onClick {
thread(name="Copy to clipboard") { // the Gzip rarely leads to ANRs thread(name="Copy to clipboard") { // the Gzip rarely leads to ANRs
try { try {
Gdx.app.clipboard.contents = game.gameSaver.gameInfoToString(gameInfo, forceZip = true) Gdx.app.clipboard.contents = GameSaver.gameInfoToString(gameInfo, forceZip = true)
} catch (OOM: OutOfMemoryError) { } catch (OOM: OutOfMemoryError) {
// you don't get a special toast, this isn't nearly common enough, this is a total edge-case // you don't get a special toast, this isn't nearly common enough, this is a total edge-case
} }

View File

@ -12,14 +12,14 @@ import javax.swing.JFileChooser
import javax.swing.JFrame import javax.swing.JFrame
class CustomSaveLocationHelperDesktop : CustomSaveLocationHelper { class CustomSaveLocationHelperDesktop : CustomSaveLocationHelper {
override fun saveGame(gameSaver: GameSaver, gameInfo: GameInfo, gameName: String, forcePrompt: Boolean, saveCompleteCallback: ((Exception?) -> Unit)?) { override fun saveGame(gameInfo: GameInfo, gameName: String, forcePrompt: Boolean, saveCompleteCallback: ((Exception?) -> Unit)?) {
val customSaveLocation = gameInfo.customSaveLocation val customSaveLocation = gameInfo.customSaveLocation
if (customSaveLocation != null && !forcePrompt) { if (customSaveLocation != null && !forcePrompt) {
try { try {
File(customSaveLocation).outputStream() File(customSaveLocation).outputStream()
.writer() .writer()
.use { writer -> .use { writer ->
writer.write(gameSaver.gameInfoToString(gameInfo)) writer.write(GameSaver.gameInfoToString(gameInfo))
} }
saveCompleteCallback?.invoke(null) saveCompleteCallback?.invoke(null)
} catch (e: Exception) { } catch (e: Exception) {
@ -59,7 +59,7 @@ class CustomSaveLocationHelperDesktop : CustomSaveLocationHelper {
saveCompleteCallback?.invoke(exception) saveCompleteCallback?.invoke(exception)
} }
override fun loadGame(gameSaver: GameSaver, loadCompleteCallback: (GameInfo?, Exception?) -> Unit) { override fun loadGame(loadCompleteCallback: (GameInfo?, Exception?) -> Unit) {
val fileChooser = JFileChooser().apply fileChooser@{ val fileChooser = JFileChooser().apply fileChooser@{
currentDirectory = Gdx.files.local("").file() currentDirectory = Gdx.files.local("").file()
} }
@ -79,7 +79,7 @@ class CustomSaveLocationHelperDesktop : CustomSaveLocationHelper {
file.inputStream() file.inputStream()
.reader() .reader()
.readText() .readText()
.run { gameSaver.gameInfoFromString(this) } .run { GameSaver.gameInfoFromString(this) }
.apply { .apply {
// If the user has saved the game from another platform (like Android), // If the user has saved the game from another platform (like Android),
// then the save location might not be right so we have to correct for that // then the save location might not be right so we have to correct for that