40% performance save on tileInfo.getImprovementStats() by caching citywide uniques (#7492)

This commit is contained in:
Yair Morgenstern 2022-07-20 19:53:30 +03:00 committed by GitHub
parent 8574ad4624
commit 448efc813d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 111 additions and 39 deletions

View File

@ -387,7 +387,12 @@ object Automation {
// Improvements are good: less points
if (tile.improvement != null &&
tile.getImprovementStats(tile.getTileImprovement()!!, cityInfo.civInfo, cityInfo).values.sum() > 0f
tile.getImprovementStats(
tile.getTileImprovement()!!,
cityInfo.civInfo,
cityInfo,
localUniqueCache
).values.sum() > 0f
) score -= 5
if (tile.naturalWonder != null) score -= 105

View File

@ -368,7 +368,7 @@ open class TileInfo : IsPartOfGameInfoSerialization {
val improvement = getTileImprovement()
if (improvement != null)
stats.add(getImprovementStats(improvement, observingCiv, city))
stats.add(getImprovementStats(improvement, observingCiv, city, localUniqueCache))
if (stats.gold != 0f && observingCiv.goldenAges.isGoldenAge())
stats.gold++
@ -488,7 +488,12 @@ open class TileInfo : IsPartOfGameInfoSerialization {
}
// Also multiplies the stats by the percentage bonus for improvements (but not for tiles)
fun getImprovementStats(improvement: TileImprovement, observingCiv: CivilizationInfo, city: CityInfo?): Stats {
fun getImprovementStats(
improvement: TileImprovement,
observingCiv: CivilizationInfo,
city: CityInfo?,
cityUniqueCache:LocalUniqueCache = LocalUniqueCache(false)
): Stats {
val stats = improvement.cloneStats()
if (hasViewableResource(observingCiv) && tileResource.isImprovedBy(improvement.name)
&& tileResource.improvementStats != null
@ -509,39 +514,67 @@ open class TileInfo : IsPartOfGameInfoSerialization {
stats.add(unique.stats.times(numberOfBonuses.toFloat()))
}
if (city != null) {
val tileUniques = city.getMatchingUniques(UniqueType.StatsFromTiles, conditionalState)
.filter { city.matchesFilter(it.params[2]) }
val improvementUniques =
improvement.getMatchingUniques(UniqueType.ImprovementStatsOnTile, conditionalState)
if (city != null) stats.add(getImprovementStatsForCity(improvement, city, conditionalState, cityUniqueCache))
for (unique in tileUniques + improvementUniques) {
if (improvement.matchesFilter(unique.params[1])
// Freshwater and non-freshwater cannot be moved to matchesUniqueFilter since that creates an endless feedback.
// If you're attempting that, check that it works!
// Edit: It seems to have been moved?
|| unique.params[1] == Constants.freshWater && isAdjacentTo(Constants.freshWater)
|| unique.params[1] == "non-fresh water" && !isAdjacentTo(Constants.freshWater)
)
stats.add(unique.stats)
}
for (unique in city.getMatchingUniques(UniqueType.StatsFromObject, conditionalState)) {
if (improvement.matchesFilter(unique.params[1])) {
stats.add(unique.stats)
}
}
}
for ((stat, value) in getImprovementPercentageStats(improvement, observingCiv, city)) {
for ((stat, value) in getImprovementPercentageStats(improvement, observingCiv, city, cityUniqueCache)) {
stats[stat] *= value.toPercent()
}
return stats
}
fun getImprovementStatsForCity(
improvement: TileImprovement,
city: CityInfo,
conditionalState: StateForConditionals,
cityUniqueCache: LocalUniqueCache
):Stats{
val stats = Stats()
fun statsFromTiles(){
// Since the conditionalState contains the current tile, it is different for each tile,
// therefore if we want the cache to be useful it needs to hold the pre-filtered uniques,
// and then for each improvement we'll filter the uniques locally.
// This is still a MASSIVE save of RAM!
val tileUniques = cityUniqueCache.get("StatsFromTiles",
city.getMatchingUniques(UniqueType.StatsFromTiles, StateForConditionals.IgnoreConditionals)
.filter { city.matchesFilter(it.params[2]) }) // These are the uniques for all improvements for this city,
.filter { it.conditionalsApply(conditionalState) } // ...and this is those with applicable conditions
val improvementUniques =
improvement.getMatchingUniques(UniqueType.ImprovementStatsOnTile, conditionalState)
for (unique in tileUniques + improvementUniques) {
if (improvement.matchesFilter(unique.params[1])
|| unique.params[1] == Constants.freshWater && isAdjacentTo(Constants.freshWater)
|| unique.params[1] == "non-fresh water" && !isAdjacentTo(Constants.freshWater)
)
stats.add(unique.stats)
}
}
statsFromTiles()
fun statsFromObject() {
// Same as above - cache holds unfiltered uniques for the city, while we use only the filtered ones
val uniques = cityUniqueCache.get("statsFromObjects",
city.getMatchingUniques(UniqueType.StatsFromObject, StateForConditionals.IgnoreConditionals))
.filter { it.conditionalsApply(conditionalState) }
for (unique in uniques) {
if (improvement.matchesFilter(unique.params[1])) {
stats.add(unique.stats)
}
}
}
statsFromObject()
return stats
}
@Suppress("MemberVisibilityCanBePrivate")
fun getImprovementPercentageStats(improvement: TileImprovement, observingCiv: CivilizationInfo, city: CityInfo?): Stats {
fun getImprovementPercentageStats(
improvement: TileImprovement,
observingCiv: CivilizationInfo,
city: CityInfo?,
cityUniqueCache: LocalUniqueCache
): Stats {
val stats = Stats()
val conditionalState = StateForConditionals(civInfo = observingCiv, cityInfo = city, tile = this)
@ -550,14 +583,24 @@ open class TileInfo : IsPartOfGameInfoSerialization {
// But something something too much for this PR.
if (city != null) {
for (unique in city.getMatchingUniques(UniqueType.AllStatsPercentFromObject, conditionalState)) {
// As above, since the conditional is tile-dependant,
// we save uniques in the cache without conditional filtering, and use only filtered ones
val allStatPercentUniques = cityUniqueCache.get("allSatPercentFromObject",
city.getMatchingUniques(UniqueType.AllStatsPercentFromObject, StateForConditionals.IgnoreConditionals))
.filter { it.conditionalsApply(conditionalState) }
for (unique in allStatPercentUniques) {
if (!improvement.matchesFilter(unique.params[1])) continue
for (stat in Stat.values()) {
stats[stat] += unique.params[0].toFloat()
}
}
for (unique in city.getMatchingUniques(UniqueType.StatPercentFromObject, conditionalState)) {
// Same trick different unique - not sure if worth generalizing this 'late apply' of conditions?
val statPercentUniques = cityUniqueCache.get("allSatPercentFromObject",
city.getMatchingUniques(UniqueType.StatPercentFromObject, StateForConditionals.IgnoreConditionals))
.filter { it.conditionalsApply(conditionalState) }
for (unique in statPercentUniques) {
if (!improvement.matchesFilter(unique.params[2])) continue
val stat = Stat.valueOf(unique.params[1])
stats[stat] += unique.params[0].toFloat()

View File

@ -29,3 +29,4 @@ interface IHasUniques {
fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals? = null) =
getMatchingUniques(uniqueType.placeholderText, stateForConditionals).any()
}

View File

@ -13,6 +13,7 @@ import com.unciv.logic.map.TileInfo
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.ui.audio.CityAmbiencePlayer
@ -31,7 +32,6 @@ import com.unciv.ui.utils.extensions.onActivation
import com.unciv.ui.utils.extensions.onClick
import com.unciv.ui.utils.extensions.packIfNeeded
import com.unciv.ui.utils.extensions.toTextButton
import kotlin.collections.ArrayList
import com.unciv.ui.worldscreen.WorldScreen
class CityScreen(
@ -208,13 +208,25 @@ class CityScreen(
}
private fun updateTileGroups() {
val cityUniqueCache = LocalUniqueCache()
fun isExistingImprovementValuable(tileInfo: TileInfo, improvementToPlace: TileImprovement): Boolean {
if (tileInfo.improvement == null) return false
val civInfo = city.civInfo
val existingStats = tileInfo.getImprovementStats(tileInfo.getTileImprovement()!!, civInfo, city)
val replacingStats = tileInfo.getImprovementStats(improvementToPlace, civInfo, city)
val existingStats = tileInfo.getImprovementStats(
tileInfo.getTileImprovement()!!,
civInfo,
city,
cityUniqueCache
)
val replacingStats = tileInfo.getImprovementStats(
improvementToPlace,
civInfo,
city,
cityUniqueCache
)
return Automation.rankStatsValue(existingStats, civInfo) > Automation.rankStatsValue(replacingStats, civInfo)
}
fun getPickImprovementColor(tileInfo: TileInfo): Pair<Color, Float> {
val improvementToPlace = pickTileData!!.improvement
return when {

View File

@ -4,16 +4,15 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.utils.Fonts
import com.unciv.ui.utils.KeyCharAndCode
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
import com.unciv.ui.utils.extensions.disable
import com.unciv.ui.utils.extensions.keyShortcuts
@ -81,6 +80,8 @@ class ImprovementPickerScreen(
tileInfoWithoutLastTerrain.removeTerrainFeature(tileInfoWithoutLastTerrain.getLastTerrain().name)
}
val cityUniqueCache = LocalUniqueCache()
for (improvement in ruleSet.tileImprovements.values) {
var suggestRemoval = false
// canBuildImprovement() would allow e.g. great improvements thus we need to exclude them - except cancel
@ -152,11 +153,21 @@ class ImprovementPickerScreen(
val statIcons = getStatIconsTable(provideResource, removeImprovement)
// get benefits of the new improvement
val stats = tileInfo.getImprovementStats(improvement, currentPlayerCiv, tileInfo.getCity())
val stats = tileInfo.getImprovementStats(
improvement,
currentPlayerCiv,
tileInfo.getCity(),
cityUniqueCache
)
// subtract the benefits of the replaced improvement, if any
val existingImprovement = tileInfo.getTileImprovement()
if (existingImprovement != null && removeImprovement) {
val existingStats = tileInfo.getImprovementStats(existingImprovement, currentPlayerCiv, tileInfo.getCity())
val existingStats = tileInfo.getImprovementStats(
existingImprovement,
currentPlayerCiv,
tileInfo.getCity(),
cityUniqueCache
)
stats.add(existingStats.times(-1.0f))
}