Caffeine как библиотека для кешей

This commit is contained in:
DBotThePony 2023-03-28 22:33:54 +07:00
parent b47abdced4
commit 0910b093f8
Signed by: DBot
GPG Key ID: DCC23B5715498507
5 changed files with 77 additions and 24 deletions

View File

@ -82,6 +82,8 @@ dependencies {
implementation("ru.dbotthepony:kbox2d:2.4.1.+")
implementation("ru.dbotthepony:kvector:1.3.2")
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
}
tasks.getByName<Test>("test") {

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kstarbound
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.google.common.cache.CacheBuilder
import com.google.common.collect.Interner
import com.google.common.collect.Interners
@ -70,9 +72,11 @@ import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.util.traverseJsonPath
import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.io.*
import java.lang.ref.WeakReference
import java.text.DateFormat
import java.time.Duration
import java.util.*
import java.util.concurrent.locks.LockSupport
import java.util.function.BiConsumer
import java.util.function.BinaryOperator
import java.util.function.Function
@ -223,11 +227,29 @@ class Starbound : ISBFileLocator {
val atlasRegistry = AtlasConfiguration.Registry(this, pathStack, gson)
private val imageCache = CacheBuilder.newBuilder()
.concurrencyLevel(8)
private val imageCache: Cache<String, ImageData> = Caffeine.newBuilder()
.softValues()
.expireAfterAccess(Duration.ofMinutes(10))
.build<String, ImageData>()
.expireAfterAccess(Duration.ofMinutes(20))
.weigher<String, ImageData> { key, value -> value.data.capacity() }
.maximumWeight(1_024L * 1_024L * 256L /* 256 МиБ */)
.build()
init {
val ref = WeakReference(imageCache)
val worker = Runnable {
while (true) {
val get = ref.get() ?: break
get.cleanUp()
LockSupport.parkNanos(1_000_000_000L)
}
}
Thread(worker, "Image Data Cache Cleaner for $this").also {
it.isDaemon = true
it.start()
}
}
fun item(name: String): ItemStack {
return ItemStack(items[name] ?: return ItemStack.EMPTY)

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kstarbound.client.gl
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL46.*
@ -23,8 +25,11 @@ import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.Color
import java.io.File
import java.lang.ref.Cleaner
import java.lang.ref.WeakReference
import java.time.Duration
import java.util.*
import java.util.concurrent.ThreadFactory
import java.util.concurrent.locks.LockSupport
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.roundToInt
@ -127,7 +132,6 @@ class GLStateTracker(val client: StarboundClient) {
init {
check(TRACKERS.get() == null) { "Already has state tracker existing at this thread!" }
TRACKERS.set(this)
}
// This line is critical for LWJGL's interoperation with GLFW's
@ -161,13 +165,11 @@ class GLStateTracker(val client: StarboundClient) {
var gcHits = 0L
private set
private val cleaner = Cleaner.create(object : ThreadFactory {
override fun newThread(r: Runnable): Thread {
val thread = Thread(r, "OpenGL Object Cleaner@" + System.identityHashCode(this))
thread.priority = 2
return thread
}
})
private val cleaner = Cleaner.create { r ->
val thread = Thread(r, "OpenGL Object Cleaner for ${this@GLStateTracker}")
thread.priority = 2
thread
}
fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable {
val cleanable = cleaner.register(ref) {
@ -433,25 +435,48 @@ class GLStateTracker(val client: StarboundClient) {
fun newVAO() = VertexArrayObject(this)
fun newTexture(name: String = "<unknown>") = GLTexture2D(this, name)
private val named2DTextures = HashMap<String, GLTexture2D>()
/**
* Так как текстуры не занимают память в куче JVM, а только видеопамять, то
* такой кеш довольно хрупкий
*/
private val named2DTextures: Cache<String, GLTexture2D> = Caffeine.newBuilder()
.softValues()
.expireAfterAccess(Duration.ofMinutes(5))
.build()
init {
val ref = WeakReference(named2DTextures)
val worker = Runnable {
while (true) {
val get = ref.get() ?: break
get.cleanUp()
LockSupport.parkNanos(1_000_000_000L)
}
}
Thread(worker, "OpenGL Texture Cache Cleaner for $this").also {
it.isDaemon = true
it.start()
}
}
private val missingTexture: GLTexture2D by lazy {
newTexture(missingTexturePath).upload(client.starbound.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also {
it.textureMinFilter = GL_NEAREST
it.textureMagFilter = GL_NEAREST
}
}
private var missingTexture: GLTexture2D? = null
private val missingTexturePath = "/assetmissing.png"
fun loadTexture(path: String): GLTexture2D {
ensureSameThread()
if (missingTexture == null) {
missingTexture = newTexture(missingTexturePath).upload(client.starbound.readDirect(missingTexturePath), GL_RGBA, GL_RGBA).generateMips().also {
it.textureMinFilter = GL_NEAREST
it.textureMagFilter = GL_NEAREST
}
}
return named2DTextures.computeIfAbsent(path) {
return named2DTextures.get(path) {
if (!client.starbound.exists(path)) {
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
missingTexture!!
missingTexture
} else {
newTexture(path).upload(client.starbound.imageData(path)).generateMips().also {
it.textureMinFilter = GL_NEAREST

View File

@ -30,6 +30,10 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value:
@Suppress("SameParameterValue")
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : AutoCloseable {
init {
state.ensureSameThread()
}
val pointer = glGenTextures()
init {

View File

@ -1038,7 +1038,7 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
private val LOGGER = LogManager.getLogger()
private val CLEANER: Cleaner = Cleaner.create {
val thread = Thread(it, "Lua cleaner")
val thread = Thread(it, "Lua State Cleaner")
thread.priority = 1
thread
}