Save uncaught exception to file (#7247)

* Refactor: Rename GameSaver to UncivFiles

* Save last uncaught exception to file

Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
Timo T 2022-06-25 21:30:27 +02:00 committed by GitHub
parent 98af4207ec
commit bc9a42e452
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 115 additions and 98 deletions

View File

@ -15,7 +15,7 @@ import com.badlogic.gdx.backends.android.AndroidGraphics
import com.badlogic.gdx.math.Rectangle
import com.unciv.UncivGame
import com.unciv.UncivGameParameters
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.logic.event.EventBus
import com.unciv.ui.UncivStage
import com.unciv.ui.utils.BaseScreen
@ -40,7 +40,7 @@ open class AndroidLauncher : AndroidApplication() {
useImmersiveMode = true
}
val settings = GameSaver.getSettingsForPlatformLaunchers(filesDir.path)
val settings = UncivFiles.getSettingsForPlatformLaunchers(filesDir.path)
val fontFamily = settings.fontFamily
// Manage orientation lock and display cutout
@ -119,10 +119,10 @@ open class AndroidLauncher : AndroidApplication() {
if (UncivGame.isCurrentInitialized()
&& UncivGame.Current.gameInfo != null
&& UncivGame.Current.settings.multiplayer.turnCheckerEnabled
&& UncivGame.Current.gameSaver.getMultiplayerSaves().any()
&& UncivGame.Current.files.getMultiplayerSaves().any()
) {
MultiplayerTurnCheckWorker.startTurnChecker(
applicationContext, UncivGame.Current.gameSaver,
applicationContext, UncivGame.Current.files,
UncivGame.Current.gameInfo!!, UncivGame.Current.settings.multiplayer
)
}

View File

@ -20,10 +20,9 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.backends.android.AndroidApplication
import com.badlogic.gdx.backends.android.DefaultAndroidFiles
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
import com.unciv.models.metadata.GameSettings
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerGameSaver
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerFiles
import com.unciv.models.metadata.GameSettingsMultiplayer
import kotlinx.coroutines.runBlocking
import java.io.FileNotFoundException
@ -181,16 +180,16 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
}
}
fun startTurnChecker(applicationContext: Context, gameSaver: GameSaver, currentGameInfo: GameInfo, settings: GameSettingsMultiplayer) {
fun startTurnChecker(applicationContext: Context, files: UncivFiles, currentGameInfo: GameInfo, settings: GameSettingsMultiplayer) {
Log.i(LOG_TAG, "startTurnChecker")
val gameFiles = gameSaver.getMultiplayerSaves()
val gameFiles = files.getMultiplayerSaves()
val gameIds = Array(gameFiles.count()) {""}
val gameNames = Array(gameFiles.count()) {""}
var count = 0
for (gameFile in gameFiles) {
try {
val gamePreview = gameSaver.loadGamePreviewFromFile(gameFile)
val gamePreview = files.loadGamePreviewFromFile(gameFile)
gameIds[count] = gamePreview.gameId
gameNames[count] = gameFile.name()
count++
@ -260,14 +259,14 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
*/
private val notFoundRemotely = mutableMapOf<String, Boolean>()
private val gameSaver: GameSaver
private val files: UncivFiles
init {
// We can't use Gdx.files since that is only initialized within a com.badlogic.gdx.backends.android.AndroidApplication.
// Worker instances may be stopped & recreated by the Android WorkManager, so no AndroidApplication and thus no Gdx.files available
val files = DefaultAndroidFiles(applicationContext.assets, ContextWrapper(applicationContext), true)
val gdxFiles = DefaultAndroidFiles(applicationContext.assets, ContextWrapper(applicationContext), true)
// GDX's AndroidFileHandle uses Gdx.files internally, so we need to set that to our new instance
Gdx.files = files
gameSaver = GameSaver(files, null, true)
Gdx.files = gdxFiles
files = UncivFiles(gdxFiles, null, true)
}
override fun doWork(): Result = runBlocking {
@ -297,7 +296,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
try {
Log.d(LOG_TAG, "doWork download ${gameId}")
val gamePreview = OnlineMultiplayerGameSaver(fileStorage).tryDownloadGamePreview(gameId)
val gamePreview = OnlineMultiplayerFiles(fileStorage).tryDownloadGamePreview(gameId)
Log.d(LOG_TAG, "doWork download ${gameId} done")
val currentTurnPlayer = gamePreview.getCivilization(gamePreview.currentPlayer)
@ -310,7 +309,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
Lets hope it works with gamePreview as they are a lot smaller and faster to save
*/
Log.i(LOG_TAG, "doWork save gameName: ${gameNames[idx]}")
gameSaver.saveGame(gamePreview, gameNames[idx])
files.saveGame(gamePreview, gameNames[idx])
Log.i(LOG_TAG, "doWork save ${gameNames[idx]} done")
if (currentTurnPlayer.playerId == inputData.getString(USER_ID)!! && foundGame == null) {

View File

@ -24,7 +24,7 @@ buildscript {
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${com.unciv.build.BuildConfig.kotlinVersion}")
classpath("de.richsource.gradle.plugins:gwt-gradle-plugin:0.6")
classpath("com.android.tools.build:gradle:7.1.3")
classpath("com.android.tools.build:gradle:7.0.4")
classpath("com.mobidevelop.robovm:robovm-gradle-plugin:2.3.1")
// This is for wrapping the .jar file into a standalone executable

View File

@ -139,7 +139,7 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize {
val column1 = Table().apply { defaults().pad(10f).fillX() }
val column2 = if (singleColumn) column1 else Table().apply { defaults().pad(10f).fillX() }
if (game.gameSaver.autosaveExists()) {
if (game.files.autosaveExists()) {
val resumeTable = getMenuButton("Resume","OtherIcons/Resume", 'r')
{ resumeGame() }
column1.add(resumeTable).row()
@ -153,7 +153,7 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize {
{ game.pushScreen(NewGameScreen()) }
column1.add(newGameButton).row()
if (game.gameSaver.getSaves().any()) {
if (game.files.getSaves().any()) {
val loadGameTable = getMenuButton("Load game", "OtherIcons/Load", 'l')
{ game.pushScreen(LoadGameScreen(this)) }
column1.add(loadGameTable).row()

View File

@ -8,7 +8,7 @@ import com.badlogic.gdx.Screen
import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.utils.Align
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.multiplayer.OnlineMultiplayer
import com.unciv.models.metadata.GameSettings
@ -37,6 +37,7 @@ import com.unciv.utils.concurrency.launchOnGLThread
import com.unciv.utils.concurrency.withGLContext
import com.unciv.utils.concurrency.withThreadPoolContext
import com.unciv.utils.debug
import java.io.PrintWriter
import java.util.*
import kotlin.collections.ArrayDeque
@ -59,7 +60,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
lateinit var settings: GameSettings
lateinit var musicController: MusicController
lateinit var onlineMultiplayer: OnlineMultiplayer
lateinit var gameSaver: GameSaver
lateinit var files: UncivFiles
/**
* This exists so that when debugging we can see the entire map.
@ -95,7 +96,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
viewEntireMapForDebug = false
}
Current = this
gameSaver = GameSaver(Gdx.files, customSaveLocationHelper, platformSpecificHelper?.shouldPreferExternalStorage() == true)
files = UncivFiles(Gdx.files, customSaveLocationHelper, platformSpecificHelper?.shouldPreferExternalStorage() == true)
// If this takes too long players, especially with older phones, get ANR problems.
// Whatever needs graphics needs to be done on the main thread,
@ -109,7 +110,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
* - Skin (hence BaseScreen.setSkin())
* - Font (hence Fonts.resetFont() inside setSkin())
*/
settings = gameSaver.getGeneralSettings() // needed for the screen
settings = files.getGeneralSettings() // needed for the screen
setAsRootScreen(GameStartScreen()) // NOT dependent on any atlas or skin
GameSounds.init()
@ -355,7 +356,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
override fun pause() {
val curGameInfo = gameInfo
if (curGameInfo != null) gameSaver.requestAutoSave(curGameInfo)
if (curGameInfo != null) files.requestAutoSave(curGameInfo)
musicController.pause()
super.pause()
}
@ -375,7 +376,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
val curGameInfo = gameInfo
if (curGameInfo != null) {
val autoSaveJob = gameSaver.autoSaveJob
val autoSaveJob = files.autoSaveJob
if (autoSaveJob != null && autoSaveJob.isActive) {
// auto save is already in progress (e.g. started by onPause() event)
// let's allow it to finish and do not try to autosave second time
@ -383,7 +384,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
autoSaveJob.join()
}
} else {
gameSaver.autoSave(curGameInfo) // NO new thread
files.autoSave(curGameInfo) // NO new thread
}
}
settings.save()
@ -405,6 +406,13 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
/** Handles an uncaught exception or error. First attempts a platform-specific handler, and if that didn't handle the exception or error, brings the game to a [CrashScreen]. */
fun handleUncaughtThrowable(ex: Throwable) {
Log.error("Uncaught throwable", ex)
try {
PrintWriter(files.fileWriter("lasterror.txt")).use {
ex.printStackTrace(it)
}
} catch (ex: Exception) {
// ignore
}
if (platformSpecificHelper?.handleUncaughtThrowable(ex) == true) return
Gdx.app.postRunnable {
setAsRootScreen(CrashScreen(ex))
@ -419,7 +427,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
fun goToMainMenu(): MainMenuScreen {
val curGameInfo = gameInfo
if (curGameInfo != null) {
gameSaver.requestAutoSaveUnCloned(curGameInfo) // Can save gameInfo directly because the user can't modify it on the MainMenuScreen
files.requestAutoSaveUnCloned(curGameInfo) // Can save gameInfo directly because the user can't modify it on the MainMenuScreen
}
val mainMenuScreen = MainMenuScreen()
pushScreen(mainMenuScreen)

View File

@ -1,7 +1,7 @@
package com.unciv.logic
import com.unciv.logic.GameSaver.CustomLoadResult
import com.unciv.logic.GameSaver.CustomSaveResult
import com.unciv.logic.UncivFiles.CustomLoadResult
import com.unciv.logic.UncivFiles.CustomSaveResult
import com.unciv.utils.concurrency.Concurrency
import java.io.InputStream
import java.io.OutputStream

View File

@ -16,13 +16,14 @@ import com.unciv.utils.Log
import com.unciv.utils.debug
import kotlinx.coroutines.Job
import java.io.File
import java.io.Writer
private const val SAVE_FILES_FOLDER = "SaveFiles"
private const val MULTIPLAYER_FILES_FOLDER = "MultiplayerGames"
private const val AUTOSAVE_FILE_NAME = "Autosave"
private const val SETTINGS_FILE_NAME = "GameSettings.json"
class GameSaver(
class UncivFiles(
/**
* This is necessary because the Android turn check background worker does not hold any reference to the actual [com.badlogic.gdx.Application],
* which is normally responsible for keeping the [Gdx] static variables from being garbage collected.
@ -32,7 +33,7 @@ class GameSaver(
private val preferExternalStorage: Boolean = false
) {
init {
debug("Creating GameSaver, localStoragePath: %s, externalStoragePath: %s, preferExternalStorage: %s",
debug("Creating UncivFiles, localStoragePath: %s, externalStoragePath: %s, preferExternalStorage: %s",
files.localStoragePath, files.externalStoragePath, preferExternalStorage)
}
//region Data
@ -66,6 +67,18 @@ class GameSaver(
return toReturn
}
/**
* @throws GdxRuntimeException if the [path] represents a directory
*/
fun fileWriter(path: String, append: Boolean = false): Writer {
val file = if (preferExternalStorage && files.isExternalStorageAvailable) {
files.external(path)
} else {
files.local(path)
}
return file.writer(append)
}
fun getMultiplayerSaves(): Sequence<FileHandle> {
return getSaves(MULTIPLAYER_FILES_FOLDER)
}

View File

@ -8,7 +8,7 @@ import com.unciv.logic.GameInfoPreview
import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.event.EventBus
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerGameSaver
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerFiles
import com.unciv.ui.utils.extensions.isLargerThan
import com.unciv.utils.concurrency.Concurrency
import com.unciv.utils.concurrency.Dispatcher
@ -39,8 +39,8 @@ private val FILE_UPDATE_THROTTLE_PERIOD = Duration.ofSeconds(60)
* See the file of [com.unciv.logic.multiplayer.MultiplayerGameAdded] for all available [EventBus] events.
*/
class OnlineMultiplayer {
private val gameSaver = UncivGame.Current.gameSaver
private val onlineGameSaver = OnlineMultiplayerGameSaver()
private val files = UncivGame.Current.files
private val multiplayerFiles = OnlineMultiplayerFiles()
private val savedGames: MutableMap<FileHandle, OnlineMultiplayerGame> = Collections.synchronizedMap(mutableMapOf())
@ -100,7 +100,7 @@ class OnlineMultiplayer {
}
private suspend fun updateSavesFromFiles() {
val saves = gameSaver.getMultiplayerSaves()
val saves = files.getMultiplayerSaves()
val removedSaves = savedGames.keys - saves.toSet()
for (saveFile in removedSaves) {
@ -120,7 +120,7 @@ class OnlineMultiplayer {
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
*/
suspend fun createGame(newGame: GameInfo) {
onlineGameSaver.tryUploadGame(newGame, withPreview = true)
multiplayerFiles.tryUploadGame(newGame, withPreview = true)
addGame(newGame)
}
@ -136,10 +136,10 @@ class OnlineMultiplayer {
val saveFileName = if (gameName.isNullOrBlank()) gameId else gameName
var gamePreview: GameInfoPreview
try {
gamePreview = onlineGameSaver.tryDownloadGamePreview(gameId)
gamePreview = multiplayerFiles.tryDownloadGamePreview(gameId)
} catch (ex: FileNotFoundException) {
// Game is so old that a preview could not be found on dropbox lets try the real gameInfo instead
gamePreview = onlineGameSaver.tryDownloadGame(gameId).asPreview()
gamePreview = multiplayerFiles.tryDownloadGame(gameId).asPreview()
}
addGame(gamePreview, saveFileName)
}
@ -150,11 +150,11 @@ class OnlineMultiplayer {
}
private suspend fun addGame(preview: GameInfoPreview, saveFileName: String) {
val fileHandle = gameSaver.saveGame(preview, saveFileName)
val fileHandle = files.saveGame(preview, saveFileName)
return addGame(fileHandle, preview)
}
private suspend fun addGame(fileHandle: FileHandle, preview: GameInfoPreview = gameSaver.loadGamePreviewFromFile(fileHandle)) {
private suspend fun addGame(fileHandle: FileHandle, preview: GameInfoPreview = files.loadGamePreviewFromFile(fileHandle)) {
debug("Adding game %s", preview.gameId)
val game = OnlineMultiplayerGame(fileHandle, preview, Instant.now())
savedGames[fileHandle] = game
@ -184,7 +184,7 @@ class OnlineMultiplayer {
suspend fun resign(game: OnlineMultiplayerGame): Boolean {
val preview = game.preview ?: throw game.error!!
// download to work with the latest game state
val gameInfo = onlineGameSaver.tryDownloadGame(preview.gameId)
val gameInfo = multiplayerFiles.tryDownloadGame(preview.gameId)
val playerCiv = gameInfo.currentPlayerCiv
if (!gameInfo.isUsersTurn()) {
@ -205,8 +205,8 @@ class OnlineMultiplayer {
}
val newPreview = gameInfo.asPreview()
gameSaver.saveGame(newPreview, game.fileHandle)
onlineGameSaver.tryUploadGame(gameInfo, withPreview = true)
files.saveGame(newPreview, game.fileHandle)
multiplayerFiles.tryUploadGame(gameInfo, withPreview = true)
game.doManualUpdate(newPreview)
return true
}
@ -242,7 +242,7 @@ class OnlineMultiplayer {
*/
suspend fun loadGame(gameInfo: GameInfo) = coroutineScope {
val gameId = gameInfo.gameId
val preview = onlineGameSaver.tryDownloadGamePreview(gameId)
val preview = multiplayerFiles.tryDownloadGamePreview(gameId)
if (hasLatestGameState(gameInfo, preview)) {
gameInfo.isUpToDate = true
UncivGame.Current.loadGame(gameInfo)
@ -256,7 +256,7 @@ class OnlineMultiplayer {
* @throws FileNotFoundException if the file can't be found
*/
suspend fun downloadGame(gameId: String): GameInfo {
val latestGame = onlineGameSaver.tryDownloadGame(gameId)
val latestGame = multiplayerFiles.tryDownloadGame(gameId)
latestGame.isUpToDate = true
return latestGame
}
@ -271,7 +271,7 @@ class OnlineMultiplayer {
}
private fun deleteGame(fileHandle: FileHandle) {
gameSaver.deleteSave(fileHandle)
files.deleteSave(fileHandle)
val game = savedGames[fileHandle]
if (game == null) return
@ -291,8 +291,8 @@ class OnlineMultiplayer {
val oldName = game.name
savedGames.remove(game.fileHandle)
gameSaver.deleteSave(game.fileHandle)
val newFileHandle = gameSaver.saveGame(oldPreview, newName)
files.deleteSave(game.fileHandle)
val newFileHandle = files.saveGame(oldPreview, newName)
val newGame = OnlineMultiplayerGame(newFileHandle, oldPreview, oldLastUpdate)
savedGames[newFileHandle] = newGame
@ -305,7 +305,7 @@ class OnlineMultiplayer {
*/
suspend fun updateGame(gameInfo: GameInfo) {
debug("Updating remote game %s", gameInfo.gameId)
onlineGameSaver.tryUploadGame(gameInfo, withPreview = true)
multiplayerFiles.tryUploadGame(gameInfo, withPreview = true)
val game = getGameByGameId(gameInfo.gameId)
debug("Existing OnlineMultiplayerGame: %s", game)
if (game == null) {

View File

@ -5,9 +5,8 @@ import com.unciv.UncivGame
import com.unciv.logic.GameInfoPreview
import com.unciv.logic.event.EventBus
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerGameSaver
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerFiles
import com.unciv.ui.utils.extensions.isLargerThan
import com.unciv.utils.concurrency.Concurrency
import com.unciv.utils.concurrency.launchOnGLThread
import com.unciv.utils.concurrency.withGLContext
import com.unciv.utils.debug
@ -53,7 +52,7 @@ class OnlineMultiplayerGame(
}
private fun loadPreviewFromFile(): GameInfoPreview {
val previewFromFile = UncivGame.Current.gameSaver.loadGamePreviewFromFile(fileHandle)
val previewFromFile = UncivGame.Current.files.loadGamePreviewFromFile(fileHandle)
preview = previewFromFile
return previewFromFile
}
@ -104,9 +103,9 @@ class OnlineMultiplayerGame(
private suspend fun update(): GameUpdateResult {
val curPreview = if (preview != null) preview!! else loadPreviewFromFile()
val newPreview = OnlineMultiplayerGameSaver().tryDownloadGamePreview(curPreview.gameId)
val newPreview = OnlineMultiplayerFiles().tryDownloadGamePreview(curPreview.gameId)
if (newPreview.turns == curPreview.turns && newPreview.currentPlayer == curPreview.currentPlayer) return GameUpdateResult.UNCHANGED
UncivGame.Current.gameSaver.saveGame(newPreview, fileHandle)
UncivGame.Current.files.saveGame(newPreview, fileHandle)
preview = newPreview
return GameUpdateResult.CHANGED
}

View File

@ -4,7 +4,7 @@ import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.GameInfo
import com.unciv.logic.GameInfoPreview
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
/**
* Allows access to games stored on a server for multiplayer purposes.
@ -17,7 +17,7 @@ import com.unciv.logic.GameSaver
* @see UncivGame.Current.settings.multiplayerServer
*/
@Suppress("RedundantSuspendModifier") // Methods can take a long time, so force users to use them in a coroutine to not get ANRs on Android
class OnlineMultiplayerGameSaver(
class OnlineMultiplayerFiles(
private var fileStorageIdentifier: String? = null
) {
fun fileStorage(): FileStorage {
@ -28,7 +28,7 @@ class OnlineMultiplayerGameSaver(
/** @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time */
suspend fun tryUploadGame(gameInfo: GameInfo, withPreview: Boolean) {
val zippedGameInfo = GameSaver.gameInfoToString(gameInfo, forceZip = true)
val zippedGameInfo = UncivFiles.gameInfoToString(gameInfo, forceZip = true)
fileStorage().saveFileData(gameInfo.gameId, zippedGameInfo, true)
// We upload the preview after the game because otherwise the following race condition will happen:
@ -53,7 +53,7 @@ class OnlineMultiplayerGameSaver(
* @see GameInfo.asPreview
*/
suspend fun tryUploadGamePreview(gameInfo: GameInfoPreview) {
val zippedGameInfo = GameSaver.gameInfoToString(gameInfo)
val zippedGameInfo = UncivFiles.gameInfoToString(gameInfo)
fileStorage().saveFileData("${gameInfo.gameId}_Preview", zippedGameInfo, true)
}
@ -63,7 +63,7 @@ class OnlineMultiplayerGameSaver(
*/
suspend fun tryDownloadGame(gameId: String): GameInfo {
val zippedGameInfo = fileStorage().loadFileData(gameId)
return GameSaver.gameInfoFromString(zippedGameInfo)
return UncivFiles.gameInfoFromString(zippedGameInfo)
}
/**
@ -72,6 +72,6 @@ class OnlineMultiplayerGameSaver(
*/
suspend fun tryDownloadGamePreview(gameId: String): GameInfoPreview {
val zippedGameInfo = fileStorage().loadFileData("${gameId}_Preview")
return GameSaver.gameInfoPreviewFromString(zippedGameInfo)
return UncivFiles.gameInfoPreviewFromString(zippedGameInfo)
}
}

View File

@ -91,7 +91,7 @@ class GameSettings {
if (!isFreshlyCreated && Gdx.app?.type == Application.ApplicationType.Desktop) {
windowState = WindowState(Gdx.graphics.width, Gdx.graphics.height)
}
UncivGame.Current.gameSaver.setGeneralSettings(this)
UncivGame.Current.files.setGeneralSettings(this)
}
fun addCompletedTutorialTask(tutorialTask: String) {

View File

@ -8,7 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.models.ruleset.RulesetCache
import com.unciv.ui.images.IconTextButton
import com.unciv.ui.images.ImageGetter
@ -19,10 +19,8 @@ import com.unciv.ui.utils.extensions.addBorder
import com.unciv.ui.utils.extensions.onClick
import com.unciv.ui.utils.extensions.setFontSize
import com.unciv.ui.utils.extensions.toLabel
import com.unciv.utils.Log
import java.io.PrintWriter
import java.io.StringWriter
import kotlin.concurrent.thread
/** Screen to crash to when an otherwise unhandled exception or error is thrown. */
class CrashScreen(val exception: Throwable): BaseScreen() {
@ -52,7 +50,7 @@ class CrashScreen(val exception: Throwable): BaseScreen() {
return ""
return "\n**Save Data:**\n<details><summary>Show Saved Game</summary>\n\n```\n" +
try {
GameSaver.gameInfoToString(UncivGame.Current.gameInfo!!, forceZip = true)
UncivFiles.gameInfoToString(UncivGame.Current.gameInfo!!, forceZip = true)
} catch (e: Throwable) {
"No save data: $e" // In theory .toString() could still error here.
} + "\n```\n</details>\n"

View File

@ -272,7 +272,7 @@ class NewGameScreen(
newGame.isUpToDate = true // So we don't try to download it from dropbox the second after we upload it - the file is not yet ready for loading!
try {
game.onlineMultiplayer.createGame(newGame)
game.gameSaver.requestAutoSave(newGame)
game.files.requestAutoSave(newGame)
} catch (ex: FileStorageRateLimitReached) {
launchOnGLThread {
popup.reuseWith("Server limit reached! Please wait for [${ex.limitRemainingSeconds}] seconds", true)

View File

@ -2,7 +2,7 @@ package com.unciv.ui.options
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.logic.MapSaver
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.tile.ResourceType
@ -51,8 +51,8 @@ fun debugTab() = Table(BaseScreen.skin).apply {
curGameInfo.gameParameters.godMode = it
}).colspan(2).row()
}
add("Save games compressed".toCheckBox(GameSaver.saveZipped) {
GameSaver.saveZipped = it
add("Save games compressed".toCheckBox(UncivFiles.saveZipped) {
UncivFiles.saveZipped = it
}).colspan(2).row()
add("Save maps compressed".toCheckBox(MapSaver.saveZipped) {
MapSaver.saveZipped = it

View File

@ -7,7 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.SerializationException
import com.unciv.Constants
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.logic.MissingModsException
import com.unciv.logic.UncivShowableException
import com.unciv.models.ruleset.RulesetCache
@ -82,7 +82,7 @@ class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() {
Concurrency.run(loadGame) {
try {
// This is what can lead to ANRs - reading the file and setting the transients, that's why this is in another thread
val loadedGame = game.gameSaver.loadGameByName(selectedSave)
val loadedGame = game.files.loadGameByName(selectedSave)
game.loadGame(loadedGame)
} catch (ex: Exception) {
launchOnGLThread {
@ -119,7 +119,7 @@ class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() {
Concurrency.run(loadFromClipboard) {
try {
val clipboardContentsString = Gdx.app.clipboard.contents.trim()
val loadedGame = GameSaver.gameInfoFromString(clipboardContentsString)
val loadedGame = UncivFiles.gameInfoFromString(clipboardContentsString)
game.loadGame(loadedGame)
} catch (ex: Exception) {
launchOnGLThread { handleLoadGameException("Could not load game from clipboard!", ex) }
@ -133,14 +133,14 @@ class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() {
}
private fun Table.addLoadFromCustomLocationButton() {
if (!game.gameSaver.canLoadFromCustomSaveLocation()) return
if (!game.files.canLoadFromCustomSaveLocation()) return
val loadFromCustomLocation = loadFromCustomLocation.toTextButton()
loadFromCustomLocation.onClick {
errorLabel.isVisible = false
loadFromCustomLocation.setText(Constants.loading.tr())
loadFromCustomLocation.disable()
Concurrency.run(Companion.loadFromCustomLocation) {
game.gameSaver.loadGameFromCustomLocation { result ->
game.files.loadGameFromCustomLocation { result ->
if (result.isError()) {
handleLoadGameException("Could not load game from custom location!", result.exception)
} else if (result.isSuccessful()) {
@ -159,7 +159,7 @@ class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() {
copyButton.onActivation {
Concurrency.run(copyExistingSaveToClipboard) {
try {
val gameText = game.gameSaver.getSave(selectedSave).readString()
val gameText = game.files.getSave(selectedSave).readString()
Gdx.app.clipboard.contents = if (gameText[0] == '{') Gzip.zip(gameText) else gameText
} catch (ex: Throwable) {
ex.printStackTrace()

View File

@ -77,7 +77,7 @@ abstract class LoadOrSaveScreen(
if (selectedSave.isEmpty()) return
ConfirmPopup(this, "Are you sure you want to delete this save?", "Delete save") {
val result = try {
if (game.gameSaver.deleteSave(selectedSave)) {
if (game.files.deleteSave(selectedSave)) {
resetWindowState()
"[$selectedSave] deleted successfully."
} else {
@ -93,7 +93,7 @@ abstract class LoadOrSaveScreen(
}
private fun updateShownSaves(showAutosaves: Boolean) {
savesScrollPane.updateSaveGames(game.gameSaver, showAutosaves)
savesScrollPane.updateSaveGames(game.files, showAutosaves)
}
private fun selectExistingSave(saveGameFile: FileHandle) {
@ -109,7 +109,7 @@ abstract class LoadOrSaveScreen(
Concurrency.run("LoadMetaData") { // Even loading the game to get its metadata can take a long time on older phones
val textToSet = try {
val savedAt = Date(saveGameFile.lastModified())
val game = game.gameSaver.loadGamePreviewFromFile(saveGameFile)
val game = game.files.loadGamePreviewFromFile(saveGameFile)
val playerCivNames = game.civilizations
.filter { it.isPlayerCivilization() }.joinToString { it.civName.tr() }
val mods = if (game.gameParameters.mods.isEmpty()) ""

View File

@ -17,10 +17,10 @@ import com.unciv.utils.Log
object QuickSave {
fun save(gameInfo: GameInfo, screen: WorldScreen) {
val gameSaver = UncivGame.Current.gameSaver
val files = UncivGame.Current.files
val toast = ToastPopup("Quicksaving...", screen)
Concurrency.runOnNonDaemonThreadPool("QuickSaveGame") {
gameSaver.saveGame(gameInfo, "QuickSave") {
files.saveGame(gameInfo, "QuickSave") {
launchOnGLThread {
toast.close()
if (it != null)
@ -33,11 +33,11 @@ object QuickSave {
}
fun load(screen: WorldScreen) {
val gameSaver = UncivGame.Current.gameSaver
val files = UncivGame.Current.files
val toast = ToastPopup("Quickloading...", screen)
Concurrency.run("QuickLoadGame") {
try {
val loadedGame = gameSaver.loadGameByName("QuickSave")
val loadedGame = files.loadGameByName("QuickSave")
launchOnGLThread {
toast.close()
UncivGame.Current.loadGame(loadedGame)
@ -67,7 +67,7 @@ object QuickSave {
val savedGame: GameInfo
try {
savedGame = screen.game.gameSaver.loadLatestAutosave()
savedGame = screen.game.files.loadLatestAutosave()
} catch (oom: OutOfMemoryError) {
outOfMemory()
return@run

View File

@ -6,7 +6,7 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.models.translations.tr
import com.unciv.ui.popup.ConfirmPopup
import com.unciv.ui.popup.ToastPopup
@ -34,7 +34,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves")
rightSideButton.setText("Save game".tr())
rightSideButton.onActivation {
if (game.gameSaver.getSave(gameNameTextField.text).exists())
if (game.files.getSave(gameNameTextField.text).exists())
ConfirmPopup(
this,
"Overwrite existing file?",
@ -78,7 +78,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves")
Concurrency.run("Copy game to clipboard") {
// the Gzip rarely leads to ANRs
try {
Gdx.app.clipboard.contents = GameSaver.gameInfoToString(gameInfo, forceZip = true)
Gdx.app.clipboard.contents = UncivFiles.gameInfoToString(gameInfo, forceZip = true)
} catch (ex: Throwable) {
ex.printStackTrace()
launchOnGLThread {
@ -89,7 +89,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves")
}
private fun Table.addSaveToCustomLocation() {
if (!game.gameSaver.canLoadFromCustomSaveLocation()) return
if (!game.files.canLoadFromCustomSaveLocation()) return
val saveToCustomLocation = "Save to custom location".toTextButton()
val errorLabel = "".toLabel(Color.RED)
saveToCustomLocation.onClick {
@ -97,7 +97,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves")
saveToCustomLocation.setText("Saving...".tr())
saveToCustomLocation.disable()
Concurrency.runOnNonDaemonThreadPool("Save to custom location") {
game.gameSaver.saveGameToCustomLocation(gameInfo, gameNameTextField.text) { result ->
game.files.saveGameToCustomLocation(gameInfo, gameNameTextField.text) { result ->
if (result.isError()) {
errorLabel.setText("Could not save game to custom location!".tr())
result.exception?.printStackTrace()
@ -115,7 +115,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves")
private fun saveGame() {
rightSideButton.setText("Saving...".tr())
Concurrency.runOnNonDaemonThreadPool("SaveGame") {
game.gameSaver.saveGame(gameInfo, gameNameTextField.text) {
game.files.saveGame(gameInfo, gameNameTextField.text) {
launchOnGLThread {
if (it != null) ToastPopup("Could not save game!", this@SaveGameScreen)
else UncivGame.Current.popScreen()

View File

@ -7,7 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.Align
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.utils.AutoScrollPane
import com.unciv.ui.utils.BaseScreen
@ -48,8 +48,8 @@ class VerticalFileListScrollPane(
}
/** repopulate with existing saved games */
fun updateSaveGames(gameSaver: GameSaver, showAutosaves: Boolean) {
update(gameSaver.getSaves(showAutosaves)
fun updateSaveGames(files: UncivFiles, showAutosaves: Boolean) {
update(files.getSaves(showAutosaves)
.sortedByDescending { it.lastModified() })
}

View File

@ -816,7 +816,7 @@ class WorldScreen(
fun autoSave() {
waitingForAutosave = true
shouldUpdate = true
UncivGame.Current.gameSaver.requestAutoSave(gameInfo).invokeOnCompletion {
UncivGame.Current.files.requestAutoSave(gameInfo).invokeOnCompletion {
// only enable the user to next turn once we've saved the current one
waitingForAutosave = false
shouldUpdate = true

View File

@ -9,7 +9,7 @@ import com.badlogic.gdx.graphics.glutils.HdpiMode
import com.sun.jna.Native
import com.unciv.UncivGame
import com.unciv.UncivGameParameters
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.ui.utils.Fonts
import com.unciv.utils.Log
import com.unciv.utils.debug
@ -42,7 +42,7 @@ internal object DesktopLauncher {
// Note that means config.setAudioConfig() would be ignored too, those would need to go into the HardenedGdxAudio constructor.
config.disableAudio(true)
val settings = GameSaver.getSettingsForPlatformLaunchers()
val settings = UncivFiles.getSettingsForPlatformLaunchers()
if (!settings.isFreshlyCreated) {
config.setWindowedMode(settings.windowState.width.coerceAtLeast(120), settings.windowState.height.coerceAtLeast(80))
}

View File

@ -4,7 +4,7 @@ import com.badlogic.gdx.Gdx
import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.logic.UncivFiles
import com.unciv.logic.GameStarter
import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.map.MapParameters
@ -61,10 +61,10 @@ class SerializationTests {
}
val setup = GameSetupInfo(param, mapParameters)
UncivGame.Current = UncivGame("")
UncivGame.Current.gameSaver = GameSaver(Gdx.files)
UncivGame.Current.files = UncivFiles(Gdx.files)
// Both startNewGame and makeCivilizationsMeet will cause a save to storage of our empty settings
settingsBackup = UncivGame.Current.gameSaver.getGeneralSettings()
settingsBackup = UncivGame.Current.files.getGeneralSettings()
UncivGame.Current.settings = GameSettings()
game = GameStarter.startNewGame(setup)