mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-21 13:18:56 +07:00
perf: Unique mapping overhaul - don't multiply when checking 'has unique', prep for EnumMap for typed uniques
This commit is contained in:
@ -47,7 +47,7 @@ class VictoryManager : IsPartOfGameInfoSerialization {
|
||||
fun getUNBuildingAndOwnerNames(): Pair<String?, String?> = getVotingCivs()
|
||||
.flatMap { civ -> civ.cities.asSequence()
|
||||
.flatMap { it.cityConstructions.getBuiltBuildings() }
|
||||
.filter { it.hasUnique(UniqueType.OneTimeTriggerVoting, stateForConditionals = StateForConditionals.IgnoreConditionals) }
|
||||
.filter { it.hasUnique(UniqueType.OneTimeTriggerVoting, StateForConditionals.IgnoreConditionals) }
|
||||
.map { it.name to civ.civName }
|
||||
}.firstOrNull() ?: (null to null)
|
||||
|
||||
|
@ -49,13 +49,14 @@ class CivInfoTransientCache(val civInfo: Civilization) {
|
||||
fun setTransients() {
|
||||
val ruleset = civInfo.gameInfo.ruleset
|
||||
|
||||
val state = StateForConditionals(civInfo)
|
||||
val buildingsToRequiredResources = ruleset.buildings.values
|
||||
.filter { civInfo.getEquivalentBuilding(it) == it }
|
||||
.associateWith { it.requiredResources() }
|
||||
.associateWith { it.requiredResources(state) }
|
||||
|
||||
val unitsToRequiredResources = ruleset.units.values
|
||||
.filter { civInfo.getEquivalentUnit(it) == it }
|
||||
.associateWith { it.requiredResources() }
|
||||
.associateWith { it.requiredResources(state) }
|
||||
|
||||
for (resource in ruleset.tileResources.values.asSequence().filter { it.resourceType == ResourceType.Strategic }.map { it.name }) {
|
||||
val applicableBuildings = buildingsToRequiredResources.filter { it.value.contains(resource) }.map { it.key }
|
||||
|
@ -276,7 +276,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
return !(isFortified() || isExploring() || isSleeping() || isAutomated() || isMoving())
|
||||
}
|
||||
|
||||
fun getUniques(): Sequence<Unique> = tempUniquesMap.values.asSequence().flatten()
|
||||
fun getUniques(): Sequence<Unique> = tempUniquesMap.getAllUniques()
|
||||
|
||||
fun getMatchingUniques(
|
||||
uniqueType: UniqueType,
|
||||
@ -577,7 +577,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
else -> {
|
||||
if (baseUnit.matchesFilter(filter)) return true
|
||||
if (civ.matchesFilter(filter)) return true
|
||||
if (tempUniquesMap.containsKey(filter)) return true
|
||||
if (tempUniquesMap.hasTagUnique(filter)) return true
|
||||
if (promotions.promotions.contains(filter)) return true
|
||||
return false
|
||||
}
|
||||
|
@ -536,10 +536,11 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
|
||||
fun isSellable() = !isAnyWonder() && !hasUnique(UniqueType.Unsellable)
|
||||
|
||||
override fun getResourceRequirementsPerTurn(stateForConditionals: StateForConditionals?): Counter<String> {
|
||||
override fun getResourceRequirementsPerTurn(state: StateForConditionals?): Counter<String> {
|
||||
val resourceRequirements = Counter<String>()
|
||||
if (requiredResource != null) resourceRequirements[requiredResource!!] = 1
|
||||
for (unique in getMatchingUniques(UniqueType.ConsumesResources, stateForConditionals))
|
||||
for (unique in getMatchingUniques(UniqueType.ConsumesResources,
|
||||
state ?: StateForConditionals.EmptyState))
|
||||
resourceRequirements[unique.params[1]] += unique.params[0].toInt()
|
||||
return resourceRequirements
|
||||
}
|
||||
|
@ -20,9 +20,9 @@ interface IConstruction : INamed {
|
||||
fun isBuildable(cityConstructions: CityConstructions): Boolean
|
||||
fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean
|
||||
/** Gets *per turn* resource requirements - does not include immediate costs for stockpiled resources.
|
||||
* Uses [stateForConditionals] to determine which civ or city this is built for*/
|
||||
fun getResourceRequirementsPerTurn(stateForConditionals: StateForConditionals? = null): Counter<String>
|
||||
fun requiredResources(stateForConditionals: StateForConditionals? = null): Set<String>
|
||||
* Uses [state] to determine which civ or city this is built for*/
|
||||
fun getResourceRequirementsPerTurn(state: StateForConditionals? = null): Counter<String>
|
||||
fun requiredResources(state: StateForConditionals = StateForConditionals.EmptyState): Set<String>
|
||||
/** We can't call this getMatchingUniques because then it would conflict with IHasUniques */
|
||||
fun getMatchingUniquesNotConflicting(uniqueType: UniqueType, stateForConditionals: StateForConditionals) = sequenceOf<Unique>()
|
||||
}
|
||||
@ -105,9 +105,9 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
|
||||
override fun getMatchingUniquesNotConflicting(uniqueType: UniqueType, stateForConditionals: StateForConditionals): Sequence<Unique> =
|
||||
getMatchingUniques(uniqueType, stateForConditionals)
|
||||
|
||||
override fun requiredResources(stateForConditionals: StateForConditionals?): Set<String> {
|
||||
return getResourceRequirementsPerTurn(stateForConditionals).keys +
|
||||
getMatchingUniques(UniqueType.CostsResources, stateForConditionals).map { it.params[1] }
|
||||
override fun requiredResources(state: StateForConditionals): Set<String> {
|
||||
return getResourceRequirementsPerTurn(state).keys +
|
||||
getMatchingUniques(UniqueType.CostsResources, state).map { it.params[1] }
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,9 +256,9 @@ open class PerpetualConstruction(override var name: String, val description: Str
|
||||
override fun isBuildable(cityConstructions: CityConstructions): Boolean =
|
||||
throw Exception("Impossible!")
|
||||
|
||||
override fun getResourceRequirementsPerTurn(stateForConditionals: StateForConditionals?) = Counter.ZERO
|
||||
override fun getResourceRequirementsPerTurn(state: StateForConditionals?) = Counter.ZERO
|
||||
|
||||
override fun requiredResources(stateForConditionals: StateForConditionals?): Set<String> = emptySet()
|
||||
override fun requiredResources(state: StateForConditionals): Set<String> = emptySet()
|
||||
}
|
||||
|
||||
open class PerpetualStatConversion(val stat: Stat) :
|
||||
|
@ -96,8 +96,8 @@ class Nation : RulesetObject() {
|
||||
innerColorObject = if (innerColor == null) Color.BLACK
|
||||
else colorFromRGB(innerColor!!)
|
||||
|
||||
forestsAndJunglesAreRoads = uniqueMap.containsKey(UniqueType.ForestsAndJunglesAreRoads.placeholderText)
|
||||
ignoreHillMovementCost = uniqueMap.containsKey(UniqueType.IgnoreHillMovementCost.placeholderText)
|
||||
forestsAndJunglesAreRoads = uniqueMap.hasUnique(UniqueType.ForestsAndJunglesAreRoads)
|
||||
ignoreHillMovementCost = uniqueMap.hasUnique(UniqueType.IgnoreHillMovementCost)
|
||||
}
|
||||
|
||||
|
||||
|
@ -45,7 +45,7 @@ class TileImprovement : RulesetStatsObject() {
|
||||
fun getShortDecription() = ImprovementDescriptions.getShortDescription(this)
|
||||
|
||||
fun isGreatImprovement() = hasUnique(UniqueType.GreatImprovement)
|
||||
fun isRoad() = RoadStatus.values().any { it != RoadStatus.None && it.name == this.name }
|
||||
fun isRoad() = RoadStatus.entries.any { it != RoadStatus.None && it.name == this.name }
|
||||
fun isAncientRuinsEquivalent() = hasUnique(UniqueType.IsAncientRuinsEquivalent)
|
||||
|
||||
fun canBeBuiltOn(terrain: String): Boolean {
|
||||
@ -83,8 +83,7 @@ class TileImprovement : RulesetStatsObject() {
|
||||
"Improvement" -> true // For situations involving tileFilter
|
||||
"All Road" -> isRoad()
|
||||
"Great Improvement", "Great" -> isGreatImprovement()
|
||||
in uniqueMap -> true
|
||||
else -> false
|
||||
else -> uniqueMap.hasTagUnique(filter)
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +119,8 @@ class TileImprovement : RulesetStatsObject() {
|
||||
val terrainsCanBeBuiltOnTypes = sequence {
|
||||
yieldAll(expandedTerrainsCanBeBuiltOn.asSequence()
|
||||
.mapNotNull { ruleset.terrains[it]?.type })
|
||||
yieldAll(TerrainType.values().asSequence()
|
||||
yieldAll(
|
||||
TerrainType.entries.asSequence()
|
||||
.filter { it.name in expandedTerrainsCanBeBuiltOn })
|
||||
}.filter { it.name !in cannotFilters }.toMutableSet()
|
||||
|
||||
|
@ -18,7 +18,7 @@ interface IHasUniques : INamed {
|
||||
// Every implementation should override these with the same `by lazy (::thingsProvider)`
|
||||
// AND every implementation should annotate these with `@delegate:Transient`
|
||||
val uniqueObjects: List<Unique>
|
||||
val uniqueMap: Map<String, List<Unique>>
|
||||
val uniqueMap: UniqueMap
|
||||
|
||||
fun uniqueObjectsProvider(): List<Unique> {
|
||||
if (uniques.isEmpty()) return emptyList()
|
||||
@ -36,24 +36,15 @@ interface IHasUniques : INamed {
|
||||
* */
|
||||
fun getUniqueTarget(): UniqueTarget
|
||||
|
||||
fun getMatchingUniques(uniqueTemplate: String, stateForConditionals: StateForConditionals? = null): Sequence<Unique> {
|
||||
val matchingUniques = uniqueMap[uniqueTemplate]
|
||||
?: return emptySequence()
|
||||
|
||||
val actualStateForConditionals = stateForConditionals ?: StateForConditionals()
|
||||
val uniques = matchingUniques.asSequence().filter { it.conditionalsApply(actualStateForConditionals) }
|
||||
return uniques.flatMap { it.getMultiplied(actualStateForConditionals) }
|
||||
}
|
||||
|
||||
fun getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals? = null) =
|
||||
getMatchingUniques(uniqueType.placeholderText, stateForConditionals)
|
||||
|
||||
fun hasUnique(uniqueTemplate: String, stateForConditionals: StateForConditionals? = null) =
|
||||
getMatchingUniques(uniqueTemplate, stateForConditionals).any()
|
||||
|
||||
fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals? = null) =
|
||||
getMatchingUniques(uniqueType.placeholderText, stateForConditionals).any()
|
||||
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
uniqueMap.getMatchingUniques(uniqueType, state)
|
||||
|
||||
fun hasUnique(uniqueType: UniqueType, state: StateForConditionals? = null) =
|
||||
uniqueMap.hasMatchingUnique(uniqueType, state ?: StateForConditionals.EmptyState)
|
||||
|
||||
fun hasUnique(tagUnique: String) =
|
||||
uniqueMap.hasTagUnique(tagUnique)
|
||||
|
||||
fun availabilityUniques(): Sequence<Unique> = getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals) + getMatchingUniques(UniqueType.CanOnlyBeBuiltWhen, StateForConditionals.IgnoreConditionals)
|
||||
|
||||
fun techsRequiredByUniques(): Sequence<String> {
|
||||
|
@ -92,6 +92,7 @@ data class StateForConditionals(
|
||||
|
||||
companion object {
|
||||
val IgnoreConditionals = StateForConditionals(ignoreConditionals = true)
|
||||
val EmptyState = StateForConditionals()
|
||||
}
|
||||
|
||||
/** Used ONLY for stateBasedRandom in [Conditionals.conditionalApplies] to prevent save scumming on [UniqueType.ConditionalChance] */
|
||||
|
@ -241,10 +241,12 @@ class LocalUniqueCache(val cache: Boolean = true) {
|
||||
}
|
||||
}
|
||||
|
||||
class UniqueMap() : HashMap<String, ArrayList<Unique>>() {
|
||||
class UniqueMap() {
|
||||
//todo Once all untyped Uniques are converted, this should be HashMap<UniqueType, *>
|
||||
// For now, we can have both map types "side by side" each serving their own purpose,
|
||||
// and gradually this one will be deprecated in favor of the other
|
||||
|
||||
private val innerUniqueMap = HashMap<String, ArrayList<Unique>>()
|
||||
|
||||
constructor(uniques: Sequence<Unique>) : this() {
|
||||
addUniques(uniques.asIterable())
|
||||
@ -252,9 +254,9 @@ class UniqueMap() : HashMap<String, ArrayList<Unique>>() {
|
||||
|
||||
/** Adds one [unique] unless it has a ConditionalTimedUnique conditional */
|
||||
fun addUnique(unique: Unique) {
|
||||
val existingArrayList = get(unique.placeholderText)
|
||||
val existingArrayList = innerUniqueMap[unique.placeholderText]
|
||||
if (existingArrayList != null) existingArrayList.add(unique)
|
||||
else this[unique.placeholderText] = arrayListOf(unique)
|
||||
else innerUniqueMap[unique.placeholderText] = arrayListOf(unique)
|
||||
}
|
||||
|
||||
/** Calls [addUnique] on each item from [uniques] */
|
||||
@ -263,18 +265,32 @@ class UniqueMap() : HashMap<String, ArrayList<Unique>>() {
|
||||
}
|
||||
|
||||
fun removeUnique(unique: Unique) {
|
||||
val existingArrayList = get(unique.placeholderText)
|
||||
val existingArrayList = innerUniqueMap[unique.placeholderText]
|
||||
existingArrayList?.remove(unique)
|
||||
}
|
||||
|
||||
fun clear() = innerUniqueMap.clear()
|
||||
|
||||
// Pure functions
|
||||
|
||||
fun hasUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueType).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
||||
|
||||
fun hasTagUnique(tagUnique: String) =
|
||||
innerUniqueMap.containsKey(tagUnique)
|
||||
|
||||
fun getUniques(uniqueType: UniqueType) =
|
||||
this[uniqueType.placeholderText]?.asSequence() ?: emptySequence()
|
||||
innerUniqueMap[uniqueType.placeholderText]?.asSequence() ?: emptySequence()
|
||||
|
||||
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals) = getUniques(uniqueType)
|
||||
.filter { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
||||
.flatMap { it.getMultiplied(state) }
|
||||
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueType)
|
||||
.filter { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
||||
.flatMap { it.getMultiplied(state) }
|
||||
|
||||
fun hasMatchingUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueType).any { it.conditionalsApply(state) }
|
||||
|
||||
fun getAllUniques() = this.asSequence().flatMap { it.value.asSequence() }
|
||||
fun getAllUniques() = innerUniqueMap.values.asSequence().flatten()
|
||||
|
||||
fun getTriggeredUniques(trigger: UniqueType, stateForConditionals: StateForConditionals): Sequence<Unique> {
|
||||
return getAllUniques().filter { unique ->
|
||||
|
@ -86,7 +86,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
super<INonPerpetualConstruction>.isHiddenBySettings(gameInfo) ||
|
||||
(!gameInfo.gameParameters.nuclearWeaponsEnabled && isNuclearWeapon())
|
||||
|
||||
fun getUpgradeUnits(stateForConditionals: StateForConditionals? = null): Sequence<String> {
|
||||
fun getUpgradeUnits(stateForConditionals: StateForConditionals = StateForConditionals.EmptyState): Sequence<String> {
|
||||
return sequence {
|
||||
yieldIfNotNull(upgradesTo)
|
||||
for (unique in getMatchingUniques(UniqueType.CanUpgrade, stateForConditionals))
|
||||
@ -94,7 +94,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
}
|
||||
}
|
||||
|
||||
fun getRulesetUpgradeUnits(stateForConditionals: StateForConditionals? = null): Sequence<BaseUnit> {
|
||||
fun getRulesetUpgradeUnits(stateForConditionals: StateForConditionals = StateForConditionals.EmptyState): Sequence<BaseUnit> {
|
||||
return sequence {
|
||||
for (unit in getUpgradeUnits(stateForConditionals))
|
||||
yieldIfNotNull(ruleset.units[unit])
|
||||
@ -115,18 +115,27 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
return unit
|
||||
}
|
||||
|
||||
|
||||
override fun hasUnique(uniqueType: UniqueType, state: StateForConditionals?): Boolean {
|
||||
return super<RulesetObject>.hasUnique(uniqueType, state) || ::ruleset.isInitialized && type.hasUnique(uniqueType, state)
|
||||
}
|
||||
|
||||
override fun hasUnique(tagUnique: String): Boolean {
|
||||
return super<RulesetObject>.hasUnique(tagUnique) || ::ruleset.isInitialized && type.hasUnique(tagUnique)
|
||||
}
|
||||
|
||||
/** Allows unique functions (getMatchingUniques, hasUnique) to "see" uniques from the UnitType */
|
||||
override fun getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals?): Sequence<Unique> {
|
||||
val ourUniques = super<RulesetObject>.getMatchingUniques(uniqueType, stateForConditionals)
|
||||
override fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals): Sequence<Unique> {
|
||||
val ourUniques = super<RulesetObject>.getMatchingUniques(uniqueType, state)
|
||||
if (! ::ruleset.isInitialized) { // Not sure if this will ever actually happen, but better safe than sorry
|
||||
return ourUniques
|
||||
}
|
||||
val typeUniques = type.getMatchingUniques(uniqueType, stateForConditionals)
|
||||
val typeUniques = type.getMatchingUniques(uniqueType, state)
|
||||
// Memory optimization - very rarely do we actually get uniques from both sources,
|
||||
// and sequence addition is expensive relative to the rare case that we'll actually need it
|
||||
if (ourUniques.none()) return typeUniques
|
||||
if (typeUniques.none()) return ourUniques
|
||||
return ourUniques + type.getMatchingUniques(uniqueType, stateForConditionals)
|
||||
return ourUniques + type.getMatchingUniques(uniqueType, state)
|
||||
}
|
||||
|
||||
override fun getProductionCost(civInfo: Civilization, city: City?): Int = costFunctions.getProductionCost(civInfo, city)
|
||||
@ -140,15 +149,6 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
return super.canBePurchasedWithStat(city, stat)
|
||||
}
|
||||
|
||||
/** Whenever we call .hasUniques() or .getMatchingUniques(), we also want to return the uniques from the unit type
|
||||
* All of the IHasUniques functions converge to getMatchingUniques, so overriding this one function gives us all of them */
|
||||
override fun getMatchingUniques(uniqueTemplate: String, stateForConditionals: StateForConditionals?): Sequence<Unique> {
|
||||
val baseUnitMatchingUniques = super<RulesetObject>.getMatchingUniques(uniqueTemplate, stateForConditionals)
|
||||
return if (::ruleset.isInitialized) baseUnitMatchingUniques +
|
||||
type.getMatchingUniques(uniqueTemplate, stateForConditionals)
|
||||
else baseUnitMatchingUniques // for e.g. Mod Checker, we may check a BaseUnit's uniques without initializing ruleset
|
||||
}
|
||||
|
||||
override fun getBaseBuyCost(city: City, stat: Stat): Float? {
|
||||
return sequence {
|
||||
val baseCost = super.getBaseBuyCost(city, stat)
|
||||
@ -413,7 +413,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
// "military units" --> "Military", using invariant locale
|
||||
&& matchesFilter(filter.removeSuffix(" units").lowercase().replaceFirstChar { it.uppercaseChar() })
|
||||
) return true
|
||||
return uniqueMap.contains(filter)
|
||||
return uniqueMap.hasTagUnique(filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -431,10 +431,10 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
val movesLikeAirUnits by lazy { type.getMovementType() == UnitMovementType.Air }
|
||||
|
||||
/** Returns resource requirements from both uniques and requiredResource field */
|
||||
override fun getResourceRequirementsPerTurn(stateForConditionals: StateForConditionals?): Counter<String> {
|
||||
override fun getResourceRequirementsPerTurn(state: StateForConditionals?): Counter<String> {
|
||||
val resourceRequirements = Counter<String>()
|
||||
if (requiredResource != null) resourceRequirements[requiredResource!!] = 1
|
||||
for (unique in getMatchingUniques(UniqueType.ConsumesResources, stateForConditionals))
|
||||
for (unique in getMatchingUniques(UniqueType.ConsumesResources, state ?: StateForConditionals.EmptyState))
|
||||
resourceRequirements[unique.params[1]] += unique.params[0].toInt()
|
||||
return resourceRequirements
|
||||
}
|
||||
|
@ -6,12 +6,6 @@ import com.unciv.models.ruleset.unique.UniqueTarget
|
||||
import com.unciv.ui.objectdescriptions.BaseUnitDescriptions.getUnitTypeCivilopediaTextLines
|
||||
|
||||
|
||||
enum class UnitLayer { // The layer in which the unit moves
|
||||
Civilian,
|
||||
Military,
|
||||
Air
|
||||
}
|
||||
|
||||
enum class UnitMovementType { // The types of tiles the unit can by default enter
|
||||
Land, // Only land tiles except when certain techs are researched
|
||||
Water, // Only water tiles
|
||||
@ -42,8 +36,7 @@ class UnitType() : RulesetObject() {
|
||||
"Land" -> isLandUnit()
|
||||
"Water" -> isWaterUnit()
|
||||
"Air" -> isAirUnit()
|
||||
in uniqueMap -> true
|
||||
else -> false
|
||||
else -> uniqueMap.hasTagUnique(filter)
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +51,7 @@ class UnitType() : RulesetObject() {
|
||||
val City = UnitType("City", "Land")
|
||||
|
||||
fun getCivilopediaIterator(ruleset: Ruleset): Collection<UnitType> {
|
||||
return UnitMovementType.values().map {
|
||||
return UnitMovementType.entries.map {
|
||||
// Create virtual UnitTypes to describe the movement domains - Civilopedia only.
|
||||
// It is important that the name includes the [] _everywhere_
|
||||
// (here, CivilopediaImageGetters, links, etc.) so translation comes as cheap as possible.
|
||||
|
Reference in New Issue
Block a user