Performance: Viewable tiles are now a transient list in civInfo

This commit is contained in:
Yair Morgenstern 2018-11-21 19:16:06 +02:00
parent bf4d9b609d
commit d7ddd85c32
14 changed files with 46 additions and 39 deletions

View file

@ -60,7 +60,7 @@ class GameInfo {
player.startTurn() player.startTurn()
val enemyUnitsCloseToTerritory = player.getViewableTiles() val enemyUnitsCloseToTerritory = player.viewableTiles
.filter { .filter {
it.militaryUnit != null && it.militaryUnit!!.civInfo != player it.militaryUnit != null && it.militaryUnit!!.civInfo != player
&& player.isAtWarWith(it.militaryUnit!!.civInfo) && player.isAtWarWith(it.militaryUnit!!.civInfo)
@ -80,7 +80,7 @@ class GameInfo {
if (tileToPlace == null) { if (tileToPlace == null) {
// Barbarians will only spawn in places that no one can see // Barbarians will only spawn in places that no one can see
val allViewableTiles = civilizations.filterNot { it.isBarbarianCivilization() } val allViewableTiles = civilizations.filterNot { it.isBarbarianCivilization() }
.flatMap { it.getViewableTiles() }.toHashSet() .flatMap { it.viewableTiles }.toHashSet()
val viableTiles = tileMap.values.filterNot { allViewableTiles.contains(it) || it.militaryUnit != null || it.civilianUnit != null } val viableTiles = tileMap.values.filterNot { allViewableTiles.contains(it) || it.militaryUnit != null || it.civilianUnit != null }
if (viableTiles.isEmpty()) return // no place for more barbs =( if (viableTiles.isEmpty()) return // no place for more barbs =(
tile = viableTiles.getRandom() tile = viableTiles.getRandom()
@ -92,10 +92,10 @@ class GameInfo {
tileMap.gameInfo = this tileMap.gameInfo = this
tileMap.setTransients() tileMap.setTransients()
for (civInfo in civilizations) { // this is separated into 2 loops because when we activate updateViewableTiles in civ.setTransients,
civInfo.gameInfo = this // we try to find new civs, and we check if civ is barbarian, which we can't know unless the gameInfo is already set.
civInfo.setTransients() for (civInfo in civilizations) civInfo.gameInfo = this
} for (civInfo in civilizations) civInfo.setTransients()
for (civInfo in civilizations) { for (civInfo in civilizations) {
// we have to remove hydro plants from all cities BEFORE we update a single one, // we have to remove hydro plants from all cities BEFORE we update a single one,

View file

@ -146,7 +146,7 @@ class UnitAutomation{
class AttackableTile(val tileToAttackFrom:TileInfo, val tileToAttack:TileInfo) class AttackableTile(val tileToAttackFrom:TileInfo, val tileToAttack:TileInfo)
fun getAttackableEnemies(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): ArrayList<AttackableTile> { fun getAttackableEnemies(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): ArrayList<AttackableTile> {
val tilesWithEnemies = unit.civInfo.getViewableTiles() val tilesWithEnemies = unit.civInfo.viewableTiles
.filter { containsAttackableEnemy(it,unit) } .filter { containsAttackableEnemy(it,unit) }
val rangeOfAttack = unit.getRange() val rangeOfAttack = unit.getRange()

View file

@ -95,6 +95,8 @@ class CityExpansionManager {
for(unit in tileInfo.getUnits()) for(unit in tileInfo.getUnits())
if(!unit.civInfo.canEnterTiles(cityInfo.civInfo)) if(!unit.civInfo.canEnterTiles(cityInfo.civInfo))
unit.movementAlgs().teleportToClosestMoveableTile() unit.movementAlgs().teleportToClosestMoveableTile()
cityInfo.civInfo.updateViewableTiles()
} }

View file

@ -35,6 +35,7 @@ class CityInfo {
constructor() // for json parsing, we need to have a default constructor constructor() // for json parsing, we need to have a default constructor
constructor(civInfo: CivilizationInfo, cityLocation: Vector2) { constructor(civInfo: CivilizationInfo, cityLocation: Vector2) {
this.civInfo = civInfo this.civInfo = civInfo
this.location = cityLocation
setTransients() setTransients()
// Since cities can be captures between civilizations, // Since cities can be captures between civilizations,
@ -48,7 +49,6 @@ class CityInfo {
else name = civInfo.getNation().cities.map { "Newer $it" }.first{ !allExistingCityNames.contains(it) } else name = civInfo.getNation().cities.map { "Newer $it" }.first{ !allExistingCityNames.contains(it) }
} }
this.location = cityLocation
civInfo.cities = civInfo.cities.toMutableList().apply { add(this@CityInfo) } civInfo.cities = civInfo.cities.toMutableList().apply { add(this@CityInfo) }
if(civInfo == civInfo.gameInfo.getPlayerCivilization()) if(civInfo == civInfo.gameInfo.getPlayerCivilization())
civInfo.addNotification("[$name] has been founded!", cityLocation, Color.PURPLE) civInfo.addNotification("[$name] has been founded!", cityLocation, Color.PURPLE)

View file

@ -26,6 +26,7 @@ import kotlin.math.roundToInt
class CivilizationInfo { class CivilizationInfo {
@Transient lateinit var gameInfo: GameInfo @Transient lateinit var gameInfo: GameInfo
@Transient var units=ArrayList<MapUnit>() @Transient var units=ArrayList<MapUnit>()
@Transient var viewableTiles = HashSet<TileInfo>()
var gold = 0 var gold = 0
var happiness = 15 var happiness = 15
@ -203,25 +204,26 @@ class CivilizationInfo {
return units.toList() // to avoid comodification problems (ie concurrency again...) return units.toList() // to avoid comodification problems (ie concurrency again...)
} }
fun getViewableTiles(): List<TileInfo> {
var viewablePositions = emptyList<TileInfo>()
viewablePositions += cities.flatMap { it.getTiles() }
.flatMap { it.neighbors } // tiles adjacent to city tiles
viewablePositions += getCivUnits()
.flatMap { it.getViewableTiles()} // Tiles within 2 tiles of units
viewablePositions.map { it.position }.filterNot { exploredTiles.contains(it) }.toCollection(exploredTiles)
val viewedCivs = viewablePositions fun updateViewableTiles() {
.flatMap { it.getUnits().map { u->u.civInfo }.union(listOf(it.getOwner())) } viewableTiles.clear()
.filterNotNull().filterNot { it==this || it.isBarbarianCivilization() } viewableTiles.addAll(cities.flatMap { it.getTiles() }.flatMap { it.neighbors }) // tiles adjacent to city tiles
viewableTiles.addAll(getCivUnits().flatMap { it.getViewableTiles()})
// updating the viewable tiles also affects the explored tiles, obvs
viewableTiles.asSequence().map { it.position }
.filterNot { exploredTiles.contains(it) }.toCollection(exploredTiles)
val viewedCivs = viewableTiles
.flatMap { it.getUnits().map { unit->unit.civInfo }.union(listOf(it.getOwner())) }
// we can meet a civ either by meeting its unit, or its tile
.asSequence().filterNotNull().filterNot { it==this || it.isBarbarianCivilization() }
for(otherCiv in viewedCivs) for(otherCiv in viewedCivs)
if(!diplomacy.containsKey(otherCiv.civName)){ if(!diplomacy.containsKey(otherCiv.civName)){
meetCivilization(otherCiv) meetCivilization(otherCiv)
addNotification("We have encountered [${otherCiv.civName}]!".tr(),null, Color.GOLD) addNotification("We have encountered [${otherCiv.civName}]!".tr(),null, Color.GOLD)
} }
return viewablePositions.distinct()
} }
fun meetCivilization(otherCiv: CivilizationInfo) { fun meetCivilization(otherCiv: CivilizationInfo) {
@ -254,7 +256,6 @@ class CivilizationInfo {
//region state-changing functions //region state-changing functions
fun setTransients() { fun setTransients() {
if(civName=="") civName="Babylon" // this is because it used to be a default but now it isn't so we can change it.
goldenAges.civInfo = this goldenAges.civInfo = this
policies.civInfo = this policies.civInfo = this
if(policies.adoptedPolicies.size>0 && policies.numberOfAdoptedPolicies == 0) if(policies.adoptedPolicies.size>0 && policies.numberOfAdoptedPolicies == 0)
@ -263,16 +264,13 @@ class CivilizationInfo {
tech.civInfo = this tech.civInfo = this
diplomacy.values.forEach { it.civInfo=this} diplomacy.values.forEach { it.civInfo=this}
for (unit in gameInfo.tileMap.values.flatMap { it.getUnits() }.filter { it.owner==civName }) {
unit.assignOwner(this)
unit.setTransients()
}
for (cityInfo in cities) { for (cityInfo in cities) {
cityInfo.civInfo = this // must be before the city's setTransients because it depends on the tilemap, that comes from the civInfo cityInfo.civInfo = this // must be before the city's setTransients because it depends on the tilemap, that comes from the civInfo
cityInfo.setTransients() cityInfo.setTransients()
} }
setCitiesConnectedToCapitalTransients() setCitiesConnectedToCapitalTransients()
updateViewableTiles()
} }
fun endTurn() { fun endTurn() {
@ -320,7 +318,7 @@ class CivilizationInfo {
} }
fun startTurn(){ fun startTurn(){
getViewableTiles() // adds explored tiles so that the units will be able to perform automated actions better updateViewableTiles() // adds explored tiles so that the units will be able to perform automated actions better
setCitiesConnectedToCapitalTransients() setCitiesConnectedToCapitalTransients()
for (city in cities) for (city in cities)
city.cityStats.update() city.cityStats.update()

View file

@ -314,6 +314,7 @@ class MapUnit {
currentTile = tile currentTile = tile
if(tile.improvement=="Ancient ruins" && !civInfo.isBarbarianCivilization()) if(tile.improvement=="Ancient ruins" && !civInfo.isBarbarianCivilization())
getAncientRuinBonus() getAncientRuinBonus()
civInfo.updateViewableTiles()
} }
private fun getAncientRuinBonus() { private fun getAncientRuinBonus() {

View file

@ -200,7 +200,7 @@ open class TileInfo {
override fun toString(): String { override fun toString(): String {
val SB = StringBuilder() val SB = StringBuilder()
val isViewableToPlayer = UnCivGame.Current.gameInfo.getPlayerCivilization().getViewableTiles().contains(this) val isViewableToPlayer = UnCivGame.Current.gameInfo.getPlayerCivilization().viewableTiles.contains(this)
|| UnCivGame.Current.viewEntireMapForDebug || UnCivGame.Current.viewEntireMapForDebug
if (isCityCenter()) { if (isCityCenter()) {

View file

@ -117,6 +117,11 @@ class TileMap {
tileInfo.tileMap = this tileInfo.tileMap = this
if(tileInfo.militaryUnit!=null) tileInfo.militaryUnit!!.currentTile = tileInfo if(tileInfo.militaryUnit!=null) tileInfo.militaryUnit!!.currentTile = tileInfo
if(tileInfo.civilianUnit!=null) tileInfo.civilianUnit!!.currentTile = tileInfo if(tileInfo.civilianUnit!=null) tileInfo.civilianUnit!!.currentTile = tileInfo
for (unit in tileInfo.getUnits()) {
unit.assignOwner(gameInfo.civilizations.first { it.civName == unit.owner })
unit.setTransients()
}
} }
} }

View file

@ -20,6 +20,7 @@ class UnitPromotions{
promotions.add(promotionName) promotions.add(promotionName)
numberOfPromotions++ numberOfPromotions++
unit.updateUniques() unit.updateUniques()
unit.civInfo.updateViewableTiles() // some promotions give the unit bonus sight
} }
fun getAvailablePromotions(): List<Promotion> { fun getAvailablePromotions(): List<Promotion> {

View file

@ -331,17 +331,19 @@ open class TileGroup(var tileInfo: TileInfo) : Group() {
return newImage return newImage
} }
fun getBackgroundImageForUnit(unit: MapUnit):Image{
return when {
unit.isEmbarked() -> ImageGetter.getImage("OtherIcons/Banner")
unit.isFortified() -> ImageGetter.getImage("OtherIcons/Shield.png")
else -> ImageGetter.getImage("OtherIcons/Circle.png")
}
}
fun getUnitImage(unit: MapUnit, size: Float): Group { fun getUnitImage(unit: MapUnit, size: Float): Group {
val unitBaseImage = ImageGetter.getUnitIcon(unit.name, unit.civInfo.getNation().getSecondaryColor()) val unitBaseImage = ImageGetter.getUnitIcon(unit.name, unit.civInfo.getNation().getSecondaryColor())
.apply { setSize(size*0.75f, size*0.75f) } .apply { setSize(size*0.75f, size*0.75f) }
val background = val background = getBackgroundImageForUnit(unit)
when {
unit.isEmbarked() -> ImageGetter.getImage("OtherIcons/Banner")
unit.isFortified() -> ImageGetter.getImage("OtherIcons/Shield.png")
else -> ImageGetter.getImage("OtherIcons/Circle.png")
}
background.apply { background.apply {
this.color = unit.civInfo.getNation().getColor() this.color = unit.civInfo.getNation().getColor()
setSize(size, size) setSize(size, size)

View file

@ -5,7 +5,6 @@ import com.unciv.logic.city.CityInfo
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.ui.utils.CameraStageBaseScreen import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.center import com.unciv.ui.utils.center
@ -13,8 +12,7 @@ class WorldTileGroup(tileInfo: TileInfo) : TileGroup(tileInfo) {
var cityButton: CityButton? = null var cityButton: CityButton? = null
fun addWhiteHaloAroundUnit(unit: MapUnit) { fun addWhiteHaloAroundUnit(unit: MapUnit) {
val whiteHalo = if(unit.isFortified()) ImageGetter.getImage("OtherIcons/Shield.png") val whiteHalo = getBackgroundImageForUnit(unit)
else ImageGetter.getImage("OtherIcons/Circle.png")
whiteHalo.setSize(30f,30f) whiteHalo.setSize(30f,30f)
val unitImage = if(unit.type.isCivilian()) civilianUnitImage val unitImage = if(unit.type.isCivilian()) civilianUnitImage
else militaryUnitImage else militaryUnitImage

View file

@ -145,7 +145,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
} }
internal fun updateTiles(civInfo: CivilizationInfo) { internal fun updateTiles(civInfo: CivilizationInfo) {
val playerViewableTilePositions = civInfo.getViewableTiles().map { it.position }.toHashSet() val playerViewableTilePositions = civInfo.viewableTiles.map { it.position }.toHashSet()
cityButtonOverlays.forEach{it.remove()} cityButtonOverlays.forEach{it.remove()}
cityButtonOverlays.clear() cityButtonOverlays.clear()

View file

@ -265,7 +265,7 @@ class WorldScreen : CameraStageBaseScreen() {
val shownTutorials = UnCivGame.Current.settings.tutorialsShown val shownTutorials = UnCivGame.Current.settings.tutorialsShown
displayTutorials("NextTurn") displayTutorials("NextTurn")
if("BarbarianEncountered" !in shownTutorials if("BarbarianEncountered" !in shownTutorials
&& civInfo.getViewableTiles().any { it.getUnits().any { unit -> unit.civInfo.isBarbarianCivilization() } }) && civInfo.viewableTiles.any { it.getUnits().any { unit -> unit.civInfo.isBarbarianCivilization() } })
displayTutorials("BarbarianEncountered") displayTutorials("BarbarianEncountered")
if(civInfo.cities.size > 2) displayTutorials("SecondCity") if(civInfo.cities.size > 2) displayTutorials("SecondCity")
if(civInfo.happiness<0) displayTutorials("Unhappiness") if(civInfo.happiness<0) displayTutorials("Unhappiness")

View file

@ -127,7 +127,7 @@ class WorldScreenTopBar(val screen: WorldScreen) : Table() {
else -> 2020+(turns-440)/2 else -> 2020+(turns-440)/2
} }
turnsLabel.setText("Turn".tr()+" " + civInfo.gameInfo.turns + " | "+ abs(year)+(if (year<0) " BE" else " AD")) turnsLabel.setText("Turn".tr()+" " + civInfo.gameInfo.turns + " | "+ abs(year)+(if (year<0) " BC" else " AD"))
val nextTurnStats = civInfo.getStatsForNextTurn() val nextTurnStats = civInfo.getStatsForNextTurn()
val goldPerTurn = "(" + (if (nextTurnStats.gold > 0) "+" else "") + Math.round(nextTurnStats.gold) + ")" val goldPerTurn = "(" + (if (nextTurnStats.gold > 0) "+" else "") + Math.round(nextTurnStats.gold) + ")"