From b4b80b0b3b3f3624537522012190cd84d8e7065b Mon Sep 17 00:00:00 2001 From: Johny Muffin Date: Tue, 6 Aug 2024 12:09:13 +1000 Subject: [PATCH] Parallelized optimization to allow multi-threading --- pom.xml | 12 --- .../world/level/storage/AnvilConverter.java | 73 +++++++++++++++++- .../storage/AnvilLevelStorageSource.java | 75 +++++++++++++++---- 3 files changed, 129 insertions(+), 31 deletions(-) diff --git a/pom.xml b/pom.xml index 5551716..1c055f3 100644 --- a/pom.xml +++ b/pom.xml @@ -7,16 +7,4 @@ org.example Server 1.0-SNAPSHOT - - - - org.apache.maven.plugins - maven-compiler-plugin - - 8 - 8 - - - - \ No newline at end of file diff --git a/src/main/java/net/minecraft/world/level/storage/AnvilConverter.java b/src/main/java/net/minecraft/world/level/storage/AnvilConverter.java index eebb5f7..b71bea5 100644 --- a/src/main/java/net/minecraft/world/level/storage/AnvilConverter.java +++ b/src/main/java/net/minecraft/world/level/storage/AnvilConverter.java @@ -8,15 +8,31 @@ import java.io.File; +import java.util.concurrent.TimeUnit; public class AnvilConverter { public static void main(String[] args) { - - if (args.length != 2) { + if (args.length < 2) { printUsageAndExit(); } + // Handle optional third argument for number of threads + int numThreads = 1; + if (args.length == 3) { + try { + numThreads = Integer.parseInt(args[2]); + if (numThreads < 0) { + throw new NumberFormatException(); + } + } catch (NumberFormatException e) { + System.err.println("Invalid number of threads: " + args[2]); + System.out.println(""); + printUsageAndExit(); + return; + } + } + File baseFolder; try { baseFolder = new File(args[0]); @@ -40,7 +56,40 @@ public static void main(String[] args) { return; } + // Remove old .mca region files if they exist (they will be regenerated) + File regionFolder = new File(baseFolder, args[1] + "/region"); + int regionCount = 0; + if (regionFolder.exists()) { + File[] regionFiles = regionFolder.listFiles(); + if (regionFiles != null) { + for (File regionFile : regionFiles) { + if (regionFile.getName().endsWith(".mca")) { + regionFile.delete(); + regionCount++; + } + } + } + } + if (regionCount > 0) { + System.out.println("Deleted " + regionCount + " old mca region files"); + } + + // Rename level.dat_mcr to level.dat + File levelDatMcr = new File(baseFolder, args[1] + "/level.dat_mcr"); + File levelDat = new File(baseFolder, args[1] + "/level.dat"); + if (levelDatMcr.exists()) { + if (levelDat.exists()) { + levelDat.delete(); + } + levelDatMcr.renameTo(levelDat); + System.out.println("Renamed level.dat_mcr to level.dat"); + } + System.out.println("Converting map!"); + + // Duration duration = new Duration(); + long startTime = System.currentTimeMillis(); + storage.convertLevel(args[1], new ProgressListener() { private long timeStamp = System.currentTimeMillis(); @@ -59,7 +108,25 @@ public void progressStagePercentage(int i) { public void progressStage(String string) { } - }); + }, numThreads); + + long endTime = System.currentTimeMillis(); + long durationMillis = endTime - startTime; + + // Calculate minutes, seconds, and milliseconds + long hours = TimeUnit.MILLISECONDS.toHours(durationMillis); + long minutes = TimeUnit.MILLISECONDS.toMinutes(durationMillis) - + TimeUnit.HOURS.toMinutes(hours); + long seconds = TimeUnit.MILLISECONDS.toSeconds(durationMillis) - + TimeUnit.MINUTES.toSeconds(minutes); + long millis = durationMillis - TimeUnit.MINUTES.toMillis(minutes) - + TimeUnit.SECONDS.toMillis(seconds); + + // Format and print the duration time + String duration = String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, millis); + System.out.println("Conversion completed in: " + duration); + + System.out.println("Done!"); System.out.println("To revert, replace level.dat with level.dat_mcr. Old mcr region files have not been modified."); } diff --git a/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorageSource.java index 3a42ca3..b01613b 100644 --- a/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorageSource.java +++ b/src/main/java/net/minecraft/world/level/storage/AnvilLevelStorageSource.java @@ -6,14 +6,18 @@ * Don't do evil. */ -import java.io.*; -import java.util.ArrayList; - +import com.mojang.nbt.CompoundTag; +import com.mojang.nbt.NbtIo; import net.minecraft.world.level.biome.BiomeSource; -import net.minecraft.world.level.chunk.storage.*; +import net.minecraft.world.level.chunk.storage.OldChunkStorage; import net.minecraft.world.level.chunk.storage.OldChunkStorage.OldLevelChunk; +import net.minecraft.world.level.chunk.storage.RegionFile; -import com.mojang.nbt.*; +import java.io.*; +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; public class AnvilLevelStorageSource { @@ -79,7 +83,7 @@ private void saveDataTag(String levelId, CompoundTag dataTag) { } } - public boolean convertLevel(String levelId, ProgressListener progress) { + public boolean convertLevel(String levelId, ProgressListener progress, int threadCount) { progress.progressStagePercentage(0); @@ -109,12 +113,23 @@ public boolean convertLevel(String levelId, ProgressListener progress) { CompoundTag levelData = getDataTagFor(levelId); + //int threadCount = Runtime.getRuntime().availableProcessors(); + + // Print in what mode we are running + if (threadCount == 0) { + System.out.println("Running sequentially"); + } else { + System.out.println("Running " + threadCount + " threads in parallel"); + } + + AtomicInteger currentCount = new AtomicInteger(0); + // convert normal world - convertRegions(new File(baseFolder, "region"), normalRegions, null, 0, totalCount, progress); + convertRegions(new File(baseFolder, "region"), normalRegions, null, currentCount, totalCount, progress, threadCount); // convert hell world - convertRegions(new File(netherFolder, "region"), netherRegions, null, normalRegions.size(), totalCount, progress); + convertRegions(new File(netherFolder, "region"), netherRegions, null, currentCount, totalCount, progress, threadCount); // convert end world - convertRegions(new File(enderFolder, "region"), enderRegions, null, normalRegions.size() + netherRegions.size(), totalCount, progress); + convertRegions(new File(enderFolder, "region"), enderRegions, null, currentCount, totalCount, progress, threadCount); makeMcrLevelDatBackup(levelId); @@ -143,19 +158,40 @@ private void makeMcrLevelDatBackup(String levelId) { } } - private void convertRegions(File baseFolder, ArrayList regionFiles, BiomeSource biomeSource, int currentCount, int totalCount, ProgressListener progress) { + private void convertRegions(File baseFolder, ArrayList regionFiles, BiomeSource biomeSource, AtomicInteger currentCount, int totalCount, ProgressListener progress, int numThreads) { + // Create an ExecutorService with a fixed thread pool - for (File regionFile : regionFiles) { - convertRegion(baseFolder, regionFile, biomeSource, currentCount, totalCount, progress); + if (numThreads <= 0) { + //Program is being run sequentially + for (File regionFile : regionFiles) { + convertRegion(baseFolder, regionFile, biomeSource, currentCount, totalCount, progress); + } + } else { + //Program is being run in parallel + ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + + // Submit tasks for each region file + for (File regionFile : regionFiles) { + executorService.submit(() -> { + convertRegion(baseFolder, regionFile, biomeSource, currentCount, totalCount, progress); + }); + } - currentCount++; - int percent = (int) Math.round(100.0d * (double) currentCount / (double) totalCount); - progress.progressStagePercentage(percent); + // Shut down the executor service and wait for all tasks to complete + executorService.shutdown(); + try { + executorService.awaitTermination(Long.MAX_VALUE, java.util.concurrent.TimeUnit.NANOSECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } } + // Update progress after all tasks are completed + int percent = (int) Math.round(100.0d * (double) totalCount / (double) totalCount); + progress.progressStagePercentage(percent); } - private void convertRegion(File baseFolder, File regionFile, BiomeSource biomeSource, int currentCount, int totalCount, ProgressListener progress) { + private void convertRegion(File baseFolder, File regionFile, BiomeSource biomeSource, AtomicInteger currentCounter, int totalCount, ProgressListener progress) { try { String name = regionFile.getName(); @@ -189,6 +225,10 @@ private void convertRegion(File baseFolder, File regionFile, BiomeSource biomeSo } } } + + // Update progress + int currentCount = currentCounter.get(); + int basePercent = (int) Math.round(100.0d * (double) (currentCount * 1024) / (double) (totalCount * 1024)); int newPercent = (int) Math.round(100.0d * (double) ((x + 1) * 32 + currentCount * 1024) / (double) (totalCount * 1024)); if (newPercent > basePercent) { @@ -196,6 +236,9 @@ private void convertRegion(File baseFolder, File regionFile, BiomeSource biomeSo } } + // Increment the counter + currentCounter.incrementAndGet(); + regionSource.close(); regionDest.close(); } catch (IOException e) {