diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 61bb6efe..b1f6c09a 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -29,6 +29,7 @@ public class ModernFixEarlyConfig { this.addMixinRule("perf.parallelize_model_loading", true); this.addMixinRule("perf.parallelize_model_loading.multipart", false); this.addMixinRule("perf.cache_strongholds", true); + this.addMixinRule("perf.cache_upgraded_structures", true); this.addMixinRule("bugfix.concurrency", true); this.addMixinRule("bugfix.edge_chunk_not_saved", true); this.addMixinRule("bugfix.packet_leak", false); diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_upgraded_structures/StructureManagerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_upgraded_structures/StructureManagerMixin.java new file mode 100644 index 00000000..b275666b --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/cache_upgraded_structures/StructureManagerMixin.java @@ -0,0 +1,25 @@ +package org.embeddedt.modernfix.mixin.perf.cache_upgraded_structures; + +import com.mojang.datafixers.DataFixer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import org.embeddedt.modernfix.structure.CachingStructureManager; +import org.spongepowered.asm.mixin.Final; +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.Redirect; + +import java.io.IOException; +import java.io.InputStream; + +@Mixin(StructureManager.class) +public class StructureManagerMixin { + @Shadow @Final private DataFixer fixerUpper; + + @Redirect(method = "loadFromResource", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureManager;readStructure(Ljava/io/InputStream;)Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate;")) + private StructureTemplate readViaCache(StructureManager manager, InputStream stream, ResourceLocation arg) throws IOException { + return CachingStructureManager.readStructure(arg, this.fixerUpper, stream); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java b/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java new file mode 100644 index 00000000..86ef586b --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/structure/CachingStructureManager.java @@ -0,0 +1,97 @@ +package org.embeddedt.modernfix.structure; + +import com.mojang.datafixers.DataFixer; +import cpw.mods.modlauncher.api.LamdbaExceptionUtils; +import net.minecraft.SharedConstants; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.datafix.DataFixTypes; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraftforge.fml.loading.FMLPaths; +import org.apache.commons.codec.binary.Hex; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.util.FileUtil; + +import java.io.*; +import java.security.MessageDigest; + +public class CachingStructureManager { + private static ThreadLocal digestThreadLocal = ThreadLocal.withInitial(() -> LamdbaExceptionUtils.uncheck(() -> MessageDigest.getInstance("SHA-256"))); + private static final File STRUCTURE_CACHE_FOLDER = FileUtil.childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("structureCacheV1").toFile()); + + static { + STRUCTURE_CACHE_FOLDER.mkdirs(); + } + + public static StructureTemplate readStructure(ResourceLocation location, DataFixer datafixer, InputStream stream) throws IOException { + CompoundTag tag = readStructureTag(location, datafixer, stream); + StructureTemplate template = new StructureTemplate(); + template.load(tag); + return template; + } + + private static CompoundTag readStructureTag(ResourceLocation location, DataFixer datafixer, InputStream stream) throws IOException { + byte[] structureBytes = toBytes(stream); + CompoundTag currentTag = NbtIo.readCompressed(new ByteArrayInputStream(structureBytes)); + if (!currentTag.contains("DataVersion", 99)) { + currentTag.putInt("DataVersion", 500); + } + int currentDataVersion = currentTag.getInt("DataVersion"); + if(currentDataVersion < SharedConstants.getCurrentVersion().getWorldVersion()) { + /* Needs upgrade, try looking up from cache */ + MessageDigest hasher = digestThreadLocal.get(); + hasher.reset(); + String hash = new String(Hex.encodeHex(hasher.digest(structureBytes))); + CompoundTag cachedUpgraded = getCachedUpgraded(location, hash); + if(cachedUpgraded != null && cachedUpgraded.getInt("DataVersion") == SharedConstants.getCurrentVersion().getWorldVersion()) { + ModernFix.LOGGER.warn("Using cached upgraded version of {}", location); + currentTag = cachedUpgraded; + } else { + currentTag = NbtUtils.update(datafixer, DataFixTypes.STRUCTURE, currentTag, currentDataVersion, + SharedConstants.getCurrentVersion().getWorldVersion()); + saveCachedUpgraded(location, hash, currentTag); + } + } + return currentTag; + } + + private static File getCachePath(ResourceLocation location, String hash) { + String fileName = location.getNamespace() + "_" + location.getPath().replace('/', '_') + "_" + hash + ".nbt"; + return new File(STRUCTURE_CACHE_FOLDER, fileName); + } + + private static synchronized CompoundTag getCachedUpgraded(ResourceLocation location, String hash) { + File theFile = getCachePath(location, hash); + try { + return NbtIo.readCompressed(theFile); + } catch(FileNotFoundException e) { + return null; + } catch(IOException e) { + e.printStackTrace(); + return null; + } + } + + private static synchronized void saveCachedUpgraded(ResourceLocation location, String hash, CompoundTag tagToSave) { + File theFile = getCachePath(location, hash); + try { + NbtIo.writeCompressed(tagToSave, theFile); + } catch(IOException e) { + e.printStackTrace(); + } + } + + private static byte[] toBytes(InputStream stream) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + byte[] tmp = new byte[16384]; + int n; + while ((n = stream.read(tmp, 0, tmp.length)) != -1) { + buffer.write(tmp, 0, n); + } + + return buffer.toByteArray(); + } +} diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 493b60a6..5d7dec48 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -51,6 +51,7 @@ "perf.kubejs.CustomIngredientMixin", "perf.fast_registry_validation.ForgeRegistryMixin", "perf.cache_strongholds.ChunkGeneratorMixin", + "perf.cache_upgraded_structures.StructureManagerMixin", "perf.cache_strongholds.ServerLevelMixin" ], "client": [