(CQ) Linting and comments on TileSetStrings and ImageAttempter (#10582)

* (CQ) Linting and comments on TileSetStrings and ImageAttempter

* (CQ) Linting and comments on TileSetStrings and ImageAttempter - some more
This commit is contained in:
SomeTroglodyte 2023-11-28 11:07:54 +01:00 committed by GitHub
parent 50ce8b30b6
commit 20fa1d3f06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 53 additions and 24 deletions

View File

@ -9,11 +9,29 @@ import com.unciv.models.tilesets.TileSetConfig
import com.unciv.ui.images.ImageAttempter
import com.unciv.ui.images.ImageGetter
@Suppress("MemberVisibilityCanBePrivate") // No advandage hiding them
/**
* Resolver translating more abstract tile data to paint on a map into actual texture names.
*
* Deals with variants, e.g. there could be a "City center-asian-Ancient era.png" that would be chosen
* for a "City center"-containing Tile when it is to be drawn for a Nation defining it's style as "asian"
* and whose techs say it's still in the first vanilla Era.
*
* Instantiated once per [TileGroupMap] and per [TileSet][com.unciv.models.tilesets.TileSet] -
* typically once for HexaRealm and once for FantasyHex (fallback) at the start of a player turn,
* and the same two every time they enter a CityScreen.
*
* @param tileSet Name of the tileset. Defaults to active at time of instantiation.
* @param fallbackDepth Maximum number of fallback tilesets to try. Used to prevent infinite recursion.
* */
class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitSet: String? = UncivGame.Current.settings.unitSet, fallbackDepth: Int = 1) {
class TileSetStrings(
tileSet: String = UncivGame.Current.settings.tileSet,
unitSet: String? = UncivGame.Current.settings.unitSet,
fallbackDepth: Int = 1
) {
/** Separator used to mark variants, e.g. nation style or era specific */
val tag = "-"
// this is so that when we have 100s of TileGroups, they won't all individually come up with all these strings themselves,
// it gets pretty memory-intensive (10s of MBs which is a lot for lower-end phones)
@ -24,7 +42,7 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
val tileSetConfig = TileSetCache[tileSet]?.config ?: TileSetConfig()
// These need to be by lazy since the orFallback expects a tileset, which it may not get.
val hexagon: String by lazy { orFallback {tileSetLocation + "Hexagon"} }
val hexagon: String by lazy { orFallback { tileSetLocation + "Hexagon"} }
val hexagonList by lazy { listOf(hexagon) }
val crosshatchHexagon by lazy { orFallback { tileSetLocation + "CrosshatchHexagon" } }
val unexploredTile by lazy { orFallback { tileSetLocation + "UnexploredTile" } }
@ -68,7 +86,6 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
return currentString
}
val tag = "-"
fun getTile(baseTerrain: String) = getString(tilesLocation, baseTerrain)
fun getBorder(borderShapeString: String, innerOrOuter:String) = getString(bordersLocation, borderShapeString, innerOrOuter)
@ -81,7 +98,6 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
TileSetStrings(tileSetConfig.fallbackTileSet!!, tileSetConfig.fallbackTileSet!!, fallbackDepth-1)
}
@Suppress("MemberVisibilityCanBePrivate")
/**
* @param image An image path string, such as returned from an instance of [TileSetStrings].
* @param fallbackImage A lambda function that will be run with the [fallback] as its receiver if the original image does not exist according to [ImageGetter.imageExists].
@ -90,18 +106,18 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
fun orFallback(image: String, fallbackImage: TileSetStrings.() -> String): String {
return if (fallback == null || ImageGetter.imageExists(image))
image
else fallback!!.run(fallbackImage)
else fallbackImage.invoke(fallback!!)
}
/** @see orFallback */
fun orFallback(image: TileSetStrings.() -> String)
= orFallback(this.run(image), image)
= orFallback(image.invoke(this), image)
/** For caching image locations based on given parameters (era, style, etc)
* Based on what the final image would look like if all parameters existed,
* like "pikeman-Medieval era-France": "pikeman" */
* like "pikeman-France-Medieval era": "pikeman" */
val imageParamsToImageLocation = HashMap<String,String>()
@ -110,6 +126,7 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
val embarkedCivilianUnitLocation = getString(unitsLocation, "EmbarkedUnit-Civilian")
val hasEmbarkedCivilianUnitImage = ImageGetter.imageExists(embarkedCivilianUnitLocation)
/**
* Image fallbacks work by precedence.
* So currently, if you're france, it's the modern era, and you have a pikeman:
@ -119,7 +136,6 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
* This means that if there's a "pikeman-France" and a "pikeman-Medieval era",
* The era-based image wins out, even though it's not the current era.
*/
private fun tryGetUnitImageLocation(unit: MapUnit): String? {
var baseUnitIconLocation = getString(this.unitsLocation, unit.name)
@ -139,13 +155,13 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
val style = civInfo.nation.getStyleOrCivName()
var imageAttempter = ImageAttempter(baseUnitIconLocation)
// Era+style image: looks like "pikeman-Medieval era-France"
// Era+style image: looks like "pikeman-France-Medieval era"
// More advanced eras default to older eras
.tryEraImage(civInfo, baseUnitIconLocation, style, this)
// Era-only image: looks like "pikeman-Medieval era"
.tryEraImage(civInfo, baseUnitIconLocation, null, this)
// Style era: looks like "pikeman-France" or "pikeman-European"
.tryImage { getString(baseUnitIconLocation, tag, civInfo.nation.getStyleOrCivName()) }
.tryImage { getString(baseUnitIconLocation, tag, style) }
.tryImage { baseUnitIconLocation }
if (unit.baseUnit.replaces != null)
@ -154,7 +170,7 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
return imageAttempter.getPathOrNull()
}
fun getUnitImageLocation(unit: MapUnit):String {
fun getUnitImageLocation(unit: MapUnit): String {
val imageKey = getString(
unit.name, tag,
unit.civ.getEra().name, tag,
@ -163,7 +179,7 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
)
// if in cache return that
val currentImageMapping = imageParamsToImageLocation[imageKey]
if (currentImageMapping!=null) return currentImageMapping
if (currentImageMapping != null) return currentImageMapping
val imageLocation = tryGetUnitImageLocation(unit)
?: fallback?.tryGetUnitImageLocation(unit)
@ -172,7 +188,7 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
return imageLocation
}
private fun tryGetOwnedTileImageLocation(baseLocation:String, owner:Civilization): String? {
private fun tryGetOwnedTileImageLocation(baseLocation: String, owner: Civilization): String? {
val ownersStyle = owner.nation.getStyleOrCivName()
return ImageAttempter(baseLocation)
.tryEraImage(owner, baseLocation, ownersStyle, this)
@ -181,12 +197,12 @@ class TileSetStrings(tileSet: String = UncivGame.Current.settings.tileSet, unitS
.getPathOrNull()
}
fun getOwnedTileImageLocation(baseLocation:String, owner:Civilization): String {
fun getOwnedTileImageLocation(baseLocation: String, owner: Civilization): String {
val imageKey = getString(baseLocation, tag,
owner.getEra().name, tag,
owner.nation.getStyleOrCivName())
val currentImageMapping = imageParamsToImageLocation[imageKey]
if (currentImageMapping!=null) return currentImageMapping
if (currentImageMapping != null) return currentImageMapping
val imageLocation = tryGetOwnedTileImageLocation(baseLocation, owner)
?: baseLocation

View File

@ -3,16 +3,24 @@ package com.unciv.ui.images
import com.unciv.logic.civilization.Civilization
import com.unciv.ui.components.tilegroups.TileSetStrings
@Suppress("MemberVisibilityCanBePrivate") // no problem for clients to see scope, imageFound, unused/internally used API
/**
* Metaprogrammy class for short-circuitingly finding the first existing of multiple image options according to [ImageGetter.imageExists].
*
* Has a [tryImage] method that can be chain-called with functions which return candidate image paths. The first function to return a valid image path stops subsequently chained calls from having any effect, and its result is saved to be retrieved by the [getPath] and [getImage] methods at the end of the candidate chain.
* Has a [tryImage] method that can be chain-called with functions which return candidate image paths.
* The first function to return a valid image path stops subsequently chained calls from having any effect,
* and its result is saved to be retrieved by the [getPath] and [getImage] methods at the end of the candidate chain.
*
* Binds candidate functions to a [scope] instance provided to primary constructor, for syntactic convenience. Bind to [Unit] when not needed.
* (So it is similar to Sequence in that intermediate "transforms" are only evaluated when necessary,
* but it is also different as resolution happens early, while chaining, not triggered by a terminating transform.)
*
* Binds candidate functions to a [scope] instance of type [T] provided to primary constructor, for syntactic convenience. Bind to [Unit] when not needed.
*
* Non-reusable.
*
* @property scope Instance to which to bind the candidate-returning functions. For syntactic terseness when making lots of calls to, E.G., [com.unciv.ui.tilegroups.TileSetStrings].
* @param T type of [scope]
* @property scope Instance to which to bind the candidate-returning functions. For syntactic terseness when making lots of calls to, e.g., [TileSetStrings][com.unciv.ui.components.tilegroups.TileSetStrings].
*/
class ImageAttempter<out T: Any>(val scope: T) {
/** The first valid filename tried if any, or the last filename tried if none have succeeded. */
@ -26,11 +34,11 @@ class ImageAttempter<out T: Any>(val scope: T) {
*
* @see ImageAttempter
* @param fileName Function that returns the filename of the image to check. Bound to [scope]. Will not be run if a valid image has already been found. May return `null` to skip this candidate entirely.
* @return This [ImageAttempter], for chaining.
* @return Chainable `this` [ImageAttempter] extended by one check for [fileName]
*/
fun tryImage(fileName: T.() -> String?): ImageAttempter<T> {
if (!imageFound) {
val imagePath = scope.run(fileName)
val imagePath = fileName.invoke(scope)
lastTriedFileName = imagePath ?: lastTriedFileName
if (imagePath != null && ImageGetter.imageExists(imagePath))
imageFound = true
@ -42,7 +50,7 @@ class ImageAttempter<out T: Any>(val scope: T) {
*
* @see tryImage
* @param fileNames Any number of image candidate returning functions to pass to [tryImage].
* @return This [ImageAttempter], for chaining.
* @return Chainable `this` [ImageAttempter] extended by zero or more checks for [fileNames]
*/
fun tryImages(fileNames: Sequence<T.() -> String?>): ImageAttempter<T> {
for (fileName in fileNames) {
@ -52,9 +60,14 @@ class ImageAttempter<out T: Any>(val scope: T) {
}
/** Try to load era-specific image variants
* [civInfo]: the civ who owns the tile or unit
* [locationToCheck]: the beginning of the filename to check
* [style]: an optional string to load a civ- or style-specific sprite
*
* Tries eras from the civ's current one down to the first era defined, by json order of eras.
* Result looks like "Plains-Rome-Ancient era": [style] goes before era if supplied.
*
* @param civInfo the civ who owns the tile or unit, used for getEraNumber and ruleset (but not for nation.getStyleOrCivName)
* @param locationToCheck the beginning of the filename to check
* @param style an optional string to load a civ- or style-specific sprite
* @return Chainable `this` [ImageAttempter] extended by one or more checks for era-specific images
* */
fun tryEraImage(civInfo: Civilization, locationToCheck: String, style: String?, tileSetStrings: TileSetStrings): ImageAttempter<T> {
return tryImages(