Added support for new historyranking serialization - saved ~10% of save file size (#12079)

* Added support for new historyranking serialization - saved ~10% of save file size!

* Convert RankingType strings to chars

* Join to string using ranking char instead of generic delimiter

* Keep writing old format for now :)

* Handle negative rankings

* Fix test

* Minor perf

* Revert "Minor perf"

This reverts commit 470b3e9286.
This commit is contained in:
Yair Morgenstern
2024-08-08 14:54:33 +03:00
committed by GitHub
parent b9f907e74d
commit 210895295f
3 changed files with 49 additions and 23 deletions

View File

@ -20,32 +20,58 @@ class CivRankingHistory : HashMap<Int, Map<RankingType, Int>>(), IsPartOfGameInf
fun recordRankingStats(civilization: Civilization) { fun recordRankingStats(civilization: Civilization) {
this[civilization.gameInfo.turns] = this[civilization.gameInfo.turns] =
RankingType.values().associateWith { civilization.getStatForRanking(it) } RankingType.entries.associateWith { civilization.getStatForRanking(it) }
} }
/** Implement Json.Serializable /** Implement Json.Serializable
* - Output looked like this: `statsHistory:{0:{S:50,G:120,...},1:{S:55,G:80,...}}` * - Output looked like this: `statsHistory:{0:{S:50,G:120,...},1:{S:55,G:80,...}}`
* (but now we have turned off simplifed json, so it's properly quoted) * (but now we have turned off simplifed json, so it's properly quoted)
* - New format looks like this: `statsHistory:{0:"S50G120,...",1:"S55G80,..."}`
*/ */
override fun write(json: Json) { override fun write(json: Json) {
for ((turn, rankings) in this) { for ((turn, rankings) in this) {
// Old format - deprecated 4.12.18
json.writeObjectStart(turn.toString()) json.writeObjectStart(turn.toString())
for ((rankingType, score) in rankings) { for ((rankingType, score) in rankings) {
json.writeValue(rankingType.idForSerialization, score) json.writeValue(rankingType.idForSerialization.toString(), score)
} }
json.writeObjectEnd() json.writeObjectEnd()
// New format (disabled)
// val rankingsString = rankings.entries
// .joinToString("") { it.key.idForSerialization.toString() + it.value }
// json.writeValue(turn.toString(), rankingsString)
} }
} }
private val nonNumber = Regex("[^\\d-]") // Rankings can be negative, so we can't just \D :(
override fun read(json: Json, jsonData: JsonValue) { override fun read(json: Json, jsonData: JsonValue) {
for (entry in jsonData) { for (entry in jsonData) {
val turn = entry.name.toInt() val turn = entry.name.toInt()
val rankings = mutableMapOf<RankingType, Int>() val rankings = mutableMapOf<RankingType, Int>()
if (entry.isString){
// split into key-value pairs by adding a space before every non-digit, and splitting by spaces
val pairs = entry.asString().replace(nonNumber, " $0").split(" ")
.filter { it.isNotEmpty() } // remove empty entries
for (pair in pairs) {
val rankingType = RankingType.fromIdForSerialization(pair[0]) ?: continue
val value = pair.substring(1).toIntOrNull() ?: continue
rankings[rankingType] = value
}
// New format
} else {
// Old format
for (rankingEntry in entry) { for (rankingEntry in entry) {
val rankingType = RankingType.fromIdForSerialization(rankingEntry.name) if (rankingEntry.name.length != 1) continue
val rankingType = RankingType.fromIdForSerialization(rankingEntry.name[0])
?: continue // Silently drop unknown ranking types. ?: continue // Silently drop unknown ranking types.
rankings[rankingType] = rankingEntry.asInt() rankings[rankingType] = rankingEntry.asInt()
} }
}
this[turn] = rankings this[turn] = rankings
} }
} }

View File

@ -7,26 +7,26 @@ import com.unciv.ui.images.ImageGetter
enum class RankingType( enum class RankingType(
label: String?, label: String?,
val getImage: () -> Image?, val getImage: () -> Image?,
val idForSerialization: String val idForSerialization: Char
) { ) {
// production, gold, happiness, and culture already have icons added when the line is `tr()`anslated // production, gold, happiness, and culture already have icons added when the line is `tr()`anslated
Score({ ImageGetter.getImage("OtherIcons/Score").apply { color = Color.FIREBRICK } }, "S"), Score({ ImageGetter.getImage("OtherIcons/Score").apply { color = Color.FIREBRICK } }, 'S'),
Population({ ImageGetter.getStatIcon("Population") }, "N"), Population({ ImageGetter.getStatIcon("Population") }, 'N'),
CropYield("Crop Yield", { ImageGetter.getStatIcon("Food") }, "C"), CropYield("Crop Yield", { ImageGetter.getStatIcon("Food") }, 'C'),
Production("P"), Production('P'),
Gold("G"), Gold('G'),
Territory({ ImageGetter.getImage("OtherIcons/Hexagon") }, "T"), Territory({ ImageGetter.getImage("OtherIcons/Hexagon") }, 'T'),
Force({ ImageGetter.getImage("OtherIcons/Shield") }, "F"), Force({ ImageGetter.getImage("OtherIcons/Shield") }, 'F'),
Happiness("H"), Happiness('H'),
Technologies({ ImageGetter.getStatIcon("Science") }, "W"), Technologies({ ImageGetter.getStatIcon("Science") }, 'W'),
Culture("A") Culture('A'),
; ;
val label = label ?: name val label = label ?: name
constructor(getImage: () -> Image?, idForSerialization: String) : this(null, getImage, idForSerialization) constructor(getImage: () -> Image?, idForSerialization: Char) : this(null, getImage, idForSerialization)
constructor(idForSerialization: String) : this(null, { null }, idForSerialization) constructor(idForSerialization: Char) : this(null, { null }, idForSerialization)
companion object { companion object {
fun fromIdForSerialization(s: String): RankingType? = fun fromIdForSerialization(char: Char): RankingType? =
values().firstOrNull { it.idForSerialization == s } entries.firstOrNull { it.idForSerialization == char }
} }
} }

View File

@ -10,8 +10,8 @@ class RankingTypeTests {
@Test @Test
fun checkIdForSerializationUniqueness() { fun checkIdForSerializationUniqueness() {
val uniqueIds = HashSet<String>() val uniqueIds = HashSet<Char>()
for (rankingType in RankingType.values()) { for (rankingType in RankingType.entries) {
val id = rankingType.idForSerialization val id = rankingType.idForSerialization
Assert.assertTrue( Assert.assertTrue(
"Id $id for RankingType $rankingType is not unique", "Id $id for RankingType $rankingType is not unique",