mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-24 20:31:51 -04:00
40% performance save on tileInfo.getImprovementStats() by caching citywide uniques (#7492)
This commit is contained in:
parent
8574ad4624
commit
448efc813d
@ -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
|
||||
|
@ -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()
|
||||
|
@ -9,12 +9,12 @@ interface IHasUniques {
|
||||
val uniqueObjects: List<Unique>
|
||||
|
||||
val uniqueMap: Map<String, List<Unique>>
|
||||
|
||||
|
||||
/** Technically not currently needed, since the unique target can be retrieved from every unique in the uniqueObjects,
|
||||
* But making this a function is relevant for future "unify Unciv object" plans ;)
|
||||
* */
|
||||
fun getUniqueTarget(): UniqueTarget
|
||||
|
||||
|
||||
fun getMatchingUniques(uniqueTemplate: String, stateForConditionals: StateForConditionals? = null): Sequence<Unique> {
|
||||
val matchingUniques = uniqueMap[uniqueTemplate] ?: return sequenceOf()
|
||||
return matchingUniques.asSequence().filter { it.conditionalsApply(stateForConditionals) }
|
||||
@ -29,3 +29,4 @@ interface IHasUniques {
|
||||
fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals? = null) =
|
||||
getMatchingUniques(uniqueType.placeholderText, stateForConditionals).any()
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user