Skip to content

Commit 4718d34

Browse files
committed
Extract other classes out of Unique.kt
Allow temporary and city-state uniques to accept multiplication modifiers
1 parent d5c6758 commit 4718d34

File tree

5 files changed

+207
-196
lines changed

5 files changed

+207
-196
lines changed

Diff for: core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt

+1
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,7 @@ class CityStateFunctions(val civInfo: Civilization) {
806806
getCityStateBonuses(it.cityStateType, relationshipLevel, uniqueType)
807807
}
808808
.filter { it.conditionalsApply(stateForConditionals) }
809+
.flatMap { it.getMultiplied(stateForConditionals) }
809810
}
810811

811812

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.unciv.models.ruleset.unique
2+
3+
import com.unciv.logic.city.City
4+
import com.unciv.logic.civilization.Civilization
5+
6+
/** Used to cache results of getMatchingUniques
7+
* Must only be used when we're sure the matching uniques will not change in the meantime */
8+
class LocalUniqueCache(val cache: Boolean = true) {
9+
// This stores sequences *that iterate directly on a list* - that is, pre-resolved
10+
private val keyToUniques = HashMap<String, Sequence<Unique>>()
11+
12+
fun forCityGetMatchingUniques(
13+
city: City,
14+
uniqueType: UniqueType,
15+
stateForConditionals: StateForConditionals = city.state
16+
): Sequence<Unique> {
17+
// City uniques are a combination of *global civ* uniques plus *city relevant* uniques (see City.getMatchingUniques())
18+
// We can cache the civ uniques separately, so if we have several cities using the same cache,
19+
// we can cache the list of *civ uniques* to reuse between cities.
20+
21+
val citySpecificUniques = get(
22+
"city-${city.id}-${uniqueType.name}",
23+
city.getLocalMatchingUniques(uniqueType, StateForConditionals.IgnoreMultiplicationForCaching)
24+
).filter { it.conditionalsApply(stateForConditionals) }
25+
.flatMap { it.getMultiplied(stateForConditionals) }
26+
27+
val civUniques = forCivGetMatchingUniques(city.civ, uniqueType, stateForConditionals)
28+
29+
return citySpecificUniques + civUniques
30+
}
31+
32+
fun forCivGetMatchingUniques(
33+
civ: Civilization,
34+
uniqueType: UniqueType,
35+
stateForConditionals: StateForConditionals = civ.state
36+
): Sequence<Unique> {
37+
val sequence = civ.getMatchingUniques(uniqueType, StateForConditionals.IgnoreMultiplicationForCaching)
38+
// The uniques CACHED are ALL civ uniques, regardless of conditional matching.
39+
// The uniques RETURNED are uniques AFTER conditional matching.
40+
// This allows reuse of the cached values, between runs with different conditionals -
41+
// for example, iterate on all tiles and get StatPercentForObject uniques relevant for each tile,
42+
// each tile will have different conditional state, but they will all reuse the same list of uniques for the civ
43+
return get(
44+
"civ-${civ.civName}-${uniqueType.name}",
45+
sequence
46+
).filter { it.conditionalsApply(stateForConditionals) }
47+
.flatMap { it.getMultiplied(stateForConditionals) }
48+
}
49+
50+
/** Get cached results as a sequence */
51+
private fun get(key: String, sequence: Sequence<Unique>): Sequence<Unique> {
52+
if (!cache) return sequence
53+
val valueInMap = keyToUniques[key]
54+
if (valueInMap != null) return valueInMap
55+
// Iterate the sequence, save actual results as a list, as return a sequence to that
56+
val results = sequence.toList().asSequence()
57+
keyToUniques[key] = results
58+
return results
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.unciv.models.ruleset.unique
2+
3+
import com.unciv.logic.IsPartOfGameInfoSerialization
4+
5+
class TemporaryUnique() : IsPartOfGameInfoSerialization {
6+
7+
constructor(uniqueObject: Unique, turns: Int) : this() {
8+
val turnsText = uniqueObject.getModifiers(UniqueType.ConditionalTimedUnique).first().text
9+
unique = uniqueObject.text.replaceFirst("<$turnsText>", "").trim()
10+
sourceObjectType = uniqueObject.sourceObjectType
11+
sourceObjectName = uniqueObject.sourceObjectName
12+
turnsLeft = turns
13+
}
14+
15+
var unique: String = ""
16+
17+
private var sourceObjectType: UniqueTarget? = null
18+
private var sourceObjectName: String? = null
19+
20+
@delegate:Transient
21+
val uniqueObject: Unique by lazy { Unique(unique, sourceObjectType, sourceObjectName) }
22+
23+
var turnsLeft: Int = 0
24+
}
25+
26+
27+
fun ArrayList<TemporaryUnique>.endTurn() {
28+
for (unique in this) {
29+
if (unique.turnsLeft >= 0)
30+
unique.turnsLeft -= 1
31+
}
32+
removeAll { it.turnsLeft == 0 }
33+
}
34+
35+
fun ArrayList<TemporaryUnique>.getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals): Sequence<Unique> {
36+
return this.asSequence()
37+
.map { it.uniqueObject }
38+
.filter { it.type == uniqueType && it.conditionalsApply(stateForConditionals) }
39+
.flatMap { it.getMultiplied(stateForConditionals) }
40+
}

Diff for: core/src/com/unciv/models/ruleset/unique/Unique.kt

-196
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.unciv.models.ruleset.unique
22

33
import com.unciv.Constants
4-
import com.unciv.logic.IsPartOfGameInfoSerialization
54
import com.unciv.logic.city.City
65
import com.unciv.logic.civilization.Civilization
76
import com.unciv.models.ruleset.GlobalUniques
@@ -12,7 +11,6 @@ import com.unciv.models.translations.getModifiers
1211
import com.unciv.models.translations.getPlaceholderParameters
1312
import com.unciv.models.translations.getPlaceholderText
1413
import com.unciv.models.translations.removeConditionals
15-
import java.util.EnumMap
1614
import kotlin.math.max
1715

1816

@@ -219,197 +217,3 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
219217
fun getDisplayText(): String = if (modifiers.none { it.isHiddenToUsers() }) text
220218
else text.removeConditionals() + " " + modifiers.filter { !it.isHiddenToUsers() }.joinToString(" ") { "<${it.text}>" }
221219
}
222-
223-
/** Used to cache results of getMatchingUniques
224-
* Must only be used when we're sure the matching uniques will not change in the meantime */
225-
class LocalUniqueCache(val cache: Boolean = true) {
226-
// This stores sequences *that iterate directly on a list* - that is, pre-resolved
227-
private val keyToUniques = HashMap<String, Sequence<Unique>>()
228-
229-
fun forCityGetMatchingUniques(
230-
city: City,
231-
uniqueType: UniqueType,
232-
stateForConditionals: StateForConditionals = city.state
233-
): Sequence<Unique> {
234-
// City uniques are a combination of *global civ* uniques plus *city relevant* uniques (see City.getMatchingUniques())
235-
// We can cache the civ uniques separately, so if we have several cities using the same cache,
236-
// we can cache the list of *civ uniques* to reuse between cities.
237-
238-
val citySpecificUniques = get(
239-
"city-${city.id}-${uniqueType.name}",
240-
city.getLocalMatchingUniques(uniqueType, StateForConditionals.IgnoreMultiplicationForCaching)
241-
).filter { it.conditionalsApply(stateForConditionals) }
242-
.flatMap { it.getMultiplied(stateForConditionals) }
243-
244-
val civUniques = forCivGetMatchingUniques(city.civ, uniqueType, stateForConditionals)
245-
246-
return citySpecificUniques + civUniques
247-
}
248-
249-
fun forCivGetMatchingUniques(
250-
civ: Civilization,
251-
uniqueType: UniqueType,
252-
stateForConditionals: StateForConditionals = civ.state
253-
): Sequence<Unique> {
254-
val sequence = civ.getMatchingUniques(uniqueType, StateForConditionals.IgnoreMultiplicationForCaching)
255-
// The uniques CACHED are ALL civ uniques, regardless of conditional matching.
256-
// The uniques RETURNED are uniques AFTER conditional matching.
257-
// This allows reuse of the cached values, between runs with different conditionals -
258-
// for example, iterate on all tiles and get StatPercentForObject uniques relevant for each tile,
259-
// each tile will have different conditional state, but they will all reuse the same list of uniques for the civ
260-
return get(
261-
"civ-${civ.civName}-${uniqueType.name}",
262-
sequence
263-
).filter { it.conditionalsApply(stateForConditionals) }
264-
.flatMap { it.getMultiplied(stateForConditionals) }
265-
}
266-
267-
/** Get cached results as a sequence */
268-
private fun get(key: String, sequence: Sequence<Unique>): Sequence<Unique> {
269-
if (!cache) return sequence
270-
val valueInMap = keyToUniques[key]
271-
if (valueInMap != null) return valueInMap
272-
// Iterate the sequence, save actual results as a list, as return a sequence to that
273-
val results = sequence.toList().asSequence()
274-
keyToUniques[key] = results
275-
return results
276-
}
277-
}
278-
279-
open class UniqueMap() {
280-
protected val innerUniqueMap = HashMap<String, ArrayList<Unique>>()
281-
282-
// *shares* the list of uniques with the other map, to save on memory and allocations
283-
// This is a memory/speed tradeoff, since there are *600 unique types*,
284-
// 750 including deprecated, and EnumMap creates a N-sized array where N is the number of objects in the enum
285-
val typedUniqueMap = EnumMap<UniqueType, ArrayList<Unique>>(UniqueType::class.java)
286-
287-
constructor(uniques: Sequence<Unique>) : this() {
288-
addUniques(uniques.asIterable())
289-
}
290-
291-
fun isEmpty(): Boolean = innerUniqueMap.isEmpty()
292-
293-
/** Adds one [unique] unless it has a ConditionalTimedUnique conditional */
294-
open fun addUnique(unique: Unique) {
295-
val existingArrayList = innerUniqueMap[unique.placeholderText]
296-
if (existingArrayList != null) existingArrayList.add(unique)
297-
else innerUniqueMap[unique.placeholderText] = arrayListOf(unique)
298-
299-
if (unique.type == null) return
300-
if (typedUniqueMap[unique.type] != null) return
301-
typedUniqueMap[unique.type] = innerUniqueMap[unique.placeholderText]
302-
}
303-
304-
/** Calls [addUnique] on each item from [uniques] */
305-
fun addUniques(uniques: Iterable<Unique>) {
306-
for (unique in uniques) addUnique(unique)
307-
}
308-
309-
fun removeUnique(unique: Unique) {
310-
val existingArrayList = innerUniqueMap[unique.placeholderText]
311-
existingArrayList?.remove(unique)
312-
}
313-
314-
fun clear() {
315-
innerUniqueMap.clear()
316-
typedUniqueMap.clear()
317-
}
318-
319-
// Pure functions
320-
321-
fun hasUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
322-
getUniques(uniqueType).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
323-
324-
fun hasUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
325-
getUniques(uniqueTag).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
326-
327-
fun hasTagUnique(tagUnique: String) =
328-
innerUniqueMap.containsKey(tagUnique)
329-
330-
// 160ms vs 1000-1250ms/30s
331-
fun getUniques(uniqueType: UniqueType) = typedUniqueMap[uniqueType]
332-
?.asSequence()
333-
?: emptySequence()
334-
335-
fun getUniques(uniqueTag: String) = innerUniqueMap[uniqueTag]
336-
?.asSequence()
337-
?: emptySequence()
338-
339-
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
340-
getUniques(uniqueType)
341-
// Same as .filter | .flatMap, but more cpu/mem performant (7.7 GB vs ?? for test)
342-
.flatMap {
343-
when {
344-
it.isTimedTriggerable -> emptySequence()
345-
!it.conditionalsApply(state) -> emptySequence()
346-
else -> it.getMultiplied(state)
347-
}
348-
}
349-
350-
fun getMatchingUniques(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
351-
getUniques(uniqueTag)
352-
// Same as .filter | .flatMap, but more cpu/mem performant (7.7 GB vs ?? for test)
353-
.flatMap {
354-
when {
355-
it.isTimedTriggerable -> emptySequence()
356-
!it.conditionalsApply(state) -> emptySequence()
357-
else -> it.getMultiplied(state)
358-
}
359-
}
360-
361-
fun hasMatchingUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
362-
getUniques(uniqueType).any { it.conditionalsApply(state) }
363-
364-
fun hasMatchingUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
365-
getUniques(uniqueTag)
366-
.any { it.conditionalsApply(state) }
367-
368-
fun getAllUniques() = innerUniqueMap.values.asSequence().flatten()
369-
370-
fun getTriggeredUniques(trigger: UniqueType, stateForConditionals: StateForConditionals,
371-
triggerFilter: (Unique) -> Boolean = { true }): Sequence<Unique> {
372-
return getAllUniques().filter { unique ->
373-
unique.getModifiers(trigger).any(triggerFilter) && unique.conditionalsApply(stateForConditionals)
374-
}.flatMap { it.getMultiplied(stateForConditionals) }
375-
}
376-
377-
companion object{
378-
val EMPTY = UniqueMap()
379-
}
380-
}
381-
382-
class TemporaryUnique() : IsPartOfGameInfoSerialization {
383-
384-
constructor(uniqueObject: Unique, turns: Int) : this() {
385-
val turnsText = uniqueObject.getModifiers(UniqueType.ConditionalTimedUnique).first().text
386-
unique = uniqueObject.text.replaceFirst("<$turnsText>", "").trim()
387-
sourceObjectType = uniqueObject.sourceObjectType
388-
sourceObjectName = uniqueObject.sourceObjectName
389-
turnsLeft = turns
390-
}
391-
392-
var unique: String = ""
393-
394-
private var sourceObjectType: UniqueTarget? = null
395-
private var sourceObjectName: String? = null
396-
397-
@delegate:Transient
398-
val uniqueObject: Unique by lazy { Unique(unique, sourceObjectType, sourceObjectName) }
399-
400-
var turnsLeft: Int = 0
401-
}
402-
403-
fun ArrayList<TemporaryUnique>.endTurn() {
404-
for (unique in this) {
405-
if (unique.turnsLeft >= 0)
406-
unique.turnsLeft -= 1
407-
}
408-
removeAll { it.turnsLeft == 0 }
409-
}
410-
411-
fun ArrayList<TemporaryUnique>.getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals): Sequence<Unique> {
412-
return this.asSequence()
413-
.map { it.uniqueObject }
414-
.filter { it.type == uniqueType && it.conditionalsApply(stateForConditionals) }
415-
}

0 commit comments

Comments
 (0)