Many UI updates are now done on the clone, so we won't get concurrency exceptions

This commit is contained in:
Yair Morgenstern 2018-08-25 23:15:40 +03:00
parent ce00fd43e0
commit 86464ccdb0
8 changed files with 73 additions and 47 deletions

View File

@ -41,8 +41,8 @@ class NewGameScreen: PickerScreen(){
private fun getUniqueLabel(nation: Nation): CharSequence? { private fun getUniqueLabel(nation: Nation): CharSequence? {
for (building in GameBasics.Buildings.values) for (building in GameBasics.Buildings.values)
if (building.uniqueTo == nation.name) { if (building.uniqueTo == nation.name) {
var text = building.name + " - {replaces} " + building.replaces + "\n" var text = building.name.tr() + " - {replaces} " + building.replaces!!.tr() + "\n"
val originalBuilding = GameBasics.Buildings[building.replaces]!! val originalBuilding = GameBasics.Buildings[building.replaces!!]!!
val originalBuildingStatMap = originalBuilding.toHashMap() val originalBuildingStatMap = originalBuilding.toHashMap()
for (stat in building.toHashMap()) for (stat in building.toHashMap())
if (stat.value != originalBuildingStatMap[stat.key]) if (stat.value != originalBuildingStatMap[stat.key])
@ -54,8 +54,8 @@ class NewGameScreen: PickerScreen(){
for (unit in GameBasics.Units.values) for (unit in GameBasics.Units.values)
if (unit.uniqueTo == nation.name) { if (unit.uniqueTo == nation.name) {
var text = unit.name + " - {replaces} " + unit.replaces + "\n" var text = unit.name.tr() + " - {replaces} " + unit.replaces!!.tr() + "\n"
val originalUnit = GameBasics.Units[unit.replaces]!! val originalUnit = GameBasics.Units[unit.replaces!!]!!
if (unit.strength != originalUnit.strength) if (unit.strength != originalUnit.strength)
text += "{Combat strength} " + unit.strength + " vs " + originalUnit.strength + "\n" text += "{Combat strength} " + unit.strength + " vs " + originalUnit.strength + "\n"
if (unit.rangedStrength!= originalUnit.rangedStrength) if (unit.rangedStrength!= originalUnit.rangedStrength)

View File

@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.unciv.UnCivGame import com.unciv.UnCivGame
import com.unciv.logic.HexMath import com.unciv.logic.HexMath
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.addClickListener import com.unciv.ui.utils.addClickListener
@ -67,11 +68,10 @@ class Minimap(val tileMapHolder: TileMapHolder) : ScrollPane(null){
return true return true
} }
}) })
update()
} }
fun update(){ fun update(cloneCivilization: CivilizationInfo) {
val exploredTiles = tileMapHolder.worldScreen.civInfo.exploredTiles val exploredTiles = cloneCivilization.exploredTiles
for(tileInfo in tileMapHolder.tileMap.values) { for(tileInfo in tileMapHolder.tileMap.values) {
val RGB = tileInfo.getBaseTerrain().RGB!! val RGB = tileInfo.getBaseTerrain().RGB!!
val hex = tileImages[tileInfo]!! val hex = tileImages[tileInfo]!!

View File

@ -8,14 +8,14 @@ import com.unciv.logic.civilization.Notification
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
import kotlin.math.min import kotlin.math.min
class NotificationsScroll(private val notifications: List<Notification>, internal val worldScreen: WorldScreen) : ScrollPane(null) { class NotificationsScroll(internal val worldScreen: WorldScreen) : ScrollPane(null) {
private var notificationsTable = Table() private var notificationsTable = Table()
init { init {
widget = notificationsTable widget = notificationsTable
} }
internal fun update() { internal fun update(notifications: MutableList<Notification>) {
notificationsTable.clearChildren() notificationsTable.clearChildren()
for (notification in notifications) { for (notification in notifications) {
val label = Label(notification.text.tr(), CameraStageBaseScreen.skin).setFontColor(Color.BLACK) val label = Label(notification.text.tr(), CameraStageBaseScreen.skin).setFontColor(Color.BLACK)

View File

@ -2,6 +2,7 @@ package com.unciv.ui.worldscreen
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.InputEvent import com.badlogic.gdx.scenes.scene2d.InputEvent
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
@ -15,12 +16,15 @@ import com.unciv.logic.map.TileMap
import com.unciv.models.gamebasics.unit.UnitType import com.unciv.models.gamebasics.unit.UnitType
import com.unciv.ui.tilegroups.WorldTileGroup import com.unciv.ui.tilegroups.WorldTileGroup
import com.unciv.ui.utils.addClickListener import com.unciv.ui.utils.addClickListener
import com.unciv.ui.utils.center
import com.unciv.ui.utils.colorFromRGB import com.unciv.ui.utils.colorFromRGB
class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap, internal val civInfo: CivilizationInfo) : ScrollPane(null) { class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap) : ScrollPane(null) {
internal var selectedTile: TileInfo? = null internal var selectedTile: TileInfo? = null
val tileGroups = HashMap<TileInfo, WorldTileGroup>() val tileGroups = HashMap<TileInfo, WorldTileGroup>()
var overlayActor :Actor?=null
internal fun addTiles() { internal fun addTiles() {
val allTiles = Group() val allTiles = Group()
val groupPadding = 300f // This is so that no tile will be stuck "on the side" and be unreachable or difficult to reach val groupPadding = 300f // This is so that no tile will be stuck "on the side" and be unreachable or difficult to reach
@ -35,7 +39,22 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
group.addClickListener { group.addClickListener {
worldScreen.displayTutorials("TileClicked") worldScreen.displayTutorials("TileClicked")
if(overlayActor!=null) overlayActor!!.remove()
selectedTile = tileInfo selectedTile = tileInfo
// val selectedUnit = worldScreen.bottomBar.unitTable.selectedUnit
// if(selectedUnit!=null && selectedUnit.getTile()!=tileInfo
// && selectedUnit.canMoveTo(tileInfo) && selectedUnit.movementAlgs().canReach(tileInfo)) {
// val size = 40f
// val moveHereGroup = Group().apply { width = size;height = size; }
// moveHereGroup.addActor(ImageGetter.getImage("OtherIcons/Circle").apply { width = size; height = size })
// moveHereGroup.addActor(ImageGetter.getStatIcon("Movement").apply { width = size / 2; height = size / 2; center(moveHereGroup) })
// if(selectedUnit.currentMovement>0)
// moveHereGroup.addClickListener { selectedUnit.movementAlgs().headTowards(tileInfo);worldScreen.update() }
// else moveHereGroup.color.a=0.5f
// addAboveGroup(group, moveHereGroup).apply { width = size; height = size }
// }
worldScreen.bottomBar.unitTable.tileSelected(tileInfo) worldScreen.bottomBar.unitTable.tileSelected(tileInfo)
worldScreen.update() worldScreen.update()
} }
@ -69,7 +88,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
setSize(worldScreen.stage.width, worldScreen.stage.height) setSize(worldScreen.stage.width, worldScreen.stage.height)
addListener(object : ActorGestureListener() { addListener(object : ActorGestureListener() {
var lastScale = 1f var lastScale = 1f
internal var lastInitialDistance = 0f var lastInitialDistance = 0f
override fun zoom(event: InputEvent?, initialDistance: Float, distance: Float) { override fun zoom(event: InputEvent?, initialDistance: Float, distance: Float) {
if (lastInitialDistance != initialDistance) { if (lastInitialDistance != initialDistance) {
@ -87,14 +106,23 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
layout() // Fit the scroll pane to the contents - otherwise, setScroll won't work! layout() // Fit the scroll pane to the contents - otherwise, setScroll won't work!
} }
internal fun updateTiles() { private fun addAboveGroup(group:Group, actor: Actor) {
val playerViewableTiles = civInfo.getViewableTiles().toHashSet() actor.center(group)
actor.x+=group.x
actor.y+=group.y+group.height
group.parent.addActor(actor)
actor.toFront()
overlayActor=actor
}
internal fun updateTiles(civInfo: CivilizationInfo) {
val playerViewableTilePositions = civInfo.getViewableTiles().map { it.position }.toHashSet()
for (WG in tileGroups.values){ for (WG in tileGroups.values){
WG.update(playerViewableTiles.contains(WG.tileInfo)) WG.update(playerViewableTilePositions.contains(WG.tileInfo.position))
val unitsInTile = WG.tileInfo.getUnits() val unitsInTile = WG.tileInfo.getUnits()
if((playerViewableTiles.contains(WG.tileInfo) || UnCivGame.Current.viewEntireMapForDebug) if((playerViewableTilePositions.contains(WG.tileInfo.position) || UnCivGame.Current.viewEntireMapForDebug)
&& unitsInTile.isNotEmpty() && unitsInTile.first().civInfo!=civInfo) && unitsInTile.isNotEmpty() && !unitsInTile.first().civInfo.isPlayerCivilization())
WG.showCircle(Color.RED) WG.showCircle(Color.RED)
} // Display ALL viewable enemies with a red circle so that users don't need to go "hunting" for enemy units } // Display ALL viewable enemies with a red circle so that users don't need to go "hunting" for enemy units
@ -116,7 +144,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
for (tile in attackableTiles.filter { for (tile in attackableTiles.filter {
it.getUnits().isNotEmpty() it.getUnits().isNotEmpty()
&& it.getUnits().first().owner != unit.owner && it.getUnits().first().owner != unit.owner
&& (playerViewableTiles.contains(it) || UnCivGame.Current.viewEntireMapForDebug)}) { && (playerViewableTilePositions.contains(it.position) || UnCivGame.Current.viewEntireMapForDebug)}) {
if(unit.baseUnit().unitType== UnitType.Civilian) tileGroups[tile]!!.hideCircle() if(unit.baseUnit().unitType== UnitType.Civilian) tileGroups[tile]!!.hideCircle()
else { else {
tileGroups[tile]!!.showCircle(colorFromRGB(237, 41, 57)) tileGroups[tile]!!.showCircle(colorFromRGB(237, 41, 57))

View File

@ -22,7 +22,7 @@ class WorldScreen : CameraStageBaseScreen() {
val gameInfo = game.gameInfo val gameInfo = game.gameInfo
internal val civInfo: CivilizationInfo = gameInfo.getPlayerCivilization() internal val civInfo: CivilizationInfo = gameInfo.getPlayerCivilization()
val tileMapHolder: TileMapHolder = TileMapHolder(this, gameInfo.tileMap, civInfo) val tileMapHolder: TileMapHolder = TileMapHolder(this, gameInfo.tileMap)
val minimap = Minimap(tileMapHolder) val minimap = Minimap(tileMapHolder)
internal var buttonScale = 0.9f internal var buttonScale = 0.9f
@ -42,7 +42,7 @@ class WorldScreen : CameraStageBaseScreen() {
nextTurnButton.setPosition(stage.width - nextTurnButton.width - 10f, nextTurnButton.setPosition(stage.width - nextTurnButton.width - 10f,
topBar.y - nextTurnButton.height - 10f) topBar.y - nextTurnButton.height - 10f)
notificationsScroll = NotificationsScroll(gameInfo.notifications, this) notificationsScroll = NotificationsScroll(this)
notificationsScroll.width = stage.width/3 notificationsScroll.width = stage.width/3
minimap.setSize(stage.width/5,stage.height/5) minimap.setSize(stage.width/5,stage.height/5)
minimap.x = stage.width - minimap.width minimap.x = stage.width - minimap.width
@ -83,8 +83,11 @@ class WorldScreen : CameraStageBaseScreen() {
fun update() { fun update() {
// many of the display functions will be called with the game clone and not the actual game,
// because that's guaranteed to stay the exact same and so we won't get any concurrent modification exceptions
val gameClone = gameInfo.clone() val gameClone = gameInfo.clone()
// so we don't get a concurrent modification exception, we clone the entire game (yes really, it's actually very fast) val cloneCivilization = gameClone.getPlayerCivilization()
kotlin.concurrent.thread { kotlin.concurrent.thread {
civInfo.happiness = gameClone.getPlayerCivilization().getHappinessForNextTurn().values.sum().toInt() civInfo.happiness = gameClone.getPlayerCivilization().getHappinessForNextTurn().values.sum().toInt()
} }
@ -101,27 +104,27 @@ class WorldScreen : CameraStageBaseScreen() {
} }
if(!UnCivGame.Current.settings.tutorialsShown.contains("EnemyCityNeedsConqueringWithMeleeUnit")) { if(!UnCivGame.Current.settings.tutorialsShown.contains("EnemyCityNeedsConqueringWithMeleeUnit")) {
for (enemyCity in civInfo.diplomacy.values.filter { it.diplomaticStatus == DiplomaticStatus.War } for (enemyCity in cloneCivilization.diplomacy.values.filter { it.diplomaticStatus == DiplomaticStatus.War }
.map { it.otherCiv() }.flatMap { it.cities }) { .map { it.otherCiv() }.flatMap { it.cities }) {
if (enemyCity.health == 1 && enemyCity.getCenterTile().getTilesInDistance(2) if (enemyCity.health == 1 && enemyCity.getCenterTile().getTilesInDistance(2)
.any { it.getUnits().any { unit -> unit.civInfo == civInfo } }) .any { it.getUnits().any { unit -> unit.civInfo == cloneCivilization } })
displayTutorials("EnemyCityNeedsConqueringWithMeleeUnit") displayTutorials("EnemyCityNeedsConqueringWithMeleeUnit")
} }
} }
updateTechButton() updateTechButton(cloneCivilization)
updateDiplomacyButton() updateDiplomacyButton(cloneCivilization)
bottomBar.update(tileMapHolder.selectedTile) // has to come before tilemapholder update because the tilemapholder actions depend on the selected unit! bottomBar.update(tileMapHolder.selectedTile) // has to come before tilemapholder update because the tilemapholder actions depend on the selected unit!
minimap.update() minimap.update(cloneCivilization)
minimap.y = bottomBar.height minimap.y = bottomBar.height
unitActionsTable.update(bottomBar.unitTable.selectedUnit) unitActionsTable.update(bottomBar.unitTable.selectedUnit)
unitActionsTable.y = bottomBar.height unitActionsTable.y = bottomBar.height
tileMapHolder.updateTiles() tileMapHolder.updateTiles(cloneCivilization)
topBar.update() topBar.update(cloneCivilization)
notificationsScroll.update() notificationsScroll.update(gameClone.notifications)
notificationsScroll.width = stage.width/3 notificationsScroll.width = stage.width/3
notificationsScroll.setPosition(stage.width - notificationsScroll.width - 5f, notificationsScroll.setPosition(stage.width - notificationsScroll.width - 5f,
nextTurnButton.y - notificationsScroll.height - 5f) nextTurnButton.y - notificationsScroll.height - 5f)
@ -130,7 +133,7 @@ class WorldScreen : CameraStageBaseScreen() {
else if(civInfo.greatPeople.freeGreatPeople>0) game.screen = GreatPersonPickerScreen() else if(civInfo.greatPeople.freeGreatPeople>0) game.screen = GreatPersonPickerScreen()
} }
private fun updateDiplomacyButton() { private fun updateDiplomacyButton(civInfo: CivilizationInfo) {
diplomacyButtonWrapper.clear() diplomacyButtonWrapper.clear()
if(civInfo.diplomacy.values.map { it.otherCiv() } if(civInfo.diplomacy.values.map { it.otherCiv() }
.filterNot { it.isDefeated() || it.isPlayerCivilization() || it.isBarbarianCivilization() } .filterNot { it.isDefeated() || it.isPlayerCivilization() || it.isBarbarianCivilization() }
@ -144,7 +147,7 @@ class WorldScreen : CameraStageBaseScreen() {
diplomacyButtonWrapper.y = techButton.y -20 - diplomacyButtonWrapper.height diplomacyButtonWrapper.y = techButton.y -20 - diplomacyButtonWrapper.height
} }
private fun updateTechButton() { private fun updateTechButton(civInfo: CivilizationInfo) {
techButton.isVisible = civInfo.cities.isNotEmpty() techButton.isVisible = civInfo.cities.isNotEmpty()
if (civInfo.tech.currentTechnology() == null) if (civInfo.tech.currentTechnology() == null)

View File

@ -103,9 +103,7 @@ class WorldScreenTopBar(val screen: WorldScreen) : Table() {
} }
internal fun update() { internal fun update(civInfo: CivilizationInfo) {
val civInfo = screen.civInfo
val revealedStrategicResources = GameBasics.TileResources.values val revealedStrategicResources = GameBasics.TileResources.values
.filter { it.resourceType == ResourceType.Strategic } // && } .filter { it.resourceType == ResourceType.Strategic } // && }
val civResources = civInfo.getCivResources() val civResources = civInfo.getCivResources()

View File

@ -18,7 +18,6 @@ class WorldScreenDisplayOptionsTable() : PopupTable(){
val settings = UnCivGame.Current.settings val settings = UnCivGame.Current.settings
settings.save() settings.save()
clear() clear()
val tileMapHolder = UnCivGame.Current.worldScreen.tileMapHolder
if (settings.showWorkedTiles) addButton("{Hide} {worked tiles}") { settings.showWorkedTiles = false; update() } if (settings.showWorkedTiles) addButton("{Hide} {worked tiles}") { settings.showWorkedTiles = false; update() }
else addButton("{Show} {worked tiles}") { settings.showWorkedTiles = true; update() } else addButton("{Show} {worked tiles}") { settings.showWorkedTiles = true; update() }
@ -64,6 +63,6 @@ class WorldScreenDisplayOptionsTable() : PopupTable(){
pack() // Needed to show the background. pack() // Needed to show the background.
center(UnCivGame.Current.worldScreen.stage) center(UnCivGame.Current.worldScreen.stage)
tileMapHolder.updateTiles() UnCivGame.Current.worldScreen.update()
} }
} }

View File

@ -2,22 +2,20 @@ package com.unciv.game.desktop;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.tools.texturepacker.TexturePacker;
import com.unciv.UnCivGame; import com.unciv.UnCivGame;
class DesktopLauncher { class DesktopLauncher {
public static void main (String[] arg) { public static void main (String[] arg) {
//
TexturePacker.Settings settings = new TexturePacker.Settings(); // TexturePacker.Settings settings = new TexturePacker.Settings();
settings.maxWidth = 2048; // settings.maxWidth = 2048;
settings.maxHeight = 2048; // settings.maxHeight = 2048;
settings.combineSubdirectories=true; // settings.combineSubdirectories=true;
//
// This is so they don't look all pixelated // // This is so they don't look all pixelated
settings.filterMag = Texture.TextureFilter.MipMapLinearLinear; // settings.filterMag = Texture.TextureFilter.MipMapLinearLinear;
settings.filterMin = Texture.TextureFilter.MipMapLinearLinear; // settings.filterMin = Texture.TextureFilter.MipMapLinearLinear;
TexturePacker.process(settings, "../images", ".", "game"); // TexturePacker.process(settings, "../images", ".", "game");
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
new LwjglApplication(new UnCivGame(), config); new LwjglApplication(new UnCivGame(), config);