perf: Performance improvements for worker automation by caching uniques

This commit is contained in:
yairm210
2024-06-21 16:23:46 +03:00
parent c6620f7470
commit d9d704da60
3 changed files with 29 additions and 30 deletions

View File

@ -72,7 +72,7 @@ object Automation {
if (surplusFood > 0 && city.avoidGrowth) {
yieldStats.food = 0f // don't need more food!
} else if (cityAIFocus in CityFocus.zeroFoodFocuses()) {
} else if (cityAIFocus in CityFocus.zeroFoodFocuses) {
// Focus on non-food/growth
if (surplusFood < 0)
yieldStats.food *= 8 // Starving, need Food, get to 0

View File

@ -68,16 +68,16 @@ class WorkerAutomation(
/**
* Automate one Worker - decide what to do and where, move, start or continue work.
*/
fun automateWorkerAction(unit: MapUnit, dangerousTiles: HashSet<Tile>) {
fun automateWorkerAction(unit: MapUnit, dangerousTiles: HashSet<Tile>, localUniqueCache: LocalUniqueCache = LocalUniqueCache()) {
val currentTile = unit.getTile()
// Must be called before any getPriority checks to guarantee the local road cache is processed
val citiesToConnect = roadBetweenCitiesAutomation.getNearbyCitiesToConnect(unit)
// Shortcut, we are working a good tile (like resource) and don't need to check for other tiles to work
if (!dangerousTiles.contains(currentTile) && getFullPriority(unit.getTile(), unit) >= 10
if (!dangerousTiles.contains(currentTile) && getFullPriority(unit.getTile(), unit, localUniqueCache) >= 10
&& currentTile.improvementInProgress != null) {
return
}
val tileToWork = findTileToWork(unit, dangerousTiles)
val tileToWork = findTileToWork(unit, dangerousTiles, localUniqueCache)
if (tileToWork != currentTile) {
debug("WorkerAutomation: %s -> head towards %s", unit.toString(), tileToWork)
@ -106,7 +106,7 @@ class WorkerAutomation(
return
}
if (reachedTile.improvementInProgress == null && reachedTile.isLand
&& tileHasWorkToDo(reachedTile, unit)
&& tileHasWorkToDo(reachedTile, unit, localUniqueCache)
) {
debug("WorkerAutomation: $unit -> start improving $reachedTile")
return reachedTile.startWorkingOnImprovement(tileRankings[reachedTile]!!.bestImprovement!!, civInfo, unit)
@ -117,7 +117,7 @@ class WorkerAutomation(
if (currentTile.improvementInProgress != null) return // we're working!
if (tileHasWorkToDo(currentTile, unit)) {
if (tileHasWorkToDo(currentTile, unit, localUniqueCache)) {
val tileRankings = tileRankings[currentTile]!!
if (tileRankings.repairImprovment!!) {
debug("WorkerAutomation: $unit -> repairs $currentTile")
@ -140,7 +140,9 @@ class WorkerAutomation(
val citiesToNumberOfUnimprovedTiles = HashMap<String, Int>()
for (city in unit.civ.cities) {
citiesToNumberOfUnimprovedTiles[city.id] = city.getTiles()
.count { tile -> tile.isLand && tile.getUnits().any { unit -> unit.cache.hasUniqueToBuildImprovements } && (tile.isPillaged() || tileHasWorkToDo(tile, unit)) }
.count { tile -> tile.isLand
&& tile.getUnits().any { unit -> unit.cache.hasUniqueToBuildImprovements }
&& (tile.isPillaged() || tileHasWorkToDo(tile, unit, localUniqueCache)) }
}
val closestUndevelopedCity = unit.civ.cities.asSequence()
@ -171,10 +173,10 @@ class WorkerAutomation(
* Looks for a worthwhile tile to improve
* @return The current tile if no tile to work was found
*/
private fun findTileToWork(unit: MapUnit, tilesToAvoid: Set<Tile>): Tile {
private fun findTileToWork(unit: MapUnit, tilesToAvoid: Set<Tile>, localUniqueCache: LocalUniqueCache): Tile {
val currentTile = unit.getTile()
if (currentTile !in tilesToAvoid && getBasePriority(currentTile, unit) >= 5
&& (tileHasWorkToDo(currentTile, unit) || currentTile.isPillaged() || currentTile.hasFalloutEquivalent())) {
&& (tileHasWorkToDo(currentTile, unit, localUniqueCache) || currentTile.isPillaged() || currentTile.hasFalloutEquivalent())) {
return currentTile
}
val workableTilesCenterFirst = currentTile.getTilesInDistance(4)
@ -197,10 +199,10 @@ class WorkerAutomation(
var bestTile: Tile? = null
for (tileInGroup in tilePriorityGroup.value.sortedBy { unit.getTile().aerialDistanceTo(it) }) {
// These are the expensive calculations (tileCanBeImproved, canReach), so we only apply these filters after everything else it done.
if (!tileHasWorkToDo(tileInGroup, unit)) continue
if (!tileHasWorkToDo(tileInGroup, unit, localUniqueCache)) continue
if (unit.getTile() == tileInGroup) return unit.getTile()
if (!unit.movement.canReach(tileInGroup)) continue
if (bestTile == null || getFullPriority(tileInGroup, unit) > getFullPriority(bestTile, unit)) {
if (bestTile == null || getFullPriority(tileInGroup, unit, localUniqueCache) > getFullPriority(bestTile, unit, localUniqueCache)) {
bestTile = tileInGroup
}
}
@ -252,23 +254,23 @@ class WorkerAutomation(
/**
* Calculates the priority building the improvement on the tile
*/
private fun getImprovementPriority(tile: Tile, unit: MapUnit): Float {
private fun getImprovementPriority(tile: Tile, unit: MapUnit, localUniqueCache: LocalUniqueCache): Float {
getBasePriority(tile, unit)
val rank = tileRankings[tile]
if(rank!!.improvementPriority == null) {
if (rank!!.improvementPriority == null) {
// All values of rank have to be initialized
rank.improvementPriority = -100f
rank.bestImprovement = null
rank.repairImprovment = false
val bestImprovement = chooseImprovement(unit, tile)
val bestImprovement = chooseImprovement(unit, tile, localUniqueCache)
if (bestImprovement != null) {
rank.bestImprovement = bestImprovement
// Increased priority if the improvement has been worked on longer
val timeSpentPriority = if (tile.improvementInProgress == bestImprovement.name)
bestImprovement.getTurnsToBuild(unit.civ,unit) - tile.turnsToImprovement else 0
rank.improvementPriority = getImprovementRanking(tile, unit, rank.bestImprovement!!.name, LocalUniqueCache()) + timeSpentPriority
rank.improvementPriority = getImprovementRanking(tile, unit, rank.bestImprovement!!.name, localUniqueCache) + timeSpentPriority
}
if (tile.improvement != null && tile.isPillaged() && tile.owningCity != null) {
@ -293,15 +295,15 @@ class WorkerAutomation(
/**
* Calculates the full priority of the tile
*/
private fun getFullPriority(tile: Tile, unit: MapUnit): Float {
return getBasePriority(tile, unit) + getImprovementPriority(tile, unit)
private fun getFullPriority(tile: Tile, unit: MapUnit, localUniqueCache: LocalUniqueCache): Float {
return getBasePriority(tile, unit) + getImprovementPriority(tile, unit, localUniqueCache)
}
/**
* Returns the best improvement
*/
private fun tileHasWorkToDo(tile: Tile, unit: MapUnit): Boolean {
if (getImprovementPriority(tile, unit) <= 0) return false
private fun tileHasWorkToDo(tile: Tile, unit: MapUnit, localUniqueCache: LocalUniqueCache): Boolean {
if (getImprovementPriority(tile, unit, localUniqueCache) <= 0) return false
if (!(tileRankings[tile]!!.bestImprovement != null || tileRankings[tile]!!.repairImprovment!!))
throw IllegalStateException("There was an improvementPriority > 0 and nothing to do")
return true
@ -311,7 +313,7 @@ class WorkerAutomation(
* Determine the improvement appropriate to a given tile and worker
* Returns null if
* */
private fun chooseImprovement(unit: MapUnit, tile: Tile): TileImprovement? {
private fun chooseImprovement(unit: MapUnit, tile: Tile, localUniqueCache: LocalUniqueCache): TileImprovement? {
// You can keep working on half-built improvements, even if they're unique to another civ
if (tile.improvementInProgress != null) return ruleSet.tileImprovements[tile.improvementInProgress!!]
@ -322,8 +324,6 @@ class WorkerAutomation(
}
if (potentialTileImprovements.isEmpty()) return null
val localUniqueCache = LocalUniqueCache()
var bestBuildableImprovement = potentialTileImprovements.values.asSequence()
.map { Pair(it, getImprovementRanking(tile, unit, it.name, localUniqueCache)) }
.filter { it.second > 0f }
@ -410,7 +410,7 @@ class WorkerAutomation(
newTile.removeTerrainFeature(removedFeature)
if (removedImprovement != null)
newTile.removeImprovement()
val wantedFinalImprovement = chooseImprovement(unit, newTile)
val wantedFinalImprovement = chooseImprovement(unit, newTile, localUniqueCache)
if (wantedFinalImprovement != null)
stats.add(newTile.stats.getStatDiffForImprovement(wantedFinalImprovement, civInfo, newTile.getCity(), localUniqueCache))
}

View File

@ -68,7 +68,8 @@ enum class CityFocus(
fun applyWeightTo(stats: Stats) {
for (stat in Stat.values()) {
stats[stat] *= getStatMultiplier(stat)
val currentStat = stats[stat]
if (currentStat != 0f) stats[stat] *= getStatMultiplier(stat)
}
}
@ -78,12 +79,10 @@ enum class CityFocus(
}
// set used in Automation. All non-Food Focuses, so targets 0 Surplus Food
fun zeroFoodFocuses(): Set<CityFocus> {
return setOf(
CultureFocus, FaithFocus, GoldFocus,
HappinessFocus, ProductionFocus, ScienceFocus
)
}
val zeroFoodFocuses = setOf(
CultureFocus, FaithFocus, GoldFocus,
HappinessFocus, ProductionFocus, ScienceFocus
)
}
}