Separate higher-level autosave functions from lower-level file functions

This commit is contained in:
Yair Morgenstern 2023-12-19 23:45:12 +02:00
parent 7a28f15106
commit 851ab2e7b8
7 changed files with 36 additions and 39 deletions

View File

@ -47,10 +47,10 @@ import com.unciv.utils.debug
import com.unciv.utils.launchOnGLThread
import com.unciv.utils.withGLContext
import com.unciv.utils.withThreadPoolContext
import kotlinx.coroutines.CancellationException
import java.io.PrintWriter
import java.util.EnumSet
import java.util.UUID
import kotlinx.coroutines.CancellationException
open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpecific {
@ -376,7 +376,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
// already has, but if we do _our_ pause before the MusicController timer notices, it will at least remember the current track.
if (::musicController.isInitialized) musicController.pause()
val curGameInfo = gameInfo
if (curGameInfo != null) files.requestAutoSave(curGameInfo)
if (curGameInfo != null) files.autosaves.requestAutoSave(curGameInfo)
super.pause()
}
@ -395,7 +395,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
val curGameInfo = gameInfo
if (curGameInfo != null) {
val autoSaveJob = files.autoSaveJob
val autoSaveJob = files.autosaves.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
@ -403,7 +403,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
autoSaveJob.join()
}
} else {
files.autoSave(curGameInfo) // NO new thread
files.autosaves.autoSave(curGameInfo) // NO new thread
}
}
settings.save()
@ -450,7 +450,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
fun goToMainMenu(): MainMenuScreen {
val curGameInfo = gameInfo
if (curGameInfo != null) {
files.requestAutoSaveUnCloned(curGameInfo) // Can save gameInfo directly because the user can't modify it on the MainMenuScreen
files.autosaves.requestAutoSaveUnCloned(curGameInfo) // Can save gameInfo directly because the user can't modify it on the MainMenuScreen
}
val mainMenuScreen = MainMenuScreen()
pushScreen(mainMenuScreen)

View File

@ -22,9 +22,9 @@ import com.unciv.ui.screens.savescreens.Gzip
import com.unciv.utils.Concurrency
import com.unciv.utils.Log
import com.unciv.utils.debug
import kotlinx.coroutines.Job
import java.io.File
import java.io.Writer
import kotlinx.coroutines.Job
private const val SAVE_FILES_FOLDER = "SaveFiles"
private const val MULTIPLAYER_FILES_FOLDER = "MultiplayerGames"
@ -42,11 +42,9 @@ class UncivFiles(
debug("Creating UncivFiles, localStoragePath: %s, externalStoragePath: %s",
files.localStoragePath, files.externalStoragePath)
}
//region Data
var autoSaveJob: Job? = null
val autosaves = Autosaves(this)
//endregion
//region Helpers
fun getSave(gameName: String): FileHandle {
@ -64,8 +62,8 @@ class UncivFiles(
val externalFile = files.external(location)
val toReturn = if (files.isExternalStorageAvailable && (
preferExternalStorage && (externalFile.exists() || !localFile.exists())
|| !preferExternalStorage && (externalFile.exists() && !localFile.exists())
externalFile.exists() && !localFile.exists() || // external file is only valid choice
preferExternalStorage && (externalFile.exists() || !localFile.exists()) // unless local file is only valid choice, choose external
) ) {
externalFile
} else {
@ -80,14 +78,16 @@ class UncivFiles(
* @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)
}
val file = pathToFileHandler(path)
return file.writer(append, Charsets.UTF_8.name())
}
fun pathToFileHandler(path: String): FileHandle {
return if (preferExternalStorage && files.isExternalStorageAvailable) files.external(path)
else files.local(path)
}
fun getMultiplayerSaves(): Sequence<FileHandle> {
return getSaves(MULTIPLAYER_FILES_FOLDER)
}
@ -387,9 +387,11 @@ class UncivFiles(
}
}
}
//endregion
//region Autosave
class Autosaves(val files: UncivFiles) {
var autoSaveJob: Job? = null
/**
* Auto-saves a snapshot of the [gameInfo] in a new thread.
@ -414,7 +416,7 @@ class UncivFiles(
fun autoSave(gameInfo: GameInfo, nextTurn: Boolean = false) {
try {
saveGame(gameInfo, AUTOSAVE_FILE_NAME)
files.saveGame(gameInfo, AUTOSAVE_FILE_NAME)
} catch (oom: OutOfMemoryError) {
Log.error("Ran out of memory during autosave", oom)
return // not much we can do here
@ -424,40 +426,32 @@ class UncivFiles(
if (nextTurn) {
val newAutosaveFilename =
SAVE_FILES_FOLDER + File.separator + AUTOSAVE_FILE_NAME + "-${gameInfo.currentPlayer}-${gameInfo.turns}"
val file =
if (preferExternalStorage && files.isExternalStorageAvailable)
files.external(newAutosaveFilename)
else
files.local(newAutosaveFilename)
getSave(AUTOSAVE_FILE_NAME).copyTo(file)
val file = files.pathToFileHandler(newAutosaveFilename)
files.getSave(AUTOSAVE_FILE_NAME).copyTo(file)
fun getAutosaves(): Sequence<FileHandle> {
return getSaves().filter { it.name().startsWith(AUTOSAVE_FILE_NAME) }
return files.getSaves().filter { it.name().startsWith(AUTOSAVE_FILE_NAME) }
}
while (getAutosaves().count() > 10) {
val saveToDelete = getAutosaves().minByOrNull { it.lastModified() }!!
deleteSave(saveToDelete.name())
files.deleteSave(saveToDelete.name())
}
}
}
fun loadLatestAutosave(): GameInfo {
return try {
loadGameByName(AUTOSAVE_FILE_NAME)
files.loadGameByName(AUTOSAVE_FILE_NAME)
} catch (_: Exception) {
// silent fail if we can't read the autosave for any reason - try to load the last autosave by turn number first
val autosaves = getSaves().filter { it.name() != AUTOSAVE_FILE_NAME && it.name().startsWith(
val autosaves = files.getSaves().filter { it.name() != AUTOSAVE_FILE_NAME && it.name().startsWith(
AUTOSAVE_FILE_NAME
) }
loadGameFromFile(autosaves.maxByOrNull { it.lastModified() }!!)
files.loadGameFromFile(autosaves.maxByOrNull { it.lastModified() }!!)
}
}
fun autosaveExists(): Boolean {
return getSave(AUTOSAVE_FILE_NAME).exists()
}
// endregion
fun autosaveExists(): Boolean = files.getSave(AUTOSAVE_FILE_NAME).exists()
}
class IncompatibleGameInfoVersionException(

View File

@ -137,7 +137,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.files.autosaveExists()) {
if (game.files.autosaves.autosaveExists()) {
val resumeTable = getMenuButton("Resume","OtherIcons/Resume", KeyboardBinding.Resume)
{ resumeGame() }
column1.add(resumeTable).row()

View File

@ -322,7 +322,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.files.requestAutoSave(newGame)
game.files.autosaves.requestAutoSave(newGame)
} catch (ex: FileStorageRateLimitReached) {
launchOnGLThread {
popup.reuseWith("Server limit reached! Please wait for [${ex.limitRemainingSeconds}] seconds", true)

View File

@ -71,7 +71,7 @@ object QuickSave {
val savedGame: GameInfo
try {
savedGame = screen.game.files.loadLatestAutosave()
savedGame = screen.game.files.autosaves.loadLatestAutosave()
} catch (_: OutOfMemoryError) {
outOfMemory()
return@run

View File

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

View File

@ -2137,6 +2137,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: TriggerCondition
??? example "&lt;upon turn end&gt;"
Applicable to: TriggerCondition
??? example "&lt;upon founding a Pantheon&gt;"
Applicable to: TriggerCondition