feat: Improve performance of slime particles and armour with texture packs

This commit is contained in:
Linnea Gräf
2025-04-01 13:40:31 +02:00
parent 735b3b704f
commit 65038b7b37
5 changed files with 134 additions and 91 deletions

View File

@@ -1,10 +1,13 @@
package moe.nea.firmament.events package moe.nea.firmament.events
import java.util.Objects
import java.util.Optional import java.util.Optional
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
import net.minecraft.component.DataComponentTypes
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import moe.nea.firmament.util.collections.WeakCache import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.collections.WeakCache.CacheFunction
import moe.nea.firmament.util.mc.IntrospectableItemModelManager import moe.nea.firmament.util.mc.IntrospectableItemModelManager
// TODO: assert an order on these events // TODO: assert an order on these events
@@ -14,7 +17,36 @@ data class CustomItemModelEvent(
var overrideModel: Identifier? = null, var overrideModel: Identifier? = null,
) : FirmamentEvent() { ) : FirmamentEvent() {
companion object : FirmamentEventBus<CustomItemModelEvent>() { companion object : FirmamentEventBus<CustomItemModelEvent>() {
val cache = WeakCache.memoize("ItemModelIdentifier", ::getModelIdentifier0) val weakCache =
object : WeakCache<ItemStack, IntrospectableItemModelManager, Optional<Identifier>>("ItemModelIdentifier") {
override fun mkRef(
key: ItemStack,
extraData: IntrospectableItemModelManager
): WeakCache<ItemStack, IntrospectableItemModelManager, Optional<Identifier>>.Ref {
return IRef(key, extraData)
}
inner class IRef(weakInstance: ItemStack, data: IntrospectableItemModelManager) :
Ref(weakInstance, data) {
override fun shouldBeEvicted(): Boolean = false
val isSimpleStack = weakInstance.componentChanges.isEmpty || (weakInstance.componentChanges.size() == 1 && weakInstance.get(
DataComponentTypes.CUSTOM_DATA)?.isEmpty == true)
val item = weakInstance.item
override fun hashCode(): Int {
if (isSimpleStack)
return Objects.hash(item, extraData)
return super.hashCode()
}
override fun equals(other: Any?): Boolean {
if (other is IRef && isSimpleStack) {
return other.isSimpleStack && item == other.item
}
return super.equals(other)
}
}
}
val cache = CacheFunction.WithExtraData(weakCache, ::getModelIdentifier0)
@JvmStatic @JvmStatic
fun getModelIdentifier(itemStack: ItemStack?, itemModelManager: IntrospectableItemModelManager): Identifier? { fun getModelIdentifier(itemStack: ItemStack?, itemModelManager: IntrospectableItemModelManager): Identifier? {

View File

@@ -10,6 +10,7 @@ class DebugLogger(val tag: String) {
companion object { companion object {
val allInstances = InstanceList<DebugLogger>("DebugLogger") val allInstances = InstanceList<DebugLogger>("DebugLogger")
} }
object EnabledLogs : DataHolder<MutableSet<String>>(serializer(), "DebugLogs", ::mutableSetOf) object EnabledLogs : DataHolder<MutableSet<String>>(serializer(), "DebugLogs", ::mutableSetOf)
init { init {
@@ -17,6 +18,7 @@ class DebugLogger(val tag: String) {
} }
fun isEnabled() = DeveloperFeatures.isEnabled && EnabledLogs.data.contains(tag) fun isEnabled() = DeveloperFeatures.isEnabled && EnabledLogs.data.contains(tag)
fun log(text: String) = log { text }
fun log(text: () -> String) { fun log(text: () -> String) {
if (!isEnabled()) return if (!isEnabled()) return
MC.sendChat(Text.literal(text())) MC.sendChat(Text.literal(text()))

View File

@@ -9,102 +9,108 @@ import moe.nea.firmament.features.debug.DebugLogger
* the key. Each key can have additional extra data that is used to look up values. That extra data is not required 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. * 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) { open class WeakCache<Key : Any, ExtraKey : Any, Value : Any>(val name: String) {
private val queue = object : ReferenceQueue<Key>() {} private val queue = object : ReferenceQueue<Key>() {}
private val map = mutableMapOf<Ref, Value>() private val map = mutableMapOf<Ref, Value>()
val size: Int val size: Int
get() { get() {
clearOldReferences() clearOldReferences()
return map.size return map.size
} }
fun clearOldReferences() { fun clearOldReferences() {
var successCount = 0 var successCount = 0
var totalCount = 0 var totalCount = 0
while (true) { while (true) {
val reference = queue.poll() ?: break val reference = queue.poll() as WeakCache<*, *, *>.Ref? ?: break
totalCount++ totalCount++
if (map.remove(reference) != null) if (reference.shouldBeEvicted() && map.remove(reference) != null)
successCount++ successCount++
} }
if (totalCount > 0) if (totalCount > 0)
logger.log { "Cleared $successCount/$totalCount references from queue" } logger.log("Cleared $successCount/$totalCount references from queue")
} }
fun get(key: Key, extraData: ExtraKey): Value? { open fun mkRef(key: Key, extraData: ExtraKey): Ref {
clearOldReferences() return Ref(key, extraData)
return map[Ref(key, extraData)] }
}
fun put(key: Key, extraData: ExtraKey, value: Value) { fun get(key: Key, extraData: ExtraKey): Value? {
clearOldReferences() clearOldReferences()
map[Ref(key, extraData)] = value return map[mkRef(key, extraData)]
} }
fun getOrPut(key: Key, extraData: ExtraKey, value: (Key, ExtraKey) -> Value): Value { fun put(key: Key, extraData: ExtraKey, value: Value) {
clearOldReferences() clearOldReferences()
return map.getOrPut(Ref(key, extraData)) { value(key, extraData) } map[mkRef(key, extraData)] = value
} }
fun clear() { fun getOrPut(key: Key, extraData: ExtraKey, value: (Key, ExtraKey) -> Value): Value {
map.clear() clearOldReferences()
} return map.getOrPut(mkRef(key, extraData)) { value(key, extraData) }
}
init { fun clear() {
allInstances.add(this) map.clear()
} }
companion object { init {
val allInstances = InstanceList<WeakCache<*, *, *>>("WeakCaches") allInstances.add(this)
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): companion object {
CacheFunction.WithExtraData<Key, ExtraKey, Value> { val allInstances = InstanceList<WeakCache<*, *, *>>("WeakCaches")
return CacheFunction.WithExtraData(WeakCache(name), function) 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)
}
inner class Ref( fun <Key : Any, ExtraKey : Any, Value : Any> dontMemoize(name: String, function: (Key, ExtraKey) -> Value) = function
weakInstance: Key, fun <Key : Any, ExtraKey : Any, Value : Any> memoize(name: String, function: (Key, ExtraKey) -> Value):
val extraData: ExtraKey, CacheFunction.WithExtraData<Key, ExtraKey, Value> {
) : WeakReference<Key>(weakInstance, queue) { return CacheFunction.WithExtraData(WeakCache(name), function)
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 { open inner class Ref(
return hashCode weakInstance: Key,
} val extraData: ExtraKey,
} ) : WeakReference<Key>(weakInstance, queue) {
open fun shouldBeEvicted() = true
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
}
interface CacheFunction { override fun hashCode(): Int {
val cache: WeakCache<*, *, *> return hashCode
}
}
data class NoExtraData<Key : Any, Value : Any>( interface CacheFunction {
override val cache: WeakCache<Key, Unit, Value>, val cache: WeakCache<*, *, *>
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>( data class NoExtraData<Key : Any, Value : Any>(
override val cache: WeakCache<Key, ExtraKey, Value>, override val cache: WeakCache<Key, Unit, Value>,
val wrapped: (Key, ExtraKey) -> Value, val wrapped: (Key) -> Value,
) : CacheFunction, (Key, ExtraKey) -> Value { ) : CacheFunction, (Key) -> Value {
override fun invoke(p1: Key, p2: ExtraKey): Value { override fun invoke(p1: Key): Value {
return cache.getOrPut(p1, p2, wrapped) 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)
}
}
}
} }

View File

@@ -85,16 +85,19 @@ object CustomGlobalArmorOverrides {
) )
} }
// TODO: BipedEntityRenderer.getEquippedStack create copies of itemstacks for rendering. This means this cache is essentially useless
// If i figure out how to circumvent this (maybe track the origin of those copied itemstacks in some sort of variable in the itemstack to track back the original instance) i should reenable this cache.
// Then also re add this to the cache clearing function
val overrideCache = val overrideCache =
WeakCache.memoize<ItemStack, EquipmentSlot, Optional<EquippableComponent>>("ArmorOverrides") { stack, slot -> WeakCache.dontMemoize<ItemStack, EquipmentSlot, Optional<EquippableComponent>>("ArmorOverrides") { stack, slot ->
val id = stack.skyBlockId ?: return@memoize Optional.empty() val id = stack.skyBlockId ?: return@dontMemoize Optional.empty()
val override = overrides[id.neuItem] ?: return@memoize Optional.empty() val override = overrides[id.neuItem] ?: return@dontMemoize Optional.empty()
for (suboverride in override.overrides) { for (suboverride in override.overrides) {
if (suboverride.predicate.test(stack)) { if (suboverride.predicate.test(stack)) {
return@memoize resolveComponent(slot, suboverride.modelIdentifier).intoOptional() return@dontMemoize resolveComponent(slot, suboverride.modelIdentifier).intoOptional()
} }
} }
return@memoize resolveComponent(slot, override.modelIdentifier).intoOptional() return@dontMemoize resolveComponent(slot, override.modelIdentifier).intoOptional()
} }
var overrides: Map<String, ArmorOverride> = mapOf() var overrides: Map<String, ArmorOverride> = mapOf()

View File

@@ -45,7 +45,7 @@ object CustomSkyBlockTextures : FirmamentFeature {
listOf( listOf(
skullTextureCache.cache, skullTextureCache.cache,
CustomItemModelEvent.cache.cache, CustomItemModelEvent.cache.cache,
CustomGlobalArmorOverrides.overrideCache.cache // TODO: re-add this once i figure out how to make the cache useful again CustomGlobalArmorOverrides.overrideCache.cache
) )
} }