aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/util/collections
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-09-24 11:40:15 +0200
committerLinnea Gräf <nea@nea.moe>2024-09-24 11:40:15 +0200
commit420f2a61e1cc64d68bf03825e8fd70cf49ac6a01 (patch)
tree540f2beaf99bda96af3c145cbfe81faebba76bee /src/main/kotlin/util/collections
parent64099bd2628490b06392766c6d1b9425f26788a3 (diff)
downloadFirmament-420f2a61e1cc64d68bf03825e8fd70cf49ac6a01.tar.gz
Firmament-420f2a61e1cc64d68bf03825e8fd70cf49ac6a01.tar.bz2
Firmament-420f2a61e1cc64d68bf03825e8fd70cf49ac6a01.zip
Use weak caches for custom textures
Diffstat (limited to 'src/main/kotlin/util/collections')
-rw-r--r--src/main/kotlin/util/collections/InstanceList.kt57
-rw-r--r--src/main/kotlin/util/collections/MutableMapWithMaxSize.kt40
-rw-r--r--src/main/kotlin/util/collections/WeakCache.kt110
-rw-r--r--src/main/kotlin/util/collections/listutil.kt9
4 files changed, 216 insertions, 0 deletions
diff --git a/src/main/kotlin/util/collections/InstanceList.kt b/src/main/kotlin/util/collections/InstanceList.kt
new file mode 100644
index 0000000..fd8c786
--- /dev/null
+++ b/src/main/kotlin/util/collections/InstanceList.kt
@@ -0,0 +1,57 @@
+package moe.nea.firmament.util.collections
+
+import java.lang.ref.ReferenceQueue
+import java.lang.ref.WeakReference
+
+class InstanceList<T : Any>(val name: String) {
+ val queue = object : ReferenceQueue<T>() {}
+ val set = mutableSetOf<Ref>()
+
+ val size: Int
+ get() {
+ clearOldReferences()
+ return set.size
+ }
+
+ fun clearOldReferences() {
+ while (true) {
+ val reference = queue.poll() ?: break
+ set.remove(reference)
+ }
+ }
+
+ fun getAll(): List<T> {
+ clearOldReferences()
+ return set.mapNotNull { it.get() }
+ }
+
+ fun add(t: T) {
+ set.add(Ref(t))
+ }
+
+ init {
+ if (init)
+ allInstances.add(this)
+ }
+
+ inner class Ref(referent: T) : WeakReference<T>(referent) {
+ val hashCode = System.identityHashCode(referent)
+ override fun equals(other: Any?): Boolean {
+ return other is InstanceList<*>.Ref && hashCode == other.hashCode && get() === other.get()
+ }
+
+ override fun hashCode(): Int {
+ return hashCode
+ }
+ }
+
+ companion object {
+ private var init = false
+ val allInstances = InstanceList<InstanceList<*>>("InstanceLists")
+
+ init {
+ init = true
+ allInstances.add(allInstances)
+ }
+ }
+}
diff --git a/src/main/kotlin/util/collections/MutableMapWithMaxSize.kt b/src/main/kotlin/util/collections/MutableMapWithMaxSize.kt
new file mode 100644
index 0000000..218bc55
--- /dev/null
+++ b/src/main/kotlin/util/collections/MutableMapWithMaxSize.kt
@@ -0,0 +1,40 @@
+
+package moe.nea.firmament.util.collections
+
+import moe.nea.firmament.util.IdentityCharacteristics
+
+fun <K, V> mutableMapWithMaxSize(maxSize: Int): MutableMap<K, V> = object : LinkedHashMap<K, V>() {
+ override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>): Boolean {
+ return size > maxSize
+ }
+}
+
+fun <T, R> ((T) -> R).memoizeIdentity(maxCacheSize: Int): (T) -> R {
+ val memoized = { it: IdentityCharacteristics<T> ->
+ this(it.value)
+ }.memoize(maxCacheSize)
+ return { memoized(IdentityCharacteristics(it)) }
+}
+
+@PublishedApi
+internal val SENTINEL_NULL = java.lang.Object()
+
+/**
+ * Requires the map to only contain values of type [R] or [SENTINEL_NULL]. This is ensured if the map is only ever
+ * accessed via this function.
+ */
+inline fun <T, R> MutableMap<T, Any>.computeNullableFunction(key: T, crossinline func: () -> R): R {
+ val value = this.getOrPut(key) {
+ func() ?: SENTINEL_NULL
+ }
+ @Suppress("UNCHECKED_CAST")
+ return if (value === SENTINEL_NULL) null as R
+ else value as R
+}
+
+fun <T, R> ((T) -> R).memoize(maxCacheSize: Int): (T) -> R {
+ val map = mutableMapWithMaxSize<T, Any>(maxCacheSize)
+ return {
+ map.computeNullableFunction(it) { this@memoize(it) }
+ }
+}
diff --git a/src/main/kotlin/util/collections/WeakCache.kt b/src/main/kotlin/util/collections/WeakCache.kt
new file mode 100644
index 0000000..38f9886
--- /dev/null
+++ b/src/main/kotlin/util/collections/WeakCache.kt
@@ -0,0 +1,110 @@
+package moe.nea.firmament.util.collections
+
+import java.lang.ref.ReferenceQueue
+import java.lang.ref.WeakReference
+import moe.nea.firmament.features.debug.DebugLogger
+
+/**
+ * Cache class that uses [WeakReferences][WeakReference] to only cache values while there is still a life reference to
+ * the key. Each key can have additional extra data that is used to look up values. That extra data is not required to
+ * be a life reference. The main Key is compared using strict reference equality. This map is not synchronized.
+ */
+class WeakCache<Key : Any, ExtraKey : Any, Value : Any>(val name: String) {
+ private val queue = object : ReferenceQueue<Key>() {}
+ private val map = mutableMapOf<Ref, Value>()
+
+ val size: Int
+ get() {
+ clearOldReferences()
+ return map.size
+ }
+
+ fun clearOldReferences() {
+ var successCount = 0
+ var totalCount = 0
+ while (true) {
+ val reference = queue.poll() ?: break
+ totalCount++
+ if (map.remove(reference) != null)
+ successCount++
+ }
+ if (totalCount > 0)
+ logger.log { "Cleared $successCount/$totalCount references from queue" }
+ }
+
+ fun get(key: Key, extraData: ExtraKey): Value? {
+ clearOldReferences()
+ return map[Ref(key, extraData)]
+ }
+
+ fun put(key: Key, extraData: ExtraKey, value: Value) {
+ clearOldReferences()
+ map[Ref(key, extraData)] = value
+ }
+
+ fun getOrPut(key: Key, extraData: ExtraKey, value: (Key, ExtraKey) -> Value): Value {
+ clearOldReferences()
+ return map.getOrPut(Ref(key, extraData)) { value(key, extraData) }
+ }
+
+ fun clear() {
+ map.clear()
+ }
+
+ init {
+ allInstances.add(this)
+ }
+
+ companion object {
+ val allInstances = InstanceList<WeakCache<*, *, *>>("WeakCaches")
+ private val logger = DebugLogger("WeakCache")
+ fun <Key : Any, Value : Any> memoize(name: String, function: (Key) -> Value):
+ CacheFunction.NoExtraData<Key, Value> {
+ return CacheFunction.NoExtraData(WeakCache(name), function)
+ }
+
+ fun <Key : Any, ExtraKey : Any, Value : Any> memoize(name: String, function: (Key, ExtraKey) -> Value):
+ CacheFunction.WithExtraData<Key, ExtraKey, Value> {
+ return CacheFunction.WithExtraData(WeakCache(name), function)
+ }
+ }
+
+ inner class Ref(
+ weakInstance: Key,
+ val extraData: ExtraKey,
+ ) : WeakReference<Key>(weakInstance, queue) {
+ val hashCode = System.identityHashCode(weakInstance) * 31 + extraData.hashCode()
+ override fun equals(other: Any?): Boolean {
+ if (other !is WeakCache<*, *, *>.Ref) return false
+ return other.hashCode == this.hashCode
+ && other.get() === this.get()
+ && other.extraData == this.extraData
+ }
+
+ override fun hashCode(): Int {
+ return hashCode
+ }
+ }
+
+ interface CacheFunction {
+ val cache: WeakCache<*, *, *>
+
+ data class NoExtraData<Key : Any, Value : Any>(
+ override val cache: WeakCache<Key, Unit, Value>,
+ val wrapped: (Key) -> Value,
+ ) : CacheFunction, (Key) -> Value {
+ override fun invoke(p1: Key): Value {
+ return cache.getOrPut(p1, Unit, { a, _ -> wrapped(a) })
+ }
+ }
+
+ data class WithExtraData<Key : Any, ExtraKey : Any, Value : Any>(
+ override val cache: WeakCache<Key, ExtraKey, Value>,
+ val wrapped: (Key, ExtraKey) -> Value,
+ ) : CacheFunction, (Key, ExtraKey) -> Value {
+ override fun invoke(p1: Key, p2: ExtraKey): Value {
+ return cache.getOrPut(p1, p2, wrapped)
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/util/collections/listutil.kt b/src/main/kotlin/util/collections/listutil.kt
new file mode 100644
index 0000000..7f85a30
--- /dev/null
+++ b/src/main/kotlin/util/collections/listutil.kt
@@ -0,0 +1,9 @@
+
+package moe.nea.firmament.util.collections
+
+fun <T, R> List<T>.lastNotNullOfOrNull(func: (T) -> R?): R? {
+ for (i in indices.reversed()) {
+ return func(this[i]) ?: continue
+ }
+ return null
+}