Use weak caches for custom textures
This commit is contained in:
@@ -23,6 +23,8 @@ import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.SBData
|
||||
import moe.nea.firmament.util.ScreenUtil
|
||||
import moe.nea.firmament.util.SkyblockId
|
||||
import moe.nea.firmament.util.collections.InstanceList
|
||||
import moe.nea.firmament.util.collections.WeakCache
|
||||
|
||||
|
||||
fun firmamentCommand() = literal("firmament") {
|
||||
@@ -229,6 +231,18 @@ fun firmamentCommand() = literal("firmament") {
|
||||
}
|
||||
}
|
||||
}
|
||||
thenLiteral("caches") {
|
||||
thenExecute {
|
||||
source.sendFeedback(Text.literal("Caches:"))
|
||||
WeakCache.allInstances.getAll().forEach {
|
||||
source.sendFeedback(Text.literal(" - ${it.name}: ${it.size}"))
|
||||
}
|
||||
source.sendFeedback(Text.translatable("Instance lists:"))
|
||||
InstanceList.allInstances.getAll().forEach {
|
||||
source.sendFeedback(Text.literal(" - ${it.name}: ${it.size}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
thenLiteral("mixins") {
|
||||
thenExecute {
|
||||
source.sendFeedback(Text.translatable("firmament.mixins.start"))
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
|
||||
|
||||
package moe.nea.firmament.events
|
||||
|
||||
import java.util.*
|
||||
import java.util.Optional
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import net.minecraft.client.render.model.BakedModel
|
||||
import net.minecraft.client.render.model.BakedModelManager
|
||||
import net.minecraft.client.util.ModelIdentifier
|
||||
import net.minecraft.item.ItemStack
|
||||
import moe.nea.firmament.util.collections.WeakCache
|
||||
|
||||
data class CustomItemModelEvent(
|
||||
val itemStack: ItemStack,
|
||||
var overrideModel: ModelIdentifier? = null,
|
||||
) : FirmamentEvent() {
|
||||
companion object : FirmamentEventBus<CustomItemModelEvent>() {
|
||||
private val cache = IdentityHashMap<ItemStack?, Any>()
|
||||
private val sentinelNull = Object()
|
||||
|
||||
fun clearCache() {
|
||||
cache.clear()
|
||||
val cache =
|
||||
WeakCache.memoize<ItemStack, BakedModelManager, Optional<BakedModel>>("CustomItemModels") { stack, models ->
|
||||
val modelId = getModelIdentifier(stack) ?: return@memoize Optional.empty()
|
||||
val bakedModel = models.getModel(modelId)
|
||||
if (bakedModel === models.missingModel) return@memoize Optional.empty()
|
||||
Optional.of(bakedModel)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -29,15 +30,7 @@ data class CustomItemModelEvent(
|
||||
@JvmStatic
|
||||
fun getModel(itemStack: ItemStack?, thing: BakedModelManager): BakedModel? {
|
||||
if (itemStack == null) return null
|
||||
val cachedValue = cache.getOrPut(itemStack) {
|
||||
val modelId = getModelIdentifier(itemStack) ?: return@getOrPut sentinelNull
|
||||
val bakedModel = thing.getModel(modelId)
|
||||
if (bakedModel === thing.missingModel) return@getOrPut sentinelNull
|
||||
bakedModel
|
||||
}
|
||||
if (cachedValue === sentinelNull)
|
||||
return null
|
||||
return cachedValue as BakedModel
|
||||
return cache.invoke(itemStack, thing).getOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,35 @@
|
||||
|
||||
package moe.nea.firmament.events
|
||||
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Executor
|
||||
import net.minecraft.resource.ReloadableResourceManagerImpl
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.resource.ResourceReloader
|
||||
import net.minecraft.util.profiler.Profiler
|
||||
|
||||
data class FinalizeResourceManagerEvent(
|
||||
val resourceManager: ReloadableResourceManagerImpl,
|
||||
) : FirmamentEvent() {
|
||||
companion object : FirmamentEventBus<FinalizeResourceManagerEvent>()
|
||||
|
||||
inline fun registerOnApply(name: String, crossinline function: () -> Unit) {
|
||||
resourceManager.registerReloader(object : ResourceReloader {
|
||||
override fun reload(
|
||||
synchronizer: ResourceReloader.Synchronizer,
|
||||
manager: ResourceManager?,
|
||||
prepareProfiler: Profiler?,
|
||||
applyProfiler: Profiler?,
|
||||
prepareExecutor: Executor?,
|
||||
applyExecutor: Executor
|
||||
): CompletableFuture<Void> {
|
||||
return CompletableFuture.completedFuture(Unit)
|
||||
.thenCompose(synchronizer::whenPrepared)
|
||||
.thenAcceptAsync({ function() }, applyExecutor)
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return name
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,15 @@ package moe.nea.firmament.features.debug
|
||||
|
||||
import net.minecraft.text.Text
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.collections.InstanceList
|
||||
|
||||
class DebugLogger(val tag: String) {
|
||||
companion object {
|
||||
val allInstances = InstanceList<DebugLogger>("DebugLogger")
|
||||
}
|
||||
init {
|
||||
allInstances.add(this)
|
||||
}
|
||||
fun isEnabled() = DeveloperFeatures.isEnabled // TODO: allow filtering by tag
|
||||
fun log(text: () -> String) {
|
||||
if (!isEnabled()) return
|
||||
|
||||
@@ -14,7 +14,7 @@ import moe.nea.firmament.events.WorldRenderLastEvent
|
||||
import moe.nea.firmament.events.subscription.SubscriptionOwner
|
||||
import moe.nea.firmament.features.FirmamentFeature
|
||||
import moe.nea.firmament.util.TimeMark
|
||||
import moe.nea.firmament.util.mutableMapWithMaxSize
|
||||
import moe.nea.firmament.util.collections.mutableMapWithMaxSize
|
||||
import moe.nea.firmament.util.render.RenderInWorldContext.Companion.renderInWorld
|
||||
|
||||
object NearbyBurrowsSolver : SubscriptionOwner {
|
||||
|
||||
@@ -14,9 +14,8 @@ import moe.nea.firmament.features.FirmamentFeature
|
||||
import moe.nea.firmament.gui.config.ManagedConfig
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.item.loreAccordingToNbt
|
||||
import moe.nea.firmament.util.lastNotNullOfOrNull
|
||||
import moe.nea.firmament.util.memoize
|
||||
import moe.nea.firmament.util.memoizeIdentity
|
||||
import moe.nea.firmament.util.collections.lastNotNullOfOrNull
|
||||
import moe.nea.firmament.util.collections.memoizeIdentity
|
||||
import moe.nea.firmament.util.unformattedString
|
||||
|
||||
object ItemRarityCosmetics : FirmamentFeature {
|
||||
|
||||
@@ -17,7 +17,7 @@ import moe.nea.firmament.repo.ItemCache.asItemStack
|
||||
import moe.nea.firmament.repo.RepoManager
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.SkyblockId
|
||||
import moe.nea.firmament.util.memoize
|
||||
import moe.nea.firmament.util.collections.memoize
|
||||
|
||||
@Serializable
|
||||
data class InventoryButton(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
|
||||
@file:UseSerializers(IdentifierSerializer::class)
|
||||
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import java.util.Optional
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import net.minecraft.item.ArmorMaterial
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.resource.ResourceManager
|
||||
@@ -20,8 +21,7 @@ import moe.nea.firmament.events.subscription.SubscriptionOwner
|
||||
import moe.nea.firmament.features.FirmamentFeature
|
||||
import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger
|
||||
import moe.nea.firmament.util.IdentifierSerializer
|
||||
import moe.nea.firmament.util.IdentityCharacteristics
|
||||
import moe.nea.firmament.util.computeNullableFunction
|
||||
import moe.nea.firmament.util.collections.WeakCache
|
||||
import moe.nea.firmament.util.skyBlockId
|
||||
|
||||
object CustomGlobalArmorOverrides : SubscriptionOwner {
|
||||
@@ -59,21 +59,21 @@ object CustomGlobalArmorOverrides : SubscriptionOwner {
|
||||
override val delegateFeature: FirmamentFeature
|
||||
get() = CustomSkyBlockTextures
|
||||
|
||||
val overrideCache = mutableMapOf<IdentityCharacteristics<ItemStack>, Any>()
|
||||
val overrideCache = WeakCache.memoize<ItemStack, Optional<List<ArmorMaterial.Layer>>>("ArmorOverrides") { stack ->
|
||||
val id = stack.skyBlockId ?: return@memoize Optional.empty()
|
||||
val override = overrides[id.neuItem] ?: return@memoize Optional.empty()
|
||||
for (suboverride in override.overrides) {
|
||||
if (suboverride.predicate.test(stack)) {
|
||||
return@memoize Optional.of(suboverride.bakedLayers)
|
||||
}
|
||||
}
|
||||
return@memoize Optional.of(override.bakedLayers)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun overrideArmor(stack: ItemStack): List<ArmorMaterial.Layer>? {
|
||||
if (!CustomSkyBlockTextures.TConfig.enableArmorOverrides) return null
|
||||
return overrideCache.computeNullableFunction(IdentityCharacteristics(stack)) {
|
||||
val id = stack.skyBlockId ?: return@computeNullableFunction null
|
||||
val override = overrides[id.neuItem] ?: return@computeNullableFunction null
|
||||
for (suboverride in override.overrides) {
|
||||
if (suboverride.predicate.test(stack)) {
|
||||
return@computeNullableFunction suboverride.bakedLayers
|
||||
}
|
||||
}
|
||||
return@computeNullableFunction override.bakedLayers
|
||||
}
|
||||
return overrideCache.invoke(stack).getOrNull()
|
||||
}
|
||||
|
||||
var overrides: Map<String, ArmorOverride> = mapOf()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
@file:UseSerializers(IdentifierSerializer::class, CustomModelOverrideParser.FirmamentRootPredicateSerializer::class)
|
||||
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
|
||||
@@ -28,9 +28,9 @@ import moe.nea.firmament.events.ScreenChangeEvent
|
||||
import moe.nea.firmament.events.subscription.SubscriptionOwner
|
||||
import moe.nea.firmament.features.FirmamentFeature
|
||||
import moe.nea.firmament.util.IdentifierSerializer
|
||||
import moe.nea.firmament.util.IdentityCharacteristics
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.computeNullableFunction
|
||||
import moe.nea.firmament.util.collections.WeakCache
|
||||
import moe.nea.firmament.util.intoOptional
|
||||
import moe.nea.firmament.util.json.SingletonSerializableList
|
||||
import moe.nea.firmament.util.runNull
|
||||
|
||||
@@ -140,7 +140,17 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
|
||||
.filterTo(mutableSetOf()) { it.screenFilter.title.matches(newTitle) }
|
||||
}
|
||||
|
||||
val overrideCache = mutableMapOf<IdentityCharacteristics<ItemStack>, Any>()
|
||||
val overrideCache = WeakCache.memoize<ItemStack, ItemModels, Optional<BakedModel>>("CustomGlobalTextureModelOverrides") { stack, models ->
|
||||
matchingOverrides
|
||||
.firstNotNullOfOrNull {
|
||||
it.overrides
|
||||
.asSequence()
|
||||
.filter { it.predicate.test(stack) }
|
||||
.map { models.modelManager.getModel(ModelIdentifier(it.model, "inventory")) }
|
||||
.firstOrNull()
|
||||
}
|
||||
.intoOptional()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun replaceGlobalModel(
|
||||
@@ -148,19 +158,8 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
|
||||
stack: ItemStack,
|
||||
cir: CallbackInfoReturnable<BakedModel>
|
||||
) {
|
||||
val value = overrideCache.computeNullableFunction(IdentityCharacteristics(stack)) {
|
||||
for (guiClassOverride in matchingOverrides) {
|
||||
for (override in guiClassOverride.overrides) {
|
||||
if (override.predicate.test(stack)) {
|
||||
return@computeNullableFunction models.modelManager.getModel(
|
||||
ModelIdentifier(override.model, "inventory"))
|
||||
}
|
||||
}
|
||||
}
|
||||
null
|
||||
}
|
||||
if (value != null)
|
||||
cir.returnValue = value
|
||||
overrideCache.invoke(stack, models)
|
||||
.ifPresent(cir::setReturnValue)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture
|
||||
import com.mojang.authlib.properties.Property
|
||||
import java.util.Optional
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import net.minecraft.block.SkullBlock
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.render.RenderLayer
|
||||
@@ -12,10 +14,11 @@ import net.minecraft.util.Identifier
|
||||
import moe.nea.firmament.annotations.Subscribe
|
||||
import moe.nea.firmament.events.BakeExtraModelsEvent
|
||||
import moe.nea.firmament.events.CustomItemModelEvent
|
||||
import moe.nea.firmament.events.FinalizeResourceManagerEvent
|
||||
import moe.nea.firmament.events.TickEvent
|
||||
import moe.nea.firmament.features.FirmamentFeature
|
||||
import moe.nea.firmament.gui.config.ManagedConfig
|
||||
import moe.nea.firmament.util.IdentityCharacteristics
|
||||
import moe.nea.firmament.util.collections.WeakCache
|
||||
import moe.nea.firmament.util.item.decodeProfileTextureProperty
|
||||
import moe.nea.firmament.util.skyBlockId
|
||||
|
||||
@@ -26,7 +29,8 @@ object CustomSkyBlockTextures : FirmamentFeature {
|
||||
object TConfig : ManagedConfig(identifier) {
|
||||
val enabled by toggle("enabled") { true }
|
||||
val skullsEnabled by toggle("skulls-enabled") { true }
|
||||
val cacheDuration by integer("cache-duration", 0, 20) { 1 }
|
||||
val cacheForever by toggle("cache-forever") { true }
|
||||
val cacheDuration by integer("cache-duration", 0, 100) { 1 }
|
||||
val enableModelOverrides by toggle("model-overrides") { true }
|
||||
val enableArmorOverrides by toggle("armor-overrides") { true }
|
||||
val enableBlockOverrides by toggle("block-overrides") { true }
|
||||
@@ -36,14 +40,31 @@ object CustomSkyBlockTextures : FirmamentFeature {
|
||||
override val config: ManagedConfig
|
||||
get() = TConfig
|
||||
|
||||
val allItemCaches by lazy {
|
||||
listOf(
|
||||
CustomItemModelEvent.cache.cache,
|
||||
skullTextureCache.cache,
|
||||
CustomGlobalTextures.overrideCache.cache,
|
||||
CustomGlobalArmorOverrides.overrideCache.cache
|
||||
)
|
||||
}
|
||||
|
||||
fun clearAllCaches() {
|
||||
allItemCaches.forEach(WeakCache<*, *, *>::clear)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onTick(it: TickEvent) {
|
||||
if (TConfig.cacheForever) return
|
||||
if (TConfig.cacheDuration < 1 || it.tickCount % TConfig.cacheDuration == 0) {
|
||||
// TODO: unify all of those caches somehow
|
||||
CustomItemModelEvent.clearCache()
|
||||
skullTextureCache.clear()
|
||||
CustomGlobalTextures.overrideCache.clear()
|
||||
CustomGlobalArmorOverrides.overrideCache.clear()
|
||||
clearAllCaches()
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onStart(event: FinalizeResourceManagerEvent) {
|
||||
event.registerOnApply("Clear firmament CIT caches") {
|
||||
clearAllCaches()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +95,14 @@ object CustomSkyBlockTextures : FirmamentFeature {
|
||||
it.overrideModel = ModelIdentifier.ofInventoryVariant(Identifier.of("firmskyblock", id.identifier.path))
|
||||
}
|
||||
|
||||
private val skullTextureCache = mutableMapOf<IdentityCharacteristics<ProfileComponent>, Any>()
|
||||
private val sentinelPresentInvalid = Object()
|
||||
private val skullTextureCache =
|
||||
WeakCache.memoize<ProfileComponent, Optional<Identifier>>("SkullTextureCache") { component ->
|
||||
val id = getSkullTexture(component) ?: return@memoize Optional.empty()
|
||||
if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) {
|
||||
return@memoize Optional.empty()
|
||||
}
|
||||
return@memoize Optional.of(id)
|
||||
}
|
||||
|
||||
private val mcUrlRegex = "https?://textures.minecraft.net/texture/([a-fA-F0-9]+)".toRegex()
|
||||
|
||||
@@ -100,16 +127,8 @@ object CustomSkyBlockTextures : FirmamentFeature {
|
||||
if (type != SkullBlock.Type.PLAYER) return
|
||||
if (!TConfig.skullsEnabled) return
|
||||
if (component == null) return
|
||||
val ic = IdentityCharacteristics(component)
|
||||
|
||||
val n = skullTextureCache.getOrPut(ic) {
|
||||
val id = getSkullTexture(component) ?: return@getOrPut sentinelPresentInvalid
|
||||
if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) {
|
||||
return@getOrPut sentinelPresentInvalid
|
||||
}
|
||||
return@getOrPut id
|
||||
}
|
||||
if (n === sentinelPresentInvalid) return
|
||||
cir.returnValue = RenderLayer.getEntityTranslucent(n as Identifier)
|
||||
val n = skullTextureCache.invoke(component).getOrNull() ?: return
|
||||
cir.returnValue = RenderLayer.getEntityTranslucent(n)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ object SBItemEntryDefinition : EntryDefinition<SBItemStack> {
|
||||
}
|
||||
|
||||
override fun asFormattedText(entry: EntryStack<SBItemStack>, value: SBItemStack): Text {
|
||||
return VanillaEntryTypes.ITEM.definition.asFormattedText(entry.asItemEntry(), value.asItemStack())
|
||||
return VanillaEntryTypes.ITEM.definition.asFormattedText(entry.asItemEntry(), value.asImmutableItemStack())
|
||||
}
|
||||
|
||||
override fun hash(entry: EntryStack<SBItemStack>, value: SBItemStack, context: ComparisonContext): Long {
|
||||
|
||||
5
src/main/kotlin/util/Optionalutil.kt
Normal file
5
src/main/kotlin/util/Optionalutil.kt
Normal file
@@ -0,0 +1,5 @@
|
||||
package moe.nea.firmament.util
|
||||
|
||||
import java.util.Optional
|
||||
|
||||
fun <T : Any> T?.intoOptional(): Optional<T> = Optional.ofNullable(this)
|
||||
57
src/main/kotlin/util/collections/InstanceList.kt
Normal file
57
src/main/kotlin/util/collections/InstanceList.kt
Normal file
@@ -0,0 +1,57 @@
|
||||
package moe.nea.firmament.util.collections
|
||||
|
||||
import java.lang.ref.ReferenceQueue
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class InstanceList<T : Any>(val name: String) {
|
||||
val queue = object : ReferenceQueue<T>() {}
|
||||
val set = mutableSetOf<Ref>()
|
||||
|
||||
val size: Int
|
||||
get() {
|
||||
clearOldReferences()
|
||||
return set.size
|
||||
}
|
||||
|
||||
fun clearOldReferences() {
|
||||
while (true) {
|
||||
val reference = queue.poll() ?: break
|
||||
set.remove(reference)
|
||||
}
|
||||
}
|
||||
|
||||
fun getAll(): List<T> {
|
||||
clearOldReferences()
|
||||
return set.mapNotNull { it.get() }
|
||||
}
|
||||
|
||||
fun add(t: T) {
|
||||
set.add(Ref(t))
|
||||
}
|
||||
|
||||
init {
|
||||
if (init)
|
||||
allInstances.add(this)
|
||||
}
|
||||
|
||||
inner class Ref(referent: T) : WeakReference<T>(referent) {
|
||||
val hashCode = System.identityHashCode(referent)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is InstanceList<*>.Ref && hashCode == other.hashCode && get() === other.get()
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return hashCode
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var init = false
|
||||
val allInstances = InstanceList<InstanceList<*>>("InstanceLists")
|
||||
|
||||
init {
|
||||
init = true
|
||||
allInstances.add(allInstances)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
|
||||
package moe.nea.firmament.util
|
||||
package moe.nea.firmament.util.collections
|
||||
|
||||
import moe.nea.firmament.util.IdentityCharacteristics
|
||||
|
||||
fun <K, V> mutableMapWithMaxSize(maxSize: Int): MutableMap<K, V> = object : LinkedHashMap<K, V>() {
|
||||
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<K, V>): Boolean {
|
||||
110
src/main/kotlin/util/collections/WeakCache.kt
Normal file
110
src/main/kotlin/util/collections/WeakCache.kt
Normal file
@@ -0,0 +1,110 @@
|
||||
package moe.nea.firmament.util.collections
|
||||
|
||||
import java.lang.ref.ReferenceQueue
|
||||
import java.lang.ref.WeakReference
|
||||
import moe.nea.firmament.features.debug.DebugLogger
|
||||
|
||||
/**
|
||||
* Cache class that uses [WeakReferences][WeakReference] to only cache values while there is still a life reference to
|
||||
* the key. Each key can have additional extra data that is used to look up values. That extra data is not required to
|
||||
* be a life reference. The main Key is compared using strict reference equality. This map is not synchronized.
|
||||
*/
|
||||
class WeakCache<Key : Any, ExtraKey : Any, Value : Any>(val name: String) {
|
||||
private val queue = object : ReferenceQueue<Key>() {}
|
||||
private val map = mutableMapOf<Ref, Value>()
|
||||
|
||||
val size: Int
|
||||
get() {
|
||||
clearOldReferences()
|
||||
return map.size
|
||||
}
|
||||
|
||||
fun clearOldReferences() {
|
||||
var successCount = 0
|
||||
var totalCount = 0
|
||||
while (true) {
|
||||
val reference = queue.poll() ?: break
|
||||
totalCount++
|
||||
if (map.remove(reference) != null)
|
||||
successCount++
|
||||
}
|
||||
if (totalCount > 0)
|
||||
logger.log { "Cleared $successCount/$totalCount references from queue" }
|
||||
}
|
||||
|
||||
fun get(key: Key, extraData: ExtraKey): Value? {
|
||||
clearOldReferences()
|
||||
return map[Ref(key, extraData)]
|
||||
}
|
||||
|
||||
fun put(key: Key, extraData: ExtraKey, value: Value) {
|
||||
clearOldReferences()
|
||||
map[Ref(key, extraData)] = value
|
||||
}
|
||||
|
||||
fun getOrPut(key: Key, extraData: ExtraKey, value: (Key, ExtraKey) -> Value): Value {
|
||||
clearOldReferences()
|
||||
return map.getOrPut(Ref(key, extraData)) { value(key, extraData) }
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
map.clear()
|
||||
}
|
||||
|
||||
init {
|
||||
allInstances.add(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val allInstances = InstanceList<WeakCache<*, *, *>>("WeakCaches")
|
||||
private val logger = DebugLogger("WeakCache")
|
||||
fun <Key : Any, Value : Any> memoize(name: String, function: (Key) -> Value):
|
||||
CacheFunction.NoExtraData<Key, Value> {
|
||||
return CacheFunction.NoExtraData(WeakCache(name), function)
|
||||
}
|
||||
|
||||
fun <Key : Any, ExtraKey : Any, Value : Any> memoize(name: String, function: (Key, ExtraKey) -> Value):
|
||||
CacheFunction.WithExtraData<Key, ExtraKey, Value> {
|
||||
return CacheFunction.WithExtraData(WeakCache(name), function)
|
||||
}
|
||||
}
|
||||
|
||||
inner class Ref(
|
||||
weakInstance: Key,
|
||||
val extraData: ExtraKey,
|
||||
) : WeakReference<Key>(weakInstance, queue) {
|
||||
val hashCode = System.identityHashCode(weakInstance) * 31 + extraData.hashCode()
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is WeakCache<*, *, *>.Ref) return false
|
||||
return other.hashCode == this.hashCode
|
||||
&& other.get() === this.get()
|
||||
&& other.extraData == this.extraData
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return hashCode
|
||||
}
|
||||
}
|
||||
|
||||
interface CacheFunction {
|
||||
val cache: WeakCache<*, *, *>
|
||||
|
||||
data class NoExtraData<Key : Any, Value : Any>(
|
||||
override val cache: WeakCache<Key, Unit, Value>,
|
||||
val wrapped: (Key) -> Value,
|
||||
) : CacheFunction, (Key) -> Value {
|
||||
override fun invoke(p1: Key): Value {
|
||||
return cache.getOrPut(p1, Unit, { a, _ -> wrapped(a) })
|
||||
}
|
||||
}
|
||||
|
||||
data class WithExtraData<Key : Any, ExtraKey : Any, Value : Any>(
|
||||
override val cache: WeakCache<Key, ExtraKey, Value>,
|
||||
val wrapped: (Key, ExtraKey) -> Value,
|
||||
) : CacheFunction, (Key, ExtraKey) -> Value {
|
||||
override fun invoke(p1: Key, p2: ExtraKey): Value {
|
||||
return cache.getOrPut(p1, p2, wrapped)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
package moe.nea.firmament.util
|
||||
package moe.nea.firmament.util.collections
|
||||
|
||||
fun <T, R> List<T>.lastNotNullOfOrNull(func: (T) -> R?): R? {
|
||||
for (i in indices.reversed()) {
|
||||
@@ -173,6 +173,7 @@
|
||||
"firmament.config.custom-skyblock-textures.block-overrides": "Enable Block re-modelling",
|
||||
"firmament.config.custom-skyblock-textures.enabled": "Enable Custom Item Textures",
|
||||
"firmament.config.custom-skyblock-textures.skulls-enabled": "Enable Custom Placed Skull Textures",
|
||||
"firmament.config.custom-skyblock-textures.cache-forever": "Disable cache clearing",
|
||||
"firmament.config.fixes": "Fixes",
|
||||
"firmament.config.fixes.player-skins": "Fix unsigned Player Skins",
|
||||
"firmament.config.power-user.show-item-id": "Show SkyBlock Ids",
|
||||
|
||||
Reference in New Issue
Block a user