Performance: Save civ tiles and neighbors in cache to update visible tiles faster

This commit is contained in:
Yair Morgenstern 2023-04-03 10:32:09 +03:00
parent 3db03a78d2
commit f50b88c9a5
6 changed files with 43 additions and 26 deletions

View File

@ -551,8 +551,10 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
) {
for (unit in civInfo.units.getCivUnits())
unit.updateVisibleTiles(false) // this needs to be done after all the units are assigned to their civs and all other transients are set
if(civInfo.playerType == PlayerType.Human)
if (civInfo.playerType == PlayerType.Human)
civInfo.exploredRegion.setMapParameters(tileMap.mapParameters) // Required for the correct calculation of the explored region on world wrap maps
civInfo.cache.updateOurTiles()
civInfo.cache.updateSightAndResources() // only run ONCE and not for each unit - this is a huge performance saver!
// Since this depends on the cities of ALL civilizations,

View File

@ -17,6 +17,7 @@ import com.unciv.logic.map.tile.RoadStatus
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
import com.unciv.utils.Log
@ -370,9 +371,14 @@ class WorkerAutomation(
}
if (potentialTileImprovements.isEmpty()) return null
val cityUniqueCaches = HashMap<City, LocalUniqueCache>()
fun getRankingWithImprovement(improvementName: String): Float {
val improvement = ruleSet.tileImprovements[improvementName]!!
val stats = tile.stats.getImprovementStats(improvement, civInfo, tile.getCity())
val city = tile.getCity()
val cache =
if (city == null) LocalUniqueCache(false)
else cityUniqueCaches.getOrPut(city) { LocalUniqueCache() }
val stats = tile.stats.getImprovementStats(improvement, civInfo, tile.getCity(), cache)
return Automation.rankStatsValue(stats, unit.civ)
}

View File

@ -147,7 +147,7 @@ class CityExpansionManager : IsPartOfGameInfoSerialization {
tile.setOwningCity(null)
city.civ.cache.updateCivResources()
city.civ.cache.updateOurTiles()
city.cityStats.update()
tile.history.recordRelinquishOwnership(tile)
@ -169,14 +169,13 @@ class CityExpansionManager : IsPartOfGameInfoSerialization {
city.tiles = city.tiles.withItem(tile.position)
tile.setOwningCity(city)
city.population.autoAssignPopulation()
city.civ.cache.updateCivResources()
city.civ.cache.updateOurTiles()
city.cityStats.update()
for (unit in tile.getUnits().toList()) // toListed because we're modifying
if (!unit.civ.diplomacyFunctions.canPassThroughTiles(city.civ))
unit.movement.teleportToClosestMoveableTile()
city.civ.cache.updateViewableTiles()
tile.history.recordTakeOwnership(tile)
}
@ -187,7 +186,8 @@ class CityExpansionManager : IsPartOfGameInfoSerialization {
val location = addNewTileWithCulture()
if (location != null) {
val locations = LocationAction(location, city.location)
city.civ.addNotification("[" + city.name + "] has expanded its borders!", locations, NotificationCategory.Cities, NotificationIcon.Culture)
city.civ.addNotification("[${city.name}] has expanded its borders!", locations,
NotificationCategory.Cities, NotificationIcon.Culture)
}
}
}

View File

@ -126,7 +126,6 @@ class CityInfoConquestFunctions(val city: City){
}
}
conqueringCiv.cache.updateViewableTiles() // Might see new tiles from this city
}
@ -266,18 +265,16 @@ class CityInfoConquestFunctions(val city: City){
}
fun moveToCiv(newCivInfo: Civilization) {
fun moveToCiv(newCiv: Civilization) {
val oldCiv = city.civ
city.apply {
val oldCiv = civ
// Remove/relocate palace for old Civ - need to do this BEFORE we move the cities between
// civs so the capitalCityIndicator recognizes the unique buildings of the conquered civ
if (oldCiv.getCapital() == this) oldCiv.moveCapitalToNextLargest()
oldCiv.cities = oldCiv.cities.toMutableList().apply { remove(city) }
newCivInfo.cities = newCivInfo.cities.toMutableList().apply { add(city) }
civ = newCivInfo
newCiv.cities = newCiv.cities.toMutableList().apply { add(city) }
civ = newCiv
hasJustBeenConquered = false
turnAcquired = civ.gameInfo.turns
previousOwner = oldCiv.civName
@ -297,8 +294,8 @@ class CityInfoConquestFunctions(val city: City){
// Place palace for newCiv if this is the only city they have
// This needs to happen _before_ free buildings are added, as sometimes these should
// only be placed in the capital, and then there needs to be a capital.
if (newCivInfo.cities.size == 1) {
newCivInfo.moveCapitalTo(this)
if (newCiv.cities.size == 1) {
newCiv.moveCapitalTo(this)
}
// Add our free buildings to this city and add free buildings provided by the city to other cities
@ -308,7 +305,7 @@ class CityInfoConquestFunctions(val city: City){
// Transfer unique buildings
for (building in cityConstructions.getBuiltBuildings()) {
val civEquivalentBuilding = newCivInfo.getEquivalentBuilding(building.name)
val civEquivalentBuilding = newCiv.getEquivalentBuilding(building.name)
if (building != civEquivalentBuilding) {
cityConstructions.removeBuilding(building.name)
cityConstructions.addBuilding(civEquivalentBuilding.name)
@ -329,6 +326,8 @@ class CityInfoConquestFunctions(val city: City){
tile.history.recordTakeOwnership(tile)
}
}
newCiv.cache.updateOurTiles()
oldCiv.cache.updateOurTiles()
}
}

View File

@ -138,8 +138,24 @@ class CivInfoTransientCache(val civInfo: Civilization) {
civInfo.viewableInvisibleUnitsTiles = newViewableInvisibleTiles
}
private var ourTilesAndNeighboringTiles: Set<Tile> = HashSet()
/** Our tiles update pretty infrequently - most 'viewable tile' changes are due to unit movements,
* which means we can store this separately and use it 'as is' so we don't need to find the neighboring tiles every time
* a unit moves */
fun updateOurTiles(){
val newOurTilesAndNeighboring = HashSet<Tile>()
val ownedTiles = civInfo.cities.asSequence().flatMap { it.getTiles() }
newOurTilesAndNeighboring.addAll(ownedTiles)
val neighboringUnownedTiles = ownedTiles.flatMap { tile -> tile.neighbors.filter { it.getOwner() != civInfo } }
newOurTilesAndNeighboring.addAll(neighboringUnownedTiles)
ourTilesAndNeighboringTiles = newOurTilesAndNeighboring
updateViewableTiles()
updateCivResources()
}
private fun setNewViewableTiles() {
val newViewableTiles = HashSet<Tile>()
// while spectating all map is visible
if (civInfo.isSpectator() || DebugUtils.VISIBLE_MAP) {
@ -149,13 +165,7 @@ class CivInfoTransientCache(val civInfo: Civilization) {
return
}
// There are a LOT of tiles usually.
// And making large lists of them just as intermediaries before we shove them into the hashset is very space-inefficient.
// And so, sequences to the rescue!
val ownedTiles = civInfo.cities.asSequence().flatMap { it.getTiles() }
newViewableTiles.addAll(ownedTiles)
val neighboringUnownedTiles = ownedTiles.flatMap { tile -> tile.neighbors.filter { it.getOwner() != civInfo } }
newViewableTiles.addAll(neighboringUnownedTiles)
val newViewableTiles = HashSet<Tile>(ourTilesAndNeighboringTiles)
newViewableTiles.addAll(civInfo.units.getCivUnits().flatMap { unit -> unit.viewableTiles.asSequence().filter { it.getOwner() != civInfo } })
for (otherCiv in civInfo.getKnownCivs()) {

View File

@ -105,8 +105,8 @@ class TradeLogic(val ourCivilization:Civilization, val otherCivilization: Civili
unit.movement.teleportToClosestMoveableTile()
}
}
to.cache.updateViewableTiles()
from.cache.updateViewableTiles()
to.cache.updateOurTiles()
from.cache.updateOurTiles()
// suggest an option to liberate the city
if (to.isHuman()