Fixed concurrency problems when working on "next turn" by working on a clone of the game state

This commit is contained in:
Yair Morgenstern 2019-08-15 10:15:27 +03:00
parent 05d3aa9193
commit 202c1828a7
3 changed files with 49 additions and 46 deletions

View File

@ -29,7 +29,7 @@ enum class GameSpeed{
} }
} }
class GameParameters{ class GameParameters { // Default values are the default new game
var difficulty = "Prince" var difficulty = "Prince"
var gameSpeed = GameSpeed.Standard var gameSpeed = GameSpeed.Standard
var mapRadius = 20 var mapRadius = 20

View File

@ -16,9 +16,7 @@ class PlayerReadyScreen(currentPlayerCiv: CivilizationInfo) : CameraStageBaseScr
.setFontColor(currentPlayerCiv.getNation().getSecondaryColor())) .setFontColor(currentPlayerCiv.getNation().getSecondaryColor()))
table.onClick { table.onClick {
UnCivGame.Current.worldScreen = WorldScreen(currentPlayerCiv).apply { UnCivGame.Current.worldScreen = WorldScreen(currentPlayerCiv)
shouldUpdate = true
}
UnCivGame.Current.setWorldScreen() UnCivGame.Current.setWorldScreen()
} }
table.setFillParent(true) table.setFillParent(true)

View File

@ -165,16 +165,16 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
notificationsScroll.setPosition(stage.width - notificationsScroll.width - 5f, notificationsScroll.setPosition(stage.width - notificationsScroll.width - 5f,
nextTurnButton.y - notificationsScroll.height - 5f) nextTurnButton.y - notificationsScroll.height - 5f)
val isSomethingOpen = tutorials.isTutorialShowing || stage.actors.any { it is TradePopup }
|| AlertPopup.isOpen
if(!isSomethingOpen) {
when { when {
!gameInfo.oneMoreTurnMode && gameInfo.civilizations.any { it.victoryManager.hasWon() } -> game.screen = VictoryScreen() !gameInfo.oneMoreTurnMode && gameInfo.civilizations.any { it.victoryManager.hasWon() } -> game.screen = VictoryScreen()
viewingCiv.policies.freePolicies > 0 -> game.screen = PolicyPickerScreen(viewingCiv) viewingCiv.policies.freePolicies > 0 -> game.screen = PolicyPickerScreen(viewingCiv)
viewingCiv.greatPeople.freeGreatPeople > 0 -> game.screen = GreatPersonPickerScreen() viewingCiv.greatPeople.freeGreatPeople > 0 -> game.screen = GreatPersonPickerScreen()
viewingCiv.tradeRequests.isNotEmpty() ->{ viewingCiv.tradeRequests.isNotEmpty() -> TradePopup(this)
TradePopup(this) viewingCiv.popupAlerts.any() -> AlertPopup(this, viewingCiv.popupAlerts.first())
} }
!tutorials.isTutorialShowing
&& viewingCiv.popupAlerts.any() && !AlertPopup.isOpen ->
AlertPopup(this,viewingCiv.popupAlerts.first())
} }
updateNextTurnButton() updateNextTurnButton()
} }
@ -259,42 +259,53 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
return@onClick return@onClick
} }
nextTurn(nextTurnButton) // If none of the above nextTurn() // If none of the above
} }
return nextTurnButton return nextTurnButton
} }
private fun nextTurn(nextTurnButton: TextButton) { private fun nextTurn() {
isPlayersTurn = false isPlayersTurn = false
shouldUpdate = true shouldUpdate = true
thread { thread {
val gameInfoClone = gameInfo.clone()
gameInfoClone.setTransients()
try { try {
gameInfo.nextTurn() gameInfoClone.nextTurn()
} catch (ex: Exception) { } catch (ex: Exception) {
game.settings.hasCrashedRecently = true game.settings.hasCrashedRecently = true
game.settings.save() game.settings.save()
throw ex throw ex
} }
if (gameInfo.turns % game.settings.turnsBetweenAutosaves == 0) { game.gameInfo = gameInfoClone
GameSaver().autoSave(gameInfo) {
nextTurnButton.enable() // only enable the user to next turn once we've saved the current one
updateNextTurnButton()
}
} else nextTurnButton.enable() // Enable immediately
// If we put this BEFORE the save game, then we try to save the game... val shouldAutoSave = gameInfoClone.turns % game.settings.turnsBetweenAutosaves == 0
// but the main thread does other stuff, including showing tutorials which guess what? Changes the game data
// BOOM! Exception!
// That's why this needs to be after the game is saved.
isPlayersTurn = true
shouldUpdate = true
// do this on main thread // 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
Gdx.app.postRunnable { Gdx.app.postRunnable {
updateNextTurnButton() if(gameInfoClone.currentPlayerCiv.civName == viewingCiv.civName) {
val newWorldScreen = WorldScreen(gameInfoClone.currentPlayerCiv)
newWorldScreen.tileMapHolder.scrollX = tileMapHolder.scrollX
newWorldScreen.tileMapHolder.scrollY = tileMapHolder.scrollY
newWorldScreen.tileMapHolder.scaleX = tileMapHolder.scaleX
newWorldScreen.tileMapHolder.scaleY = tileMapHolder.scaleY
newWorldScreen.tileMapHolder.updateVisualScroll()
game.worldScreen = newWorldScreen
game.setWorldScreen()
}
else UnCivGame.Current.screen = PlayerReadyScreen(gameInfoClone.getCurrentPlayerCivilization())
if(shouldAutoSave) {
game.worldScreen.nextTurnButton.disable()
GameSaver().autoSave(gameInfoClone) {
game.worldScreen.nextTurnButton.enable() // only enable the user to next turn once we've saved the current one
}
}
} }
} }
} }
@ -324,7 +335,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
} }
} }
var shouldUpdate=true var shouldUpdate=false
override fun render(delta: Float) { override fun render(delta: Float) {
@ -333,12 +344,6 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
if (shouldUpdate) { if (shouldUpdate) {
shouldUpdate = false shouldUpdate = false
if (viewingCiv != gameInfo.getCurrentPlayerCivilization()) {
UnCivGame.Current.worldScreen.dispose() // for memory saving
UnCivGame.Current.screen = PlayerReadyScreen(gameInfo.getCurrentPlayerCivilization())
return
}
update() update()
showTutorialsOnNextTurn() showTutorialsOnNextTurn()
} }