From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sun, 22 Aug 2021 23:03:33 -0700 Subject: [PATCH] Fix and optimize legacy world conversion CraftBukkit breaks legacy world conversion in three ways: - Writes userdata to the path of the userdata folder rather than to the correct file inside the aforementioned folder. This causes the userdata folder to fail to be created as a file already exists at its path. - Makes changes to how multiworld works, without modifying McRegionUpgrader to be aware of these changes. - Calls methods on Bukkit before the server is initialized. This patch fixes all of these issues, and also threads the McRegionUpgrader to improve performance. 1.18 update note: legacy region conversion has been entirely removed from the vanilla server diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java index 8703f97dc2f392b136c6851aa09b607cbfdfa5de..ade010fe3b62a4624b009c6d665e9909b2d314ac 100644 --- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java +++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java @@ -355,14 +355,14 @@ public class OldUsersConverter { } private void movePlayerFile(File playerDataFolder, String fileName, String uuid) { - File file5 = new File(file, fileName + ".dat"); + File file5 = new File(file, fileName + ".dat"); // Paper - diff on change File file6 = new File(playerDataFolder, uuid + ".dat"); // CraftBukkit start - Use old file name to seed lastKnownName CompoundTag root = null; try { - root = NbtIo.readCompressed(new java.io.FileInputStream(file5)); + root = NbtIo.readCompressed(new java.io.FileInputStream(file5)); // Paper - diff on change } catch (Exception exception) { exception.printStackTrace(); ServerInternalException.reportInternalException(exception); // Paper @@ -376,7 +376,7 @@ public class OldUsersConverter { data.putString("lastKnownName", fileName); try { - NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2)); + NbtIo.writeCompressed(root, new java.io.FileOutputStream(file5)); // Paper - write to correct path (diff on change) } catch (Exception exception) { exception.printStackTrace(); ServerInternalException.reportInternalException(exception); // Paper diff --git a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java index af8a555c777b5abbaa2615d2ff03f03a9a93847e..b794c02ea36bdc901b1f6a160095abb3fcfe9b60 100644 --- a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java +++ b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java @@ -348,6 +348,12 @@ public class LevelStorageSource { }); } + // Paper start - copied from vanilla before below CB diff + public File getDimensionPathForLegacyConversion(ResourceKey key) { + return DimensionType.getStorageFolder(key, this.levelPath.toFile()); + } + // Paper end + public File getDimensionPath(ResourceKey key) { return LevelStorageSource.getFolder(this.levelPath.toFile(), this.dimensionType); // CraftBukkit } diff --git a/src/main/java/net/minecraft/world/level/storage/McRegionUpgrader.java b/src/main/java/net/minecraft/world/level/storage/McRegionUpgrader.java index 87705fc8ee32016bf5ca533eb278bf32df08d3a3..b9bcbcf509ebb59d15082ce0faef8e405c16bdcc 100644 --- a/src/main/java/net/minecraft/world/level/storage/McRegionUpgrader.java +++ b/src/main/java/net/minecraft/world/level/storage/McRegionUpgrader.java @@ -35,13 +35,29 @@ public class McRegionUpgrader { private static final String MCREGION_EXTENSION = ".mcr"; static boolean convertLevel(LevelStorageSource.LevelStorageAccess storageSession, ProgressListener progressListener) { + // Paper start + progressListener = new ProgressListener() { + @Override + public void progressStartNoAbort(net.minecraft.network.chat.Component title) {} + @Override + public void progressStart(net.minecraft.network.chat.Component title) {} + @Override + public void progressStage(net.minecraft.network.chat.Component task) {} + @Override + public void progressStagePercentage(int percentage) {} + @Override + public void stop() {} + }; + // Paper end progressListener.progressStagePercentage(0); List list = Lists.newArrayList(); List list2 = Lists.newArrayList(); List list3 = Lists.newArrayList(); - File file = storageSession.getDimensionPath(Level.OVERWORLD); - File file2 = storageSession.getDimensionPath(Level.NETHER); - File file3 = storageSession.getDimensionPath(Level.END); + // Paper start + File file = storageSession.getDimensionPathForLegacyConversion(Level.OVERWORLD); + File file2 = storageSession.getDimensionPathForLegacyConversion(Level.NETHER); + File file3 = storageSession.getDimensionPathForLegacyConversion(Level.END); + // Paper end LOGGER.info("Scanning folders..."); addRegionFiles(file, list); if (file2.exists()) { @@ -88,14 +104,58 @@ public class McRegionUpgrader { } private static void convertRegions(RegistryAccess.RegistryHolder registryManager, File directory, Iterable files, BiomeSource biomeSource, int i, int j, ProgressListener progressListener) { - for(File file : files) { - convertRegion(registryManager, directory, file, biomeSource, i, j, progressListener); - ++i; - int k = (int)Math.round(100.0D * (double)i / (double)j); - progressListener.progressStagePercentage(k); + // Paper start - thread this because it's dead simple + convertRegionsThreaded(registryManager, directory, files, biomeSource, i, j, progressListener); + } + + private static void convertRegionsThreaded(RegistryAccess.RegistryHolder registryManager, File directory, Iterable files, BiomeSource biomeSource, int progress, int total, ProgressListener progressListener) { + if (!files.iterator().hasNext()) { + return; } + final int threads = Runtime.getRuntime().availableProcessors() / 2; + final java.util.concurrent.ExecutorService threadPool = java.util.concurrent.Executors.newFixedThreadPool(Math.max(1, threads), new java.util.concurrent.ThreadFactory() { + private final java.util.concurrent.atomic.AtomicInteger threadCounter = new java.util.concurrent.atomic.AtomicInteger(); + + @Override + public Thread newThread(final Runnable run) { + final Thread ret = new Thread(run); + + ret.setName("World upgrader thread for directory " + directory + " #" + this.threadCounter.getAndIncrement()); + ret.setUncaughtExceptionHandler((thread, throwable) -> { + LOGGER.fatal("Error upgrading world", throwable); + }); + + return ret; + } + }); + final java.util.concurrent.atomic.AtomicInteger converted = new java.util.concurrent.atomic.AtomicInteger(progress); + + final long start = System.nanoTime(); + + for (final File file : files) { + threadPool.execute(() -> { + convertRegion(registryManager, directory, file, biomeSource, 0, total, progressListener); + converted.incrementAndGet(); + }); + } + threadPool.shutdown(); + + final java.text.DecimalFormat format = new java.text.DecimalFormat("#0.00"); + while (!threadPool.isTerminated()) { + final int getConverted = converted.get(); + LOGGER.info("Converting... {}/{} ({}%)", getConverted, total, format.format(100.0D * (double) getConverted / (double) total)); + try { + Thread.sleep(1000L); + } catch (final InterruptedException ignored) {} + } + + final long end = System.nanoTime(); + + final double durationSeconds = Math.ceil((end - start) * 1.0e-9); + LOGGER.info("Conversion for {} completed in {}s", directory, durationSeconds); } + // Paper end private static void convertRegion(RegistryAccess.RegistryHolder registryManager, File directory, File file, BiomeSource biomeSource, int i, int j, ProgressListener progressListener) { String string = file.getName(); diff --git a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java index 4d0af984490b556a9911c3b8fdca1e168e6fe932..c20e5d69b4ad8adcdaffb56e4e2a24596ae16edf 100644 --- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java +++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java @@ -212,7 +212,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData { levelTag.putUUID("WanderingTraderId", this.wanderingTraderId); } - levelTag.putString("Bukkit.Version", Bukkit.getName() + "/" + Bukkit.getVersion() + "/" + Bukkit.getBukkitVersion()); // CraftBukkit + if (Bukkit.getServer() != null) levelTag.putString("Bukkit.Version", Bukkit.getName() + "/" + Bukkit.getVersion() + "/" + Bukkit.getBukkitVersion()); // CraftBukkit // Paper - server may not be started yet } @Override