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) { if (surplusFood > 0 && city.avoidGrowth) {
yieldStats.food = 0f // don't need more food! 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 // Focus on non-food/growth
if (surplusFood < 0) if (surplusFood < 0)
yieldStats.food *= 8 // Starving, need Food, get to 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. * 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() val currentTile = unit.getTile()
// Must be called before any getPriority checks to guarantee the local road cache is processed // Must be called before any getPriority checks to guarantee the local road cache is processed
val citiesToConnect = roadBetweenCitiesAutomation.getNearbyCitiesToConnect(unit) 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 // 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) { && currentTile.improvementInProgress != null) {
return return
} }
val tileToWork = findTileToWork(unit, dangerousTiles) val tileToWork = findTileToWork(unit, dangerousTiles, localUniqueCache)
if (tileToWork != currentTile) { if (tileToWork != currentTile) {
debug("WorkerAutomation: %s -> head towards %s", unit.toString(), tileToWork) debug("WorkerAutomation: %s -> head towards %s", unit.toString(), tileToWork)
@ -106,7 +106,7 @@ class WorkerAutomation(
return return
} }
if (reachedTile.improvementInProgress == null && reachedTile.isLand if (reachedTile.improvementInProgress == null && reachedTile.isLand
&& tileHasWorkToDo(reachedTile, unit) && tileHasWorkToDo(reachedTile, unit, localUniqueCache)
) { ) {
debug("WorkerAutomation: $unit -> start improving $reachedTile") debug("WorkerAutomation: $unit -> start improving $reachedTile")
return reachedTile.startWorkingOnImprovement(tileRankings[reachedTile]!!.bestImprovement!!, civInfo, unit) return reachedTile.startWorkingOnImprovement(tileRankings[reachedTile]!!.bestImprovement!!, civInfo, unit)
@ -117,7 +117,7 @@ class WorkerAutomation(
if (currentTile.improvementInProgress != null) return // we're working! if (currentTile.improvementInProgress != null) return // we're working!
if (tileHasWorkToDo(currentTile, unit)) { if (tileHasWorkToDo(currentTile, unit, localUniqueCache)) {
val tileRankings = tileRankings[currentTile]!! val tileRankings = tileRankings[currentTile]!!
if (tileRankings.repairImprovment!!) { if (tileRankings.repairImprovment!!) {
debug("WorkerAutomation: $unit -> repairs $currentTile") debug("WorkerAutomation: $unit -> repairs $currentTile")
@ -140,7 +140,9 @@ class WorkerAutomation(
val citiesToNumberOfUnimprovedTiles = HashMap<String, Int>() val citiesToNumberOfUnimprovedTiles = HashMap<String, Int>()
for (city in unit.civ.cities) { for (city in unit.civ.cities) {
citiesToNumberOfUnimprovedTiles[city.id] = city.getTiles() 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() val closestUndevelopedCity = unit.civ.cities.asSequence()
@ -171,10 +173,10 @@ class WorkerAutomation(
* Looks for a worthwhile tile to improve * Looks for a worthwhile tile to improve
* @return The current tile if no tile to work was found * @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() val currentTile = unit.getTile()
if (currentTile !in tilesToAvoid && getBasePriority(currentTile, unit) >= 5 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 return currentTile
} }
val workableTilesCenterFirst = currentTile.getTilesInDistance(4) val workableTilesCenterFirst = currentTile.getTilesInDistance(4)
@ -197,10 +199,10 @@ class WorkerAutomation(
var bestTile: Tile? = null var bestTile: Tile? = null
for (tileInGroup in tilePriorityGroup.value.sortedBy { unit.getTile().aerialDistanceTo(it) }) { 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. // 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.getTile() == tileInGroup) return unit.getTile()
if (!unit.movement.canReach(tileInGroup)) continue 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 bestTile = tileInGroup
} }
} }
@ -252,23 +254,23 @@ class WorkerAutomation(
/** /**
* Calculates the priority building the improvement on the tile * 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) getBasePriority(tile, unit)
val rank = tileRankings[tile] val rank = tileRankings[tile]
if(rank!!.improvementPriority == null) { if (rank!!.improvementPriority == null) {
// All values of rank have to be initialized // All values of rank have to be initialized
rank.improvementPriority = -100f rank.improvementPriority = -100f
rank.bestImprovement = null rank.bestImprovement = null
rank.repairImprovment = false rank.repairImprovment = false
val bestImprovement = chooseImprovement(unit, tile) val bestImprovement = chooseImprovement(unit, tile, localUniqueCache)
if (bestImprovement != null) { if (bestImprovement != null) {
rank.bestImprovement = bestImprovement rank.bestImprovement = bestImprovement
// Increased priority if the improvement has been worked on longer // Increased priority if the improvement has been worked on longer
val timeSpentPriority = if (tile.improvementInProgress == bestImprovement.name) val timeSpentPriority = if (tile.improvementInProgress == bestImprovement.name)
bestImprovement.getTurnsToBuild(unit.civ,unit) - tile.turnsToImprovement else 0 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) { if (tile.improvement != null && tile.isPillaged() && tile.owningCity != null) {
@ -293,15 +295,15 @@ class WorkerAutomation(
/** /**
* Calculates the full priority of the tile * Calculates the full priority of the tile
*/ */
private fun getFullPriority(tile: Tile, unit: MapUnit): Float { private fun getFullPriority(tile: Tile, unit: MapUnit, localUniqueCache: LocalUniqueCache): Float {
return getBasePriority(tile, unit) + getImprovementPriority(tile, unit) return getBasePriority(tile, unit) + getImprovementPriority(tile, unit, localUniqueCache)
} }
/** /**
* Returns the best improvement * Returns the best improvement
*/ */
private fun tileHasWorkToDo(tile: Tile, unit: MapUnit): Boolean { private fun tileHasWorkToDo(tile: Tile, unit: MapUnit, localUniqueCache: LocalUniqueCache): Boolean {
if (getImprovementPriority(tile, unit) <= 0) return false if (getImprovementPriority(tile, unit, localUniqueCache) <= 0) return false
if (!(tileRankings[tile]!!.bestImprovement != null || tileRankings[tile]!!.repairImprovment!!)) if (!(tileRankings[tile]!!.bestImprovement != null || tileRankings[tile]!!.repairImprovment!!))
throw IllegalStateException("There was an improvementPriority > 0 and nothing to do") throw IllegalStateException("There was an improvementPriority > 0 and nothing to do")
return true return true
@ -311,7 +313,7 @@ class WorkerAutomation(
* Determine the improvement appropriate to a given tile and worker * Determine the improvement appropriate to a given tile and worker
* Returns null if * 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 // 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!!] if (tile.improvementInProgress != null) return ruleSet.tileImprovements[tile.improvementInProgress!!]
@ -322,8 +324,6 @@ class WorkerAutomation(
} }
if (potentialTileImprovements.isEmpty()) return null if (potentialTileImprovements.isEmpty()) return null
val localUniqueCache = LocalUniqueCache()
var bestBuildableImprovement = potentialTileImprovements.values.asSequence() var bestBuildableImprovement = potentialTileImprovements.values.asSequence()
.map { Pair(it, getImprovementRanking(tile, unit, it.name, localUniqueCache)) } .map { Pair(it, getImprovementRanking(tile, unit, it.name, localUniqueCache)) }
.filter { it.second > 0f } .filter { it.second > 0f }
@ -410,7 +410,7 @@ class WorkerAutomation(
newTile.removeTerrainFeature(removedFeature) newTile.removeTerrainFeature(removedFeature)
if (removedImprovement != null) if (removedImprovement != null)
newTile.removeImprovement() newTile.removeImprovement()
val wantedFinalImprovement = chooseImprovement(unit, newTile) val wantedFinalImprovement = chooseImprovement(unit, newTile, localUniqueCache)
if (wantedFinalImprovement != null) if (wantedFinalImprovement != null)
stats.add(newTile.stats.getStatDiffForImprovement(wantedFinalImprovement, civInfo, newTile.getCity(), localUniqueCache)) stats.add(newTile.stats.getStatDiffForImprovement(wantedFinalImprovement, civInfo, newTile.getCity(), localUniqueCache))
} }

View File

@ -68,7 +68,8 @@ enum class CityFocus(
fun applyWeightTo(stats: Stats) { fun applyWeightTo(stats: Stats) {
for (stat in Stat.values()) { 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 // set used in Automation. All non-Food Focuses, so targets 0 Surplus Food
fun zeroFoodFocuses(): Set<CityFocus> { val zeroFoodFocuses = setOf(
return setOf( CultureFocus, FaithFocus, GoldFocus,
CultureFocus, FaithFocus, GoldFocus, HappinessFocus, ProductionFocus, ScienceFocus
HappinessFocus, ProductionFocus, ScienceFocus )
)
}
} }
} }