Notifications remove backward compatibility (#10228)

* Remove backwards compatibility code from Notification serializer

* Notification constructor simplified

* One measly typo
This commit is contained in:
SomeTroglodyte
2023-10-05 09:24:27 +02:00
committed by GitHub
parent 8c6e046fc8
commit ed97c5b0ea
2 changed files with 22 additions and 70 deletions

View File

@ -1,6 +1,5 @@
package com.unciv.logic.civilization
import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Json
@ -32,15 +31,13 @@ open class Notification() : IsPartOfGameInfoSerialization {
constructor(
text: String,
notificationIcons: Array<out String>,
notificationIcons: Array<out String>, // `out` needed so we can pass a vararg directly
actions: Iterable<NotificationAction>?,
category: NotificationCategory = NotificationCategory.General
) : this() {
this.category = category
this.text = text
if (notificationIcons.isNotEmpty()) {
this.icons = notificationIcons.toCollection(ArrayList())
}
notificationIcons.toCollection(this.icons)
actions?.toCollection(this.actions)
}
@ -98,25 +95,23 @@ open class Notification() : IsPartOfGameInfoSerialization {
/**
* Custom [Gdx.Json][Json] serializer/deserializer for one [Notification].
*
* Migration roadmap:
*
* 1.) Change internal structures but write old json format
* 2.) Wait for good distribution in multiplayer user base
* 3.) Switch to writing new format
* 4.) Wait for Versions prior to Step 3 to fade out, keep switch for quick revert
* 5.) Remove Switch, old format routines and this comment
*
* Caveats:
*
* * New format can express Notifications the old can't.
* In that case, in Phase 1, reduce to first action and throw away the rest.
* Example of the serialized format:
* ```json
* "notifications":[
* {
* "category":"Production",
* "text":"[Nobel Foundation] has been built in [Stockholm]",
* "icons":["BuildingIcons/Nobel Foundation"],
* "actions":[
* {"LocationAction":{"location":{"x":9,"y":3}}},
* {"CivilopediaAction":{"link":"Wonder/Nobel Foundation"}},
* {"CityAction":{"city":{"x":9,"y":3}}}
* ]
* }
* ]
* ```
*/
class Serializer : Json.Serializer<Notification> {
companion object {
/** The switch that starts Phase III and dies with Phase V
* @see Serializer */
private const val compatibilityMode = false
}
override fun write(json: Json, notification: Notification, knownType: Class<*>?) {
json.writeObjectStart()
@ -127,13 +122,12 @@ open class Notification() : IsPartOfGameInfoSerialization {
if (notification.icons.isNotEmpty())
json.writeValue("icons", notification.icons, null, String::class.java)
if (compatibilityMode) writeOldFormatAction(json, notification)
else writeNewFormatActions(json, notification)
writeActions(json, notification)
json.writeObjectEnd()
}
private fun writeNewFormatActions(json: Json, notification: Notification) {
private fun writeActions(json: Json, notification: Notification) {
if (notification.actions.isEmpty()) return
json.writeArrayStart("actions")
for (action in notification.actions) {
@ -146,35 +140,14 @@ open class Notification() : IsPartOfGameInfoSerialization {
json.writeArrayEnd()
}
private fun writeOldFormatAction(json: Json, notification: Notification) {
if (notification.actions.isEmpty()) return
val firstAction = notification.actions.first()
if (firstAction !is LocationAction) {
json.writeValue("action", firstAction, null)
return
}
val locations = notification.actions.filterIsInstance<LocationAction>()
.map { it.location }.toTypedArray()
json.writeObjectStart("action")
json.writeValue("class", "com.unciv.logic.civilization.LocationAction")
json.writeValue("locations", locations, Array<Vector2>::class.java, Vector2::class.java)
json.writeObjectEnd()
}
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?) = Notification().apply {
// Cannot be distinguished 100% certain by field names but if neither action / actions exist then both formats are compatible
json.readField(this, "category", jsonData)
json.readField(this, "text", jsonData)
readOldFormatAction(json, jsonData)
readNewFormatActions(json, jsonData)
readActions(json, jsonData)
json.readField(this, "icons", jsonData)
}
private fun Notification.readNewFormatActions(json: Json, jsonData: JsonValue) {
// New format looks like this: "notifications":[
// {"category":"Cities","text":"[Stockholm] has expanded its borders!","icons":["StatIcons/Culture"],"actions":[{"LocationAction":{"location":{"x":7,"y":1}}},{"LocationAction":{"location":{"x":9,"y":3}}}]},
// {"category":"Production","text":"[Nobel Foundation] has been built in [Stockholm]","icons":["BuildingIcons/Nobel Foundation"],"actions":[{"LocationAction":{"location":{"x":9,"y":3}}}]}
// ]
private fun Notification.readActions(json: Json, jsonData: JsonValue) {
if (!jsonData.hasChild("actions")) return
var entry = jsonData.get("actions").child
while (entry != null) {
@ -182,26 +155,5 @@ open class Notification() : IsPartOfGameInfoSerialization {
entry = entry.next
}
}
private fun Notification.readOldFormatAction(json: Json, jsonData: JsonValue) {
// Old format looks like: "notifications":[
// {"text":"[Stockholm] has expanded its borders!","icons":["StatIcons/Culture"],"action":{"class":"com.unciv.logic.civilization.LocationAction","locations":[{"x":7,"y":1},{"x":9,"y":3}]},"category":"Cities"},
// {"text":"[Nobel Foundation] has been built in [Stockholm]","icons":["BuildingIcons/Nobel Foundation"],"action":{"class":"com.unciv.logic.civilization.LocationAction","locations":[{"x":9,"y":3}]},"category":"Production"}
// ]
val actionData = jsonData.get("action") ?: return
val actionClass = actionData.getString("class")
when (actionClass.substring(actionClass.lastIndexOf('.') + 1)) {
"LocationAction" -> actions += getOldFormatLocations(json, actionData)
"TechAction" -> actions += json.readValue(TechAction::class.java, actionData)
"CityAction" -> actions += json.readValue(CityAction::class.java, actionData)
"DiplomacyAction" -> actions += json.readValue(DiplomacyAction::class.java, actionData)
"MayaLongCountAction" -> actions += MayaLongCountAction()
}
}
private fun getOldFormatLocations(json: Json, actionData: JsonValue): Sequence<LocationAction> {
val locations = json.readValue("locations", Array<Vector2>::class.java, actionData)
return locations.asSequence().map { LocationAction(it) }
}
}
}

View File

@ -133,7 +133,7 @@ class OverviewAction(
internal class NotificationActionsDeserializer {
/* This exists as trick to leverage readFields for Json deserialization.
// The serializer writes each NotificationAction as json object (within the actions array),
// containing the class simpleName as subfield name, which carries any (on none) subclass-
// containing the class simpleName as subfield name, which carries any (or none) subclass-
// specific data as its object value. So, reading this from json data will fill just one of the
// fields, and the listOfNotNull will output that field only.
// Even though we know there's only one result, no need to first() since it's no advantage to the caller.