Fix possible memory leaks regarding unused resources, and tighten cache store times

This commit is contained in:
DBotThePony 2023-09-23 22:59:30 +07:00
parent 178953bce7
commit 40782fd18f
Signed by: DBot
GPG Key ID: DCC23B5715498507
4 changed files with 37 additions and 19 deletions

View File

@ -254,7 +254,7 @@ class StarboundClient : Closeable {
// минимальное время хранения 5 минут и... // минимальное время хранения 5 минут и...
private val named2DTextures0: Cache<String, GLTexture2D> = Caffeine.newBuilder() private val named2DTextures0: Cache<String, GLTexture2D> = Caffeine.newBuilder()
.expireAfterAccess(Duration.ofMinutes(5)) .expireAfterAccess(Duration.ofMinutes(1))
.build() .build()
// ...бесконечное хранение пока кто-то все ещё использует текстуру // ...бесконечное хранение пока кто-то все ещё использует текстуру

View File

@ -1,9 +1,11 @@
package ru.dbotthepony.kstarbound.client.gl.shader 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.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.Reference2ObjectFunction 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_FRAGMENT_SHADER
import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER import org.lwjgl.opengl.GL20.GL_VERTEX_SHADER
import ru.dbotthepony.kstarbound.client.StarboundClient 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.api.IStruct4f
import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.arrays.Matrix3f
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import java.time.Duration
import java.util.Collections import java.util.Collections
import java.util.EnumSet import java.util.EnumSet
import java.util.function.Function import java.util.function.Function
@ -48,18 +51,22 @@ class UberShader private constructor(
colorMultiplier = RGBAColor.WHITE 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> { fun config(texture: GLTexture2D): RenderConfig<UberShader> {
return textureConfigs.computeIfAbsent(texture, Reference2ObjectFunction { return textureConfigs.get(texture) {
object : RenderConfig<UberShader>(this) { object : RenderConfig<UberShader>(this) {
override fun setup() { override fun setup() {
super.setup() super.setup()
client.textures2D[0] = texture client.textures2D[0] = it
} }
} }
}) }
} }
class Builder { class Builder {

View File

@ -39,9 +39,10 @@ object OneShotGeometryLayer : IGeometryLayer {
class LayeredRenderer { class LayeredRenderer {
class Layer(val layer: RenderLayer.Point) : IGeometryLayer { 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 meshes = ArrayList<ConfiguredMesh<*>>()
private val callbacks = ArrayList<() -> Unit>() private val callbacks = ArrayList<() -> Unit>()
private val builders = Reference2ObjectLinkedOpenHashMap<RenderConfig<*>, StreamVertexBuilder>() private val builders = Reference2ObjectLinkedOpenHashMap<RenderConfig<*>, Tracker>()
override fun add(renderer: () -> Unit) { override fun add(renderer: () -> Unit) {
callbacks.add(renderer) callbacks.add(renderer)
@ -49,17 +50,27 @@ class LayeredRenderer {
override fun getBuilder(config: RenderConfig<*>): VertexBuilder { override fun getBuilder(config: RenderConfig<*>): VertexBuilder {
return builders.computeIfAbsent(config, Function { return builders.computeIfAbsent(config, Function {
StreamVertexBuilder(config.program.attributes, initialCapacity = config.initialBuilderCapacity) Tracker(StreamVertexBuilder(config.program.attributes, initialCapacity = config.initialBuilderCapacity))
}).builder }).builder.builder
} }
override fun render() { override fun render() {
builders.entries.forEach { val iterator = builders.entries.iterator()
if (it.value.builder.isNotEmpty()) {
it.key.setup() for ((k, v) in iterator) {
it.value.upload() if (v.builder.builder.isNotEmpty()) {
it.value.draw() v.emptyFrames = 0
it.key.uninstall() 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() { override fun clear() {
builders.values.forEach { it.builder.begin() } builders.values.forEach { it.builder.builder.begin() }
meshes.clear() meshes.clear()
callbacks.clear() callbacks.clear()
} }
@ -83,8 +94,8 @@ class LayeredRenderer {
throw IllegalStateException("This layer (index $layer) contains preconfigured meshes and/or callbacks") throw IllegalStateException("This layer (index $layer) contains preconfigured meshes and/or callbacks")
return builders.entries.stream() return builders.entries.stream()
.filter { it.value.builder.isNotEmpty() } .filter { it.value.builder.builder.isNotEmpty() }
.map { ConfiguredMesh(it.key, Mesh(it.value.builder)) to layer } .map { ConfiguredMesh(it.key, Mesh(it.value.builder.builder)) to layer }
} }
} }

View File

@ -275,7 +275,7 @@ class Image private constructor(
private val dataCache: Cache<String, ByteBuffer> = Caffeine.newBuilder() private val dataCache: Cache<String, ByteBuffer> = Caffeine.newBuilder()
.softValues() .softValues()
.expireAfterAccess(Duration.ofMinutes(20)) .expireAfterAccess(Duration.ofMinutes(5))
.weigher<String, ByteBuffer> { key, value -> value.capacity() } .weigher<String, ByteBuffer> { key, value -> value.capacity() }
.maximumWeight(1_024L * 1_024L * 256L /* 256 МиБ */) .maximumWeight(1_024L * 1_024L * 256L /* 256 МиБ */)
.build() .build()