From 40782fd18f9774ab7b216d85b256b00760eb4614 Mon Sep 17 00:00:00 2001
From: DBotThePony <dbotthepony@yandex.ru>
Date: Sat, 23 Sep 2023 22:59:30 +0700
Subject: [PATCH] Fix possible memory leaks regarding unused resources, and
 tighten cache store times

---
 .../kstarbound/client/StarboundClient.kt      |  2 +-
 .../kstarbound/client/gl/shader/UberShader.kt | 17 ++++++---
 .../client/render/LayeredRenderer.kt          | 35 ++++++++++++-------
 .../kstarbound/defs/image/Image.kt            |  2 +-
 4 files changed, 37 insertions(+), 19 deletions(-)

diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt
index 65ba5c45..f16fe6b1 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt
@@ -254,7 +254,7 @@ class StarboundClient : Closeable {
 
 	// минимальное время хранения 5 минут и...
 	private val named2DTextures0: Cache<String, GLTexture2D> = Caffeine.newBuilder()
-		.expireAfterAccess(Duration.ofMinutes(5))
+		.expireAfterAccess(Duration.ofMinutes(1))
 		.build()
 
 	// ...бесконечное хранение пока кто-то все ещё использует текстуру
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt
index 94d6c696..0361f7c3 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/shader/UberShader.kt
@@ -1,9 +1,11 @@
 package ru.dbotthepony.kstarbound.client.gl.shader
 
+import com.github.benmanes.caffeine.cache.Cache
+import com.github.benmanes.caffeine.cache.Caffeine
+import com.github.benmanes.caffeine.cache.Scheduler
 import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
 import it.unimi.dsi.fastutil.objects.ObjectArraySet
 import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction
-import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
 import org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER
 import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER
 import ru.dbotthepony.kstarbound.client.StarboundClient
@@ -15,6 +17,7 @@ import ru.dbotthepony.kstarbound.client.render.RenderConfig
 import ru.dbotthepony.kvector.api.IStruct4f
 import ru.dbotthepony.kvector.arrays.Matrix3f
 import ru.dbotthepony.kvector.vector.RGBAColor
+import java.time.Duration
 import java.util.Collections
 import java.util.EnumSet
 import java.util.function.Function
@@ -48,18 +51,22 @@ class UberShader private constructor(
 		colorMultiplier = RGBAColor.WHITE
 	}
 
-	private val textureConfigs = Reference2ObjectOpenHashMap<GLTexture2D, RenderConfig<UberShader>>()
+	private val textureConfigs: Cache<GLTexture2D, RenderConfig<UberShader>> = Caffeine.newBuilder()
+		.weakValues()
+		.weakKeys()
+		.expireAfterAccess(Duration.ofMinutes(1))
+		.build()
 
 	fun config(texture: GLTexture2D): RenderConfig<UberShader> {
-		return textureConfigs.computeIfAbsent(texture, Reference2ObjectFunction {
+		return textureConfigs.get(texture) {
 			object : RenderConfig<UberShader>(this) {
 				override fun setup() {
 					super.setup()
 
-					client.textures2D[0] = texture
+					client.textures2D[0] = it
 				}
 			}
-		})
+		}
 	}
 
 	class Builder {
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LayeredRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LayeredRenderer.kt
index 65bf5919..fa2576e0 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LayeredRenderer.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/LayeredRenderer.kt
@@ -39,9 +39,10 @@ object OneShotGeometryLayer : IGeometryLayer {
 
 class LayeredRenderer {
 	class Layer(val layer: RenderLayer.Point) : IGeometryLayer {
+		private data class Tracker(val builder: StreamVertexBuilder, var emptyFrames: Int = 0)
 		private val meshes = ArrayList<ConfiguredMesh<*>>()
 		private val callbacks = ArrayList<() -> Unit>()
-		private val builders = Reference2ObjectLinkedOpenHashMap<RenderConfig<*>, StreamVertexBuilder>()
+		private val builders = Reference2ObjectLinkedOpenHashMap<RenderConfig<*>, Tracker>()
 
 		override fun add(renderer: () -> Unit) {
 			callbacks.add(renderer)
@@ -49,17 +50,27 @@ class LayeredRenderer {
 
 		override fun getBuilder(config: RenderConfig<*>): VertexBuilder {
 			return builders.computeIfAbsent(config, Function {
-				StreamVertexBuilder(config.program.attributes, initialCapacity = config.initialBuilderCapacity)
-			}).builder
+				Tracker(StreamVertexBuilder(config.program.attributes, initialCapacity = config.initialBuilderCapacity))
+			}).builder.builder
 		}
 
 		override fun render() {
-			builders.entries.forEach {
-				if (it.value.builder.isNotEmpty()) {
-					it.key.setup()
-					it.value.upload()
-					it.value.draw()
-					it.key.uninstall()
+			val iterator = builders.entries.iterator()
+
+			for ((k, v) in iterator) {
+				if (v.builder.builder.isNotEmpty()) {
+					v.emptyFrames = 0
+					k.setup()
+					v.builder.upload()
+					v.builder.draw()
+					k.uninstall()
+				} else {
+					v.emptyFrames++
+
+					if (v.emptyFrames >= 600) {
+						// ~10 seconds of inactivity, free resources
+						iterator.remove()
+					}
 				}
 			}
 
@@ -73,7 +84,7 @@ class LayeredRenderer {
 		}
 
 		override fun clear() {
-			builders.values.forEach { it.builder.begin() }
+			builders.values.forEach { it.builder.builder.begin() }
 			meshes.clear()
 			callbacks.clear()
 		}
@@ -83,8 +94,8 @@ class LayeredRenderer {
 				throw IllegalStateException("This layer (index $layer) contains preconfigured meshes and/or callbacks")
 
 			return builders.entries.stream()
-				.filter { it.value.builder.isNotEmpty() }
-				.map { ConfiguredMesh(it.key, Mesh(it.value.builder)) to layer }
+				.filter { it.value.builder.builder.isNotEmpty() }
+				.map { ConfiguredMesh(it.key, Mesh(it.value.builder.builder)) to layer }
 		}
 	}
 
diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt
index c9e8cb9b..694efd56 100644
--- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt
+++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt
@@ -275,7 +275,7 @@ class Image private constructor(
 
 		private val dataCache: Cache<String, ByteBuffer> = Caffeine.newBuilder()
 			.softValues()
-			.expireAfterAccess(Duration.ofMinutes(20))
+			.expireAfterAccess(Duration.ofMinutes(5))
 			.weigher<String, ByteBuffer> { key, value -> value.capacity() }
 			.maximumWeight(1_024L * 1_024L * 256L /* 256 МиБ */)
 			.build()