Performance: Cache uniques when choosing best tiles to settle/create improvements on

Resuse cache between cities for choosing best improvement for worker to pick (but not between workers...YET)

Alternate title: MOAR CACHES EVERYWHERE
This commit is contained in:
Yair Morgenstern 2023-08-31 14:42:10 +03:00
parent f8ccefd10c
commit ad7397ecc9
5 changed files with 22 additions and 20 deletions

View File

@ -362,11 +362,12 @@ object Automation {
} }
// Ranks a tile for any purpose except the expansion algorithm of cities // Ranks a tile for any purpose except the expansion algorithm of cities
internal fun rankTile(tile: Tile?, civInfo: Civilization): Float { internal fun rankTile(tile: Tile?, civInfo: Civilization,
localUniqueCache: LocalUniqueCache): Float {
if (tile == null) return 0f if (tile == null) return 0f
val tileOwner = tile.getOwner() val tileOwner = tile.getOwner()
if (tileOwner != null && tileOwner != civInfo) return 0f // Already belongs to another civilization, useless to us if (tileOwner != null && tileOwner != civInfo) return 0f // Already belongs to another civilization, useless to us
val stats = tile.stats.getTileStats(null, civInfo) val stats = tile.stats.getTileStats(null, civInfo, localUniqueCache)
var rank = rankStatsValue(stats, civInfo) var rank = rankStatsValue(stats, civInfo)
if (tile.improvement == null) rank += 0.5f // improvement potential! if (tile.improvement == null) rank += 0.5f // improvement potential!
if (tile.isPillaged()) rank += 0.6f if (tile.isPillaged()) rank += 0.6f

View File

@ -7,6 +7,7 @@ import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.TileResource import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.LocalUniqueCache
object CityLocationTileRanker { object CityLocationTileRanker {
fun getBestTilesToFoundCity(unit: MapUnit): Sequence<Pair<Tile, Float>> { fun getBestTilesToFoundCity(unit: MapUnit): Sequence<Pair<Tile, Float>> {
@ -93,14 +94,15 @@ object CityLocationTileRanker {
tile: Tile, tile: Tile,
civ: Civilization civ: Civilization
): Map<Tile, Float> { ): Map<Tile, Float> {
val uniqueCache = LocalUniqueCache()
return tile.getTilesInDistance(7) return tile.getTilesInDistance(7)
.filter { canUseTileForRanking(it, civ) } .filter { canUseTileForRanking(it, civ) }
.associateBy({ it }, { Automation.rankTile(it, civ) }) .associateBy({ it }, { Automation.rankTile(it, civ, uniqueCache) })
} }
private fun getLuxuryResourcesInCivArea(civ: Civilization): Sequence<TileResource> { private fun getLuxuryResourcesInCivArea(civ: Civilization): Sequence<TileResource> {
return civ.cities.asSequence() return civ.cities.asSequence()
.flatMap { it.getTiles().asSequence() }.filter { it.resource != null } .flatMap { it.getTiles() }.filter { it.resource != null }
.map { it.tileResource }.filter { it.resourceType == ResourceType.Luxury } .map { it.tileResource }.filter { it.resourceType == ResourceType.Luxury }
.distinct() .distinct()
} }

View File

@ -14,6 +14,7 @@ import com.unciv.logic.map.tile.Tile
import com.unciv.models.UnitAction import com.unciv.models.UnitAction
import com.unciv.models.UnitActionType import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
@ -199,10 +200,12 @@ object SpecificUnitAutomation {
} }
// if we got here, we're pretty close, start looking! // if we got here, we're pretty close, start looking!
val localUniqueCache = LocalUniqueCache()
val chosenTile = applicableTiles.sortedByDescending { val chosenTile = applicableTiles.sortedByDescending {
Automation.rankTile( Automation.rankTile(
it, it,
unit.civ unit.civ,
localUniqueCache
) )
} }
.firstOrNull { unit.movement.canReach(it) } .firstOrNull { unit.movement.canReach(it) }
@ -500,7 +503,7 @@ object SpecificUnitAutomation {
val tilesInRange = unit.currentTile.getTilesInDistanceRange(2..unit.getRange()) val tilesInRange = unit.currentTile.getTilesInDistanceRange(2..unit.getRange())
var highestTileNukeValue = 0 var highestTileNukeValue = 0
var tileToNuke: Tile? = null var tileToNuke: Tile? = null
tilesInRange.forEach { tilesInRange.forEach {
val value = getNukeLocationValue(unit, it) val value = getNukeLocationValue(unit, it)
if (value > highestTileNukeValue) { if (value > highestTileNukeValue) {
highestTileNukeValue = value highestTileNukeValue = value
@ -524,15 +527,15 @@ object SpecificUnitAutomation {
val tilesInBlastRadius = tile.getTilesInDistance(blastRadius) val tilesInBlastRadius = tile.getTilesInDistance(blastRadius)
val civsInBlastRadius = tilesInBlastRadius.mapNotNull { it.getOwner() } + val civsInBlastRadius = tilesInBlastRadius.mapNotNull { it.getOwner() } +
tilesInBlastRadius.mapNotNull { it.getFirstUnit()?.civ } tilesInBlastRadius.mapNotNull { it.getFirstUnit()?.civ }
// Don't nuke if it means we will be declaring war on someone! // Don't nuke if it means we will be declaring war on someone!
if (civsInBlastRadius.any { it != civ && !it.isAtWarWith(civ) }) return -100000 if (civsInBlastRadius.any { it != civ && !it.isAtWarWith(civ) }) return -100000
// If there are no enemies to hit, don't nuke // If there are no enemies to hit, don't nuke
if (!civsInBlastRadius.any { it.isAtWarWith(civ) }) return -100000 if (!civsInBlastRadius.any { it.isAtWarWith(civ) }) return -100000
// Launching a Nuke uses resources, therefore don't launch it by default // Launching a Nuke uses resources, therefore don't launch it by default
var explosionValue = -500 var explosionValue = -500
// Returns either ourValue or thierValue depending on if the input Civ matches the Nuke's Civ // Returns either ourValue or thierValue depending on if the input Civ matches the Nuke's Civ
fun evaluateCivValue(targetCiv: Civilization, ourValue: Int, theirValue: Int): Int { fun evaluateCivValue(targetCiv: Civilization, ourValue: Int, theirValue: Int): Int {
if (targetCiv == civ) // We are nuking something that we own! if (targetCiv == civ) // We are nuking something that we own!
@ -548,8 +551,8 @@ object SpecificUnitAutomation {
explosionValue += evaluateCivValue(targetTile.civilianUnit?.civ!!, -100, 25) explosionValue += evaluateCivValue(targetTile.civilianUnit?.civ!!, -100, 25)
} }
// Never nuke our own Civ, don't nuke single enemy civs as well // Never nuke our own Civ, don't nuke single enemy civs as well
if (targetTile.isCityCenter() if (targetTile.isCityCenter()
&& !(targetTile.getCity()!!.health <= 50f && !(targetTile.getCity()!!.health <= 50f
&& targetTile.neighbors.any {it.militaryUnit?.civ == civ})) // Prefer not to nuke cities that we are about to take && targetTile.neighbors.any {it.militaryUnit?.civ == civ})) // Prefer not to nuke cities that we are about to take
explosionValue += evaluateCivValue(targetTile.getCity()?.civ!!, -100000, 250) explosionValue += evaluateCivValue(targetTile.getCity()?.civ!!, -100000, 250)
else if (targetTile.owningCity != null) { else if (targetTile.owningCity != null) {

View File

@ -390,14 +390,10 @@ class WorkerAutomation(
} }
if (potentialTileImprovements.isEmpty()) return null if (potentialTileImprovements.isEmpty()) return null
val cityUniqueCaches = HashMap<City, LocalUniqueCache>() val localUniqueCache = LocalUniqueCache()
fun getImprovementRanking(improvementName: String): Float { fun getImprovementRanking(improvementName: String): Float {
val improvement = ruleSet.tileImprovements[improvementName]!! val improvement = ruleSet.tileImprovements[improvementName]!!
val city = tile.getCity() val stats = tile.stats.getStatDiffForImprovement(improvement, civInfo, tile.getCity(), localUniqueCache)
val cache =
if (city == null) LocalUniqueCache(false)
else cityUniqueCaches.getOrPut(city) { LocalUniqueCache() }
val stats = tile.stats.getStatDiffForImprovement(improvement, civInfo, tile.getCity(), cache)
return Automation.rankStatsValue(stats, unit.civ) return Automation.rankStatsValue(stats, unit.civ)
} }

View File

@ -169,10 +169,10 @@ class ImprovementPickerScreen(
//Warn when the current improvement will increase a stat for the tile, //Warn when the current improvement will increase a stat for the tile,
// but the tile is outside of the range (> 3 tiles from city center) that can be // but the tile is outside of the range (> 3 tiles from city center) that can be
// worked by a city's population // worked by a city's population
if (stats.values.any { it > 0f } if (tile.owningCity != null
&& !improvement.isRoad()
&& stats.values.any { it > 0f }
&& !improvement.name.startsWith(Constants.remove) && !improvement.name.startsWith(Constants.remove)
&& !improvement.isRoad()
&& tile.owningCity != null
&& !tile.owningCity!!.getWorkableTiles().contains(tile) && !tile.owningCity!!.getWorkableTiles().contains(tile)
) )
labelText += "\n" + "Not in city work range".tr() labelText += "\n" + "Not in city work range".tr()