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
|
// This is *with* gradle 8.2 downloaded according the project specs, no idea what that's about
|
||||||
kotlin("multiplatform") version "1.9.24"
|
kotlin("multiplatform") version "1.9.24"
|
||||||
kotlin("plugin.serialization") version "1.9.24"
|
kotlin("plugin.serialization") version "1.9.24"
|
||||||
|
id("io.github.yairm210.purity-plugin") version "0.0.15" apply(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
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 = "eclipse")
|
||||||
apply(plugin = "idea")
|
apply(plugin = "idea")
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import com.unciv.logic.civilization.Civilization
|
|||||||
import com.unciv.logic.civilization.managers.ReligionState
|
import com.unciv.logic.civilization.managers.ReligionState
|
||||||
import com.unciv.models.ruleset.validation.ModCompatibility
|
import com.unciv.models.ruleset.validation.ModCompatibility
|
||||||
import com.unciv.models.stats.Stat
|
import com.unciv.models.stats.Stat
|
||||||
|
import org.jetbrains.annotations.Contract
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
object Conditionals {
|
object Conditionals {
|
||||||
@ -18,7 +19,8 @@ object Conditionals {
|
|||||||
seed = seed * 31 + state.hashCode()
|
seed = seed * 31 + state.hashCode()
|
||||||
return Random(seed).nextFloat()
|
return Random(seed).nextFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("readonly") @Suppress("purity")
|
||||||
fun conditionalApplies(
|
fun conditionalApplies(
|
||||||
unique: Unique?,
|
unique: Unique?,
|
||||||
conditional: Unique,
|
conditional: Unique,
|
||||||
|
@ -8,6 +8,7 @@ import com.unciv.models.translations.equalsPlaceholderText
|
|||||||
import com.unciv.models.translations.fillPlaceholders
|
import com.unciv.models.translations.fillPlaceholders
|
||||||
import com.unciv.models.translations.getPlaceholderParameters
|
import com.unciv.models.translations.getPlaceholderParameters
|
||||||
import com.unciv.models.translations.getPlaceholderText
|
import com.unciv.models.translations.getPlaceholderText
|
||||||
|
import org.jetbrains.annotations.Contract
|
||||||
import org.jetbrains.annotations.VisibleForTesting
|
import org.jetbrains.annotations.VisibleForTesting
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -257,6 +258,7 @@ enum class Countables(
|
|||||||
open val noPlaceholders = !text.contains('[')
|
open val noPlaceholders = !text.contains('[')
|
||||||
|
|
||||||
// Leave these in place only for the really simple cases
|
// Leave these in place only for the really simple cases
|
||||||
|
@Contract("readonly")
|
||||||
open fun matches(parameterText: String) = if (noPlaceholders) parameterText == text
|
open fun matches(parameterText: String) = if (noPlaceholders) parameterText == text
|
||||||
else parameterText.equalsPlaceholderText(placeholderText)
|
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*
|
/** 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.
|
* 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" */
|
* 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
|
open fun matches(parameterText: String, ruleset: Ruleset): Boolean = false
|
||||||
|
@Contract("readonly")
|
||||||
abstract fun eval(parameterText: String, stateForConditionals: StateForConditionals): Int?
|
abstract fun eval(parameterText: String, stateForConditionals: StateForConditionals): Int?
|
||||||
|
|
||||||
open val documentationHeader get() =
|
open val documentationHeader get() =
|
||||||
@ -289,6 +293,7 @@ enum class Countables(
|
|||||||
getErrorSeverity(parameterText.getPlaceholderParameters().first(), ruleset)
|
getErrorSeverity(parameterText.getPlaceholderParameters().first(), ruleset)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@Contract("readonly")
|
||||||
fun getMatching(parameterText: String, ruleset: Ruleset?) = Countables.entries
|
fun getMatching(parameterText: String, ruleset: Ruleset?) = Countables.entries
|
||||||
.firstOrNull {
|
.firstOrNull {
|
||||||
if (it.matchesWithRuleset)
|
if (it.matchesWithRuleset)
|
||||||
@ -296,6 +301,7 @@ enum class Countables(
|
|||||||
else it.matches(parameterText)
|
else it.matches(parameterText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun getCountableAmount(parameterText: String, stateForConditionals: StateForConditionals): Int? {
|
fun getCountableAmount(parameterText: String, stateForConditionals: StateForConditionals): Int? {
|
||||||
val ruleset = stateForConditionals.gameInfo?.ruleset
|
val ruleset = stateForConditionals.gameInfo?.ruleset
|
||||||
val countable = getMatching(parameterText, ruleset) ?: return null
|
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.getPlaceholderParameters
|
||||||
import com.unciv.models.translations.getPlaceholderText
|
import com.unciv.models.translations.getPlaceholderText
|
||||||
import com.unciv.models.translations.removeConditionals
|
import com.unciv.models.translations.removeConditionals
|
||||||
|
import org.jetbrains.annotations.Contract
|
||||||
import kotlin.math.max
|
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 hasFlag(flag: UniqueFlag) = type != null && type.flags.contains(flag)
|
||||||
fun isHiddenToUsers() = hasFlag(UniqueFlag.HiddenToUsers) || hasModifier(UniqueType.ModifierHiddenFromUsers)
|
fun isHiddenToUsers() = hasFlag(UniqueFlag.HiddenToUsers) || hasModifier(UniqueType.ModifierHiddenFromUsers)
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun getModifiers(type: UniqueType) = modifiersMap[type] ?: emptyList()
|
fun getModifiers(type: UniqueType) = modifiersMap[type] ?: emptyList()
|
||||||
|
@Contract("readonly")
|
||||||
fun hasModifier(type: UniqueType) = modifiersMap.containsKey(type)
|
fun hasModifier(type: UniqueType) = modifiersMap.containsKey(type)
|
||||||
fun isModifiedByGameSpeed() = hasModifier(UniqueType.ModifiedByGameSpeed)
|
fun isModifiedByGameSpeed() = hasModifier(UniqueType.ModifiedByGameSpeed)
|
||||||
fun isModifiedByGameProgress() = hasModifier(UniqueType.ModifiedByGameProgress)
|
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))
|
return conditionalsApply(StateForConditionals(civInfo, city))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun conditionalsApply(state: StateForConditionals): Boolean {
|
fun conditionalsApply(state: StateForConditionals): Boolean {
|
||||||
if (state.ignoreConditionals) return true
|
if (state.ignoreConditionals) return true
|
||||||
// Always allow Timed conditional uniques. They are managed elsewhere
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("readonly") @Suppress("purity")
|
||||||
private fun getUniqueMultiplier(stateForConditionals: StateForConditionals): Int {
|
private fun getUniqueMultiplier(stateForConditionals: StateForConditionals): Int {
|
||||||
if (stateForConditionals == StateForConditionals.IgnoreMultiplicationForCaching)
|
if (stateForConditionals == StateForConditionals.IgnoreMultiplicationForCaching)
|
||||||
return 1
|
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 */
|
/** Multiplies the unique according to the multiplication conditionals */
|
||||||
|
@Contract("readonly")
|
||||||
fun getMultiplied(stateForConditionals: StateForConditionals): Sequence<Unique> {
|
fun getMultiplied(stateForConditionals: StateForConditionals): Sequence<Unique> {
|
||||||
val multiplier = getUniqueMultiplier(stateForConditionals)
|
val multiplier = getUniqueMultiplier(stateForConditionals)
|
||||||
return EndlessSequenceOf(this).take(multiplier)
|
return EndlessSequenceOf(this).take(multiplier)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.unciv.models.ruleset.unique
|
package com.unciv.models.ruleset.unique
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Contract
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
open class UniqueMap() {
|
open class UniqueMap() {
|
||||||
@ -14,8 +15,6 @@ open class UniqueMap() {
|
|||||||
addUniques(uniques.asIterable())
|
addUniques(uniques.asIterable())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isEmpty(): Boolean = innerUniqueMap.isEmpty()
|
|
||||||
|
|
||||||
/** Adds one [unique] unless it has a ConditionalTimedUnique conditional */
|
/** Adds one [unique] unless it has a ConditionalTimedUnique conditional */
|
||||||
open fun addUnique(unique: Unique) {
|
open fun addUnique(unique: Unique) {
|
||||||
val existingArrayList = innerUniqueMap[unique.placeholderText]
|
val existingArrayList = innerUniqueMap[unique.placeholderText]
|
||||||
@ -42,26 +41,33 @@ open class UniqueMap() {
|
|||||||
typedUniqueMap.clear()
|
typedUniqueMap.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pure functions
|
@Contract("readonly")
|
||||||
|
fun isEmpty(): Boolean = innerUniqueMap.isEmpty()
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun hasUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
fun hasUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||||
getUniques(uniqueType).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
getUniques(uniqueType).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun hasUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
fun hasUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||||
getUniques(uniqueTag).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
getUniques(uniqueTag).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun hasTagUnique(tagUnique: String) =
|
fun hasTagUnique(tagUnique: String) =
|
||||||
innerUniqueMap.containsKey(tagUnique)
|
innerUniqueMap.containsKey(tagUnique)
|
||||||
|
|
||||||
// 160ms vs 1000-1250ms/30s
|
// 160ms vs 1000-1250ms/30s
|
||||||
|
@Contract("readonly")
|
||||||
fun getUniques(uniqueType: UniqueType) = typedUniqueMap[uniqueType]
|
fun getUniques(uniqueType: UniqueType) = typedUniqueMap[uniqueType]
|
||||||
?.asSequence()
|
?.asSequence()
|
||||||
?: emptySequence()
|
?: emptySequence()
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun getUniques(uniqueTag: String) = innerUniqueMap[uniqueTag]
|
fun getUniques(uniqueTag: String) = innerUniqueMap[uniqueTag]
|
||||||
?.asSequence()
|
?.asSequence()
|
||||||
?: emptySequence()
|
?: emptySequence()
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||||
getUniques(uniqueType)
|
getUniques(uniqueType)
|
||||||
// Same as .filter | .flatMap, but more cpu/mem performant (7.7 GB vs ?? for test)
|
// 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) =
|
fun getMatchingUniques(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||||
getUniques(uniqueTag)
|
getUniques(uniqueTag)
|
||||||
// Same as .filter | .flatMap, but more cpu/mem performant (7.7 GB vs ?? for test)
|
// 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)
|
else -> it.getMultiplied(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun hasMatchingUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
fun hasMatchingUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||||
getUniques(uniqueType).any { it.conditionalsApply(state) }
|
getUniques(uniqueType).any { it.conditionalsApply(state) }
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun hasMatchingUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
fun hasMatchingUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||||
getUniques(uniqueTag)
|
getUniques(uniqueTag)
|
||||||
.any { it.conditionalsApply(state) }
|
.any { it.conditionalsApply(state) }
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun getAllUniques() = innerUniqueMap.values.asSequence().flatten()
|
fun getAllUniques() = innerUniqueMap.values.asSequence().flatten()
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun getTriggeredUniques(trigger: UniqueType, stateForConditionals: StateForConditionals,
|
fun getTriggeredUniques(trigger: UniqueType, stateForConditionals: StateForConditionals,
|
||||||
triggerFilter: (Unique) -> Boolean = { true }): Sequence<Unique> {
|
triggerFilter: (Unique) -> Boolean = { true }): Sequence<Unique> {
|
||||||
return getAllUniques().filter { 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.ui.components.fonts.FontRulesetIcons
|
||||||
import com.unciv.utils.Log
|
import com.unciv.utils.Log
|
||||||
import com.unciv.utils.debug
|
import com.unciv.utils.debug
|
||||||
|
import org.jetbrains.annotations.Contract
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import org.jetbrains.annotations.VisibleForTesting
|
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]'],
|
* For example, a string like 'The city of [New [York]]' will return ['New [York]'],
|
||||||
* allowing us to have nested translations!
|
* allowing us to have nested translations!
|
||||||
*/
|
*/
|
||||||
|
@Contract("readonly")
|
||||||
fun String.getPlaceholderParameters(): List<String> {
|
fun String.getPlaceholderParameters(): List<String> {
|
||||||
if (!this.contains('[')) return emptyList()
|
if (!this.contains('[')) return emptyList()
|
||||||
|
|
||||||
@ -492,6 +494,7 @@ fun String.getPlaceholderParameters(): List<String> {
|
|||||||
return parameters
|
return parameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun String.getPlaceholderText(): String {
|
fun String.getPlaceholderText(): String {
|
||||||
var stringToReturn = this.removeConditionals()
|
var stringToReturn = this.removeConditionals()
|
||||||
val placeholderParameters = stringToReturn.getPlaceholderParameters()
|
val placeholderParameters = stringToReturn.getPlaceholderParameters()
|
||||||
@ -500,6 +503,7 @@ fun String.getPlaceholderText(): String {
|
|||||||
return stringToReturn
|
return stringToReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun String.equalsPlaceholderText(str: String): Boolean {
|
fun String.equalsPlaceholderText(str: String): Boolean {
|
||||||
if (isEmpty()) return str.isEmpty()
|
if (isEmpty()) return str.isEmpty()
|
||||||
if (str.isEmpty()) return false // Empty strings have no .first()
|
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()
|
return pointyBraceRegex.findAll(this).map { Unique(it.groups[1]!!.value) }.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Contract("readonly")
|
||||||
fun String.removeConditionals(): String {
|
fun String.removeConditionals(): String {
|
||||||
if (!this.contains('<')) return this // no need to regex search
|
if (!this.contains('<')) return this // no need to regex search
|
||||||
return this
|
return this
|
||||||
|
Loading…
x
Reference in New Issue
Block a user