From 24f31dd92a7808e970507b61317fe8e91c38c7ae Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:45:13 -0400 Subject: [PATCH] Initial version of resource pack caching for 1.19.4+ --- .../resources/NewResourcePackAdapter.java | 17 ++++ .../ForgePathPackResourcesMixin.java | 95 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 common/src/main/java/org/embeddedt/modernfix/resources/NewResourcePackAdapter.java create mode 100644 forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/resourcepacks/ForgePathPackResourcesMixin.java diff --git a/common/src/main/java/org/embeddedt/modernfix/resources/NewResourcePackAdapter.java b/common/src/main/java/org/embeddedt/modernfix/resources/NewResourcePackAdapter.java new file mode 100644 index 00000000..fb1d16a6 --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/resources/NewResourcePackAdapter.java @@ -0,0 +1,17 @@ +package org.embeddedt.modernfix.resources; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.resources.IoSupplier; + +import java.io.InputStream; +import java.util.Collection; +import java.util.function.Function; + +public class NewResourcePackAdapter { + public static void sendToOutput(Function> streamCreator, PackResources.ResourceOutput output, Collection locations) { + for(ResourceLocation rl : locations) { + output.accept(rl, streamCreator.apply(rl)); + } + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/resourcepacks/ForgePathPackResourcesMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/resourcepacks/ForgePathPackResourcesMixin.java new file mode 100644 index 00000000..51f0cd23 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/resourcepacks/ForgePathPackResourcesMixin.java @@ -0,0 +1,95 @@ +package org.embeddedt.modernfix.forge.mixin.perf.resourcepacks; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackResources; +import net.minecraft.server.packs.PackType; +import net.minecraftforge.resource.PathPackResources; +import org.embeddedt.modernfix.resources.ICachingResourcePack; +import org.embeddedt.modernfix.resources.NewResourcePackAdapter; +import org.embeddedt.modernfix.resources.PackResourcesCacheEngine; +import org.embeddedt.modernfix.util.PackTypeHelper; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Set; + +@Mixin(PathPackResources.class) +public abstract class ForgePathPackResourcesMixin implements ICachingResourcePack { + @Shadow protected abstract Path resolve(String... paths); + + @Shadow @NotNull + protected abstract Set getNamespacesFromDisk(PackType type); + + @Shadow private static String[] getPathFromLocation(PackType type, ResourceLocation location) { + throw new AssertionError(); + } + + private PackResourcesCacheEngine cacheEngine; + + @Inject(method = "", at = @At("TAIL")) + private void cacheResources(CallbackInfo ci) { + invalidateCache(); + PackResourcesCacheEngine.track(this); + } + + private PackResourcesCacheEngine generateResourceCache() { + synchronized (this) { + PackResourcesCacheEngine engine = this.cacheEngine; + if(engine != null) + return engine; + this.cacheEngine = engine = new PackResourcesCacheEngine(this::getNamespacesFromDisk, (type, namespace) -> this.resolve(type.getDirectory(), namespace)); + return engine; + } + } + + @Override + public void invalidateCache() { + this.cacheEngine = null; + } + + @Inject(method = "getNamespaces", at = @At("HEAD"), cancellable = true) + private void useCacheForNamespaces(PackType type, CallbackInfoReturnable> cir) { + PackResourcesCacheEngine engine = cacheEngine; + if(engine != null) { + Set namespaces = engine.getNamespaces(type); + if(namespaces != null) + cir.setReturnValue(namespaces); + } + } + + @Redirect(method = "getRootResource", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;exists(Ljava/nio/file/Path;[Ljava/nio/file/LinkOption;)Z")) + private boolean useCacheForExistence(Path path, LinkOption[] options, String[] originalPaths) { + // the cache only stores things with a namespace and pack type + if(originalPaths.length < 3) + return Files.exists(path, options); + else + return this.generateResourceCache().hasResource(originalPaths); + } + + /** + * @author embeddedt + * @reason Use cached listing of mod resources + */ + @Inject(method = "listResources", at = @At("HEAD"), cancellable = true) + private void fastGetResources(PackType type, String namespace, String path, PackResources.ResourceOutput resourceOutput, CallbackInfo ci) + { + if(!PackTypeHelper.isVanillaPackType(type)) + return; + ci.cancel(); + Collection allPossibleResources = this.generateResourceCache().getResources(type, namespace, path, Integer.MAX_VALUE, p -> true); + NewResourcePackAdapter.sendToOutput(location -> { + Path target = resolve(getPathFromLocation(location.getPath().startsWith("lang/") ? PackType.CLIENT_RESOURCES : type, location)); + return () -> Files.newInputStream(target); + }, resourceOutput, allPossibleResources); + } +}