chore(purity): MapPathing

This commit is contained in:
yairm210 2025-08-08 14:57:01 +03:00
parent 3af195fe11
commit 62b033d1d2
4 changed files with 27 additions and 6 deletions

View File

@ -49,6 +49,9 @@ allprojects {
apply(plugin = "io.github.yairm210.purity-plugin")
configure<yairm210.purity.PurityConfiguration>{
wellKnownPureFunctions = setOf(
"java.util.regex.Pattern.matcher",
"java.util.regex.Matcher.find",
"java.util.regex.Matcher.replaceAll",
)
wellKnownReadonlyFunctions = setOf(
"com.badlogic.gdx.math.Vector2.len",
@ -61,6 +64,9 @@ allprojects {
"com.badlogic.gdx.files.FileHandle.isDirectory",
"com.badlogic.gdx.files.FileHandle.isFile",
"com.badlogic.gdx.files.FileHandle.name",
"kotlin.Throwable.getStackTrace",
"java.lang.StackTraceElement.getClassName",
)
wellKnownPureClasses = setOf(
)

View File

@ -1,6 +1,7 @@
package com.unciv.logic.map
import com.unciv.logic.map.tile.Tile
import yairm210.purity.annotations.InternalState
import java.util.PriorityQueue
@ -37,6 +38,7 @@ data class TilePriority(val tile: Tile, val priority: Float)
* val path = aStarSearch.findPath(goalTile)
* ```
*/
@InternalState
class AStar(
val startingPoint: Tile,
private val predicate : (Tile) -> Boolean,

View File

@ -15,6 +15,7 @@ object MapPathing {
* Otherwise, we set every tile to have equal value since building a road on any of them makes the original movement cost irrelevant.
*/
@Suppress("UNUSED_PARAMETER") // While `from` is unused, this function should stay close to the signatures expected by the AStar and getPath `heuristic` parameter.
@Readonly
private fun roadPreferredMovementCost(unit: MapUnit, from: Tile, to: Tile): Float{
// hasRoadConnection accounts for civs that treat jungle/forest as roads
// Ignore road over river penalties.
@ -24,6 +25,7 @@ object MapPathing {
return 1f
}
@Readonly
fun isValidRoadPathTile(unit: MapUnit, tile: Tile): Boolean {
val roadImprovement = tile.ruleset.roadImprovement
val railRoadImprovement = tile.ruleset.railroadImprovement
@ -49,6 +51,7 @@ object MapPathing {
* @param endTile The destination tile of the path.
* @return A sequence of tiles representing the path from startTile to endTile, or null if no valid path is found.
*/
@Readonly
fun getRoadPath(unit: MapUnit, startTile: Tile, endTile: Tile): List<Tile>?{
return getPath(unit,
startTile,
@ -73,6 +76,7 @@ object MapPathing {
* It takes a MapUnit, a 'from' Tile, and a 'to' Tile, returning a Float value representing the heuristic cost estimate.
* @return A list of tiles representing the path from the startTile to the endTile. Returns null if no valid path is found.
*/
@Readonly
private fun getPath(unit: MapUnit,
startTile: Tile,
endTile: Tile,
@ -104,7 +108,7 @@ object MapPathing {
* Gets the connection to the end tile. This does not take into account tile movement costs.
* Takes in a civilization instead of a specific unit.
*/
@Readonly @Suppress("purity") // todo continue
@Readonly
fun getConnection(civ: Civilization,
startTile: Tile,
endTile: Tile,

View File

@ -1,5 +1,8 @@
package com.unciv.utils
import yairm210.purity.annotations.Immutable
import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly
import java.time.Instant
import java.util.regex.Pattern
@ -21,10 +24,10 @@ object Log {
* Log tags (= class names) **containing** these Strings will not be logged.
* You _can_ disable the default exclusions with an empty `-DnoLog=` argument.
*/
val disableLogsFrom = (
@Immutable val disableLogsFrom = (
System.getProperty("noLog")
?: "Battle,Music,Sounds,Translations,WorkerAutomation,assignRegions,RoadBetweenCitiesAutomation"
).split(',').filterNot { it.isEmpty() }.toMutableSet()
).split(',').filterNot { it.isEmpty() }.toSet()
/**
* Add -DonlyLog=<comma-separated-list-of-partial-class-names> to only log specific classes.
@ -32,13 +35,15 @@ object Log {
* [disableLogsFrom] will still be respected if this is set.
* Note you cannot disable all logging with `-DonlyLog=`, use `-DonlyLog=~~~` instead.
*/
@Immutable
val enableLogsFrom = (
System.getProperty("onlyLog")
?: ""
).split(',').filterNot { it.isEmpty() }.toMutableSet()
).split(',').filterNot { it.isEmpty() }.toSet()
var backend: LogBackend = DefaultLogBackend()
@Readonly
fun shouldLog(tag: Tag = getTag()): Boolean {
return !backend.isRelease() && !isTagDisabled(tag)
}
@ -54,6 +59,7 @@ object Log {
*
* The [params] can contain value-producing lambdas, which will be called and their value used as parameter for the message instead.
*/
@Pure @Suppress("purity") // log considered pure everywhere
fun debug(msg: String, vararg params: Any?) {
if (backend.isRelease()) return
debug(getTag(), msg, *params)
@ -159,7 +165,7 @@ interface LogBackend {
fun error(tag: Tag, curThreadName: String, msg: String)
/** Do not log on release builds for performance reasons. */
fun isRelease(): Boolean
@Readonly fun isRelease(): Boolean
/** Get string information about operation system */
fun getSystemInfo(): String
@ -209,6 +215,7 @@ private fun doLog(logger: (Tag, String, String) -> Unit, tag: Tag, msg: String,
logger(tag, Thread.currentThread().name, formattedMessage)
}
@Pure
private fun isTagDisabled(tag: Tag): Boolean {
return Log.disableLogsFrom.any { it in tag.name } ||
(Log.enableLogsFrom.isNotEmpty() && Log.enableLogsFrom.none { it in tag.name })
@ -231,14 +238,16 @@ private fun replaceLambdasWithValues(params: Array<out Any?>): Array<out Any?> {
}
@Readonly
private fun getTag(): Tag {
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
val firstOutsideStacktrace = Throwable().stackTrace.filter { "com.unciv.utils.Log" !in it.className }.first()
val firstOutsideStacktrace = Throwable().stackTrace.first { "com.unciv.utils.Log" !in it.className }
val simpleClassName = firstOutsideStacktrace.className.substringAfterLast('.')
return Tag(removeAnonymousSuffix(simpleClassName))
}
private val ANONYMOUS_CLASS_PATTERN = Pattern.compile("(\\$\\d+)+$") // all "$123" at the end of the class name
@Pure
private fun removeAnonymousSuffix(tag: String): String {
val matcher = ANONYMOUS_CLASS_PATTERN.matcher(tag)
return if (matcher.find()) {