mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-07 00:41:39 +07:00
Fix OutOfMemory error when loading game state after already having a game loaded (#7145)
* Fix OutOfMemory error when loading game state after already having a game loaded * Fix screen resize not being handled correctly * Add withContext shortcut functions * Add more logging * Fix multiplayer games sometimes being loaded twice * Make the loading screen nicer * Make the loading screen hide previous popups for making the screenshot * Don't do custom rendering & dispose the texture Sometimes it makes sense to understand the library you're using... * Fix missing GL context * Refactor: increase readability of loadGame function
This commit is contained in:
@ -202,9 +202,9 @@ class MainMenuScreen: BaseScreen() {
|
|||||||
if (curWorldScreen != null) {
|
if (curWorldScreen != null) {
|
||||||
game.resetToWorldScreen()
|
game.resetToWorldScreen()
|
||||||
curWorldScreen.popups.filterIsInstance(WorldScreenMenuPopup::class.java).forEach(Popup::close)
|
curWorldScreen.popups.filterIsInstance(WorldScreenMenuPopup::class.java).forEach(Popup::close)
|
||||||
return
|
} else {
|
||||||
|
QuickSave.autoLoadGame(this)
|
||||||
}
|
}
|
||||||
QuickSave.autoLoadGame(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun quickstartNewGame() {
|
private fun quickstartNewGame() {
|
||||||
@ -221,12 +221,14 @@ class MainMenuScreen: BaseScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ...or when loading the game
|
// ...or when loading the game
|
||||||
launchOnGLThread {
|
try {
|
||||||
try {
|
game.loadGame(newGame)
|
||||||
game.loadGame(newGame)
|
} catch (outOfMemory: OutOfMemoryError) {
|
||||||
} catch (outOfMemory: OutOfMemoryError) {
|
launchOnGLThread {
|
||||||
ToastPopup("Not enough memory on phone to load game!", this@MainMenuScreen)
|
ToastPopup("Not enough memory on phone to load game!", this@MainMenuScreen)
|
||||||
} catch (ex: Exception) {
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
launchOnGLThread {
|
||||||
ToastPopup(errorText, this@MainMenuScreen)
|
ToastPopup(errorText, this@MainMenuScreen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import com.unciv.models.ruleset.RulesetCache
|
|||||||
import com.unciv.models.tilesets.TileSetCache
|
import com.unciv.models.tilesets.TileSetCache
|
||||||
import com.unciv.models.translations.Translations
|
import com.unciv.models.translations.Translations
|
||||||
import com.unciv.ui.LanguagePickerScreen
|
import com.unciv.ui.LanguagePickerScreen
|
||||||
|
import com.unciv.ui.LoadingScreen
|
||||||
import com.unciv.ui.audio.GameSounds
|
import com.unciv.ui.audio.GameSounds
|
||||||
import com.unciv.ui.audio.MusicController
|
import com.unciv.ui.audio.MusicController
|
||||||
import com.unciv.ui.audio.MusicMood
|
import com.unciv.ui.audio.MusicMood
|
||||||
@ -23,7 +24,6 @@ import com.unciv.ui.audio.SoundPlayer
|
|||||||
import com.unciv.ui.crashhandling.CrashScreen
|
import com.unciv.ui.crashhandling.CrashScreen
|
||||||
import com.unciv.ui.crashhandling.wrapCrashHandlingUnit
|
import com.unciv.ui.crashhandling.wrapCrashHandlingUnit
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.multiplayer.LoadDeepLinkScreen
|
|
||||||
import com.unciv.ui.multiplayer.MultiplayerHelpers
|
import com.unciv.ui.multiplayer.MultiplayerHelpers
|
||||||
import com.unciv.ui.popup.Popup
|
import com.unciv.ui.popup.Popup
|
||||||
import com.unciv.ui.utils.BaseScreen
|
import com.unciv.ui.utils.BaseScreen
|
||||||
@ -33,6 +33,8 @@ import com.unciv.ui.worldscreen.WorldScreen
|
|||||||
import com.unciv.utils.Log
|
import com.unciv.utils.Log
|
||||||
import com.unciv.utils.concurrency.Concurrency
|
import com.unciv.utils.concurrency.Concurrency
|
||||||
import com.unciv.utils.concurrency.launchOnGLThread
|
import com.unciv.utils.concurrency.launchOnGLThread
|
||||||
|
import com.unciv.utils.concurrency.withGLContext
|
||||||
|
import com.unciv.utils.concurrency.withThreadPoolContext
|
||||||
import com.unciv.utils.debug
|
import com.unciv.utils.debug
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -51,6 +53,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
|||||||
|
|
||||||
var deepLinkedMultiplayerGame: String? = null
|
var deepLinkedMultiplayerGame: String? = null
|
||||||
var gameInfo: GameInfo? = null
|
var gameInfo: GameInfo? = null
|
||||||
|
private set
|
||||||
lateinit var settings: GameSettings
|
lateinit var settings: GameSettings
|
||||||
lateinit var musicController: MusicController
|
lateinit var musicController: MusicController
|
||||||
lateinit var onlineMultiplayer: OnlineMultiplayer
|
lateinit var onlineMultiplayer: OnlineMultiplayer
|
||||||
@ -104,7 +107,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
|||||||
* - Font (hence Fonts.resetFont() inside setSkin())
|
* - Font (hence Fonts.resetFont() inside setSkin())
|
||||||
*/
|
*/
|
||||||
settings = gameSaver.getGeneralSettings() // needed for the screen
|
settings = gameSaver.getGeneralSettings() // needed for the screen
|
||||||
setScreen(LoadingScreen()) // NOT dependent on any atlas or skin
|
setScreen(GameStartScreen()) // NOT dependent on any atlas or skin
|
||||||
GameSounds.init()
|
GameSounds.init()
|
||||||
musicController = MusicController() // early, but at this point does only copy volume from settings
|
musicController = MusicController() // early, but at this point does only copy volume from settings
|
||||||
audioExceptionHelper?.installHooks(
|
audioExceptionHelper?.installHooks(
|
||||||
@ -151,21 +154,66 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadGame(gameInfo: GameInfo): WorldScreen {
|
/** Loads a game, initializing the state of all important modules. Automatically runs on the appropriate thread. */
|
||||||
this.gameInfo = gameInfo
|
suspend fun loadGame(newGameInfo: GameInfo): WorldScreen = withThreadPoolContext toplevel@{
|
||||||
ImageGetter.setNewRuleset(gameInfo.ruleSet)
|
val prevGameInfo = gameInfo
|
||||||
// Clone the mod list and add the base ruleset to it
|
gameInfo = newGameInfo
|
||||||
val fullModList = gameInfo.gameParameters.getModsAndBaseRuleset()
|
|
||||||
musicController.setModList(fullModList)
|
initializeResources(prevGameInfo, newGameInfo)
|
||||||
Gdx.input.inputProcessor = null // Since we will set the world screen when we're ready,
|
|
||||||
val worldScreen = WorldScreen(gameInfo, gameInfo.getPlayerToViewAs())
|
val isLoadingSameGame = worldScreen != null && prevGameInfo != null && prevGameInfo.gameId == newGameInfo.gameId
|
||||||
val newScreen = if (gameInfo.civilizations.count { it.playerType == PlayerType.Human } > 1 && !gameInfo.gameParameters.isOnlineMultiplayer)
|
val worldScreenRestoreState = if (isLoadingSameGame) worldScreen!!.getRestoreState() else null
|
||||||
PlayerReadyScreen(worldScreen)
|
|
||||||
else {
|
withGLContext { setScreen(LoadingScreen(getScreen())) }
|
||||||
worldScreen
|
|
||||||
|
worldScreen?.dispose()
|
||||||
|
worldScreen = null // This allows the GC to collect our old WorldScreen, otherwise we keep two WorldScreens in memory.
|
||||||
|
|
||||||
|
return@toplevel withGLContext {
|
||||||
|
val worldScreen = WorldScreen(newGameInfo, newGameInfo.getPlayerToViewAs(), worldScreenRestoreState)
|
||||||
|
|
||||||
|
val moreThanOnePlayer = newGameInfo.civilizations.count { it.playerType == PlayerType.Human } > 1
|
||||||
|
val isSingleplayer = !newGameInfo.gameParameters.isOnlineMultiplayer
|
||||||
|
val screenToShow = if (moreThanOnePlayer && isSingleplayer) {
|
||||||
|
PlayerReadyScreen(worldScreen)
|
||||||
|
} else {
|
||||||
|
worldScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
setScreen(screenToShow)
|
||||||
|
|
||||||
|
return@withGLContext worldScreen
|
||||||
}
|
}
|
||||||
setScreen(newScreen)
|
}
|
||||||
return worldScreen
|
|
||||||
|
/** The new game info may have different mods or rulesets, which may use different resources that need to be loaded. */
|
||||||
|
private suspend fun initializeResources(prevGameInfo: GameInfo?, newGameInfo: GameInfo) {
|
||||||
|
if (prevGameInfo == null || prevGameInfo.ruleSet != newGameInfo.ruleSet) {
|
||||||
|
withGLContext {
|
||||||
|
ImageGetter.setNewRuleset(newGameInfo.ruleSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevGameInfo == null ||
|
||||||
|
prevGameInfo.gameParameters.baseRuleset != newGameInfo.gameParameters.baseRuleset ||
|
||||||
|
prevGameInfo.gameParameters.mods != newGameInfo.gameParameters.mods
|
||||||
|
) {
|
||||||
|
val fullModList = newGameInfo.gameParameters.getModsAndBaseRuleset()
|
||||||
|
musicController.setModList(fullModList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Re-creates the current [worldScreen], if there is any. */
|
||||||
|
fun reloadWorldscreen() {
|
||||||
|
val curWorldScreen = worldScreen
|
||||||
|
val curGameInfo = gameInfo
|
||||||
|
if (curWorldScreen == null || curGameInfo == null) return
|
||||||
|
|
||||||
|
Concurrency.run { loadGame(curGameInfo) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class NewScreens(val screenToShow: BaseScreen, val worldScreen: WorldScreen) {
|
||||||
|
constructor(worldScreen: WorldScreen) : this(worldScreen, worldScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,7 +265,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
|||||||
if (deepLinkedMultiplayerGame == null) return@run
|
if (deepLinkedMultiplayerGame == null) return@run
|
||||||
|
|
||||||
launchOnGLThread {
|
launchOnGLThread {
|
||||||
setScreen(LoadDeepLinkScreen())
|
setScreen(LoadingScreen(getScreen()!!))
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
onlineMultiplayer.loadGame(deepLinkedMultiplayerGame!!)
|
onlineMultiplayer.loadGame(deepLinkedMultiplayerGame!!)
|
||||||
@ -311,6 +359,21 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
|||||||
return if (screen == worldScreen) worldScreen else null
|
return if (screen == worldScreen) worldScreen else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
val mainMenuScreen = MainMenuScreen()
|
||||||
|
setScreen(mainMenuScreen)
|
||||||
|
return mainMenuScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets a simulated [GameInfo] object this game should run on */
|
||||||
|
fun startSimulation(simulatedGameInfo: GameInfo) {
|
||||||
|
gameInfo = simulatedGameInfo
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var Current: UncivGame
|
lateinit var Current: UncivGame
|
||||||
fun isCurrentInitialized() = this::Current.isInitialized
|
fun isCurrentInitialized() = this::Current.isInitialized
|
||||||
@ -319,7 +382,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoadingScreen : BaseScreen() {
|
private class GameStartScreen : BaseScreen() {
|
||||||
init {
|
init {
|
||||||
val happinessImage = ImageGetter.getExternalImage("LoadScreen.png")
|
val happinessImage = ImageGetter.getExternalImage("LoadScreen.png")
|
||||||
happinessImage.center(stage)
|
happinessImage.center(stage)
|
||||||
|
@ -12,8 +12,9 @@ import com.unciv.logic.multiplayer.storage.OnlineMultiplayerGameSaver
|
|||||||
import com.unciv.ui.utils.extensions.isLargerThan
|
import com.unciv.ui.utils.extensions.isLargerThan
|
||||||
import com.unciv.utils.concurrency.Concurrency
|
import com.unciv.utils.concurrency.Concurrency
|
||||||
import com.unciv.utils.concurrency.Dispatcher
|
import com.unciv.utils.concurrency.Dispatcher
|
||||||
import com.unciv.utils.concurrency.launchOnGLThread
|
|
||||||
import com.unciv.utils.concurrency.launchOnThreadPool
|
import com.unciv.utils.concurrency.launchOnThreadPool
|
||||||
|
import com.unciv.utils.concurrency.withGLContext
|
||||||
|
import com.unciv.utils.debug
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -98,7 +99,7 @@ class OnlineMultiplayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSavesFromFiles() {
|
private suspend fun updateSavesFromFiles() {
|
||||||
val saves = gameSaver.getMultiplayerSaves()
|
val saves = gameSaver.getMultiplayerSaves()
|
||||||
|
|
||||||
val removedSaves = savedGames.keys - saves.toSet()
|
val removedSaves = savedGames.keys - saves.toSet()
|
||||||
@ -143,20 +144,23 @@ class OnlineMultiplayer {
|
|||||||
addGame(gamePreview, saveFileName)
|
addGame(gamePreview, saveFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addGame(newGame: GameInfo) {
|
private suspend fun addGame(newGame: GameInfo) {
|
||||||
val newGamePreview = newGame.asPreview()
|
val newGamePreview = newGame.asPreview()
|
||||||
addGame(newGamePreview, newGamePreview.gameId)
|
addGame(newGamePreview, newGamePreview.gameId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addGame(preview: GameInfoPreview, saveFileName: String) {
|
private suspend fun addGame(preview: GameInfoPreview, saveFileName: String) {
|
||||||
val fileHandle = gameSaver.saveGame(preview, saveFileName)
|
val fileHandle = gameSaver.saveGame(preview, saveFileName)
|
||||||
return addGame(fileHandle, preview)
|
return addGame(fileHandle, preview)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addGame(fileHandle: FileHandle, preview: GameInfoPreview = gameSaver.loadGamePreviewFromFile(fileHandle)) {
|
private suspend fun addGame(fileHandle: FileHandle, preview: GameInfoPreview = gameSaver.loadGamePreviewFromFile(fileHandle)) {
|
||||||
|
debug("Adding game %s", preview.gameId)
|
||||||
val game = OnlineMultiplayerGame(fileHandle, preview, Instant.now())
|
val game = OnlineMultiplayerGame(fileHandle, preview, Instant.now())
|
||||||
savedGames[fileHandle] = game
|
savedGames[fileHandle] = game
|
||||||
Concurrency.runOnGLThread { EventBus.send(MultiplayerGameAdded(game.name)) }
|
withGLContext {
|
||||||
|
EventBus.send(MultiplayerGameAdded(game.name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGameByName(name: String): OnlineMultiplayerGame? {
|
fun getGameByName(name: String): OnlineMultiplayerGame? {
|
||||||
@ -230,7 +234,7 @@ class OnlineMultiplayer {
|
|||||||
} else if (onlinePreview != null && hasNewerGameState(preview, onlinePreview)){
|
} else if (onlinePreview != null && hasNewerGameState(preview, onlinePreview)){
|
||||||
onlineGame.doManualUpdate(preview)
|
onlineGame.doManualUpdate(preview)
|
||||||
}
|
}
|
||||||
launchOnGLThread { UncivGame.Current.loadGame(gameInfo) }
|
UncivGame.Current.loadGame(gameInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -241,7 +245,7 @@ class OnlineMultiplayer {
|
|||||||
val preview = onlineGameSaver.tryDownloadGamePreview(gameId)
|
val preview = onlineGameSaver.tryDownloadGamePreview(gameId)
|
||||||
if (hasLatestGameState(gameInfo, preview)) {
|
if (hasLatestGameState(gameInfo, preview)) {
|
||||||
gameInfo.isUpToDate = true
|
gameInfo.isUpToDate = true
|
||||||
launchOnGLThread { UncivGame.Current.loadGame(gameInfo) }
|
UncivGame.Current.loadGame(gameInfo)
|
||||||
} else {
|
} else {
|
||||||
loadGame(gameId)
|
loadGame(gameId)
|
||||||
}
|
}
|
||||||
@ -272,6 +276,7 @@ class OnlineMultiplayer {
|
|||||||
val game = savedGames[fileHandle]
|
val game = savedGames[fileHandle]
|
||||||
if (game == null) return
|
if (game == null) return
|
||||||
|
|
||||||
|
debug("Deleting game %s with id %s", fileHandle.name(), game.preview?.gameId)
|
||||||
savedGames.remove(game.fileHandle)
|
savedGames.remove(game.fileHandle)
|
||||||
Concurrency.runOnGLThread { EventBus.send(MultiplayerGameDeleted(game.name)) }
|
Concurrency.runOnGLThread { EventBus.send(MultiplayerGameDeleted(game.name)) }
|
||||||
}
|
}
|
||||||
@ -280,6 +285,7 @@ class OnlineMultiplayer {
|
|||||||
* Fires [MultiplayerGameNameChanged]
|
* Fires [MultiplayerGameNameChanged]
|
||||||
*/
|
*/
|
||||||
fun changeGameName(game: OnlineMultiplayerGame, newName: String) {
|
fun changeGameName(game: OnlineMultiplayerGame, newName: String) {
|
||||||
|
debug("Changing name of game %s to", game.name, newName)
|
||||||
val oldPreview = game.preview ?: throw game.error!!
|
val oldPreview = game.preview ?: throw game.error!!
|
||||||
val oldLastUpdate = game.lastUpdate
|
val oldLastUpdate = game.lastUpdate
|
||||||
val oldName = game.name
|
val oldName = game.name
|
||||||
@ -298,8 +304,10 @@ class OnlineMultiplayer {
|
|||||||
* @throws FileNotFoundException if the file can't be found
|
* @throws FileNotFoundException if the file can't be found
|
||||||
*/
|
*/
|
||||||
suspend fun updateGame(gameInfo: GameInfo) {
|
suspend fun updateGame(gameInfo: GameInfo) {
|
||||||
|
debug("Updating remote game %s", gameInfo.gameId)
|
||||||
onlineGameSaver.tryUploadGame(gameInfo, withPreview = true)
|
onlineGameSaver.tryUploadGame(gameInfo, withPreview = true)
|
||||||
val game = getGameByGameId(gameInfo.gameId)
|
val game = getGameByGameId(gameInfo.gameId)
|
||||||
|
debug("Existing OnlineMultiplayerGame: %s", game)
|
||||||
if (game == null) {
|
if (game == null) {
|
||||||
addGame(gameInfo)
|
addGame(gameInfo)
|
||||||
} else {
|
} else {
|
||||||
|
@ -8,6 +8,10 @@ import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
|
|||||||
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerGameSaver
|
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerGameSaver
|
||||||
import com.unciv.ui.utils.extensions.isLargerThan
|
import com.unciv.ui.utils.extensions.isLargerThan
|
||||||
import com.unciv.utils.concurrency.Concurrency
|
import com.unciv.utils.concurrency.Concurrency
|
||||||
|
import com.unciv.utils.concurrency.launchOnGLThread
|
||||||
|
import com.unciv.utils.concurrency.withGLContext
|
||||||
|
import com.unciv.utils.debug
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@ -62,13 +66,14 @@ class OnlineMultiplayerGame(
|
|||||||
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
||||||
* @throws FileNotFoundException if the file can't be found
|
* @throws FileNotFoundException if the file can't be found
|
||||||
*/
|
*/
|
||||||
suspend fun requestUpdate(forceUpdate: Boolean = false) {
|
suspend fun requestUpdate(forceUpdate: Boolean = false) = coroutineScope {
|
||||||
val onUnchanged = { GameUpdateResult.UNCHANGED }
|
val onUnchanged = { GameUpdateResult.UNCHANGED }
|
||||||
val onError = { e: Exception ->
|
val onError = { e: Exception ->
|
||||||
error = e
|
error = e
|
||||||
GameUpdateResult.FAILURE
|
GameUpdateResult.FAILURE
|
||||||
}
|
}
|
||||||
Concurrency.runOnGLThread {
|
debug("Starting multiplayer game update for %s with id %s", name, preview?.gameId)
|
||||||
|
launchOnGLThread {
|
||||||
EventBus.send(MultiplayerGameUpdateStarted(name))
|
EventBus.send(MultiplayerGameUpdateStarted(name))
|
||||||
}
|
}
|
||||||
val throttleInterval = if (forceUpdate) Duration.ZERO else getUpdateThrottleInterval()
|
val throttleInterval = if (forceUpdate) Duration.ZERO else getUpdateThrottleInterval()
|
||||||
@ -77,16 +82,24 @@ class OnlineMultiplayerGame(
|
|||||||
} else {
|
} else {
|
||||||
throttle(lastOnlineUpdate, throttleInterval, onUnchanged, onError, ::update)
|
throttle(lastOnlineUpdate, throttleInterval, onUnchanged, onError, ::update)
|
||||||
}
|
}
|
||||||
when (updateResult) {
|
|
||||||
GameUpdateResult.UNCHANGED, GameUpdateResult.CHANGED -> error = null
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
val updateEvent = when (updateResult) {
|
val updateEvent = when (updateResult) {
|
||||||
GameUpdateResult.CHANGED -> MultiplayerGameUpdated(name, preview!!)
|
GameUpdateResult.CHANGED -> {
|
||||||
GameUpdateResult.FAILURE -> MultiplayerGameUpdateFailed(name, error!!)
|
debug("Game update for %s with id %s had remote change", name, preview?.gameId)
|
||||||
GameUpdateResult.UNCHANGED -> MultiplayerGameUpdateUnchanged(name, preview!!)
|
MultiplayerGameUpdated(name, preview!!)
|
||||||
|
}
|
||||||
|
GameUpdateResult.FAILURE -> {
|
||||||
|
debug("Game update for %s with id %s failed: %s", name, preview?.gameId, error)
|
||||||
|
MultiplayerGameUpdateFailed(name, error!!)
|
||||||
|
}
|
||||||
|
GameUpdateResult.UNCHANGED -> {
|
||||||
|
debug("Game update for %s with id %s had no changes", name, preview?.gameId)
|
||||||
|
error = null
|
||||||
|
MultiplayerGameUpdateUnchanged(name, preview!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launchOnGLThread {
|
||||||
|
EventBus.send(updateEvent)
|
||||||
}
|
}
|
||||||
Concurrency.runOnGLThread { EventBus.send(updateEvent) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun update(): GameUpdateResult {
|
private suspend fun update(): GameUpdateResult {
|
||||||
@ -98,11 +111,14 @@ class OnlineMultiplayerGame(
|
|||||||
return GameUpdateResult.CHANGED
|
return GameUpdateResult.CHANGED
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doManualUpdate(gameInfo: GameInfoPreview) {
|
suspend fun doManualUpdate(gameInfo: GameInfoPreview) {
|
||||||
|
debug("Doing manual update of game %s", gameInfo.gameId)
|
||||||
lastOnlineUpdate.set(Instant.now())
|
lastOnlineUpdate.set(Instant.now())
|
||||||
error = null
|
error = null
|
||||||
preview = gameInfo
|
preview = gameInfo
|
||||||
Concurrency.runOnGLThread { EventBus.send(MultiplayerGameUpdated(name, gameInfo)) }
|
withGLContext {
|
||||||
|
EventBus.send(MultiplayerGameUpdated(name, gameInfo))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean = other is OnlineMultiplayerGame && fileHandle == other.fileHandle
|
override fun equals(other: Any?): Boolean = other is OnlineMultiplayerGame && fileHandle == other.fileHandle
|
||||||
|
51
core/src/com/unciv/ui/LoadingScreen.kt
Normal file
51
core/src/com/unciv/ui/LoadingScreen.kt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package com.unciv.ui
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.badlogic.gdx.graphics.Pixmap
|
||||||
|
import com.badlogic.gdx.graphics.Texture
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
|
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Image
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable
|
||||||
|
import com.unciv.Constants
|
||||||
|
import com.unciv.ui.images.ImageWithCustomSize
|
||||||
|
import com.unciv.ui.popup.Popup
|
||||||
|
import com.unciv.ui.popup.closeAllPopups
|
||||||
|
import com.unciv.ui.popup.popups
|
||||||
|
import com.unciv.ui.utils.BaseScreen
|
||||||
|
import com.unciv.ui.utils.extensions.toLabel
|
||||||
|
|
||||||
|
/** A loading screen that creates a screenshot of the current screen and adds a "Loading..." popup on top of that */
|
||||||
|
class LoadingScreen(
|
||||||
|
previousScreen: BaseScreen? = null
|
||||||
|
) : BaseScreen() {
|
||||||
|
val screenshot: Texture
|
||||||
|
init {
|
||||||
|
screenshot = takeScreenshot(previousScreen)
|
||||||
|
val image = ImageWithCustomSize(TextureRegion(screenshot, 0, screenshot.height, screenshot.width, -screenshot.height))
|
||||||
|
image.width = stage.width
|
||||||
|
image.height= stage.height
|
||||||
|
stage.addActor(image)
|
||||||
|
val popup = Popup(stage)
|
||||||
|
popup.add(Constants.loading.toLabel())
|
||||||
|
popup.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun takeScreenshot(previousScreen: BaseScreen?): Texture {
|
||||||
|
if (previousScreen != null) {
|
||||||
|
for (popup in previousScreen.popups) popup.isVisible = false
|
||||||
|
previousScreen.render(Gdx.graphics.getDeltaTime())
|
||||||
|
}
|
||||||
|
val screenshot = Texture(Pixmap.createFromFrameBuffer(0, 0, Gdx.graphics.backBufferWidth, Gdx.graphics.backBufferHeight))
|
||||||
|
if (previousScreen != null) {
|
||||||
|
for (popup in previousScreen.popups) popup.isVisible = true
|
||||||
|
}
|
||||||
|
return screenshot
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
screenshot.dispose()
|
||||||
|
super.dispose()
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
package com.unciv.ui.multiplayer
|
|
||||||
|
|
||||||
import com.unciv.Constants
|
|
||||||
import com.unciv.ui.utils.BaseScreen
|
|
||||||
import com.unciv.ui.utils.extensions.center
|
|
||||||
import com.unciv.ui.utils.extensions.toLabel
|
|
||||||
|
|
||||||
class LoadDeepLinkScreen : BaseScreen() {
|
|
||||||
init {
|
|
||||||
val loadingLabel = Constants.loading.toLabel()
|
|
||||||
stage.addActor(loadingLabel)
|
|
||||||
loadingLabel.center(stage)
|
|
||||||
}
|
|
||||||
}
|
|
@ -286,13 +286,14 @@ class NewGameScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launchOnGLThread {
|
val worldScreen = game.loadGame(newGame)
|
||||||
val worldScreen = game.loadGame(newGame)
|
|
||||||
if (newGame.gameParameters.isOnlineMultiplayer) {
|
if (newGame.gameParameters.isOnlineMultiplayer) {
|
||||||
// Save gameId to clipboard because you have to do it anyway.
|
launchOnGLThread {
|
||||||
Gdx.app.clipboard.contents = newGame.gameId
|
// Save gameId to clipboard because you have to do it anyway.
|
||||||
// Popup to notify the User that the gameID got copied to the clipboard
|
Gdx.app.clipboard.contents = newGame.gameId
|
||||||
ToastPopup("Game ID copied to clipboard!".tr(), worldScreen, 2500)
|
// Popup to notify the User that the gameID got copied to the clipboard
|
||||||
|
ToastPopup("Game ID copied to clipboard!".tr(), worldScreen, 2500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,12 +134,7 @@ class OptionsPopup(
|
|||||||
/** Reload this Popup after major changes (resolution, tileset, language, font) */
|
/** Reload this Popup after major changes (resolution, tileset, language, font) */
|
||||||
private fun reloadWorldAndOptions() {
|
private fun reloadWorldAndOptions() {
|
||||||
settings.save()
|
settings.save()
|
||||||
val worldScreen = UncivGame.Current.getWorldScreenIfActive()
|
UncivGame.Current.reloadWorldscreen()
|
||||||
if (worldScreen != null) {
|
|
||||||
val newWorldScreen = WorldScreen(worldScreen.gameInfo, worldScreen.viewingCiv)
|
|
||||||
worldScreen.game.setScreen(newWorldScreen)
|
|
||||||
newWorldScreen.openOptionsPopup(tabs.activePage)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addCheckbox(table: Table, text: String, initialState: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
|
fun addCheckbox(table: Table, text: String, initialState: Boolean, updateWorld: Boolean = false, action: ((Boolean) -> Unit)) {
|
||||||
|
@ -81,7 +81,7 @@ class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() {
|
|||||||
try {
|
try {
|
||||||
// This is what can lead to ANRs - reading the file and setting the transients, that's why this is in another thread
|
// 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.gameSaver.loadGameByName(selectedSave)
|
||||||
launchOnGLThread { game.loadGame(loadedGame) }
|
game.loadGame(loadedGame)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
launchOnGLThread {
|
launchOnGLThread {
|
||||||
loadingPopup.close()
|
loadingPopup.close()
|
||||||
@ -118,7 +118,7 @@ class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() {
|
|||||||
try {
|
try {
|
||||||
val clipboardContentsString = Gdx.app.clipboard.contents.trim()
|
val clipboardContentsString = Gdx.app.clipboard.contents.trim()
|
||||||
val loadedGame = GameSaver.gameInfoFromString(clipboardContentsString)
|
val loadedGame = GameSaver.gameInfoFromString(clipboardContentsString)
|
||||||
launchOnGLThread { game.loadGame(loadedGame) }
|
game.loadGame(loadedGame)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
launchOnGLThread { handleLoadGameException("Could not load game from clipboard!", ex) }
|
launchOnGLThread { handleLoadGameException("Could not load game from clipboard!", ex) }
|
||||||
}
|
}
|
||||||
@ -143,7 +143,9 @@ class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() {
|
|||||||
if (result.isError()) {
|
if (result.isError()) {
|
||||||
handleLoadGameException("Could not load game from custom location!", result.exception)
|
handleLoadGameException("Could not load game from custom location!", result.exception)
|
||||||
} else if (result.isSuccessful()) {
|
} else if (result.isSuccessful()) {
|
||||||
game.loadGame(result.gameData!!)
|
Concurrency.run {
|
||||||
|
game.loadGame(result.gameData!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,12 +94,12 @@ object QuickSave {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
launchOnGLThread { /// ... and load it into the screen on main thread for GL context
|
try {
|
||||||
try {
|
screen.game.loadGame(savedGame)
|
||||||
screen.game.loadGame(savedGame)
|
} catch (oom: OutOfMemoryError) {
|
||||||
} catch (oom: OutOfMemoryError) {
|
outOfMemory()
|
||||||
outOfMemory()
|
} catch (ex: Exception) {
|
||||||
} catch (ex: Exception) {
|
launchOnGLThread {
|
||||||
Log.error("Could not autoload game", ex)
|
Log.error("Could not autoload game", ex)
|
||||||
loadingPopup.close()
|
loadingPopup.close()
|
||||||
ToastPopup("Cannot resume game!", screen)
|
ToastPopup("Cannot resume game!", screen)
|
||||||
|
@ -78,38 +78,54 @@ import com.unciv.ui.worldscreen.unit.UnitTable
|
|||||||
import com.unciv.utils.concurrency.Concurrency
|
import com.unciv.utils.concurrency.Concurrency
|
||||||
import com.unciv.utils.concurrency.launchOnGLThread
|
import com.unciv.utils.concurrency.launchOnGLThread
|
||||||
import com.unciv.utils.concurrency.launchOnThreadPool
|
import com.unciv.utils.concurrency.launchOnThreadPool
|
||||||
|
import com.unciv.utils.concurrency.withGLContext
|
||||||
import com.unciv.utils.debug
|
import com.unciv.utils.debug
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unciv's world screen
|
* Do not create this screen without seriously thinking about the implications: this is the single most memory-intensive class in the application.
|
||||||
|
* There really should ever be only one in memory at the same time, likely managed by [UncivGame].
|
||||||
|
*
|
||||||
* @param gameInfo The game state the screen should represent
|
* @param gameInfo The game state the screen should represent
|
||||||
* @param viewingCiv The currently active [civilization][CivilizationInfo]
|
* @param viewingCiv The currently active [civilization][CivilizationInfo]
|
||||||
* @property shouldUpdate When set, causes the screen to update in the next [render][BaseScreen.render] event
|
* @param restoreState
|
||||||
* @property isPlayersTurn (readonly) Indicates it's the player's ([viewingCiv]) turn
|
|
||||||
* @property selectedCiv Selected civilization, used in spectator and replay mode, equals viewingCiv in ordinary games
|
|
||||||
* @property canChangeState (readonly) `true` when it's the player's turn unless he is a spectator
|
|
||||||
* @property mapHolder A [WorldMapHolder] instance
|
|
||||||
* @property bottomUnitTable Bottom left widget holding information about a selected unit or city
|
|
||||||
*/
|
*/
|
||||||
class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : BaseScreen() {
|
class WorldScreen(
|
||||||
|
val gameInfo: GameInfo,
|
||||||
|
val viewingCiv: CivilizationInfo,
|
||||||
|
restoreState: RestoreState? = null
|
||||||
|
) : BaseScreen() {
|
||||||
|
/** When set, causes the screen to update in the next [render][BaseScreen.render] event */
|
||||||
|
var shouldUpdate = false
|
||||||
|
|
||||||
|
/** Indicates it's the player's ([viewingCiv]) turn */
|
||||||
var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv
|
var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv
|
||||||
private set // only this class is allowed to make changes
|
private set // only this class is allowed to make changes
|
||||||
|
|
||||||
|
/** Selected civilization, used in spectator and replay mode, equals viewingCiv in ordinary games */
|
||||||
var selectedCiv = viewingCiv
|
var selectedCiv = viewingCiv
|
||||||
|
|
||||||
var fogOfWar = true
|
var fogOfWar = true
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
/** `true` when it's the player's turn unless he is a spectator*/
|
||||||
val canChangeState
|
val canChangeState
|
||||||
get() = isPlayersTurn && !viewingCiv.isSpectator()
|
get() = isPlayersTurn && !viewingCiv.isSpectator()
|
||||||
|
|
||||||
|
val mapHolder = WorldMapHolder(this, gameInfo.tileMap)
|
||||||
|
|
||||||
|
/** Bottom left widget holding information about a selected unit or city */
|
||||||
|
val bottomUnitTable = UnitTable(this)
|
||||||
|
|
||||||
private var waitingForAutosave = false
|
private var waitingForAutosave = false
|
||||||
private val mapVisualization = MapVisualization(gameInfo, viewingCiv)
|
private val mapVisualization = MapVisualization(gameInfo, viewingCiv)
|
||||||
|
|
||||||
val mapHolder = WorldMapHolder(this, gameInfo.tileMap)
|
|
||||||
private val minimapWrapper = MinimapHolder(mapHolder)
|
private val minimapWrapper = MinimapHolder(mapHolder)
|
||||||
|
|
||||||
private val topBar = WorldScreenTopBar(this)
|
private val topBar = WorldScreenTopBar(this)
|
||||||
val bottomUnitTable = UnitTable(this)
|
|
||||||
|
|
||||||
private val bottomTileInfoTable = TileInfoTable(viewingCiv)
|
private val bottomTileInfoTable = TileInfoTable(viewingCiv)
|
||||||
private val battleTable = BattleTable(this)
|
private val battleTable = BattleTable(this)
|
||||||
private val unitActionsTable = UnitActionsTable(this)
|
private val unitActionsTable = UnitActionsTable(this)
|
||||||
@ -124,8 +140,6 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
ImageGetter.getBlue().darken(0.5f)) }
|
ImageGetter.getBlue().darken(0.5f)) }
|
||||||
private val notificationsScroll = NotificationsScroll(this)
|
private val notificationsScroll = NotificationsScroll(this)
|
||||||
|
|
||||||
var shouldUpdate = false
|
|
||||||
|
|
||||||
private val zoomController = ZoomButtonPair(mapHolder)
|
private val zoomController = ZoomButtonPair(mapHolder)
|
||||||
|
|
||||||
private var nextTurnUpdateJob: Job? = null
|
private var nextTurnUpdateJob: Job? = null
|
||||||
@ -139,12 +153,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
|
|
||||||
minimapWrapper.x = stage.width - minimapWrapper.width
|
minimapWrapper.x = stage.width - minimapWrapper.width
|
||||||
|
|
||||||
try { // Most memory errors occur here, so this is a sort of catch-all
|
// This is the most memory-intensive operation we have currently, most OutOfMemory errors will occur here
|
||||||
mapHolder.addTiles()
|
mapHolder.addTiles()
|
||||||
} catch (outOfMemoryError: OutOfMemoryError) {
|
|
||||||
mapHolder.clear() // hopefully enough memory will be freed to be able to display the toast popup
|
|
||||||
ToastPopup("Not enough memory on phone to load game!", this)
|
|
||||||
}
|
|
||||||
|
|
||||||
// resume music (in case choices from the menu lead to instantiation of a new WorldScreen)
|
// resume music (in case choices from the menu lead to instantiation of a new WorldScreen)
|
||||||
UncivGame.Current.musicController.resume()
|
UncivGame.Current.musicController.resume()
|
||||||
@ -228,6 +238,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (restoreState != null) restore(restoreState)
|
||||||
|
|
||||||
// don't run update() directly, because the UncivGame.worldScreen should be set so that the city buttons and tile groups
|
// don't run update() directly, because the UncivGame.worldScreen should be set so that the city buttons and tile groups
|
||||||
// know what the viewing civ is.
|
// know what the viewing civ is.
|
||||||
shouldUpdate = true
|
shouldUpdate = true
|
||||||
@ -349,10 +361,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
}
|
}
|
||||||
launchOnGLThread {
|
launchOnGLThread {
|
||||||
loadingGamePopup.close()
|
loadingGamePopup.close()
|
||||||
if (game.gameInfo!!.gameId == gameInfo.gameId) { // game could've been changed during download
|
|
||||||
game.setScreen(createNewWorldScreen(latestGame))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
startNewScreenJob(latestGame)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
launchOnGLThread {
|
launchOnGLThread {
|
||||||
val message = MultiplayerHelpers.getLoadExceptionMessage(ex)
|
val message = MultiplayerHelpers.getLoadExceptionMessage(ex)
|
||||||
@ -425,12 +435,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
// it doesn't update the explored tiles of the civ... need to think about that harder
|
// it doesn't update the explored tiles of the civ... need to think about that harder
|
||||||
// it causes a bug when we move a unit to an unexplored tile (for instance a cavalry unit which can move far)
|
// it causes a bug when we move a unit to an unexplored tile (for instance a cavalry unit which can move far)
|
||||||
|
|
||||||
try { // Most memory errors occur here, so this is a sort of catch-all
|
if (fogOfWar) mapHolder.updateTiles(selectedCiv)
|
||||||
if (fogOfWar) mapHolder.updateTiles(selectedCiv)
|
else mapHolder.updateTiles(viewingCiv)
|
||||||
else mapHolder.updateTiles(viewingCiv)
|
|
||||||
} catch (outOfMemoryError: OutOfMemoryError) {
|
|
||||||
ToastPopup("Not enough memory on phone to load game!", this)
|
|
||||||
}
|
|
||||||
|
|
||||||
topBar.update(selectedCiv)
|
topBar.update(selectedCiv)
|
||||||
|
|
||||||
@ -606,26 +612,32 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNewWorldScreen(gameInfo: GameInfo, resize:Boolean=false): WorldScreen {
|
class RestoreState(
|
||||||
|
mapHolder: WorldMapHolder,
|
||||||
|
val selectedCivName: String,
|
||||||
|
val viewingCivName: String,
|
||||||
|
val fogOfWar: Boolean
|
||||||
|
) {
|
||||||
|
val zoom = mapHolder.scaleX
|
||||||
|
val scrollX = mapHolder.scrollX
|
||||||
|
val scrollY = mapHolder.scrollY
|
||||||
|
}
|
||||||
|
fun getRestoreState(): RestoreState {
|
||||||
|
return RestoreState(mapHolder, selectedCiv.civName, viewingCiv.civName, fogOfWar)
|
||||||
|
}
|
||||||
|
|
||||||
game.gameInfo = gameInfo
|
private fun restore(restoreState: RestoreState) {
|
||||||
val newWorldScreen = WorldScreen(gameInfo, gameInfo.getPlayerToViewAs())
|
|
||||||
|
|
||||||
// This is not the case if you have a multiplayer game where you play as 2 civs
|
// This is not the case if you have a multiplayer game where you play as 2 civs
|
||||||
if (!resize && newWorldScreen.viewingCiv.civName == viewingCiv.civName) {
|
if (viewingCiv.civName == restoreState.viewingCivName) {
|
||||||
newWorldScreen.mapHolder.width = mapHolder.width
|
mapHolder.zoom(restoreState.zoom)
|
||||||
newWorldScreen.mapHolder.height = mapHolder.height
|
mapHolder.scrollX = restoreState.scrollX
|
||||||
newWorldScreen.mapHolder.scaleX = mapHolder.scaleX
|
mapHolder.scrollY = restoreState.scrollY
|
||||||
newWorldScreen.mapHolder.scaleY = mapHolder.scaleY
|
mapHolder.updateVisualScroll()
|
||||||
newWorldScreen.mapHolder.scrollX = mapHolder.scrollX
|
|
||||||
newWorldScreen.mapHolder.scrollY = mapHolder.scrollY
|
|
||||||
newWorldScreen.mapHolder.updateVisualScroll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newWorldScreen.selectedCiv = gameInfo.getCivilization(selectedCiv.civName)
|
selectedCiv = gameInfo.getCivilization(restoreState.selectedCivName)
|
||||||
newWorldScreen.fogOfWar = fogOfWar
|
fogOfWar = restoreState.fogOfWar
|
||||||
|
|
||||||
return newWorldScreen
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextTurn() {
|
fun nextTurn() {
|
||||||
@ -665,32 +677,9 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
if (game.gameInfo != originalGameInfo) // while this was turning we loaded another game
|
if (game.gameInfo != originalGameInfo) // while this was turning we loaded another game
|
||||||
return@runOnNonDaemonThreadPool
|
return@runOnNonDaemonThreadPool
|
||||||
|
|
||||||
this@WorldScreen.game.gameInfo = gameInfoClone
|
|
||||||
debug("Next turn took %sms", System.currentTimeMillis() - startTime)
|
debug("Next turn took %sms", System.currentTimeMillis() - startTime)
|
||||||
|
|
||||||
val shouldAutoSave = gameInfoClone.turns % game.settings.turnsBetweenAutosaves == 0
|
startNewScreenJob(gameInfoClone)
|
||||||
|
|
||||||
// create a new WorldScreen to show the new stuff we've changed, and switch out the current screen.
|
|
||||||
// do this on main thread - it's the only one that has a GL context to create images from
|
|
||||||
launchOnGLThread {
|
|
||||||
val newWorldScreen = createNewWorldScreen(gameInfoClone)
|
|
||||||
if (gameInfoClone.currentPlayerCiv.civName != viewingCiv.civName
|
|
||||||
&& !gameInfoClone.gameParameters.isOnlineMultiplayer) {
|
|
||||||
game.setScreen(PlayerReadyScreen(newWorldScreen))
|
|
||||||
} else {
|
|
||||||
game.setScreen(newWorldScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldAutoSave) {
|
|
||||||
newWorldScreen.waitingForAutosave = true
|
|
||||||
newWorldScreen.shouldUpdate = true
|
|
||||||
game.gameSaver.requestAutoSave(gameInfoClone).invokeOnCompletion {
|
|
||||||
// only enable the user to next turn once we've saved the current one
|
|
||||||
newWorldScreen.waitingForAutosave = false
|
|
||||||
newWorldScreen.shouldUpdate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,8 +822,9 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun resize(width: Int, height: Int) {
|
override fun resize(width: Int, height: Int) {
|
||||||
if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height)
|
if (stage.viewport.screenWidth != width || stage.viewport.screenHeight != height) {
|
||||||
createNewWorldScreen(gameInfo, resize=true) // start over
|
startNewScreenJob(gameInfo) // start over
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -899,4 +889,34 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
ExitGamePopup(this, true)
|
ExitGamePopup(this, true)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun autoSave() {
|
||||||
|
waitingForAutosave = true
|
||||||
|
shouldUpdate = true
|
||||||
|
UncivGame.Current.gameSaver.requestAutoSave(gameInfo).invokeOnCompletion {
|
||||||
|
// only enable the user to next turn once we've saved the current one
|
||||||
|
waitingForAutosave = false
|
||||||
|
shouldUpdate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This exists so that no reference to the current world screen remains, so the old world screen can get garbage collected during [UncivGame.loadGame]. */
|
||||||
|
private fun startNewScreenJob(gameInfo: GameInfo) {
|
||||||
|
Concurrency.run {
|
||||||
|
val newWorldScreen = try {
|
||||||
|
UncivGame.Current.loadGame(gameInfo)
|
||||||
|
} catch (oom: OutOfMemoryError) {
|
||||||
|
withGLContext {
|
||||||
|
val mainMenu = UncivGame.Current.goToMainMenu()
|
||||||
|
ToastPopup("Not enough memory on phone to load game!", mainMenu)
|
||||||
|
}
|
||||||
|
return@run
|
||||||
|
}
|
||||||
|
|
||||||
|
val shouldAutoSave = gameInfo.turns % UncivGame.Current.settings.turnsBetweenAutosaves == 0
|
||||||
|
if (shouldAutoSave) {
|
||||||
|
newWorldScreen.autoSave()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,7 @@ class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen) {
|
|||||||
defaults().fillX()
|
defaults().fillX()
|
||||||
|
|
||||||
addButton("Main menu") {
|
addButton("Main menu") {
|
||||||
worldScreen.game.gameSaver.requestAutoSaveUnCloned(worldScreen.gameInfo) // Can save gameInfo directly because the user can't modify it on the MainMenuScreen
|
worldScreen.game.goToMainMenu()
|
||||||
worldScreen.game.setScreen(MainMenuScreen())
|
|
||||||
}
|
}
|
||||||
addButton("Civilopedia") {
|
addButton("Civilopedia") {
|
||||||
close()
|
close()
|
||||||
|
@ -12,6 +12,7 @@ import kotlinx.coroutines.Runnable
|
|||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.concurrent.CancellationException
|
import java.util.concurrent.CancellationException
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
@ -93,6 +94,13 @@ fun CoroutineScope.launchOnNonDaemonThreadPool(name: String? = null, block: susp
|
|||||||
/** See [launch]. Runs on the GDX GL thread. Use this for all code that manipulates the GDX UI classes. */
|
/** See [launch]. Runs on the GDX GL thread. Use this for all code that manipulates the GDX UI classes. */
|
||||||
fun CoroutineScope.launchOnGLThread(name: String? = null, block: suspend CoroutineScope.() -> Unit) = launchCrashHandling(Dispatcher.GL, name, block)
|
fun CoroutineScope.launchOnGLThread(name: String? = null, block: suspend CoroutineScope.() -> Unit) = launchCrashHandling(Dispatcher.GL, name, block)
|
||||||
|
|
||||||
|
/** See [withContext]. Runs on a daemon thread pool. Use this for code that does not necessarily need to finish executing. */
|
||||||
|
suspend fun <T> withThreadPoolContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatcher.DAEMON, block)
|
||||||
|
/** See [withContext]. Runs on a non-daemon thread pool. Use this if you do something that should always finish if possible, like saving the game. */
|
||||||
|
suspend fun <T> withNonDaemonThreadPoolContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatcher.NON_DAEMON, block)
|
||||||
|
/** See [withContext]. Runs on the GDX GL thread. Use this for all code that manipulates the GDX UI classes. */
|
||||||
|
suspend fun <T> withGLContext(block: suspend CoroutineScope.() -> T): T = withContext(Dispatcher.GL, block)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All dispatchers here bring the main game loop to a [com.unciv.CrashScreen] if an exception happens.
|
* All dispatchers here bring the main game loop to a [com.unciv.CrashScreen] if an exception happens.
|
||||||
|
@ -48,7 +48,7 @@ internal object ConsoleLauncher {
|
|||||||
val mapParameters = getMapParameters()
|
val mapParameters = getMapParameters()
|
||||||
val gameSetupInfo = GameSetupInfo(gameParameters, mapParameters)
|
val gameSetupInfo = GameSetupInfo(gameParameters, mapParameters)
|
||||||
val newGame = GameStarter.startNewGame(gameSetupInfo)
|
val newGame = GameStarter.startNewGame(gameSetupInfo)
|
||||||
UncivGame.Current.gameInfo = newGame
|
UncivGame.Current.startSimulation(newGame)
|
||||||
|
|
||||||
val simulation = Simulation(newGame,10,4)
|
val simulation = Simulation(newGame,10,4)
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ class SerializationTests {
|
|||||||
|
|
||||||
UncivGame.Current.settings = GameSettings()
|
UncivGame.Current.settings = GameSettings()
|
||||||
game = GameStarter.startNewGame(setup)
|
game = GameStarter.startNewGame(setup)
|
||||||
UncivGame.Current.gameInfo = game
|
UncivGame.Current.startSimulation(game)
|
||||||
|
|
||||||
// Found a city otherwise too many classes have no instance and are not tested
|
// Found a city otherwise too many classes have no instance and are not tested
|
||||||
val civ = game.getCurrentPlayerCivilization()
|
val civ = game.getCurrentPlayerCivilization()
|
||||||
|
Reference in New Issue
Block a user