From e7a1ce74cc83d5d4327fa11af274e949f34dd1c8 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 29 Apr 2023 20:35:18 -0400 Subject: [PATCH] Move pack caching logic into separate version-independent class --- .../ModFileResourcePackMixin.java | 99 +++-------------- .../perf/resourcepacks/VanillaPackMixin.java | 4 +- .../CachedResourcePath.java | 41 +++---- .../resources/PackResourcesCacheEngine.java | 100 ++++++++++++++++++ 4 files changed, 134 insertions(+), 110 deletions(-) rename src/main/java/org/embeddedt/modernfix/{util => resources}/CachedResourcePath.java (70%) create mode 100644 src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java index cd8a862e..ee0e070a 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/ModFileResourcePackMixin.java @@ -1,20 +1,14 @@ package org.embeddedt.modernfix.mixin.perf.resourcepacks; -import com.google.common.base.Joiner; -import com.google.common.collect.Interner; -import com.google.common.collect.Interners; import net.minecraft.server.packs.PackType; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.ResourcePackFileNotFoundException; import net.minecraftforge.fml.loading.moddiscovery.ModFile; import net.minecraftforge.fml.packs.ModFileResourcePack; -import org.apache.commons.lang3.tuple.Triple; -import org.embeddedt.modernfix.util.CachedResourcePath; -import org.embeddedt.modernfix.util.FileUtil; +import org.embeddedt.modernfix.resources.PackResourcesCacheEngine; import org.embeddedt.modernfix.util.PackTypeHelper; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -27,89 +21,34 @@ import java.io.InputStream; import java.nio.file.*; import java.util.*; import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; @Mixin(ModFileResourcePack.class) public abstract class ModFileResourcePackMixin { @Shadow public abstract Set getNamespaces(PackType type); @Shadow(remap = false) @Final private ModFile modFile; - private EnumMap> namespacesByType; - private EnumMap>> rootListingByNamespaceAndType; - private Set containedPaths; - private boolean useNamespaceCaches; - private FileSystem resourcePackFS; - private static Joiner slashJoiner = Joiner.on('/'); + + private PackResourcesCacheEngine cacheEngine; @Inject(method = "", at = @At("TAIL")) private void cacheResources(ModFile modFile, CallbackInfo ci) { - this.resourcePackFS = modFile.getLocator().findPath(modFile, "").getFileSystem(); - this.useNamespaceCaches = false; - this.namespacesByType = new EnumMap<>(PackType.class); - for(PackType type : PackType.values()) { - if(!PackTypeHelper.isVanillaPackType(type)) - continue; - this.namespacesByType.put(type, this.getNamespaces(type)); - } - this.useNamespaceCaches = true; - this.rootListingByNamespaceAndType = new EnumMap<>(PackType.class); - this.containedPaths = new HashSet<>(); - for(PackType type : PackType.values()) { - Set namespaces = PackTypeHelper.isVanillaPackType(type) ? this.namespacesByType.get(type) : this.getNamespaces(type); - HashMap> rootListingForNamespaces = new HashMap<>(); - for(String namespace : namespaces) { - try { - Path root = modFile.getLocator().findPath(modFile, type.getDirectory(), namespace).toAbsolutePath(); - try (Stream stream = Files.walk(root)) { - ArrayList rootListingPaths = new ArrayList<>(); - stream - .map(path -> root.relativize(path.toAbsolutePath())) - .filter(this::isValidCachedResourcePath) - .forEach(path -> { - CachedResourcePath listing = new CachedResourcePath(path); - if(!listing.getFileName().endsWith(".mcmeta")) { - rootListingPaths.add(listing); - } - this.containedPaths.add(new CachedResourcePath(new String[] { type.getDirectory(), namespace }, listing)); - }); - rootListingPaths.trimToSize(); - rootListingForNamespaces.put(namespace, rootListingPaths); - } - } catch(IOException e) { - rootListingForNamespaces.put(namespace, Collections.emptyList()); - } - } - if(PackTypeHelper.isVanillaPackType(type)) - this.rootListingByNamespaceAndType.put(type, rootListingForNamespaces); - } + this.cacheEngine = new PackResourcesCacheEngine(this::getNamespaces, (type, namespace) -> { + return modFile.getLocator().findPath(modFile, type.getDirectory(), namespace); + }); } - - private boolean isValidCachedResourcePath(Path path) { - if(path.getFileName() == null || path.getNameCount() == 0) { - return false; - } - String str = path.toString(); - if(str.length() == 0) - return false; - for(int i = 0; i < str.length(); i++) { - if(!ResourceLocation.validPathChar(str.charAt(i))) { - return false; - } - } - return true; - } - @Inject(method = "getNamespaces", at = @At("HEAD"), cancellable = true) private void useCacheForNamespaces(PackType type, CallbackInfoReturnable> cir) { - if(useNamespaceCaches && PackTypeHelper.isVanillaPackType(type)) { - cir.setReturnValue(this.namespacesByType.get(type)); + if(cacheEngine != null) { + Set namespaces = cacheEngine.getNamespaces(type); + if(namespaces != null) + cir.setReturnValue(namespaces); } } @Inject(method = "hasResource(Ljava/lang/String;)Z", at = @At(value = "HEAD"), cancellable = true) private void useCacheForExistence(String path, CallbackInfoReturnable cir) { - cir.setReturnValue(this.containedPaths.contains(new CachedResourcePath(path))); + if(cacheEngine != null) + cir.setReturnValue(this.cacheEngine.hasResource(path)); } @Inject(method = "getResource(Ljava/lang/String;)Ljava/io/InputStream;", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;exists(Ljava/nio/file/Path;[Ljava/nio/file/LinkOption;)Z"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) @@ -128,18 +67,8 @@ public abstract class ModFileResourcePackMixin { @Inject(method = "getResources", at = @At("HEAD"), cancellable = true) private void fastGetResources(PackType type, String resourceNamespace, String pathIn, int maxDepth, Predicate filter, CallbackInfoReturnable> cir) { - if(!PackTypeHelper.isVanillaPackType(type)) + if(!PackTypeHelper.isVanillaPackType(type) || this.cacheEngine == null) return; - if(!pathIn.endsWith("/")) - pathIn = pathIn + "/"; - final String testPath = pathIn; - cir.setReturnValue(this.rootListingByNamespaceAndType.get(type).getOrDefault(resourceNamespace, Collections.emptyList()).stream(). - filter(path -> path.getNameCount() <= maxDepth). // Make sure the depth is within bounds - filter(path -> path.getFullPath().startsWith(testPath)). // Make sure the target path is inside this one - filter(path -> filter.test(path.getFileName())). // Test the file name against the predicate - // Finally we need to form the RL, so use the first name as the domain, and the rest as the path - // It is VERY IMPORTANT that we do not rely on Path.toString as this is inconsistent between operating systems - map(path -> new ResourceLocation(resourceNamespace, path.getFullPath())). - collect(Collectors.toList())); + cir.setReturnValue(this.cacheEngine.getResources(type, resourceNamespace, pathIn, maxDepth, filter)); } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/VanillaPackMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/VanillaPackMixin.java index 4e991034..54a52349 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/VanillaPackMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/resourcepacks/VanillaPackMixin.java @@ -4,6 +4,7 @@ import com.google.common.base.Joiner; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.VanillaPackResources; import net.minecraft.resources.ResourceLocation; @@ -40,7 +41,7 @@ public class VanillaPackMixin { private void cacheContainedPaths(String[] p_i47912_1_, CallbackInfo ci) { if(containedPaths != null) return; - containedPaths = new HashSet<>(); + containedPaths = new ObjectOpenHashSet<>(); Joiner slashJoiner = Joiner.on('/'); for(PackType type : PackType.values()) { if(!PackTypeHelper.isVanillaPackType(type)) @@ -59,6 +60,7 @@ public class VanillaPackMixin { e.printStackTrace(); } } + ((ObjectOpenHashSet)containedPaths).trim(); } @Redirect(method = "getResources(Ljava/util/Collection;ILjava/lang/String;Ljava/nio/file/Path;Ljava/lang/String;Ljava/util/function/Predicate;)V", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;walk(Ljava/nio/file/Path;I[Ljava/nio/file/FileVisitOption;)Ljava/util/stream/Stream;")) diff --git a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java b/src/main/java/org/embeddedt/modernfix/resources/CachedResourcePath.java similarity index 70% rename from src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java rename to src/main/java/org/embeddedt/modernfix/resources/CachedResourcePath.java index 889311ae..8f3d458f 100644 --- a/src/main/java/org/embeddedt/modernfix/util/CachedResourcePath.java +++ b/src/main/java/org/embeddedt/modernfix/resources/CachedResourcePath.java @@ -1,31 +1,23 @@ -package org.embeddedt.modernfix.util; +package org.embeddedt.modernfix.resources; -import com.google.common.base.Joiner; import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Interner; import com.google.common.collect.Interners; -import com.google.common.collect.Streams; +import org.embeddedt.modernfix.util.FileUtil; -import java.lang.ref.WeakReference; import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.List; public class CachedResourcePath { private final String[] pathComponents; - private int hashCode = 0; - private static final Interner PATH_COMPONENT_INTERNER = Interners.newStrongInterner(); + public static final Interner PATH_COMPONENT_INTERNER = Interners.newStrongInterner(); private static final Splitter SLASH_SPLITTER = Splitter.on('/'); - private static final Joiner SLASH_JOINER = Joiner.on('/'); - private WeakReference fullPathCache = new WeakReference<>(null); private static final String[] NO_PREFIX = new String[0]; - public CachedResourcePath(Path path) { - this(NO_PREFIX, path, path.getNameCount(), true); + public CachedResourcePath(String[] prefix, Path path) { + this(prefix, path, path.getNameCount(), true); } public CachedResourcePath(String s) { @@ -67,11 +59,7 @@ public class CachedResourcePath { @Override public int hashCode() { - int result = hashCode; - if(result != 0) - return result; - hashCode = Arrays.hashCode(pathComponents); - return hashCode; + return Arrays.hashCode(pathComponents); } @Override @@ -90,12 +78,17 @@ public class CachedResourcePath { return pathComponents.length; } - public String getFullPath() { - String fPath = fullPathCache.get(); - if(fPath == null) { - fPath = SLASH_JOINER.join(pathComponents); - fullPathCache = new WeakReference<>(fPath); + public String getNameAt(int i) { + return pathComponents[i]; + } + + public String getFullPath(int startIndex) { + StringBuilder sb = new StringBuilder(); + for(int i = startIndex; i < pathComponents.length; i++) { + sb.append(pathComponents[i]); + if(i != (pathComponents.length - 1)) + sb.append('/'); } - return fPath; + return sb.toString(); } } diff --git a/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java b/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java new file mode 100644 index 00000000..47577b5f --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/resources/PackResourcesCacheEngine.java @@ -0,0 +1,100 @@ +package org.embeddedt.modernfix.resources; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackType; +import org.embeddedt.modernfix.util.PackTypeHelper; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class PackResourcesCacheEngine { + private final Map> namespacesByType; + private final Set containedPaths; + + public PackResourcesCacheEngine(Function> namespacesRetriever, BiFunction basePathRetriever) { + this.namespacesByType = new EnumMap<>(PackType.class); + for(PackType type : PackType.values()) { + if(!PackTypeHelper.isVanillaPackType(type)) + continue; + this.namespacesByType.put(type, namespacesRetriever.apply(type)); + } + this.containedPaths = new ObjectOpenHashSet<>(); + for(PackType type : PackType.values()) { + Collection namespaces = PackTypeHelper.isVanillaPackType(type) ? this.namespacesByType.get(type) : namespacesRetriever.apply(type); + for(String namespace : namespaces) { + try { + Path root = basePathRetriever.apply(type, namespace).toAbsolutePath(); + try (Stream stream = Files.walk(root)) { + stream + .map(path -> root.relativize(path.toAbsolutePath())) + .filter(PackResourcesCacheEngine::isValidCachedResourcePath) + .forEach(path -> { + this.containedPaths.add(new CachedResourcePath(new String[] { type.getDirectory(), namespace }, path)); + }); + } + } catch(IOException ignored) { + } + } + } + ((ObjectOpenHashSet)this.containedPaths).trim(); + } + + private static boolean isValidCachedResourcePath(Path path) { + if(path.getFileName() == null || path.getNameCount() == 0) { + return false; + } + String str = path.toString(); + if(str.length() == 0) + return false; + for(int i = 0; i < str.length(); i++) { + if(!ResourceLocation.validPathChar(str.charAt(i))) { + return false; + } + } + return true; + } + + public Set getNamespaces(PackType type) { + if(PackTypeHelper.isVanillaPackType(type)) + return this.namespacesByType.get(type); + else + return null; + } + + public boolean hasResource(String path) { + return this.containedPaths.contains(new CachedResourcePath(path)); + } + + public Collection getResources(PackType type, String resourceNamespace, String pathIn, int maxDepth, Predicate filter) { + String testPath = pathIn.endsWith("/") ? pathIn : (pathIn + "/"); + ArrayList resources = new ArrayList<>(); + String typeDirectory = CachedResourcePath.PATH_COMPONENT_INTERNER.intern(type.getDirectory()); + resourceNamespace = CachedResourcePath.PATH_COMPONENT_INTERNER.intern(resourceNamespace); + for(CachedResourcePath cachePath : this.containedPaths) { + if(cachePath.getNameCount() < 2) + continue; + if((cachePath.getNameCount() - 2) > maxDepth) + continue; + // we interned, so reference equality is safe + if(cachePath.getNameAt(0) != typeDirectory || cachePath.getNameAt(1) != resourceNamespace) + continue; + if(cachePath.getFileName().endsWith(".mcmeta")) + continue; + String fullPath = cachePath.getFullPath(2); + if(!fullPath.startsWith(testPath)) + continue; + if(!filter.test(cachePath.getFileName())) + continue; + ResourceLocation foundResource = new ResourceLocation(resourceNamespace, fullPath); + resources.add(foundResource); + } + return resources; + } +}