mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-09 23:39:40 +07:00
MoveHereButton creation deferred to main thread, in order to LibGDX errors caused by concurrent UI alteration
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -129,3 +129,4 @@ android/android-release.apk
|
|||||||
android/assets/GameSettings.json
|
android/assets/GameSettings.json
|
||||||
android/release/output.json
|
android/release/output.json
|
||||||
android/release/android-release.apk
|
android/release/android-release.apk
|
||||||
|
android/release/release/android.aab
|
||||||
|
@ -3038,6 +3038,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
//world size
|
//world size
|
||||||
|
"Tiny":{}
|
||||||
"Small":{
|
"Small":{
|
||||||
Italian:"Piccolo"
|
Italian:"Piccolo"
|
||||||
Russian:"Маленький"
|
Russian:"Маленький"
|
||||||
|
@ -21,8 +21,8 @@ android {
|
|||||||
applicationId "com.unciv.game"
|
applicationId "com.unciv.game"
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 174
|
versionCode 175
|
||||||
versionName "2.10.11"
|
versionName "2.10.12"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Had to add this crap for Travis to build, it wanted to sign the app
|
// Had to add this crap for Travis to build, it wanted to sign the app
|
||||||
|
Binary file not shown.
@ -17,6 +17,7 @@ import com.unciv.logic.map.TileInfo
|
|||||||
import com.unciv.logic.map.TileMap
|
import com.unciv.logic.map.TileMap
|
||||||
import com.unciv.ui.tilegroups.WorldTileGroup
|
import com.unciv.ui.tilegroups.WorldTileGroup
|
||||||
import com.unciv.ui.utils.*
|
import com.unciv.ui.utils.*
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap) : ScrollPane(null) {
|
class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap: TileMap) : ScrollPane(null) {
|
||||||
internal var selectedTile: TileInfo? = null
|
internal var selectedTile: TileInfo? = null
|
||||||
@ -25,6 +26,11 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
|
|||||||
var moveToOverlay :Actor?=null
|
var moveToOverlay :Actor?=null
|
||||||
val cityButtonOverlays = ArrayList<Actor>()
|
val cityButtonOverlays = ArrayList<Actor>()
|
||||||
|
|
||||||
|
|
||||||
|
// Used to transfer data on the "move here" button that should be created, from the side thread to the main thread
|
||||||
|
class MoveHereButtonDto(val unit: MapUnit, val tileInfo: TileInfo, val turnsToGetThere: Int)
|
||||||
|
var moveHereButtonDto :MoveHereButtonDto?=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
|
||||||
@ -59,7 +65,7 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
|
|||||||
// so we zero out the starting position of the whole board so they will be displayed as well
|
// so we zero out the starting position of the whole board so they will be displayed as well
|
||||||
allTiles.setSize(topX - bottomX + groupPadding*2, topY - bottomY + groupPadding*2)
|
allTiles.setSize(topX - bottomX + groupPadding*2, topY - bottomY + groupPadding*2)
|
||||||
|
|
||||||
widget = allTiles
|
actor = allTiles
|
||||||
setFillParent(true)
|
setFillParent(true)
|
||||||
setOrigin(worldScreen.stage.width/2,worldScreen.stage.height/2)
|
setOrigin(worldScreen.stage.width/2,worldScreen.stage.height/2)
|
||||||
setSize(worldScreen.stage.width, worldScreen.stage.height)
|
setSize(worldScreen.stage.width, worldScreen.stage.height)
|
||||||
@ -96,45 +102,57 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
|
|||||||
if (selectedUnit != null && selectedUnit.getTile() != tileInfo
|
if (selectedUnit != null && selectedUnit.getTile() != tileInfo
|
||||||
&& selectedUnit.canMoveTo(tileInfo) && selectedUnit.movementAlgs().canReach(tileInfo)) {
|
&& selectedUnit.canMoveTo(tileInfo) && selectedUnit.movementAlgs().canReach(tileInfo)) {
|
||||||
// this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread
|
// this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread
|
||||||
kotlin.concurrent.thread {
|
queueAddMoveHereButton(selectedUnit, tileInfo)
|
||||||
addMoveHereButtonToTile(selectedUnit, tileInfo, tileGroup)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worldScreen.bottomBar.unitTable.tileSelected(tileInfo)
|
worldScreen.bottomBar.unitTable.tileSelected(tileInfo)
|
||||||
worldScreen.shouldUpdate=true
|
worldScreen.shouldUpdate=true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addMoveHereButtonToTile(selectedUnit: MapUnit, tileInfo: TileInfo, tileGroup: WorldTileGroup) {
|
private fun queueAddMoveHereButton(selectedUnit: MapUnit, tileInfo: TileInfo) {
|
||||||
|
thread {
|
||||||
|
/** LibGdx sometimes has these wierd errors when you try to edit the UI layout from 2 separate thread.
|
||||||
|
* And so, all UI editing will be done on the main thread.
|
||||||
|
* The only "heavy lifting" that needs to be done is getting the turns to get there,
|
||||||
|
* so that and that alone will be relegated to the concurrent thread.
|
||||||
|
*/
|
||||||
|
val turnsToGetThere = selectedUnit.movementAlgs().getShortestPath(tileInfo).size // this is what takes the most time, tbh
|
||||||
|
moveHereButtonDto = MoveHereButtonDto(selectedUnit, tileInfo, turnsToGetThere)
|
||||||
|
worldScreen.shouldUpdate = true // when the world screen updates, is calls our updateTiles,
|
||||||
|
// which will add the move here button *on the main thread*! Problem solved!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun addMoveHereButtonToTile(dto: MoveHereButtonDto, tileGroup: WorldTileGroup) {
|
||||||
val size = 60f
|
val size = 60f
|
||||||
val moveHereButton = Group().apply { width = size;height = size; }
|
val moveHereButton = Group().apply { width = size;height = size; }
|
||||||
moveHereButton.addActor(ImageGetter.getCircle().apply { width = size; height = size })
|
moveHereButton.addActor(ImageGetter.getCircle().apply { width = size; height = size })
|
||||||
moveHereButton.addActor(ImageGetter.getStatIcon("Movement").apply { width = size / 2; height = size / 2; center(moveHereButton) })
|
moveHereButton.addActor(ImageGetter.getStatIcon("Movement").apply { width = size / 2; height = size / 2; center(moveHereButton) })
|
||||||
|
|
||||||
val turnsToGetThere = selectedUnit.movementAlgs().getShortestPath(tileInfo).size
|
|
||||||
val numberCircle = ImageGetter.getCircle().apply { width = size / 2; height = size / 2;color = Color.BLUE }
|
val numberCircle = ImageGetter.getCircle().apply { width = size / 2; height = size / 2;color = Color.BLUE }
|
||||||
moveHereButton.addActor(numberCircle)
|
moveHereButton.addActor(numberCircle)
|
||||||
moveHereButton.addActor(Label(turnsToGetThere.toString(), CameraStageBaseScreen.skin)
|
moveHereButton.addActor(Label(dto.turnsToGetThere.toString(), CameraStageBaseScreen.skin)
|
||||||
.apply { center(numberCircle); setFontColor(Color.WHITE) })
|
.apply { center(numberCircle); setFontColor(Color.WHITE) })
|
||||||
|
|
||||||
val unitIcon = UnitGroup(selectedUnit, size / 2)
|
val unitIcon = UnitGroup(dto.unit, size / 2)
|
||||||
unitIcon.y = size - unitIcon.height
|
unitIcon.y = size - unitIcon.height
|
||||||
moveHereButton.addActor(unitIcon)
|
moveHereButton.addActor(unitIcon)
|
||||||
|
|
||||||
if (selectedUnit.currentMovement > 0)
|
if (dto.unit.currentMovement > 0)
|
||||||
moveHereButton.onClick {
|
moveHereButton.onClick {
|
||||||
// this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread
|
// this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread
|
||||||
kotlin.concurrent.thread {
|
kotlin.concurrent.thread {
|
||||||
if (selectedUnit.movementAlgs().canReach(tileInfo)) {
|
if (dto.unit.movementAlgs().canReach(dto.tileInfo)) {
|
||||||
try {
|
try {
|
||||||
// Because this is darned concurrent (as it MUST be to avoid ANRs),
|
// Because this is darned concurrent (as it MUST be to avoid ANRs),
|
||||||
// there are edge cases where the canReach is true,
|
// there are edge cases where the canReach is true,
|
||||||
// but until it reaches the headTowards the board has changed and so the headTowards fails.
|
// but until it reaches the headTowards the board has changed and so the headTowards fails.
|
||||||
// I can't think of any way to avoid this,
|
// I can't think of any way to avoid this,
|
||||||
// but it's so rare and edge-case-y that ignoring its failure is actually acceptable, hence the empty catch
|
// but it's so rare and edge-case-y that ignoring its failure is actually acceptable, hence the empty catch
|
||||||
selectedUnit.movementAlgs().headTowards(tileInfo)
|
dto.unit.movementAlgs().headTowards(dto.tileInfo)
|
||||||
if (selectedUnit.currentTile != tileInfo)
|
if (dto.unit.currentTile != dto.tileInfo)
|
||||||
selectedUnit.action = "moveTo " + tileInfo.position.x.toInt() + "," + tileInfo.position.y.toInt()
|
dto.unit.action = "moveTo " + dto.tileInfo.position.x.toInt() + "," + dto.tileInfo.position.y.toInt()
|
||||||
}
|
}
|
||||||
catch (e:Exception){}
|
catch (e:Exception){}
|
||||||
}
|
}
|
||||||
@ -174,8 +192,24 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
|
|||||||
tileGroup.showCircle(Color.RED) // Display ALL viewable enemies with a red circle so that users don't need to go "hunting" for enemy units
|
tileGroup.showCircle(Color.RED) // Display ALL viewable enemies with a red circle so that users don't need to go "hunting" for enemy units
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(moveHereButtonDto!=null)
|
||||||
|
addMoveHereButtonToTile(moveHereButtonDto!!, tileGroups[moveHereButtonDto!!.tileInfo]!!)
|
||||||
|
|
||||||
if(worldScreen.bottomBar.unitTable.selectedUnit!=null){
|
if(worldScreen.bottomBar.unitTable.selectedUnit!=null){
|
||||||
val unit = worldScreen.bottomBar.unitTable.selectedUnit!!
|
val unit = worldScreen.bottomBar.unitTable.selectedUnit!!
|
||||||
|
updateTilegroupsForSelectedUnit(unit, playerViewableTilePositions)
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(moveToOverlay!=null){
|
||||||
|
moveToOverlay!!.remove()
|
||||||
|
moveToOverlay=null
|
||||||
|
}
|
||||||
|
|
||||||
|
if(selectedTile!=null)
|
||||||
|
tileGroups[selectedTile!!]!!.showCircle(Color.WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateTilegroupsForSelectedUnit(unit: MapUnit, playerViewableTilePositions: HashSet<Vector2>) {
|
||||||
tileGroups[unit.getTile()]!!.selectUnit(unit)
|
tileGroups[unit.getTile()]!!.selectUnit(unit)
|
||||||
|
|
||||||
for (tile: TileInfo in unit.getDistanceToTiles().keys)
|
for (tile: TileInfo in unit.getDistanceToTiles().keys)
|
||||||
@ -186,37 +220,24 @@ class TileMapHolder(internal val worldScreen: WorldScreen, internal val tileMap:
|
|||||||
val attackableTiles: List<TileInfo> = when {
|
val attackableTiles: List<TileInfo> = when {
|
||||||
unitType.isCivilian() -> unit.getDistanceToTiles().keys.toList()
|
unitType.isCivilian() -> unit.getDistanceToTiles().keys.toList()
|
||||||
else -> UnitAutomation().getAttackableEnemies(unit, unit.getDistanceToTiles()).map { it.tileToAttack }
|
else -> UnitAutomation().getAttackableEnemies(unit, unit.getDistanceToTiles()).map { it.tileToAttack }
|
||||||
|
.filter { (UnCivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) }
|
||||||
}
|
}
|
||||||
|
for (attackableTile in attackableTiles) {
|
||||||
|
if (unit.type.isCivilian()) tileGroups[attackableTile]!!.hideCircle()
|
||||||
for (tile in attackableTiles.filter {
|
|
||||||
it.getUnits().isNotEmpty()
|
|
||||||
&& it.getUnits().first().owner != unit.owner
|
|
||||||
&& (UnCivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position))}) {
|
|
||||||
if(unit.type.isCivilian()) tileGroups[tile]!!.hideCircle()
|
|
||||||
else {
|
else {
|
||||||
tileGroups[tile]!!.showCircle(colorFromRGB(237, 41, 57))
|
tileGroups[attackableTile]!!.showCircle(colorFromRGB(237, 41, 57))
|
||||||
tileGroups[tile]!!.showCrosshair()
|
tileGroups[attackableTile]!!.showCrosshair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fadeout less relevant images if a military unit is selected
|
||||||
val fadeout = if (unit.type.isCivilian()) 1f
|
val fadeout = if (unit.type.isCivilian()) 1f
|
||||||
else 0.5f
|
else 0.5f
|
||||||
|
|
||||||
for (tile in tileGroups.values) {
|
for (tile in tileGroups.values) {
|
||||||
if (tile.populationImage != null) tile.populationImage!!.color.a = fadeout
|
if (tile.populationImage != null) tile.populationImage!!.color.a = fadeout
|
||||||
if (tile.improvementImage != null) tile.improvementImage!!.color.a = fadeout
|
if (tile.improvementImage != null) tile.improvementImage!!.color.a = fadeout
|
||||||
if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout
|
if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
else if(moveToOverlay!=null){
|
|
||||||
moveToOverlay!!.remove()
|
|
||||||
moveToOverlay=null
|
|
||||||
}
|
|
||||||
|
|
||||||
if(selectedTile!=null)
|
|
||||||
tileGroups[selectedTile!!]!!.showCircle(Color.WHITE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCenterPosition(vector: Vector2) {
|
fun setCenterPosition(vector: Vector2) {
|
||||||
|
Reference in New Issue
Block a user