package net.minecraft.util.worldupdate; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.mojang.datafixers.DataFixer; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.objects.Reference2FloatMap; import it.unimi.dsi.fastutil.objects.Reference2FloatMaps; import it.unimi.dsi.fastutil.objects.Reference2FloatOpenHashMap; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.ListIterator; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ThreadFactory; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.Nullable; import net.minecraft.ReportedException; import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.util.datafix.DataFixTypes; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.storage.ChunkStorage; import net.minecraft.world.level.chunk.storage.RecreatingChunkStorage; import net.minecraft.world.level.chunk.storage.RecreatingSimpleRegionStorage; import net.minecraft.world.level.chunk.storage.RegionFile; import net.minecraft.world.level.chunk.storage.RegionStorageInfo; import net.minecraft.world.level.chunk.storage.SimpleRegionStorage; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.saveddata.SavedData; import net.minecraft.world.level.storage.DimensionDataStorage; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.WorldData; import org.slf4j.Logger; public class WorldUpgrader implements AutoCloseable { static final Logger LOGGER = LogUtils.getLogger(); private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder().setDaemon(true).build(); private static final String NEW_DIRECTORY_PREFIX = "new_"; static final Component STATUS_UPGRADING_POI = Component.translatable("optimizeWorld.stage.upgrading.poi"); static final Component STATUS_FINISHED_POI = Component.translatable("optimizeWorld.stage.finished.poi"); static final Component STATUS_UPGRADING_ENTITIES = Component.translatable("optimizeWorld.stage.upgrading.entities"); static final Component STATUS_FINISHED_ENTITIES = Component.translatable("optimizeWorld.stage.finished.entities"); static final Component STATUS_UPGRADING_CHUNKS = Component.translatable("optimizeWorld.stage.upgrading.chunks"); static final Component STATUS_FINISHED_CHUNKS = Component.translatable("optimizeWorld.stage.finished.chunks"); final Registry dimensions; final Set> levels; final boolean eraseCache; final boolean recreateRegionFiles; final LevelStorageSource.LevelStorageAccess levelStorage; private final Thread thread; final DataFixer dataFixer; volatile boolean running = true; private volatile boolean finished; volatile float progress; volatile int totalChunks; volatile int totalFiles; volatile int converted; volatile int skipped; final Reference2FloatMap> progressMap = Reference2FloatMaps.synchronize(new Reference2FloatOpenHashMap<>()); volatile Component status = Component.translatable("optimizeWorld.stage.counting"); static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); final DimensionDataStorage overworldDataStorage; public WorldUpgrader( LevelStorageSource.LevelStorageAccess p_249922_, DataFixer p_250273_, WorldData p_395352_, RegistryAccess p_334652_, boolean p_250738_, boolean p_335488_ ) { this.dimensions = p_334652_.lookupOrThrow(Registries.LEVEL_STEM); this.levels = this.dimensions.registryKeySet().stream().map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet()); this.eraseCache = p_250738_; this.dataFixer = p_250273_; this.levelStorage = p_249922_; SavedData.Context saveddata$context = new SavedData.Context(null, p_395352_.worldGenOptions().seed()); this.overworldDataStorage = new DimensionDataStorage(saveddata$context, this.levelStorage.getDimensionPath(Level.OVERWORLD).resolve("data"), p_250273_, p_334652_); this.recreateRegionFiles = p_335488_; this.thread = THREAD_FACTORY.newThread(this::work); this.thread.setUncaughtExceptionHandler((p_18825_, p_18826_) -> { LOGGER.error("Error upgrading world", p_18826_); this.status = Component.translatable("optimizeWorld.stage.failed"); this.finished = true; }); this.thread.start(); } public void cancel() { this.running = false; try { this.thread.join(); } catch (InterruptedException interruptedexception) { } } private void work() { long i = Util.getMillis(); LOGGER.info("Upgrading entities"); new WorldUpgrader.EntityUpgrader().upgrade(); LOGGER.info("Upgrading POIs"); new WorldUpgrader.PoiUpgrader().upgrade(); LOGGER.info("Upgrading blocks"); new WorldUpgrader.ChunkUpgrader().upgrade(); this.overworldDataStorage.saveAndJoin(); i = Util.getMillis() - i; LOGGER.info("World optimizaton finished after {} seconds", i / 1000L); this.finished = true; } public boolean isFinished() { return this.finished; } public Set> levels() { return this.levels; } public float dimensionProgress(ResourceKey p_18828_) { return this.progressMap.getFloat(p_18828_); } public float getProgress() { return this.progress; } public int getTotalChunks() { return this.totalChunks; } public int getConverted() { return this.converted; } public int getSkipped() { return this.skipped; } public Component getStatus() { return this.status; } @Override public void close() { this.overworldDataStorage.close(); } static Path resolveRecreateDirectory(Path p_330107_) { return p_330107_.resolveSibling("new_" + p_330107_.getFileName().toString()); } abstract class AbstractUpgrader { private final Component upgradingStatus; private final Component finishedStatus; private final String type; private final String folderName; @Nullable protected CompletableFuture previousWriteFuture; protected final DataFixTypes dataFixType; AbstractUpgrader(final DataFixTypes p_332379_, final String p_334432_, final String p_334138_, final Component p_370034_, final Component p_366745_) { this.dataFixType = p_332379_; this.type = p_334432_; this.folderName = p_334138_; this.upgradingStatus = p_370034_; this.finishedStatus = p_366745_; } public void upgrade() { WorldUpgrader.this.totalFiles = 0; WorldUpgrader.this.totalChunks = 0; WorldUpgrader.this.converted = 0; WorldUpgrader.this.skipped = 0; List> list = this.getDimensionsToUpgrade(); if (WorldUpgrader.this.totalChunks != 0) { float f = WorldUpgrader.this.totalFiles; WorldUpgrader.this.status = this.upgradingStatus; while (WorldUpgrader.this.running) { boolean flag = false; float f1 = 0.0F; for (WorldUpgrader.DimensionToUpgrade dimensiontoupgrade : list) { ResourceKey resourcekey = dimensiontoupgrade.dimensionKey; ListIterator listiterator = dimensiontoupgrade.files; T t = dimensiontoupgrade.storage; if (listiterator.hasNext()) { WorldUpgrader.FileToUpgrade worldupgrader$filetoupgrade = listiterator.next(); boolean flag1 = true; for (ChunkPos chunkpos : worldupgrader$filetoupgrade.chunksToUpgrade) { flag1 = flag1 && this.processOnePosition(resourcekey, t, chunkpos); flag = true; } if (WorldUpgrader.this.recreateRegionFiles) { if (flag1) { this.onFileFinished(worldupgrader$filetoupgrade.file); } else { WorldUpgrader.LOGGER.error("Failed to convert region file {}", worldupgrader$filetoupgrade.file.getPath()); } } } float f2 = listiterator.nextIndex() / f; WorldUpgrader.this.progressMap.put(resourcekey, f2); f1 += f2; } WorldUpgrader.this.progress = f1; if (!flag) { break; } } WorldUpgrader.this.status = this.finishedStatus; for (WorldUpgrader.DimensionToUpgrade dimensiontoupgrade1 : list) { try { dimensiontoupgrade1.storage.close(); } catch (Exception exception) { WorldUpgrader.LOGGER.error("Error upgrading chunk", (Throwable)exception); } } } } private List> getDimensionsToUpgrade() { List> list = Lists.newArrayList(); for (ResourceKey resourcekey : WorldUpgrader.this.levels) { RegionStorageInfo regionstorageinfo = new RegionStorageInfo(WorldUpgrader.this.levelStorage.getLevelId(), resourcekey, this.type); Path path = WorldUpgrader.this.levelStorage.getDimensionPath(resourcekey).resolve(this.folderName); T t = this.createStorage(regionstorageinfo, path); ListIterator listiterator = this.getFilesToProcess(regionstorageinfo, path); list.add(new WorldUpgrader.DimensionToUpgrade<>(resourcekey, t, listiterator)); } return list; } protected abstract T createStorage(RegionStorageInfo p_328836_, Path p_332071_); private ListIterator getFilesToProcess(RegionStorageInfo p_332870_, Path p_331013_) { List list = getAllChunkPositions(p_332870_, p_331013_); WorldUpgrader.this.totalFiles = WorldUpgrader.this.totalFiles + list.size(); WorldUpgrader.this.totalChunks = WorldUpgrader.this.totalChunks + list.stream().mapToInt(p_328536_ -> p_328536_.chunksToUpgrade.size()).sum(); return list.listIterator(); } private static List getAllChunkPositions(RegionStorageInfo p_330333_, Path p_330743_) { File[] afile = p_330743_.toFile().listFiles((p_336334_, p_329184_) -> p_329184_.endsWith(".mca")); if (afile == null) { return List.of(); } else { List list = Lists.newArrayList(); for (File file1 : afile) { Matcher matcher = WorldUpgrader.REGEX.matcher(file1.getName()); if (matcher.matches()) { int i = Integer.parseInt(matcher.group(1)) << 5; int j = Integer.parseInt(matcher.group(2)) << 5; List list1 = Lists.newArrayList(); try (RegionFile regionfile = new RegionFile(p_330333_, file1.toPath(), p_330743_, true)) { for (int k = 0; k < 32; k++) { for (int l = 0; l < 32; l++) { ChunkPos chunkpos = new ChunkPos(k + i, l + j); if (regionfile.doesChunkExist(chunkpos)) { list1.add(chunkpos); } } } if (!list1.isEmpty()) { list.add(new WorldUpgrader.FileToUpgrade(regionfile, list1)); } } catch (Throwable throwable) { WorldUpgrader.LOGGER.error("Failed to read chunks from region file {}", file1.toPath(), throwable); } } } return list; } } private boolean processOnePosition(ResourceKey p_328452_, T p_333889_, ChunkPos p_332028_) { boolean flag = false; try { flag = this.tryProcessOnePosition(p_333889_, p_332028_, p_328452_); } catch (CompletionException | ReportedException reportedexception) { Throwable throwable = reportedexception.getCause(); if (!(throwable instanceof IOException)) { throw reportedexception; } WorldUpgrader.LOGGER.error("Error upgrading chunk {}", p_332028_, throwable); } if (flag) { WorldUpgrader.this.converted++; } else { WorldUpgrader.this.skipped++; } return flag; } protected abstract boolean tryProcessOnePosition(T p_329483_, ChunkPos p_327751_, ResourceKey p_335733_); private void onFileFinished(RegionFile p_332836_) { if (WorldUpgrader.this.recreateRegionFiles) { if (this.previousWriteFuture != null) { this.previousWriteFuture.join(); } Path path = p_332836_.getPath(); Path path1 = path.getParent(); Path path2 = WorldUpgrader.resolveRecreateDirectory(path1).resolve(path.getFileName().toString()); try { if (path2.toFile().exists()) { Files.delete(path); Files.move(path2, path); } else { WorldUpgrader.LOGGER.error("Failed to replace an old region file. New file {} does not exist.", path2); } } catch (IOException ioexception) { WorldUpgrader.LOGGER.error("Failed to replace an old region file", (Throwable)ioexception); } } } } class ChunkUpgrader extends WorldUpgrader.AbstractUpgrader { ChunkUpgrader() { super(DataFixTypes.CHUNK, "chunk", "region", WorldUpgrader.STATUS_UPGRADING_CHUNKS, WorldUpgrader.STATUS_FINISHED_CHUNKS); } protected boolean tryProcessOnePosition(ChunkStorage p_330540_, ChunkPos p_331086_, ResourceKey p_327850_) { CompoundTag compoundtag = p_330540_.read(p_331086_).join().orElse(null); if (compoundtag != null) { int i = ChunkStorage.getVersion(compoundtag); ChunkGenerator chunkgenerator = WorldUpgrader.this.dimensions.getValueOrThrow(Registries.levelToLevelStem(p_327850_)).generator(); CompoundTag compoundtag1 = p_330540_.upgradeChunkTag(p_327850_, () -> WorldUpgrader.this.overworldDataStorage, compoundtag, chunkgenerator.getTypeNameForDataFixer()); ChunkPos chunkpos = new ChunkPos(compoundtag1.getIntOr("xPos", 0), compoundtag1.getIntOr("zPos", 0)); if (!chunkpos.equals(p_331086_)) { WorldUpgrader.LOGGER.warn("Chunk {} has invalid position {}", p_331086_, chunkpos); } boolean flag = i < SharedConstants.getCurrentVersion().getDataVersion().getVersion(); if (WorldUpgrader.this.eraseCache) { flag = flag || compoundtag1.contains("Heightmaps"); compoundtag1.remove("Heightmaps"); flag = flag || compoundtag1.contains("isLightOn"); compoundtag1.remove("isLightOn"); ListTag listtag = compoundtag1.getListOrEmpty("sections"); for (int j = 0; j < listtag.size(); j++) { Optional optional = listtag.getCompound(j); if (!optional.isEmpty()) { CompoundTag compoundtag2 = optional.get(); flag = flag || compoundtag2.contains("BlockLight"); compoundtag2.remove("BlockLight"); flag = flag || compoundtag2.contains("SkyLight"); compoundtag2.remove("SkyLight"); } } } if (flag || WorldUpgrader.this.recreateRegionFiles) { if (this.previousWriteFuture != null) { this.previousWriteFuture.join(); } this.previousWriteFuture = p_330540_.write(p_331086_, () -> compoundtag1); return true; } } return false; } protected ChunkStorage createStorage(RegionStorageInfo p_333791_, Path p_332463_) { return (ChunkStorage)(WorldUpgrader.this.recreateRegionFiles ? new RecreatingChunkStorage( p_333791_.withTypeSuffix("source"), p_332463_, p_333791_.withTypeSuffix("target"), WorldUpgrader.resolveRecreateDirectory(p_332463_), WorldUpgrader.this.dataFixer, true ) : new ChunkStorage(p_333791_, p_332463_, WorldUpgrader.this.dataFixer, true)); } } record DimensionToUpgrade(ResourceKey dimensionKey, T storage, ListIterator files) { } class EntityUpgrader extends WorldUpgrader.SimpleRegionStorageUpgrader { EntityUpgrader() { super(DataFixTypes.ENTITY_CHUNK, "entities", WorldUpgrader.STATUS_UPGRADING_ENTITIES, WorldUpgrader.STATUS_FINISHED_ENTITIES); } @Override protected CompoundTag upgradeTag(SimpleRegionStorage p_334286_, CompoundTag p_335346_) { return p_334286_.upgradeChunkTag(p_335346_, -1); } } record FileToUpgrade(RegionFile file, List chunksToUpgrade) { } class PoiUpgrader extends WorldUpgrader.SimpleRegionStorageUpgrader { PoiUpgrader() { super(DataFixTypes.POI_CHUNK, "poi", WorldUpgrader.STATUS_UPGRADING_POI, WorldUpgrader.STATUS_FINISHED_POI); } @Override protected CompoundTag upgradeTag(SimpleRegionStorage p_329642_, CompoundTag p_336180_) { return p_329642_.upgradeChunkTag(p_336180_, 1945); } } abstract class SimpleRegionStorageUpgrader extends WorldUpgrader.AbstractUpgrader { SimpleRegionStorageUpgrader(final DataFixTypes p_332054_, final String p_328150_, final Component p_363471_, final Component p_366494_) { super(p_332054_, p_328150_, p_328150_, p_363471_, p_366494_); } protected SimpleRegionStorage createStorage(RegionStorageInfo p_328549_, Path p_333111_) { return (SimpleRegionStorage)(WorldUpgrader.this.recreateRegionFiles ? new RecreatingSimpleRegionStorage( p_328549_.withTypeSuffix("source"), p_333111_, p_328549_.withTypeSuffix("target"), WorldUpgrader.resolveRecreateDirectory(p_333111_), WorldUpgrader.this.dataFixer, true, this.dataFixType ) : new SimpleRegionStorage(p_328549_, p_333111_, WorldUpgrader.this.dataFixer, true, this.dataFixType)); } protected boolean tryProcessOnePosition(SimpleRegionStorage p_327888_, ChunkPos p_328250_, ResourceKey p_329996_) { CompoundTag compoundtag = p_327888_.read(p_328250_).join().orElse(null); if (compoundtag != null) { int i = ChunkStorage.getVersion(compoundtag); CompoundTag compoundtag1 = this.upgradeTag(p_327888_, compoundtag); boolean flag = i < SharedConstants.getCurrentVersion().getDataVersion().getVersion(); if (flag || WorldUpgrader.this.recreateRegionFiles) { if (this.previousWriteFuture != null) { this.previousWriteFuture.join(); } this.previousWriteFuture = p_327888_.write(p_328250_, compoundtag1); return true; } } return false; } protected abstract CompoundTag upgradeTag(SimpleRegionStorage p_328302_, CompoundTag p_330493_); } }