From ed97c5b0ea8ecfe98d0ecd5d5672d504492dcc65 Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Thu, 5 Oct 2023 09:24:27 +0200 Subject: [PATCH] Notifications remove backward compatibility (#10228) * Remove backwards compatibility code from Notification serializer * Notification constructor simplified * One measly typo --- .../unciv/logic/civilization/Notification.kt | 90 +++++-------------- .../logic/civilization/NotificationActions.kt | 2 +- 2 files changed, 22 insertions(+), 70 deletions(-) diff --git a/core/src/com/unciv/logic/civilization/Notification.kt b/core/src/com/unciv/logic/civilization/Notification.kt index b1aea65b51..2b33f49a18 100644 --- a/core/src/com/unciv/logic/civilization/Notification.kt +++ b/core/src/com/unciv/logic/civilization/Notification.kt @@ -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, + notificationIcons: Array, // `out` needed so we can pass a vararg directly actions: Iterable?, 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 { - 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() - .map { it.location }.toTypedArray() - json.writeObjectStart("action") - json.writeValue("class", "com.unciv.logic.civilization.LocationAction") - json.writeValue("locations", locations, Array::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 { - val locations = json.readValue("locations", Array::class.java, actionData) - return locations.asSequence().map { LocationAction(it) } - } } } diff --git a/core/src/com/unciv/logic/civilization/NotificationActions.kt b/core/src/com/unciv/logic/civilization/NotificationActions.kt index db2568ea8f..aa839d8b9c 100644 --- a/core/src/com/unciv/logic/civilization/NotificationActions.kt +++ b/core/src/com/unciv/logic/civilization/NotificationActions.kt @@ -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.