Cache city-wide uniques in CityConstructions.getStats() for reuse between buildings - see #6695

This commit is contained in:
Yair Morgenstern
2022-05-05 10:39:40 +03:00
parent 333582980f
commit c6795d7e91
4 changed files with 32 additions and 13 deletions

View File

@ -6,6 +6,7 @@ import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.PopupAlert
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueMap
import com.unciv.models.ruleset.unique.UniqueType
@ -87,8 +88,9 @@ class CityConstructions {
*/
fun getStats(): StatTreeNode {
val stats = StatTreeNode()
val localUniqueCache = LocalUniqueCache()
for (building in getBuiltBuildings())
stats.addStats(building.getStats(cityInfo), building.name)
stats.addStats(building.getStats(cityInfo, localUniqueCache), building.name)
return stats
}
@ -98,13 +100,11 @@ class CityConstructions {
fun getMaintenanceCosts(): Int {
var maintenanceCost = 0
val freeBuildings = cityInfo.civInfo.civConstructions.getFreeBuildings(cityInfo.id)
for (building in getBuiltBuildings()) {
if (building.name !in freeBuildings) {
for (building in getBuiltBuildings())
if (building.name !in freeBuildings)
maintenanceCost += building.maintenance
}
}
return maintenanceCost
}

View File

@ -39,7 +39,8 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
// We IGNORE the conditionals when we get them civ-wide, so we won't need to do the same thing for EVERY unit in the civ.
// This leads to massive memory and CPU time savings when calculating the maintenance!
val civwideDiscountUniques = civInfo.getMatchingUniques(UniqueType.UnitMaintenanceDiscount, StateForConditionals.IgnoreConditionals).toList()
val civwideDiscountUniques = civInfo.getMatchingUniques(UniqueType.UnitMaintenanceDiscount, StateForConditionals.IgnoreConditionals)
.toList().asSequence()
for (unit in unitsToPayFor) {
val stateForConditionals = StateForConditionals(civInfo = civInfo, unit = unit)
@ -47,8 +48,7 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
val uniquesThatApply = unit.getMatchingUniques(
UniqueType.UnitMaintenanceDiscount,
stateForConditionals
) + civwideDiscountUniques.asSequence()
.filter { it.conditionalsApply(stateForConditionals) }
) + civwideDiscountUniques.filter { it.conditionalsApply(stateForConditionals) }
for (unique in uniquesThatApply) {
unitMaintenance *= unique.params[0].toPercent()
}

View File

@ -163,12 +163,15 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
return lines.joinToString("\n") { it.tr() }.trim()
}
fun getStats(city: CityInfo): Stats {
fun getStats(city: CityInfo,
/* By default, do not cache - if we're getting stats for only one building this isn't efficient.
* Only use a cache if it was sent to us from outside, which means we can use the results for other buildings. */
localUniqueCache: LocalUniqueCache = LocalUniqueCache(false)): Stats {
// Calls the clone function of the NamedStats this class is derived from, not a clone function of this class
val stats = cloneStats()
val civInfo = city.civInfo
for (unique in city.getMatchingUniques(UniqueType.StatsFromObject)) {
for (unique in localUniqueCache.get("StatsFromObject", city.getMatchingUniques(UniqueType.StatsFromObject))) {
if (!matchesFilter(unique.params[1])) continue
stats.add(unique.stats)
}
@ -179,7 +182,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
stats.add(unique.stats)
if (!isWonder)
for (unique in city.getMatchingUniques(UniqueType.StatsFromBuildings)) {
for (unique in localUniqueCache.get("StatsFromBuildings", city.getMatchingUniques(UniqueType.StatsFromBuildings))) {
if (matchesFilter(unique.params[1]))
stats.add(unique.stats)
}

View File

@ -262,6 +262,22 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
override fun toString() = if (type == null) "\"$text\"" else "$type (\"$text\")"
}
/** Used to cache results of getMatchingUniques
* Must only be used when we're sure the matching uniques will not change in the meantime */
class LocalUniqueCache(val cache:Boolean = true) {
// This stores sequences *that iterate directly on a list* - that is, pre-resolved
private val keyToUniques = HashMap<String, Sequence<Unique>>()
/** Get cached results as a sequence */
fun get(key: String, sequence: Sequence<Unique>): Sequence<Unique> {
if (!cache) return sequence
if (keyToUniques.containsKey(key)) return keyToUniques[key]!!
// Iterate the sequence, save actual results as a list, as return a sequence to that
val results = sequence.toList().asSequence()
keyToUniques[key] = results
return results
}
}
class UniqueMap: HashMap<String, ArrayList<Unique>>() {
//todo Once all untyped Uniques are converted, this should be HashMap<UniqueType, *>