Fixed ANR caused by too many saved games

This commit is contained in:
Yair Morgenstern 2020-12-10 10:42:02 +02:00
parent 4578994adf
commit ab548dda54
2 changed files with 109 additions and 97 deletions

View File

@ -1,10 +1,13 @@
package com.unciv.ui.saves package com.unciv.ui.saves
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.Align
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.logic.UncivShowableException import com.unciv.logic.UncivShowableException
@ -129,37 +132,55 @@ class LoadGameScreen(previousScreen:CameraStageBaseScreen) : PickerScreen() {
private fun updateLoadableGames(showAutosaves:Boolean) { private fun updateLoadableGames(showAutosaves:Boolean) {
saveTable.clear() saveTable.clear()
for (save in GameSaver.getSaves().sortedByDescending { it.lastModified() }) {
if (save.name().startsWith("Autosave") && !showAutosaves) continue
val textButton = TextButton(save.name(), skin)
textButton.onClick {
selectedSave = save.name()
copySavedGameToClipboardButton.enable()
var textToSet = save.name()
val savedAt = Date(save.lastModified()) val loadImage =ImageGetter.getImage("OtherIcons/Load")
descriptionLabel.setText("Loading...".tr()) loadImage.setSize(50f,50f) // So the origin sets correctly
textToSet += "\n{Saved at}: ".tr() + SimpleDateFormat("yyyy-MM-dd HH:mm").format(savedAt) loadImage.setOrigin(Align.center)
thread { // Even loading the game to get its metadata can take a long time on older phones loadImage.addAction(Actions.rotateBy(360f, 2f))
try { saveTable.add(loadImage).size(50f)
val game = GameSaver.loadGameFromFile(save)
val playerCivNames = game.civilizations.filter { it.isPlayerCivilization() }.joinToString { it.civName.tr() }
textToSet += "\n" + playerCivNames +
", " + game.difficulty.tr() + ", ${Fonts.turn}" + game.turns
} catch (ex: Exception) {
textToSet += "\n{Could not load game}!".tr()
}
Gdx.app.postRunnable { thread { // Apparently, even jut getting the list of saves can cause ANRs -
descriptionLabel.setText(textToSet) // not sure how many saves these guys had but Google Play reports this to have happened hundreds of times
rightSideButton.setText("Load [${save.name()}]".tr()) // .toList() because otherwise the lastModified will only be checked inside the postRunnable
rightSideButton.enable() val saves = GameSaver.getSaves().sortedByDescending { it.lastModified() }.toList()
deleteSaveButton.enable()
deleteSaveButton.color = Color.RED Gdx.app.postRunnable {
} saveTable.clear()
for (save in saves) {
if (save.name().startsWith("Autosave") && !showAutosaves) continue
val textButton = TextButton(save.name(), skin)
textButton.onClick { onSaveSelected(save) }
saveTable.add(textButton).pad(5f).row()
} }
} }
saveTable.add(textButton).pad(5f).row() }
}
private fun onSaveSelected(save: FileHandle) {
selectedSave = save.name()
copySavedGameToClipboardButton.enable()
var textToSet = save.name()
val savedAt = Date(save.lastModified())
descriptionLabel.setText("Loading...".tr())
textToSet += "\n{Saved at}: ".tr() + SimpleDateFormat("yyyy-MM-dd HH:mm").format(savedAt)
thread { // Even loading the game to get its metadata can take a long time on older phones
try {
val game = GameSaver.loadGameFromFile(save)
val playerCivNames = game.civilizations.filter { it.isPlayerCivilization() }.joinToString { it.civName.tr() }
textToSet += "\n" + playerCivNames +
", " + game.difficulty.tr() + ", ${Fonts.turn}" + game.turns
} catch (ex: Exception) {
textToSet += "\n{Could not load game}!".tr()
}
Gdx.app.postRunnable {
descriptionLabel.setText(textToSet)
rightSideButton.setText("Load [${save.name()}]".tr())
rightSideButton.enable()
deleteSaveButton.enable()
deleteSaveButton.color = Color.RED
}
} }
} }

View File

@ -36,9 +36,9 @@ object ImageGetter {
// We then shove all the drawables into a hashmap, because the atlas specifically tells us // We then shove all the drawables into a hashmap, because the atlas specifically tells us
// that the search on it is inefficient // that the search on it is inefficient
val textureRegionDrawables = HashMap<String,TextureRegionDrawable>() val textureRegionDrawables = HashMap<String, TextureRegionDrawable>()
init{ init {
reload() reload()
} }
@ -61,7 +61,7 @@ object ImageGetter {
val tempAtlas = TextureAtlas("$singleImagesFolder.atlas") val tempAtlas = TextureAtlas("$singleImagesFolder.atlas")
for (region in tempAtlas.regions) { for (region in tempAtlas.regions) {
val drawable = TextureRegionDrawable(region) val drawable = TextureRegionDrawable(region)
textureRegionDrawables["$singleImagesFolder/"+region.name] = drawable textureRegionDrawables["$singleImagesFolder/" + region.name] = drawable
} }
} }
@ -77,12 +77,11 @@ object ImageGetter {
// These are from the mods // These are from the mods
for (mod in ruleset.mods) { for (mod in ruleset.mods) {
val modAtlasFile = Gdx.files.local("mods/$mod/game.atlas") val modAtlasFile = Gdx.files.local("mods/$mod/game.atlas")
if (modAtlasFile.exists()) { if (!modAtlasFile.exists()) continue
val modAtlas = TextureAtlas(modAtlasFile) val modAtlas = TextureAtlas(modAtlasFile)
for (region in modAtlas.regions) { for (region in modAtlas.regions) {
val drawable = TextureRegionDrawable(region) val drawable = TextureRegionDrawable(region)
textureRegionDrawables[region.name] = drawable textureRegionDrawables[region.name] = drawable
}
} }
} }
} }
@ -142,16 +141,10 @@ object ImageGetter {
return layerList return layerList
} }
/*fun refreshAtlas() { fun getWhiteDot() = getImage(whiteDotLocation)
atlas.dispose() // To avoid OutOfMemory exceptions fun getDot(dotColor: Color) = getWhiteDot().apply { color = dotColor }
atlas = TextureAtlas("game.atlas")
setTextureRegionDrawables()
}*/
fun getWhiteDot() = getImage(whiteDotLocation) fun getExternalImage(fileName: String): Image {
fun getDot(dotColor: Color) = getWhiteDot().apply { color = dotColor}
fun getExternalImage(fileName:String): Image {
return Image(TextureRegion(Texture("ExtraImages/$fileName"))) return Image(TextureRegion(Texture("ExtraImages/$fileName")))
} }
@ -160,55 +153,54 @@ object ImageGetter {
} }
fun getDrawable(fileName: String): TextureRegionDrawable { fun getDrawable(fileName: String): TextureRegionDrawable {
if(textureRegionDrawables.containsKey(fileName)) return textureRegionDrawables[fileName]!! if (textureRegionDrawables.containsKey(fileName)) return textureRegionDrawables[fileName]!!
else return textureRegionDrawables[whiteDotLocation]!! else return textureRegionDrawables[whiteDotLocation]!!
} }
fun getRoundedEdgeTableBackground(tintColor: Color?=null): NinePatchDrawable { fun getRoundedEdgeTableBackground(tintColor: Color? = null): NinePatchDrawable {
val drawable = NinePatchDrawable(NinePatch(getDrawable("OtherIcons/buttonBackground").region,25,25,0,0)).apply { val drawable = NinePatchDrawable(NinePatch(getDrawable("OtherIcons/buttonBackground").region, 25, 25, 0, 0)).apply {
setPadding(5f,15f,5f,15f) setPadding(5f, 15f, 5f, 15f)
} }
if(tintColor==null) return drawable if (tintColor == null) return drawable
return drawable.tint(tintColor) return drawable.tint(tintColor)
} }
fun imageExists(fileName:String) = textureRegionDrawables.containsKey(fileName) fun imageExists(fileName: String) = textureRegionDrawables.containsKey(fileName)
fun techIconExists(techName:String) = imageExists("TechIcons/$techName") fun techIconExists(techName: String) = imageExists("TechIcons/$techName")
fun getStatIcon(statName: String): Image { fun getStatIcon(statName: String): Image {
return getImage("StatIcons/$statName") return getImage("StatIcons/$statName")
.apply { setSize(20f,20f)} .apply { setSize(20f, 20f) }
} }
fun getUnitIcon(unitName:String,color:Color= Color.BLACK):Image{ fun getUnitIcon(unitName: String, color: Color = Color.BLACK): Image {
return getImage("UnitIcons/$unitName").apply { this.color=color } return getImage("UnitIcons/$unitName").apply { this.color = color }
} }
fun getNationIndicator(nation: Nation, size:Float): IconCircleGroup { fun getNationIndicator(nation: Nation, size: Float): IconCircleGroup {
val civIconName = if(nation.isCityState()) "CityState" else nation.name val civIconName = if (nation.isCityState()) "CityState" else nation.name
if(nationIconExists(civIconName)){ if (nationIconExists(civIconName)) {
val cityStateIcon = getNationIcon(civIconName) val cityStateIcon = getNationIcon(civIconName)
cityStateIcon.color = nation.getInnerColor() cityStateIcon.color = nation.getInnerColor()
return cityStateIcon.surroundWithCircle(size*0.9f).apply { circle.color = nation.getOuterColor() } return cityStateIcon.surroundWithCircle(size * 0.9f).apply { circle.color = nation.getOuterColor() }
.surroundWithCircle(size,false).apply { circle.color=nation.getInnerColor() } .surroundWithCircle(size, false).apply { circle.color = nation.getInnerColor() }
} } else {
else{
return getCircle().apply { color = nation.getOuterColor() } return getCircle().apply { color = nation.getOuterColor() }
.surroundWithCircle(size).apply { circle.color = nation.getInnerColor() } .surroundWithCircle(size).apply { circle.color = nation.getInnerColor() }
} }
} }
fun nationIconExists(nation:String) = imageExists("NationIcons/$nation") fun nationIconExists(nation: String) = imageExists("NationIcons/$nation")
fun getNationIcon(nation:String) = getImage("NationIcons/$nation") fun getNationIcon(nation: String) = getImage("NationIcons/$nation")
val foodCircleColor = colorFromRGB(129, 199, 132) val foodCircleColor = colorFromRGB(129, 199, 132)
val productionCircleColor = Color.BROWN.cpy().lerp(Color.WHITE,0.5f)!! val productionCircleColor = Color.BROWN.cpy().lerp(Color.WHITE, 0.5f)!!
val goldCircleColor = Color.GOLD.cpy().lerp(Color.WHITE,0.5f)!! val goldCircleColor = Color.GOLD.cpy().lerp(Color.WHITE, 0.5f)!!
val cultureCircleColor = Color.PURPLE.cpy().lerp(Color.WHITE,0.5f)!! val cultureCircleColor = Color.PURPLE.cpy().lerp(Color.WHITE, 0.5f)!!
val scienceCircleColor = Color.BLUE.cpy().lerp(Color.WHITE,0.5f)!! val scienceCircleColor = Color.BLUE.cpy().lerp(Color.WHITE, 0.5f)!!
fun getColorFromStats(stats:Stats) = when { fun getColorFromStats(stats: Stats) = when {
stats.food > 0 -> foodCircleColor stats.food > 0 -> foodCircleColor
stats.production > 0 -> productionCircleColor stats.production > 0 -> productionCircleColor
stats.gold > 0 -> goldCircleColor stats.gold > 0 -> goldCircleColor
@ -218,7 +210,7 @@ object ImageGetter {
} }
fun getImprovementIcon(improvementName:String, size:Float=20f):Actor { fun getImprovementIcon(improvementName: String, size: Float = 20f): Actor {
if (improvementName.startsWith("Remove") || improvementName == Constants.cancelImprovementOrder) if (improvementName.startsWith("Remove") || improvementName == Constants.cancelImprovementOrder)
return getImage("OtherIcons/Stop") return getImage("OtherIcons/Stop")
if (improvementName.startsWith("StartingLocation ")) { if (improvementName.startsWith("StartingLocation ")) {
@ -243,28 +235,28 @@ object ImageGetter {
return getStatIcon(construction) return getStatIcon(construction)
} }
fun getPromotionIcon(promotionName:String): Actor { fun getPromotionIcon(promotionName: String): Actor {
var level = 0 var level = 0
when { when {
promotionName.endsWith(" I") -> level=1 promotionName.endsWith(" I") -> level = 1
promotionName.endsWith(" II") -> level=2 promotionName.endsWith(" II") -> level = 2
promotionName.endsWith(" III") -> level=3 promotionName.endsWith(" III") -> level = 3
} }
val basePromotionName = if(level==0) promotionName val basePromotionName = if (level == 0) promotionName
else promotionName.substring(0, promotionName.length-level-1) else promotionName.substring(0, promotionName.length - level - 1)
if(imageExists("UnitPromotionIcons/$basePromotionName")) { if (imageExists("UnitPromotionIcons/$basePromotionName")) {
val icon = getImage("UnitPromotionIcons/$basePromotionName") val icon = getImage("UnitPromotionIcons/$basePromotionName")
icon.color = colorFromRGB(255,226,0) icon.color = colorFromRGB(255, 226, 0)
val circle = icon.surroundWithCircle(30f) val circle = icon.surroundWithCircle(30f)
circle.circle.color = colorFromRGB(0,12,49) circle.circle.color = colorFromRGB(0, 12, 49)
if(level!=0){ if (level != 0) {
val starTable = Table().apply { defaults().pad(2f) } val starTable = Table().apply { defaults().pad(2f) }
for(i in 1..level) starTable.add(getImage("OtherIcons/Star")).size(8f) for (i in 1..level) starTable.add(getImage("OtherIcons/Star")).size(8f)
starTable.centerX(circle) starTable.centerX(circle)
starTable.y=5f starTable.y = 5f
circle.addActor(starTable) circle.addActor(starTable)
} }
return circle return circle
@ -277,15 +269,15 @@ object ImageGetter {
fun getCircle() = getImage("OtherIcons/Circle") fun getCircle() = getImage("OtherIcons/Circle")
fun getTriangle() = getImage("OtherIcons/Triangle") fun getTriangle() = getImage("OtherIcons/Triangle")
fun getBackground(color:Color): Drawable { fun getBackground(color: Color): Drawable {
val drawable = getDrawable("OtherIcons/TableBackground") val drawable = getDrawable("OtherIcons/TableBackground")
drawable.minHeight=0f drawable.minHeight = 0f
drawable.minWidth=0f drawable.minWidth = 0f
return drawable.tint(color) return drawable.tint(color)
} }
fun getResourceImage(resourceName: String, size:Float): Actor { fun getResourceImage(resourceName: String, size: Float): Actor {
val iconGroup = getImage("ResourceIcons/$resourceName").surroundWithCircle(size) val iconGroup = getImage("ResourceIcons/$resourceName").surroundWithCircle(size)
val resource = ruleset.tileResources[resourceName] val resource = ruleset.tileResources[resourceName]
if (resource == null) return iconGroup // This is the result of a bad modding setup, just give em an empty circle. Their problem. if (resource == null) return iconGroup // This is the result of a bad modding setup, just give em an empty circle. Their problem.
@ -322,7 +314,7 @@ object ImageGetter {
.surroundWithCircle(circleSize) .surroundWithCircle(circleSize)
} }
fun getProgressBarVertical(width:Float,height:Float,percentComplete:Float,progressColor:Color,backgroundColor:Color): Table { fun getProgressBarVertical(width: Float, height: Float, percentComplete: Float, progressColor: Color, backgroundColor: Color): Table {
val advancementGroup = Table() val advancementGroup = Table()
val completionHeight = height * percentComplete val completionHeight = height * percentComplete
advancementGroup.add(getImage(whiteDotLocation).apply { color = backgroundColor }) advancementGroup.add(getImage(whiteDotLocation).apply { color = backgroundColor })
@ -353,7 +345,7 @@ object ImageGetter {
return healthBar return healthBar
} }
fun getLine(startX:Float,startY:Float,endX:Float,endY:Float, width:Float): Image { fun getLine(startX: Float, startY: Float, endX: Float, endY: Float, width: Float): Image {
/** The simplest way to draw a line between 2 points seems to be: /** The simplest way to draw a line between 2 points seems to be:
* A. Get a pixel dot, set its width to the required length (hypotenuse) * A. Get a pixel dot, set its width to the required length (hypotenuse)
* B. Set its rotational center, and set its rotation * B. Set its rotational center, and set its rotation
@ -362,9 +354,9 @@ object ImageGetter {
// A // A
val line = getWhiteDot() val line = getWhiteDot()
val deltaX = (startX-endX).toDouble() val deltaX = (startX - endX).toDouble()
val deltaY = (startY-endY).toDouble() val deltaY = (startY - endY).toDouble()
line.width = Math.sqrt(deltaX*deltaX+deltaY*deltaY).toFloat() line.width = Math.sqrt(deltaX * deltaX + deltaY * deltaY).toFloat()
line.height = width // the width of the line, is the height of the line.height = width // the width of the line, is the height of the
// B // B
@ -373,16 +365,15 @@ object ImageGetter {
line.rotation = (Math.atan2(deltaY, deltaX) * radiansToDegrees).toFloat() line.rotation = (Math.atan2(deltaY, deltaX) * radiansToDegrees).toFloat()
// C // C
line.x = (startX+endX)/2 - line.width/2 line.x = (startX + endX) / 2 - line.width / 2
line.y = (startY+endY)/2 - line.height/2 line.y = (startY + endY) / 2 - line.height / 2
return line return line
} }
fun getSpecialistIcon(color:Color): Image { fun getSpecialistIcon(color: Color): Image {
val specialist = getImage("StatIcons/Specialist") val specialist = getImage("StatIcons/Specialist")
specialist.color = color specialist.color = color
return specialist return specialist
} }
} }