diff --git a/common/src/main/java/org/embeddedt/modernfix/duck/ICachedProfileTexture.java b/common/src/main/java/org/embeddedt/modernfix/duck/ICachedProfileTexture.java new file mode 100644 index 00000000..91d10e36 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/duck/ICachedProfileTexture.java @@ -0,0 +1,5 @@ +package org.embeddedt.modernfix.duck; + +public interface ICachedProfileTexture { + public void setCachedHash(String url); +} diff --git a/common/src/main/resources/assets/modernfix/lang/en_us.json b/common/src/main/resources/assets/modernfix/lang/en_us.json index 98bc5ba1..bbe479b5 100644 --- a/common/src/main/resources/assets/modernfix/lang/en_us.json +++ b/common/src/main/resources/assets/modernfix/lang/en_us.json @@ -34,6 +34,7 @@ "modernfix.option.mixin.perf.boost_worker_count": "1.16 only. Removes the hardcoded cap on worker thread count, similarly to what Mojang did in 1.18.", "modernfix.option.mixin.perf.cache_blockstate_cache_arrays": "All versions. Avoids creating fresh copies of enum arrays each time a blockstate cache is initialized. Minor optimization, but easy to do.", "modernfix.option.mixin.perf.cache_model_materials": "All versions. Memoizes the `RenderMaterial` (texture) collection and dependency list that models return instead of requiring them to be recalculated on each request. Helps accelerate the model load/bake process.", + "modernfix.option.mixin.perf.cache_profile_texture_url": "Fabric 1.20+. Avoids pointlessly creating a URL object and speeds up skull block rendering.", "modernfix.option.mixin.perf.cache_strongholds": "All versions. Saves the generated list of stronghold positions with the world, instead of regenerating it on every world load. Saves a little bit of time on 1.16, and quite a bit more on 1.18 and 1.19.", "modernfix.option.mixin.perf.cache_upgraded_structures": "All versions. Many mods ship outdated structure files, which requires the game to upgrade them using DFU every single time they are loaded. This can be quite slow. This patch adds logic to instead save the upgraded version of the structure, and reuse it on the next load. To handle the case that the mod changes a structure file but not the name, the original file's hash is compared against the cached version, and if they do not match the structure will be upgraded again.", "modernfix.option.mixin.perf.compress_biome_container": "1.16 only. Minor optimization borrowed from Hydrogen, which attempts to save space in the biome container when possible. This gets disabled automatically if conflicting mods like BetterEnd or Chocolate are installed.", diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/cache_profile_texture_url/MinecraftProfileTextureMixin.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/cache_profile_texture_url/MinecraftProfileTextureMixin.java new file mode 100644 index 00000000..e144d788 --- /dev/null +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/cache_profile_texture_url/MinecraftProfileTextureMixin.java @@ -0,0 +1,44 @@ +package org.embeddedt.modernfix.fabric.mixin.perf.cache_profile_texture_url; + +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import org.apache.commons.io.FilenameUtils; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.duck.ICachedProfileTexture; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Map; +import java.net.MalformedURLException; +import java.net.URL; + +@Mixin(value = MinecraftProfileTexture.class, remap = false) +@ClientOnlyMixin +public abstract class MinecraftProfileTextureMixin implements ICachedProfileTexture { + /** + * @author Fury_Phoenix + * @see org.embeddedt.modernfix.common.mixin.perf.cache_profile_texture_url.YggdrasilGsonDeserializerMixin#setCachedURL + **/ + + private String modernfix$cachedHash; + + @Inject(method = "(Ljava/lang/String;Ljava/util/Map;)V", at = @At("RETURN"), cancellable = false) + private void cacheHash(String url, Map metadata, CallbackInfo ci) { + try { + this.modernfix$cachedHash = FilenameUtils.getBaseName(new URL(url).getPath()); + } catch (MalformedURLException e) {} + } + + public void setCachedHash(String url) { + this.cacheHash(url, null, null); + } + + // Overwrite - nuke new entirely + public String getHash() { + if (this.modernfix$cachedHash == null) { + throw new IllegalArgumentException("Invalid profile texture url"); + } + return this.modernfix$cachedHash; + } +} diff --git a/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/cache_profile_texture_url/YggdrasilGsonDeserializerMixin.java b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/cache_profile_texture_url/YggdrasilGsonDeserializerMixin.java new file mode 100644 index 00000000..c5ce1abf --- /dev/null +++ b/fabric/src/main/java/org/embeddedt/modernfix/fabric/mixin/perf/cache_profile_texture_url/YggdrasilGsonDeserializerMixin.java @@ -0,0 +1,33 @@ +package org.embeddedt.modernfix.fabric.mixin.perf.cache_profile_texture_url; + +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.duck.ICachedProfileTexture; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(value=YggdrasilMinecraftSessionService.class, remap=false) +@ClientOnlyMixin +public abstract class YggdrasilGsonDeserializerMixin { + /** + * @author Fury_Phoenix + * @reason Unexpected deserialization + * **/ + + @WrapOperation + (method = "getTextures", + at = @At( + value = "INVOKE", + target = "Lcom/mojang/authlib/minecraft/MinecraftProfileTexture;getUrl()Ljava/lang/String;" + ) + ) + private String setCachedHash(MinecraftProfileTexture texture, Operation original) { + // Because we don't have it set from deserialization + String url = original.call(texture); + ((ICachedProfileTexture)texture).setCachedHash(url); + return url; + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/cache_profile_texture_url/SkinManagerMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/cache_profile_texture_url/SkinManagerMixin.java new file mode 100644 index 00000000..426e9c14 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/cache_profile_texture_url/SkinManagerMixin.java @@ -0,0 +1,44 @@ +package org.embeddedt.modernfix.forge.mixin.perf.cache_profile_texture_url; + +import com.google.common.cache.CacheBuilder; +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.client.resources.SkinManager; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.concurrent.TimeUnit; +import java.util.Map; + +@Mixin(value=SkinManager.class) +@ClientOnlyMixin +public abstract class SkinManagerMixin { + /** + * @author Fury_Phoenix + * @reason No lib mixins on (neo)forge, yet + * **/ + @Unique + private final Map hashCache = CacheBuilder.newBuilder() + .expireAfterAccess(60, TimeUnit.SECONDS) + .concurrencyLevel(1) + // Excessive use of type hinting due to it assuming Object as the broadest correct type + .build() + .asMap(); + + @WrapOperation + ( + // Mixes (un)obfuscated types + method = "m_118828_", + at = @At( + value = "INVOKE", + target = "Lcom/mojang/authlib/minecraft/MinecraftProfileTexture;getHash()Ljava/lang/String;" + ), + remap = false + ) + private String stashCachedHash(MinecraftProfileTexture texture, Operation original) { + return hashCache.computeIfAbsent(texture, k -> original.call(k)); + } +}