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
import java.util.Objects
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
import net.minecraft.component.DataComponentTypes
import net.minecraft.item.ItemStack
import net.minecraft.util.Identifier
import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.collections.WeakCache.CacheFunction
import moe.nea.firmament.util.mc.IntrospectableItemModelManager
// TODO: assert an order on these events
@@ -14,7 +17,36 @@ data class CustomItemModelEvent(
var overrideModel: Identifier? = null,
) : FirmamentEvent() {
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
fun getModelIdentifier(itemStack: ItemStack?, itemModelManager: IntrospectableItemModelManager): Identifier? {

View File

@@ -10,6 +10,7 @@ class DebugLogger(val tag: String) {
companion object {
val allInstances = InstanceList<DebugLogger>("DebugLogger")
}
object EnabledLogs : DataHolder<MutableSet<String>>(serializer(), "DebugLogs", ::mutableSetOf)
init {
@@ -17,6 +18,7 @@ class DebugLogger(val tag: String) {
}
fun isEnabled() = DeveloperFeatures.isEnabled && EnabledLogs.data.contains(tag)
fun log(text: String) = log { text }
fun log(text: () -> String) {
if (!isEnabled()) return
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
* 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>()
open 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
}
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 clearOldReferences() {
var successCount = 0
var totalCount = 0
while (true) {
val reference = queue.poll() as WeakCache<*, *, *>.Ref? ?: break
totalCount++
if (reference.shouldBeEvicted() && 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)]
}
open fun mkRef(key: Key, extraData: ExtraKey): Ref {
return Ref(key, extraData)
}
fun put(key: Key, extraData: ExtraKey, value: Value) {
clearOldReferences()
map[Ref(key, extraData)] = value
}
fun get(key: Key, extraData: ExtraKey): Value? {
clearOldReferences()
return map[mkRef(key, extraData)]
}
fun getOrPut(key: Key, extraData: ExtraKey, value: (Key, ExtraKey) -> Value): Value {
clearOldReferences()
return map.getOrPut(Ref(key, extraData)) { value(key, extraData) }
}
fun put(key: Key, extraData: ExtraKey, value: Value) {
clearOldReferences()
map[mkRef(key, extraData)] = value
}
fun clear() {
map.clear()
}
fun getOrPut(key: Key, extraData: ExtraKey, value: (Key, ExtraKey) -> Value): Value {
clearOldReferences()
return map.getOrPut(mkRef(key, extraData)) { value(key, extraData) }
}
init {
allInstances.add(this)
}
fun clear() {
map.clear()
}
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)
}
init {
allInstances.add(this)
}
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)
}
}
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)
}
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
}
fun <Key : Any, ExtraKey : Any, Value : Any> dontMemoize(name: String, function: (Key, ExtraKey) -> Value) = 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)
}
}
override fun hashCode(): Int {
return hashCode
}
}
open inner class Ref(
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 {
val cache: WeakCache<*, *, *>
override fun hashCode(): Int {
return hashCode
}
}
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) })
}
}
interface CacheFunction {
val cache: WeakCache<*, *, *>
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)
}
}
}
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)
}
}
}
}

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 =
WeakCache.memoize<ItemStack, EquipmentSlot, Optional<EquippableComponent>>("ArmorOverrides") { stack, slot ->
val id = stack.skyBlockId ?: return@memoize Optional.empty()
val override = overrides[id.neuItem] ?: return@memoize Optional.empty()
WeakCache.dontMemoize<ItemStack, EquipmentSlot, Optional<EquippableComponent>>("ArmorOverrides") { stack, slot ->
val id = stack.skyBlockId ?: return@dontMemoize Optional.empty()
val override = overrides[id.neuItem] ?: return@dontMemoize Optional.empty()
for (suboverride in override.overrides) {
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()

View File

@@ -45,7 +45,7 @@ object CustomSkyBlockTextures : FirmamentFeature {
listOf(
skullTextureCache.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
)
}