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
internal fun rankTile(tile: Tile?, civInfo: Civilization): Float {
internal fun rankTile(tile: Tile?, civInfo: Civilization,
localUniqueCache: LocalUniqueCache): Float {
if (tile == null) return 0f
val tileOwner = tile.getOwner()
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)
if (tile.improvement == null) rank += 0.5f // improvement potential!
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.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.LocalUniqueCache
object CityLocationTileRanker {
fun getBestTilesToFoundCity(unit: MapUnit): Sequence<Pair<Tile, Float>> {
@ -93,14 +94,15 @@ object CityLocationTileRanker {
tile: Tile,
civ: Civilization
): Map<Tile, Float> {
val uniqueCache = LocalUniqueCache()
return tile.getTilesInDistance(7)
.filter { canUseTileForRanking(it, civ) }
.associateBy({ it }, { Automation.rankTile(it, civ) })
.associateBy({ it }, { Automation.rankTile(it, civ, uniqueCache) })
}
private fun getLuxuryResourcesInCivArea(civ: Civilization): Sequence<TileResource> {
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 }
.distinct()
}

View File

@ -14,6 +14,7 @@ import com.unciv.logic.map.tile.Tile
import com.unciv.models.UnitAction
import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
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!
val localUniqueCache = LocalUniqueCache()
val chosenTile = applicableTiles.sortedByDescending {
Automation.rankTile(
it,
unit.civ
unit.civ,
localUniqueCache
)
}
.firstOrNull { unit.movement.canReach(it) }
@ -500,7 +503,7 @@ object SpecificUnitAutomation {
val tilesInRange = unit.currentTile.getTilesInDistanceRange(2..unit.getRange())
var highestTileNukeValue = 0
var tileToNuke: Tile? = null
tilesInRange.forEach {
tilesInRange.forEach {
val value = getNukeLocationValue(unit, it)
if (value > highestTileNukeValue) {
highestTileNukeValue = value
@ -524,15 +527,15 @@ object SpecificUnitAutomation {
val tilesInBlastRadius = tile.getTilesInDistance(blastRadius)
val civsInBlastRadius = tilesInBlastRadius.mapNotNull { it.getOwner() } +
tilesInBlastRadius.mapNotNull { it.getFirstUnit()?.civ }
// Don't nuke if it means we will be declaring war on someone!
if (civsInBlastRadius.any { it != civ && !it.isAtWarWith(civ) }) return -100000
// If there are no enemies to hit, don't nuke
if (!civsInBlastRadius.any { it.isAtWarWith(civ) }) return -100000
// Launching a Nuke uses resources, therefore don't launch it by default
var explosionValue = -500
// 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 {
if (targetCiv == civ) // We are nuking something that we own!
@ -548,8 +551,8 @@ object SpecificUnitAutomation {
explosionValue += evaluateCivValue(targetTile.civilianUnit?.civ!!, -100, 25)
}
// Never nuke our own Civ, don't nuke single enemy civs as well
if (targetTile.isCityCenter()
&& !(targetTile.getCity()!!.health <= 50f
if (targetTile.isCityCenter()
&& !(targetTile.getCity()!!.health <= 50f
&& 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)
else if (targetTile.owningCity != null) {

View File

@ -390,14 +390,10 @@ class WorkerAutomation(
}
if (potentialTileImprovements.isEmpty()) return null
val cityUniqueCaches = HashMap<City, LocalUniqueCache>()
val localUniqueCache = LocalUniqueCache()
fun getImprovementRanking(improvementName: String): Float {
val improvement = ruleSet.tileImprovements[improvementName]!!
val city = tile.getCity()
val cache =
if (city == null) LocalUniqueCache(false)
else cityUniqueCaches.getOrPut(city) { LocalUniqueCache() }
val stats = tile.stats.getStatDiffForImprovement(improvement, civInfo, tile.getCity(), cache)
val stats = tile.stats.getStatDiffForImprovement(improvement, civInfo, tile.getCity(), localUniqueCache)
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,
// but the tile is outside of the range (> 3 tiles from city center) that can be
// 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.isRoad()
&& tile.owningCity != null
&& !tile.owningCity!!.getWorkableTiles().contains(tile)
)
labelText += "\n" + "Not in city work range".tr()