mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-10 07:48:31 +07:00
Final performance improvements for the new algotihm, before we say 'goodnight sweet prince' - it underperforms drastically compared to current
This commit is contained in:
@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
package com.unciv.logic.map.mapunit.movement
|
package com.unciv.logic.map.mapunit.movement
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
@ -95,13 +97,15 @@ class UnitMovement(val unit: MapUnit) {
|
|||||||
|
|
||||||
|
|
||||||
data class MovementStepTotalCost(/** Turn 0 means the initial turn */ val turn: Int, val movementLeft: Float):Comparable<MovementStepTotalCost> {
|
data class MovementStepTotalCost(/** Turn 0 means the initial turn */ val turn: Int, val movementLeft: Float):Comparable<MovementStepTotalCost> {
|
||||||
override operator fun compareTo(other: MovementStepTotalCost) =
|
override operator fun compareTo(other: MovementStepTotalCost): Int {
|
||||||
compareValuesBy(this, other, {it.turn}, {-it.movementLeft})
|
if (turn != other.turn) return turn.compareTo(other.turn)
|
||||||
|
return other.movementLeft.compareTo(movementLeft) // The higher the MovementLeft, the *lower* the turn cost
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Problem and solution documented at https://yairm210.medium.com/multi-turn-pathfinding-7136bd0bdaf0 */
|
/** Problem and solution documented at https://yairm210.medium.com/multi-turn-pathfinding-7136bd0bdaf0 */
|
||||||
fun getShortestPathNew(destination: Tile, considerZoneOfControl: Boolean = true,
|
fun getShortestPathNew(destination: Tile, considerZoneOfControl: Boolean = true,
|
||||||
/** For allowing optional avoid of damaging tiles, tiles outside borders, etc */ shouldAvoidTile: (Tile) -> Boolean = {false},
|
/** For allowing optional avoid of damaging tiles, tiles outside borders, etc */ shouldAvoidTile: ((Tile) -> Boolean)? = null,
|
||||||
maxTurns: Int = 25,
|
maxTurns: Int = 25,
|
||||||
): List<MovementStep> {
|
): List<MovementStep> {
|
||||||
if (unit.cache.cannotMove) return listOf()
|
if (unit.cache.cannotMove) return listOf()
|
||||||
@ -115,6 +119,7 @@ class UnitMovement(val unit: MapUnit) {
|
|||||||
return listOf(initialStep)
|
return listOf(initialStep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val tileToBestStep = HashMap<Tile, MovementStep>() // contains a map of "you can get from X to Y in that turn"
|
val tileToBestStep = HashMap<Tile, MovementStep>() // contains a map of "you can get from X to Y in that turn"
|
||||||
tileToBestStep[startingTile] = initialStep
|
tileToBestStep[startingTile] = initialStep
|
||||||
|
|
||||||
@ -122,7 +127,11 @@ class UnitMovement(val unit: MapUnit) {
|
|||||||
val tStep = tileToBestStep[t]!!
|
val tStep = tileToBestStep[t]!!
|
||||||
val t2Step = tileToBestStep[t2]!!
|
val t2Step = tileToBestStep[t2]!!
|
||||||
// This last comparitor is REQUIRED otherwise the tree will think that tiles the same distance away are the same and will throw the second one away!
|
// This last comparitor is REQUIRED otherwise the tree will think that tiles the same distance away are the same and will throw the second one away!
|
||||||
compareValuesBy(tStep, t2Step, {it.totalCost}, {it.tile.aerialDistanceTo(destination)}, {it.tile.position.hashCode()})
|
val totalCostComparison = tStep.totalCost.compareTo(t2Step.totalCost)
|
||||||
|
if (totalCostComparison != 0) return@TreeSet totalCostComparison
|
||||||
|
val aerialDistanceComparison = t.aerialDistanceTo(destination).compareTo(t2.aerialDistanceTo(destination))
|
||||||
|
if (aerialDistanceComparison != 0) return@TreeSet aerialDistanceComparison
|
||||||
|
return@TreeSet t.position.hashCode().compareTo(t2.position.hashCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
tilesToCheck.add(startingTile)
|
tilesToCheck.add(startingTile)
|
||||||
@ -138,13 +147,17 @@ class UnitMovement(val unit: MapUnit) {
|
|||||||
val currentTileStep = tileToBestStep[currentTileToCheck]!!
|
val currentTileStep = tileToBestStep[currentTileToCheck]!!
|
||||||
|
|
||||||
for (neighbor in currentTileToCheck.neighbors){
|
for (neighbor in currentTileToCheck.neighbors){
|
||||||
if (shouldAvoidTileCache.getOrPut(neighbor){ shouldAvoidTile(neighbor) }) continue
|
if (shouldAvoidTile != null && shouldAvoidTileCache.getOrPut(neighbor){
|
||||||
|
shouldAvoidTile(neighbor)
|
||||||
|
}) continue
|
||||||
val currentBestStepToNeighbor = tileToBestStep[neighbor]
|
val currentBestStepToNeighbor = tileToBestStep[neighbor]
|
||||||
// If this tile can't beat the current best then no point checking
|
// If this tile can't beat the current best then no point checking
|
||||||
if (currentBestStepToNeighbor!=null && (currentBestStepToNeighbor.totalCost < currentTileStep.totalCost))
|
if (currentBestStepToNeighbor!=null && (currentBestStepToNeighbor.totalCost < currentTileStep.totalCost))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (!canPassThroughCache.getOrPut(neighbor){ canPassThrough(neighbor) }) continue
|
if (!canPassThroughCache.getOrPut(neighbor){
|
||||||
|
canPassThrough(neighbor)
|
||||||
|
}) continue
|
||||||
|
|
||||||
val movementBetweenTiles: Float = if (!neighbor.isExplored(unit.civ)) 1f // If we don't know then we just guess it to be 1.
|
val movementBetweenTiles: Float = if (!neighbor.isExplored(unit.civ)) 1f // If we don't know then we just guess it to be 1.
|
||||||
else movementCostCache.getOrPut(currentTileToCheck to neighbor) {
|
else movementCostCache.getOrPut(currentTileToCheck to neighbor) {
|
||||||
@ -214,15 +227,6 @@ class UnitMovement(val unit: MapUnit) {
|
|||||||
* Returns an empty list if there's no way to get to the destination.
|
* Returns an empty list if there's no way to get to the destination.
|
||||||
*/
|
*/
|
||||||
fun getShortestPath(destination: Tile, avoidDamagingTerrain: Boolean = false): List<Tile> {
|
fun getShortestPath(destination: Tile, avoidDamagingTerrain: Boolean = false): List<Tile> {
|
||||||
if (UncivGame.Current.settings.experimentalMovement) {
|
|
||||||
fun shouldAvoidEnemyTile(tile:Tile) = unit.isCivilian() && unit.isAutomated() && tile.isEnemyTerritory(unit.civ)
|
|
||||||
if (avoidDamagingTerrain){
|
|
||||||
val shortestPathWithoutDamagingTiles = getShortestPathNew(destination,
|
|
||||||
shouldAvoidTile = { shouldAvoidEnemyTile(it) || unit.getDamageFromTerrain(it) > 0 })
|
|
||||||
if (shortestPathWithoutDamagingTiles.isNotEmpty()) return shortestPathWithoutDamagingTiles.toBackwardsCompatiblePath()
|
|
||||||
}
|
|
||||||
return getShortestPathNew(destination, shouldAvoidTile = ::shouldAvoidEnemyTile).toBackwardsCompatiblePath()
|
|
||||||
}
|
|
||||||
if (unit.cache.cannotMove) return listOf()
|
if (unit.cache.cannotMove) return listOf()
|
||||||
|
|
||||||
// First try and find a path without damaging terrain
|
// First try and find a path without damaging terrain
|
||||||
@ -248,6 +252,19 @@ class UnitMovement(val unit: MapUnit) {
|
|||||||
return listOf(currentTile)
|
return listOf(currentTile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (UncivGame.Current.settings.experimentalMovement) {
|
||||||
|
if (avoidDamagingTerrain){
|
||||||
|
val shouldAvoidTile: (Tile) -> Boolean = if (unit.isCivilian() && unit.isAutomated())
|
||||||
|
{{unit.getDamageFromTerrain(it) > 0 || it.isEnemyTerritory(unit.civ)}}
|
||||||
|
else {{unit.getDamageFromTerrain(it) > 0}}
|
||||||
|
return getShortestPathNew(destination,
|
||||||
|
shouldAvoidTile = shouldAvoidTile).toBackwardsCompatiblePath()
|
||||||
|
}
|
||||||
|
val shouldAvoidTile :((Tile) -> Boolean)? = if (unit.isCivilian() && unit.isAutomated())
|
||||||
|
{{it.isEnemyTerritory(unit.civ)}} else null
|
||||||
|
return getShortestPathNew(destination, shouldAvoidTile = shouldAvoidTile).toBackwardsCompatiblePath()
|
||||||
|
}
|
||||||
|
|
||||||
var tilesToCheck = listOf(currentTile)
|
var tilesToCheck = listOf(currentTile)
|
||||||
val movementTreeParents = HashMap<Tile, Tile?>() // contains a map of "you can get from X to Y in that turn"
|
val movementTreeParents = HashMap<Tile, Tile?>() // contains a map of "you can get from X to Y in that turn"
|
||||||
movementTreeParents[currentTile] = null
|
movementTreeParents[currentTile] = null
|
||||||
@ -345,9 +362,13 @@ class UnitMovement(val unit: MapUnit) {
|
|||||||
val distanceToTiles = getDistanceToTiles()
|
val distanceToTiles = getDistanceToTiles()
|
||||||
|
|
||||||
// If the tile is far away, we need to build a path how to get there, and then take the first step
|
// If the tile is far away, we need to build a path how to get there, and then take the first step
|
||||||
if (!distanceToTiles.containsKey(finalDestination))
|
if (!distanceToTiles.containsKey(finalDestination)) {
|
||||||
return getShortestPath(finalDestination).firstOrNull()
|
val shortestDestination = getShortestPath(finalDestination).firstOrNull()
|
||||||
?: throw UnreachableDestinationException("$unit ${unit.currentTile} cannot reach $finalDestination")
|
?: throw UnreachableDestinationException("$unit ${unit.currentTile} cannot reach $finalDestination")
|
||||||
|
if (shortestDestination !in distanceToTiles)
|
||||||
|
return distanceToTiles.keys.minBy { it.aerialDistanceTo(finalDestination) }
|
||||||
|
return shortestDestination
|
||||||
|
}
|
||||||
|
|
||||||
// we should be able to get there this turn
|
// we should be able to get there this turn
|
||||||
if (canMoveTo(finalDestination))
|
if (canMoveTo(finalDestination))
|
||||||
|
Reference in New Issue
Block a user