diff --git a/src/main/java/io/redspace/ironsspellbooks/datafix/IronsWorldUpgrader.java b/src/main/java/io/redspace/ironsspellbooks/datafix/IronsWorldUpgrader.java index 802005856..30e597c01 100644 --- a/src/main/java/io/redspace/ironsspellbooks/datafix/IronsWorldUpgrader.java +++ b/src/main/java/io/redspace/ironsspellbooks/datafix/IronsWorldUpgrader.java @@ -7,6 +7,8 @@ import com.mojang.datafixers.DataFixer; import com.mojang.datafixers.DataFixerBuilder; import io.redspace.ironsspellbooks.IronsSpellbooks; +import io.redspace.ironsspellbooks.util.ByteHelper; +import io.redspace.ironsspellbooks.util.CodeTimer; import it.unimi.dsi.fastutil.objects.Object2FloatMap; import it.unimi.dsi.fastutil.objects.Object2FloatMaps; import it.unimi.dsi.fastutil.objects.Object2FloatOpenCustomHashMap; @@ -15,6 +17,7 @@ import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; import net.minecraft.resources.ResourceKey; +import net.minecraft.util.Tuple; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.storage.ChunkStorage; @@ -23,18 +26,18 @@ import net.minecraft.world.level.storage.DimensionDataStorage; import net.minecraft.world.level.storage.LevelStorageSource; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.lang.reflect.Array; +import java.nio.ByteBuffer; import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class IronsWorldUpgrader { public static int IRONS_WORLD_DATA_VERSION = 1; + final int REPORT_PROGRESS_MS = 20000; + final byte[] INHABITED_TIME_MARKER = new byte[]{0x49, 0x6E, 0x68, 0x61, 0x62, 0x69, 0x74, 0x65, 0x64, 0x54, 0x69, 0x6D, 0x65}; public static final String REGION_FOLDER = "region"; public static final String ENTITY_FOLDER = "entities"; private final LevelStorageSource.LevelStorageAccess levelStorage; @@ -80,21 +83,26 @@ public void runUpgrade() { IronsSpellbooks.LOGGER.info("IronsWorldUpgrader starting upgrade"); try { + IronsSpellbooks.LOGGER.info("IronsWorldUpgrader Attempting minecraft world backup (this can take long on large worlds)"); levelStorage.makeWorldBackup(); + IronsSpellbooks.LOGGER.info("IronsWorldUpgrader Minecraft world backup complete."); } catch (Exception exception) { IronsSpellbooks.LOGGER.error("IronsWorldUpgrader Level Backup failed: {}", exception.getMessage()); } + IronsSpellbooks.LOGGER.info("IronsWorldUpgrader starting REGION_FOLDER"); long millis = Util.getMillis(); - doWork(REGION_FOLDER, "block_entities"); + doWork(REGION_FOLDER, "block_entities", true); millis = Util.getMillis() - millis; IronsSpellbooks.LOGGER.info("IronsWorldUpgrader finished REGION_FOLDER after {} ms. chunks updated:{} chunks skipped:{} tags fixed:{}", millis, this.converted, this.skipped, this.fixes); + IronsSpellbooks.LOGGER.info("IronsWorldUpgrader starting ENTITY_FOLDER"); millis = Util.getMillis(); - doWork(ENTITY_FOLDER, null); + doWork(ENTITY_FOLDER, null, false); millis = Util.getMillis() - millis; IronsSpellbooks.LOGGER.info("IronsWorldUpgrader finished ENTITY_FOLDER after {} ms. chunks updated:{} chunks skipped:{} tags fixed:{}", millis, this.converted, this.skipped, this.fixes); + IronsSpellbooks.LOGGER.info("IronsWorldUpgrader starting fixDimensionStorage"); millis = Util.getMillis(); fixDimensionStorage(); millis = Util.getMillis() - millis; @@ -126,23 +134,47 @@ private void fixDimensionStorage() { if (ironsTraverser.changesMade()) { NbtIo.writeCompressed(compoundTag, file); - IronsSpellbooks.LOGGER.debug("IronsWorldUpgrader: fixDimensionStorage updating file: {}, {}", file.getPath(), ironsTraverser.totalChanges()); } fixes += ironsTraverser.totalChanges(); } catch (Exception exception) { - IronsSpellbooks.LOGGER.debug("IronsWorldUpgrader: fixDimensionStorage error: {}", exception.getMessage()); + IronsSpellbooks.LOGGER.error("IronsWorldUpgrader FixDimensionStorage error: {}", exception.getMessage()); } }); } }); } - private void doWork(String regionFolder, String filterTag) { + private boolean preScanChunkUpdateNeeded(ChunkStorage chunkStorage, ChunkPos chunkPos) throws Exception { + var regionFile = chunkStorage.worker.storage.getRegionFile(chunkPos); + var dataInputStream = regionFile.getChunkDataInputStream(chunkPos); + + try (dataInputStream) { + if (dataInputStream == null) { + return false; + } + + int markerPos = ByteHelper.indexOf(dataInputStream, INHABITED_TIME_MARKER); + + if (markerPos == -1) { + return true; + } + + var inhabitedTime = dataInputStream.readLong(); + return inhabitedTime != 0; + + } catch (Exception ignored) { + } + + return true; + } + + private void doWork(String regionFolder, String filterTag, boolean preScan) { running = true; converted = 0; skipped = 0; fixes = 0; + long nextProgressReportMS = System.currentTimeMillis() + REPORT_PROGRESS_MS; int totalChunks = 0; ImmutableMap.Builder, ListIterator> builder = ImmutableMap.builder(); @@ -174,23 +206,27 @@ private void doWork(String regionFolder, String filterTag) { boolean updated = false; try { - CompoundTag chunkDataTag = chunkstorage.read(chunkpos).join().orElse(null); - if (chunkDataTag != null) { - ListTag blockEntitiesTag; - - if (filterTag != null) { - blockEntitiesTag = (ListTag) chunkDataTag.get(filterTag); - } else { - blockEntitiesTag = new ListTag(); - blockEntitiesTag.add(chunkDataTag); - } + if (!preScan || preScanChunkUpdateNeeded(chunkstorage, chunkpos)) { + CompoundTag chunkDataTag = chunkstorage.read(chunkpos).join().orElse(null); + + if (chunkDataTag != null && chunkDataTag.getInt("InhabitedTime") != 0) { + ListTag blockEntitiesTag; + + if (filterTag != null) { + blockEntitiesTag = (ListTag) chunkDataTag.get(filterTag); + } else { + blockEntitiesTag = new ListTag(); + blockEntitiesTag.add(chunkDataTag); + } - var ironsTagTraverser = new IronsTagTraverser(); - ironsTagTraverser.visit(blockEntitiesTag); - if (ironsTagTraverser.changesMade()) { - chunkstorage.write(chunkpos, chunkDataTag); - this.fixes = ironsTagTraverser.totalChanges(); - updated = true; + var ironsTagTraverser = new IronsTagTraverser(); + ironsTagTraverser.visit(blockEntitiesTag); + + if (ironsTagTraverser.changesMade()) { + chunkstorage.write(chunkpos, chunkDataTag); + this.fixes = ironsTagTraverser.totalChanges(); + updated = true; + } } } } catch (Exception exception) { @@ -203,6 +239,12 @@ private void doWork(String regionFolder, String filterTag) { ++this.skipped; } + if (System.currentTimeMillis() > nextProgressReportMS) { + nextProgressReportMS = System.currentTimeMillis() + REPORT_PROGRESS_MS; + int chunksProcessed = this.converted + this.skipped; + IronsSpellbooks.LOGGER.info("IronsWorldUpgrader {} PROGRESS: {} of {} chunks complete ({}%)", regionFolder, chunksProcessed, totalChunks, String.format("%.2f", (chunksProcessed / (float) totalChunks) * 100)); + } + processedItem = true; } } diff --git a/src/main/java/io/redspace/ironsspellbooks/util/ByteHelper.java b/src/main/java/io/redspace/ironsspellbooks/util/ByteHelper.java new file mode 100644 index 000000000..d27924b9e --- /dev/null +++ b/src/main/java/io/redspace/ironsspellbooks/util/ByteHelper.java @@ -0,0 +1,65 @@ +package io.redspace.ironsspellbooks.util; + +import java.io.DataInputStream; +import java.io.IOException; + +public class ByteHelper { + /** + * Returns the start position of the first occurrence of the specified {@code target} within + * {@code array}, or {@code -1} if there is no such occurrence. + * + *

More formally, returns the lowest index {@code i} such that {@code Arrays.copyOfRange(array, + * i, i + target.length)} contains exactly the same elements as {@code target}. + * + * @param array the array to search for the sequence {@code target} + * @param target the array to search for as a sub-sequence of {@code array} + */ + public static int indexOf(byte[] array, int length, byte[] target) { + if (array == null || target == null || target.length == 0 || length == 0) { + return -1; + } + + outer: + for (int i = 0; i < length - target.length + 1; i++) { + for (int j = 0; j < target.length; j++) { + if (array[i + j] != target[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + public static int indexOf(DataInputStream dataInputStream, byte[] target) throws IOException { + if (dataInputStream == null || target == null || target.length == 0) { + return -1; + } + + int index = -1; + int data; + + outer: + do { + data = dataInputStream.read(); + index++; + + int addlReadCount = 0; + + for (int j = 0; j < target.length; j++) { + if (data != target[j]) { + index += addlReadCount; + continue outer; + } else if (j < target.length - 1) { + data = dataInputStream.read(); + addlReadCount++; + if (data == -1) { + return -1; + } + } + } + return index; + } while (data != -1); + return -1; + } +} diff --git a/src/main/java/io/redspace/ironsspellbooks/util/CodeTimer.java b/src/main/java/io/redspace/ironsspellbooks/util/CodeTimer.java new file mode 100644 index 000000000..1e51359df --- /dev/null +++ b/src/main/java/io/redspace/ironsspellbooks/util/CodeTimer.java @@ -0,0 +1,43 @@ +package io.redspace.ironsspellbooks.util; + +import net.minecraft.util.Tuple; + +import java.util.ArrayList; +import java.util.List; + +public class CodeTimer { + private final List> timing = new ArrayList<>(); + + public CodeTimer() { + add("START"); + } + + public void add(String name) { + timing.add(new Tuple<>(name, System.nanoTime())); + } + + public String getOutput(String delimiter) { + StringBuilder sb = new StringBuilder(); + + long itemDelta = 0; + long totalDelta = 0; + + for (int i = 0; i < timing.size(); i++) { + var item = timing.get(i); + + if (i > 0) { + var lastItem = timing.get(i - 1); + itemDelta = item.getB() - lastItem.getB(); + totalDelta += itemDelta; + sb.append(String.format("%s%s%s%s%f%s%f\n", lastItem.getA(), delimiter, item.getA(), delimiter, (itemDelta / 1000000d), delimiter, totalDelta / 1000000d)); + } + } + + return sb.toString(); + } + + @Override + public String toString() { + return getOutput("\t"); + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 21763afd9..f408640ba 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -1,20 +1,35 @@ #Making Minecraft's UpgradeRecipe fields public for use in JEI integration public net.minecraft.world.item.crafting.UpgradeRecipe f_44518_ # base public net.minecraft.world.item.crafting.UpgradeRecipe f_44519_ # addition + #Making Minecraft's structure gen pools public for adding houses to village generation public net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool f_210560_ # templates public-f net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool f_210559_ # rawTemplates + #Making the dispenser behavior map public so that we can save the old entry before overriding it public net.minecraft.world.level.block.DispenserBlock f_52661_ #DISPENSER_REGISTRY + #Making PotionBrewing's addMix public so we can register our own recipes public net.minecraft.world.item.alchemy.PotionBrewing m_43513_(Lnet/minecraft/world/item/alchemy/Potion;Lnet/minecraft/world/item/Item;Lnet/minecraft/world/item/alchemy/Potion;)V #addMix(Potion, Item, Potion) public net.minecraft.world.item.alchemy.PotionBrewing f_43495_ + #Making the loot table of a ranomized container public for debugging public net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity f_59605_ public net.minecraft.server.MinecraftServer f_129744_ + #Making the falling block parmeters public for more control over custom falling blocks public net.minecraft.world.entity.item.FallingBlockEntity (Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/level/block/state/BlockState;)V #constructor public net.minecraft.world.entity.item.FallingBlockEntity f_31947_ #cancelDrop public net.minecraft.world.entity.item.FallingBlockEntity f_31946_ #blockstate + #Making item particles (eating, tool breaking, etc) public so that the scroll can use the same particle code -public net.minecraft.world.entity.LivingEntity m_21060_(Lnet/minecraft/world/item/ItemStack;I)V \ No newline at end of file +public net.minecraft.world.entity.LivingEntity m_21060_(Lnet/minecraft/world/item/ItemStack;I)V + +#Irons World Upgrader +public net.minecraft.world.level.chunk.storage.ChunkStorage f_63495_ #worker +public net.minecraft.world.level.chunk.storage.IOWorker f_63518_ #storage +public net.minecraft.world.level.chunk.storage.RegionFileStorage m_63711_(Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/world/level/chunk/storage/RegionFile; #getRegionFile + +# Makes public the 'makeExecutor' method in Util, +# accepting a String and returns an ExecutorService +public net.minecraft.Util m_137477_(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; #makeExecutor