mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-22 10:54:19 -04:00
Added Purity for readonly validation (#13600)
* Added purity to check readonly-ness :) * Update build.gradle.kts
This commit is contained in:
parent
b828338aa0
commit
694354af09
@ -35,9 +35,23 @@ plugins {
|
||||
// This is *with* gradle 8.2 downloaded according the project specs, no idea what that's about
|
||||
kotlin("multiplatform") version "1.9.24"
|
||||
kotlin("plugin.serialization") version "1.9.24"
|
||||
id("io.github.yairm210.purity-plugin") version "0.0.15" apply(false)
|
||||
}
|
||||
|
||||
allprojects {
|
||||
// repositories{ // for local purity
|
||||
// mavenLocal()
|
||||
// }
|
||||
|
||||
apply(plugin = "io.github.yairm210.purity-plugin")
|
||||
configure<yairm210.purity.PurityConfiguration>{
|
||||
wellKnownPureFunctions = setOf("kotlin.internal.ir.CHECK_NOT_NULL")
|
||||
wellKnownReadonlyFunctions = setOf(
|
||||
"kotlin.collections.any",
|
||||
"kotlin.collections.Iterator.hasNext"
|
||||
)
|
||||
}
|
||||
|
||||
apply(plugin = "eclipse")
|
||||
apply(plugin = "idea")
|
||||
|
||||
|
@ -8,6 +8,7 @@ import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.civilization.managers.ReligionState
|
||||
import com.unciv.models.ruleset.validation.ModCompatibility
|
||||
import com.unciv.models.stats.Stat
|
||||
import org.jetbrains.annotations.Contract
|
||||
import kotlin.random.Random
|
||||
|
||||
object Conditionals {
|
||||
@ -18,7 +19,8 @@ object Conditionals {
|
||||
seed = seed * 31 + state.hashCode()
|
||||
return Random(seed).nextFloat()
|
||||
}
|
||||
|
||||
|
||||
@Contract("readonly") @Suppress("purity")
|
||||
fun conditionalApplies(
|
||||
unique: Unique?,
|
||||
conditional: Unique,
|
||||
|
@ -8,6 +8,7 @@ import com.unciv.models.translations.equalsPlaceholderText
|
||||
import com.unciv.models.translations.fillPlaceholders
|
||||
import com.unciv.models.translations.getPlaceholderParameters
|
||||
import com.unciv.models.translations.getPlaceholderText
|
||||
import org.jetbrains.annotations.Contract
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
|
||||
/**
|
||||
@ -257,6 +258,7 @@ enum class Countables(
|
||||
open val noPlaceholders = !text.contains('[')
|
||||
|
||||
// Leave these in place only for the really simple cases
|
||||
@Contract("readonly")
|
||||
open fun matches(parameterText: String) = if (noPlaceholders) parameterText == text
|
||||
else parameterText.equalsPlaceholderText(placeholderText)
|
||||
|
||||
@ -266,7 +268,9 @@ enum class Countables(
|
||||
/** This indicates whether a parameter *is of this countable type*, not *whether its parameters are correct*
|
||||
* E.g. "[fakeBuilding] Buildings" is obviously a countable of type "[buildingFilter] Buildings", therefore matches will return true.
|
||||
* But it has another problem, which is that the building filter is bad, so its getErrorSeverity will return "ruleset specific" */
|
||||
@Contract("readonly")
|
||||
open fun matches(parameterText: String, ruleset: Ruleset): Boolean = false
|
||||
@Contract("readonly")
|
||||
abstract fun eval(parameterText: String, stateForConditionals: StateForConditionals): Int?
|
||||
|
||||
open val documentationHeader get() =
|
||||
@ -289,6 +293,7 @@ enum class Countables(
|
||||
getErrorSeverity(parameterText.getPlaceholderParameters().first(), ruleset)
|
||||
|
||||
companion object {
|
||||
@Contract("readonly")
|
||||
fun getMatching(parameterText: String, ruleset: Ruleset?) = Countables.entries
|
||||
.firstOrNull {
|
||||
if (it.matchesWithRuleset)
|
||||
@ -296,6 +301,7 @@ enum class Countables(
|
||||
else it.matches(parameterText)
|
||||
}
|
||||
|
||||
@Contract("readonly")
|
||||
fun getCountableAmount(parameterText: String, stateForConditionals: StateForConditionals): Int? {
|
||||
val ruleset = stateForConditionals.gameInfo?.ruleset
|
||||
val countable = getMatching(parameterText, ruleset) ?: return null
|
||||
|
@ -12,6 +12,7 @@ import com.unciv.models.translations.getModifiers
|
||||
import com.unciv.models.translations.getPlaceholderParameters
|
||||
import com.unciv.models.translations.getPlaceholderText
|
||||
import com.unciv.models.translations.removeConditionals
|
||||
import org.jetbrains.annotations.Contract
|
||||
import kotlin.math.max
|
||||
|
||||
|
||||
@ -47,7 +48,9 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
|
||||
fun hasFlag(flag: UniqueFlag) = type != null && type.flags.contains(flag)
|
||||
fun isHiddenToUsers() = hasFlag(UniqueFlag.HiddenToUsers) || hasModifier(UniqueType.ModifierHiddenFromUsers)
|
||||
|
||||
@Contract("readonly")
|
||||
fun getModifiers(type: UniqueType) = modifiersMap[type] ?: emptyList()
|
||||
@Contract("readonly")
|
||||
fun hasModifier(type: UniqueType) = modifiersMap.containsKey(type)
|
||||
fun isModifiedByGameSpeed() = hasModifier(UniqueType.ModifiedByGameSpeed)
|
||||
fun isModifiedByGameProgress() = hasModifier(UniqueType.ModifiedByGameProgress)
|
||||
@ -81,6 +84,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
|
||||
return conditionalsApply(StateForConditionals(civInfo, city))
|
||||
}
|
||||
|
||||
@Contract("readonly")
|
||||
fun conditionalsApply(state: StateForConditionals): Boolean {
|
||||
if (state.ignoreConditionals) return true
|
||||
// Always allow Timed conditional uniques. They are managed elsewhere
|
||||
@ -92,6 +96,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
|
||||
return true
|
||||
}
|
||||
|
||||
@Contract("readonly") @Suppress("purity")
|
||||
private fun getUniqueMultiplier(stateForConditionals: StateForConditionals): Int {
|
||||
if (stateForConditionals == StateForConditionals.IgnoreMultiplicationForCaching)
|
||||
return 1
|
||||
@ -126,6 +131,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
|
||||
}
|
||||
|
||||
/** Multiplies the unique according to the multiplication conditionals */
|
||||
@Contract("readonly")
|
||||
fun getMultiplied(stateForConditionals: StateForConditionals): Sequence<Unique> {
|
||||
val multiplier = getUniqueMultiplier(stateForConditionals)
|
||||
return EndlessSequenceOf(this).take(multiplier)
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.unciv.models.ruleset.unique
|
||||
|
||||
import org.jetbrains.annotations.Contract
|
||||
import java.util.*
|
||||
|
||||
open class UniqueMap() {
|
||||
@ -14,8 +15,6 @@ open class UniqueMap() {
|
||||
addUniques(uniques.asIterable())
|
||||
}
|
||||
|
||||
fun isEmpty(): Boolean = innerUniqueMap.isEmpty()
|
||||
|
||||
/** Adds one [unique] unless it has a ConditionalTimedUnique conditional */
|
||||
open fun addUnique(unique: Unique) {
|
||||
val existingArrayList = innerUniqueMap[unique.placeholderText]
|
||||
@ -42,26 +41,33 @@ open class UniqueMap() {
|
||||
typedUniqueMap.clear()
|
||||
}
|
||||
|
||||
// Pure functions
|
||||
@Contract("readonly")
|
||||
fun isEmpty(): Boolean = innerUniqueMap.isEmpty()
|
||||
|
||||
@Contract("readonly")
|
||||
fun hasUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueType).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
||||
|
||||
@Contract("readonly")
|
||||
fun hasUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueTag).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
||||
|
||||
|
||||
@Contract("readonly")
|
||||
fun hasTagUnique(tagUnique: String) =
|
||||
innerUniqueMap.containsKey(tagUnique)
|
||||
|
||||
// 160ms vs 1000-1250ms/30s
|
||||
@Contract("readonly")
|
||||
fun getUniques(uniqueType: UniqueType) = typedUniqueMap[uniqueType]
|
||||
?.asSequence()
|
||||
?: emptySequence()
|
||||
|
||||
@Contract("readonly")
|
||||
fun getUniques(uniqueTag: String) = innerUniqueMap[uniqueTag]
|
||||
?.asSequence()
|
||||
?: emptySequence()
|
||||
|
||||
@Contract("readonly")
|
||||
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueType)
|
||||
// Same as .filter | .flatMap, but more cpu/mem performant (7.7 GB vs ?? for test)
|
||||
@ -73,6 +79,7 @@ open class UniqueMap() {
|
||||
}
|
||||
}
|
||||
|
||||
@Contract("readonly")
|
||||
fun getMatchingUniques(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueTag)
|
||||
// Same as .filter | .flatMap, but more cpu/mem performant (7.7 GB vs ?? for test)
|
||||
@ -83,16 +90,20 @@ open class UniqueMap() {
|
||||
else -> it.getMultiplied(state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Contract("readonly")
|
||||
fun hasMatchingUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueType).any { it.conditionalsApply(state) }
|
||||
|
||||
@Contract("readonly")
|
||||
fun hasMatchingUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueTag)
|
||||
.any { it.conditionalsApply(state) }
|
||||
|
||||
@Contract("readonly")
|
||||
fun getAllUniques() = innerUniqueMap.values.asSequence().flatten()
|
||||
|
||||
@Contract("readonly")
|
||||
fun getTriggeredUniques(trigger: UniqueType, stateForConditionals: StateForConditionals,
|
||||
triggerFilter: (Unique) -> Boolean = { true }): Sequence<Unique> {
|
||||
return getAllUniques().filter { unique ->
|
||||
|
@ -12,6 +12,7 @@ import com.unciv.ui.components.fonts.DiacriticSupport
|
||||
import com.unciv.ui.components.fonts.FontRulesetIcons
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.debug
|
||||
import org.jetbrains.annotations.Contract
|
||||
import java.util.Locale
|
||||
import org.jetbrains.annotations.VisibleForTesting
|
||||
|
||||
@ -472,6 +473,7 @@ private fun String.translateIndividualWord(language: String, hideIcons: Boolean,
|
||||
* For example, a string like 'The city of [New [York]]' will return ['New [York]'],
|
||||
* allowing us to have nested translations!
|
||||
*/
|
||||
@Contract("readonly")
|
||||
fun String.getPlaceholderParameters(): List<String> {
|
||||
if (!this.contains('[')) return emptyList()
|
||||
|
||||
@ -492,6 +494,7 @@ fun String.getPlaceholderParameters(): List<String> {
|
||||
return parameters
|
||||
}
|
||||
|
||||
@Contract("readonly")
|
||||
fun String.getPlaceholderText(): String {
|
||||
var stringToReturn = this.removeConditionals()
|
||||
val placeholderParameters = stringToReturn.getPlaceholderParameters()
|
||||
@ -500,6 +503,7 @@ fun String.getPlaceholderText(): String {
|
||||
return stringToReturn
|
||||
}
|
||||
|
||||
@Contract("readonly")
|
||||
fun String.equalsPlaceholderText(str: String): Boolean {
|
||||
if (isEmpty()) return str.isEmpty()
|
||||
if (str.isEmpty()) return false // Empty strings have no .first()
|
||||
@ -529,6 +533,7 @@ fun String.getModifiers(): List<Unique> {
|
||||
return pointyBraceRegex.findAll(this).map { Unique(it.groups[1]!!.value) }.toList()
|
||||
}
|
||||
|
||||
@Contract("readonly")
|
||||
fun String.removeConditionals(): String {
|
||||
if (!this.contains('<')) return this // no need to regex search
|
||||
return this
|
||||
|
Loading…
x
Reference in New Issue
Block a user