Caffeine как библиотека для кешей
This commit is contained in:
parent
b47abdced4
commit
0910b093f8
@ -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") {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user