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:kbox2d:2.4.1.+")
|
||||||
implementation("ru.dbotthepony:kvector:1.3.2")
|
implementation("ru.dbotthepony:kvector:1.3.2")
|
||||||
|
|
||||||
|
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.getByName<Test>("test") {
|
tasks.getByName<Test>("test") {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
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.cache.CacheBuilder
|
||||||
import com.google.common.collect.Interner
|
import com.google.common.collect.Interner
|
||||||
import com.google.common.collect.Interners
|
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.kstarbound.util.traverseJsonPath
|
||||||
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
||||||
import java.io.*
|
import java.io.*
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.locks.LockSupport
|
||||||
import java.util.function.BiConsumer
|
import java.util.function.BiConsumer
|
||||||
import java.util.function.BinaryOperator
|
import java.util.function.BinaryOperator
|
||||||
import java.util.function.Function
|
import java.util.function.Function
|
||||||
@ -223,11 +227,29 @@ class Starbound : ISBFileLocator {
|
|||||||
|
|
||||||
val atlasRegistry = AtlasConfiguration.Registry(this, pathStack, gson)
|
val atlasRegistry = AtlasConfiguration.Registry(this, pathStack, gson)
|
||||||
|
|
||||||
private val imageCache = CacheBuilder.newBuilder()
|
private val imageCache: Cache<String, ImageData> = Caffeine.newBuilder()
|
||||||
.concurrencyLevel(8)
|
|
||||||
.softValues()
|
.softValues()
|
||||||
.expireAfterAccess(Duration.ofMinutes(10))
|
.expireAfterAccess(Duration.ofMinutes(20))
|
||||||
.build<String, ImageData>()
|
.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 {
|
fun item(name: String): ItemStack {
|
||||||
return ItemStack(items[name] ?: return ItemStack.EMPTY)
|
return ItemStack(items[name] ?: return ItemStack.EMPTY)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.gl
|
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.apache.logging.log4j.LogManager
|
||||||
import org.lwjgl.opengl.GL
|
import org.lwjgl.opengl.GL
|
||||||
import org.lwjgl.opengl.GL46.*
|
import org.lwjgl.opengl.GL46.*
|
||||||
@ -23,8 +25,11 @@ import ru.dbotthepony.kvector.util2d.AABB
|
|||||||
import ru.dbotthepony.kvector.vector.Color
|
import ru.dbotthepony.kvector.vector.Color
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.Cleaner
|
import java.lang.ref.Cleaner
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.time.Duration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ThreadFactory
|
import java.util.concurrent.ThreadFactory
|
||||||
|
import java.util.concurrent.locks.LockSupport
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -127,7 +132,6 @@ class GLStateTracker(val client: StarboundClient) {
|
|||||||
init {
|
init {
|
||||||
check(TRACKERS.get() == null) { "Already has state tracker existing at this thread!" }
|
check(TRACKERS.get() == null) { "Already has state tracker existing at this thread!" }
|
||||||
TRACKERS.set(this)
|
TRACKERS.set(this)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This line is critical for LWJGL's interoperation with GLFW's
|
// This line is critical for LWJGL's interoperation with GLFW's
|
||||||
@ -161,13 +165,11 @@ class GLStateTracker(val client: StarboundClient) {
|
|||||||
var gcHits = 0L
|
var gcHits = 0L
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private val cleaner = Cleaner.create(object : ThreadFactory {
|
private val cleaner = Cleaner.create { r ->
|
||||||
override fun newThread(r: Runnable): Thread {
|
val thread = Thread(r, "OpenGL Object Cleaner for ${this@GLStateTracker}")
|
||||||
val thread = Thread(r, "OpenGL Object Cleaner@" + System.identityHashCode(this))
|
thread.priority = 2
|
||||||
thread.priority = 2
|
thread
|
||||||
return thread
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable {
|
fun registerCleanable(ref: Any, fn: (Int) -> Unit, nativeRef: Int): Cleaner.Cleanable {
|
||||||
val cleanable = cleaner.register(ref) {
|
val cleanable = cleaner.register(ref) {
|
||||||
@ -433,25 +435,48 @@ class GLStateTracker(val client: StarboundClient) {
|
|||||||
fun newVAO() = VertexArrayObject(this)
|
fun newVAO() = VertexArrayObject(this)
|
||||||
fun newTexture(name: String = "<unknown>") = GLTexture2D(this, name)
|
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"
|
private val missingTexturePath = "/assetmissing.png"
|
||||||
|
|
||||||
fun loadTexture(path: String): GLTexture2D {
|
fun loadTexture(path: String): GLTexture2D {
|
||||||
ensureSameThread()
|
ensureSameThread()
|
||||||
|
|
||||||
if (missingTexture == null) {
|
return named2DTextures.get(path) {
|
||||||
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) {
|
|
||||||
if (!client.starbound.exists(path)) {
|
if (!client.starbound.exists(path)) {
|
||||||
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
LOGGER.error("Texture {} is missing! Falling back to {}", path, missingTexturePath)
|
||||||
missingTexture!!
|
missingTexture
|
||||||
} else {
|
} else {
|
||||||
newTexture(path).upload(client.starbound.imageData(path)).generateMips().also {
|
newTexture(path).upload(client.starbound.imageData(path)).generateMips().also {
|
||||||
it.textureMinFilter = GL_NEAREST
|
it.textureMinFilter = GL_NEAREST
|
||||||
|
@ -30,6 +30,10 @@ private class GLTexturePropertyTracker(private val flag: Int, private var value:
|
|||||||
|
|
||||||
@Suppress("SameParameterValue")
|
@Suppress("SameParameterValue")
|
||||||
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : AutoCloseable {
|
class GLTexture2D(val state: GLStateTracker, val name: String = "<unknown>") : AutoCloseable {
|
||||||
|
init {
|
||||||
|
state.ensureSameThread()
|
||||||
|
}
|
||||||
|
|
||||||
val pointer = glGenTextures()
|
val pointer = glGenTextures()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -1038,7 +1038,7 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
|
|||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
|
||||||
private val CLEANER: Cleaner = Cleaner.create {
|
private val CLEANER: Cleaner = Cleaner.create {
|
||||||
val thread = Thread(it, "Lua cleaner")
|
val thread = Thread(it, "Lua State Cleaner")
|
||||||
thread.priority = 1
|
thread.priority = 1
|
||||||
thread
|
thread
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user