From 411a5eed180d974ee627e3052f25085a26d60fa2 Mon Sep 17 00:00:00 2001 From: Jason <11360596+jpenilla@users.noreply.github.com> Date: Sat, 28 Aug 2021 00:07:12 -0500 Subject: [PATCH] Fix and optimize legacy world conversion (#6473) 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. --- ...and-optimize-legacy-world-conversion.patch | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 patches/server/0763-Fix-and-optimize-legacy-world-conversion.patch diff --git a/patches/server/0763-Fix-and-optimize-legacy-world-conversion.patch b/patches/server/0763-Fix-and-optimize-legacy-world-conversion.patch new file mode 100644 index 0000000000..ba0f43086d --- /dev/null +++ b/patches/server/0763-Fix-and-optimize-legacy-world-conversion.patch @@ -0,0 +1,178 @@ +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. + +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