From b7253b654915deebad0dac40f55b3a0ad0362bb7 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 22 Nov 2025 12:23:54 -0600 Subject: [PATCH] refactor chunk file handling --- .../BatchGenerationEnvironment.java | 286 +-------------- .../ChunkCompoundTagParser.java} | 212 +++-------- .../chunkFileHandling/ChunkFileReader.java | 339 ++++++++++++++++++ .../chunkFileHandling/CompoundTagUtil.java | 137 +++++++ .../RegionFileStorageExternalCache.java | 32 +- .../step/StepStructureStart.java | 2 + 6 files changed, 543 insertions(+), 465 deletions(-) rename common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/{mimicObject/ChunkFileReader.java => chunkFileHandling/ChunkCompoundTagParser.java} (83%) create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkFileReader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/CompoundTagUtil.java diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/BatchGenerationEnvironment.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/BatchGenerationEnvironment.java index 79bdb7d26..5583cc481 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/BatchGenerationEnvironment.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/BatchGenerationEnvironment.java @@ -24,9 +24,9 @@ import com.google.common.collect.ImmutableMap; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling.ChunkFileReader; import com.seibel.distanthorizons.common.wrappers.worldGeneration.internalServer.InternalServerGenerator; import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.*; -import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.level.IDhServerLevel; import com.seibel.distanthorizons.core.config.Config; @@ -39,20 +39,14 @@ import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker; import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; -import java.io.IOException; -import java.nio.channels.ClosedByInterruptException; -import java.nio.channels.ClosedChannelException; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepBiomes; import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepFeatures; @@ -61,16 +55,12 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepStruc import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepStructureStart; import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepSurface; -import net.minecraft.server.level.*; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.*; -import net.minecraft.world.level.chunk.storage.IOWorker; -import net.minecraft.world.level.chunk.storage.RegionFileStorage; import net.minecraft.world.level.levelgen.DebugLevelSource; import net.minecraft.world.level.levelgen.FlatLevelSource; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; -import net.minecraft.nbt.CompoundTag; #if MC_VER <= MC_1_17_1 #elif MC_VER <= MC_1_19_2 @@ -96,13 +86,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm .fileLevelConfig(Config.Common.Logging.logWorldGenEventToFile) .build(); - public static final DhLogger CHUNK_LOAD_LOGGER = new DhLoggerBuilder() - .name("LOD Chunk Loading") - .fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile) - .build(); - - private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class); - @NotNull public static final ImmutableMap WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP; public static final int MAX_WORLD_GEN_CHUNK_BORDER_NEEDED; @@ -114,14 +97,8 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm private final IDhServerLevel dhServerLevel; - /** - * will be true if C2ME is installed (since they require us to - * pull chunks using their async method), or if there - * was an issue with the sync pulling method. - */ - private boolean pullExistingChunkUsingMcAsyncMethod = false; - public final InternalServerGenerator internalServerGenerator; + public final ChunkFileReader chunkFileReader; @@ -139,21 +116,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm public int unknownExceptionCount = 0; public long lastExceptionTriggerTime = 0; - private final AtomicReference regionFileStorageCacheRef = new AtomicReference<>(); - public RegionFileStorageExternalCache getOrCreateRegionFileCache(RegionFileStorage storage) - { - RegionFileStorageExternalCache cache = this.regionFileStorageCacheRef.get(); - if (cache == null) - { - cache = new RegionFileStorageExternalCache(storage); - if (!this.regionFileStorageCacheRef.compareAndSet(null, cache)) - { - cache = this.regionFileStorageCacheRef.get(); - } - } - return cache; - } - public static ThreadLocal isDhWorldGenThreadRef = new ThreadLocal<>(); public static boolean isThisDhWorldGenThread() { return (isDhWorldGenThreadRef.get() != null); } @@ -201,6 +163,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm this.dhServerLevel = dhServerLevel; this.params = new GlobalWorldGenParams(dhServerLevel); this.internalServerGenerator = new InternalServerGenerator(this.params, this.dhServerLevel); + this.chunkFileReader = new ChunkFileReader(this.params); ChunkGenerator generator = ((ServerLevelWrapper) (dhServerLevel.getServerLevelWrapper())).getLevel().getChunkSource().getGenerator(); boolean isMcGenerator = @@ -226,12 +189,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm } } - if (MOD_CHECKER.isModLoaded("c2me")) - { - LOGGER.info("C2ME detected: DH's pre-existing chunk accessing will use methods handled by C2ME."); - this.pullExistingChunkUsingMcAsyncMethod = true; - } - } @@ -352,7 +309,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm CompletableFuture[] readFutures = // the extra radius of 8 is to account for structure references which need a chunk radius of 8 ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 8) - .map((chunkPos) -> this.createEmptyOrPreExistingChunkAsync(chunkPos.x, chunkPos.z, chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos)) + .map((chunkPos) -> this.chunkFileReader.createEmptyOrPreExistingChunkAsync(chunkPos.x, chunkPos.z, chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos)) .toArray(CompletableFuture[]::new); // join to prevent an issue where DH queues too many tasks or something(?) @@ -502,225 +459,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm - // get existing chunk // - - /** - * If the given chunk pos already exists in the world, that chunk will be returned, - * otherwise this will return an empty chunk. - */ - private CompletableFuture createEmptyOrPreExistingChunkAsync( - int chunkX, int chunkZ, - Map chunkSkyLightingByDhPos, - Map chunkBlockLightingByDhPos, - Map generatedChunkByDhPos) - { - ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); - DhChunkPos dhChunkPos = new DhChunkPos(chunkX, chunkZ); - - if (generatedChunkByDhPos.containsKey(dhChunkPos)) - { - return CompletableFuture.completedFuture(generatedChunkByDhPos.get(dhChunkPos)); - } - - return this.getChunkNbtDataAsync(chunkPos) - .thenApply((chunkData) -> - { - ChunkAccess newChunk = this.loadOrMakeChunk(chunkPos, chunkData); - - if (Config.Common.LodBuilding.pullLightingForPregeneratedChunks.get()) - { - // attempt to get chunk lighting - ChunkFileReader.CombinedChunkLightStorage combinedLights = ChunkFileReader.readLight(newChunk, chunkData); - if (combinedLights != null) - { - chunkSkyLightingByDhPos.put(dhChunkPos, combinedLights.skyLightStorage); - chunkBlockLightingByDhPos.put(dhChunkPos, combinedLights.blockLightStorage); - } - } - - return newChunk; - }) - // separate handle so we can cleanly handle missing chunks and/or thrown errors - .handle((newChunk, throwable) -> - { - if (newChunk != null) - { - return newChunk; - } - else - { - return CreateEmptyChunk(this.params.level, chunkPos); - } - }) - .thenApply((newChunk) -> - { - generatedChunkByDhPos.put(dhChunkPos, newChunk); - return newChunk; - }); - } - // TODO FIXME this method can be called up to 25 times for the same chunk position, why? - private CompletableFuture getChunkNbtDataAsync(ChunkPos chunkPos) - { - ServerLevel level = this.params.level; - - //if (true) - // return CompletableFuture.completedFuture(null); - - // TODO disabling drastically reduces GC overhead (2Gb/s -> 1GB/s) - - try - { - IOWorker ioWorker = level.getChunkSource().chunkMap.worker; - - #if MC_VER <= MC_1_18_2 - return CompletableFuture.completedFuture(ioWorker.load(chunkPos)); - #else - - // storage will be null if C2ME is installed - if (!this.pullExistingChunkUsingMcAsyncMethod && ioWorker.storage != null) - { - try - { - RegionFileStorage storage = this.params.level.getChunkSource().chunkMap.worker.storage; - RegionFileStorageExternalCache cache = this.getOrCreateRegionFileCache(storage); - return CompletableFuture.completedFuture(cache.read(chunkPos)); - } - catch (NullPointerException e) - { - // this shouldn't happen, if anything is null it should be - // ioWorker.storage - // but just in case - LOGGER.error("Unexpected issue pulling pre-existing chunk ["+chunkPos+"], falling back to async chunk pulling. This may cause server-tick lag.", e); - this.pullExistingChunkUsingMcAsyncMethod = true; - - // try again now using the async method - return this.getChunkNbtDataAsync(chunkPos); - } - } - else - { - // log if we unexpectedly weren't able to run the sync chunk pulling - if (!this.pullExistingChunkUsingMcAsyncMethod) - { - // this shouldn't happen, but just in case - LOGGER.info("Unable to pull pre-existing chunk using synchronous method. Falling back to async method. this may cause server-tick lag."); - this.pullExistingChunkUsingMcAsyncMethod = true; - } - - //GET_CHUNK_COUNT_REF.incrementAndGet(); - - // When running in vanilla MC on versions before 1.21.4, - // DH would attempt to run loadAsync on this same thread via a threading mixin, - // to prevent causing lag on the server thread. - // However, if a mod like C2ME is installed this will run on a C2ME thread instead. - return ioWorker.loadAsync(chunkPos) - .thenApply(optional -> - { - // Debugging note: - // If there are reports of extreme memory use when C2ME is installed, that probably means - // this method is queuing a lot of tasks (1,000+), which causes C2ME to explode. - - //GET_CHUNK_COUNT_REF.decrementAndGet(); - //PREF_LOGGER.info("chunk getter count ["+F3Screen.NUMBER_FORMAT.format(GET_CHUNK_COUNT_REF.get())+"]"); - return optional.orElse(null); - }) - .exceptionally((throwable) -> - { - // unwrap the CompletionException if necessary - Throwable actualThrowable = throwable; - while (actualThrowable instanceof CompletionException completionException) - { - actualThrowable = completionException.getCause(); - } - - boolean isShutdownException = ExceptionUtil.isShutdownException(actualThrowable); - if (!isShutdownException) - { - CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load or make chunk ["+chunkPos+"], error: ["+actualThrowable.getMessage()+"].", actualThrowable); - } - - return null; - }); - } - #endif - } - catch (ClosedByInterruptException ignore) - { - // this just means the world generator is being shut down - return CompletableFuture.completedFuture(null); - } - catch (Exception e) - { - CHUNK_LOAD_LOGGER.warn("Couldn't load or make chunk [" + chunkPos + "]. Error: [" + e.getMessage() + "].", e); - return CompletableFuture.completedFuture(null); - } - } - private ChunkAccess loadOrMakeChunk(ChunkPos chunkPos, CompoundTag chunkData) - { - ServerLevel level = this.params.level; - - if (chunkData == null) - { - return CreateEmptyChunk(level, chunkPos); - } - else - { - try - { - CHUNK_LOAD_LOGGER.debug("DistantHorizons: Loading chunk [" + chunkPos + "] from disk."); - - @Nullable - ChunkAccess chunk = ChunkFileReader.read(level, chunkPos, chunkData); - if (chunk != null) - { - if (Config.Common.LodBuilding.assumePreExistingChunksAreFinished.get()) - { - // Sometimes the chunk status is wrong - // (this might be an issue with some versions of chunky) - // which can cause issues with some world gen steps re-running and locking up - ChunkWrapper.trySetStatus(chunk, ChunkStatus.FULL); - } - } - else - { - chunk = CreateEmptyChunk(level, chunkPos); - } - return chunk; - } - catch (Exception e) - { - CHUNK_LOAD_LOGGER.error( - "DistantHorizons: couldn't load or make chunk at [" + chunkPos + "]." + - "Please try optimizing your world to fix this issue. \n" + - "World optimization can be done from the singleplayer world selection screen.\n" + - "Error: [" + e.getMessage() + "]." - , e); - - return CreateEmptyChunk(level, chunkPos); - } - } - } - private static ProtoChunk CreateEmptyChunk(ServerLevel level, ChunkPos chunkPos) - { - #if MC_VER <= MC_1_16_5 - return new ProtoChunk(chunkPos, UpgradeData.EMPTY); - #elif MC_VER <= MC_1_17_1 - return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level); - #elif MC_VER <= MC_1_19_2 - return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), null); - #elif MC_VER <= MC_1_19_4 - return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null); - #elif MC_VER < MC_1_21_3 - return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null); - #elif MC_VER < MC_1_21_9 - return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().lookupOrThrow(Registries.BIOME), null); - #else - return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, PalettedContainerFactory.create(level.registryAccess()), null); - #endif - } - - - // direct generation // public void generateDirect( @@ -884,20 +622,8 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm } - // clear the chunk cache - RegionFileStorageExternalCache regionStorage = this.regionFileStorageCacheRef.get(); - if (regionStorage != null) - { - try - { - regionStorage.close(); - } - catch (ClosedChannelException ignore) { /* world generator is being shut down */ } - catch (IOException e) - { - LOGGER.error("Failed to close region file storage cache, error: ["+e.getMessage()+"].", e); - } - } + this.chunkFileReader.close(); + } diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/ChunkFileReader.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkCompoundTagParser.java similarity index 83% rename from common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/ChunkFileReader.java rename to common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkCompoundTagParser.java index b9aa41169..d7b73644c 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/ChunkFileReader.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkCompoundTagParser.java @@ -17,14 +17,15 @@ * along with this program. If not, see . */ -package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject; +package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling; import com.mojang.serialization.Codec; import com.mojang.serialization.Dynamic; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; +import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage; @@ -34,22 +35,19 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -import it.unimi.dsi.fastutil.shorts.ShortList; import net.minecraft.core.Registry; #if MC_VER >= MC_1_19_4 -import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; #endif + import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.*; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biomes; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.*; @@ -86,14 +84,16 @@ import net.minecraft.world.level.chunk.status.ChunkType; #endif import net.minecraft.world.level.material.Fluid; -import org.jetbrains.annotations.Nullable; -public class ChunkFileReader +public class ChunkCompoundTagParser { private static final AtomicBoolean ZERO_CHUNK_POS_ERROR_LOGGED_REF = new AtomicBoolean(false); - private static final DhLogger LOGGER = BatchGenerationEnvironment.CHUNK_LOAD_LOGGER; + public static final DhLogger LOGGER = new DhLoggerBuilder() + .name("LOD Chunk Reader") + .fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile) + .build(); #if MC_VER >= MC_1_21_9 @@ -121,7 +121,7 @@ public class ChunkFileReader // read chunk // //============// - public static LevelChunk read(WorldGenLevel level, ChunkPos chunkPos, CompoundTag chunkData) + public static LevelChunk createFromTag(WorldGenLevel level, ChunkPos chunkPos, CompoundTag chunkData) { #if MC_VER < MC_1_18_2 CompoundTag tagLevel = chunkData.getCompound("Level"); @@ -129,8 +129,8 @@ public class ChunkFileReader CompoundTag tagLevel = chunkData; #endif - int chunkX = tagGetInt(tagLevel,"xPos"); - int chunkZ = tagGetInt(tagLevel, "zPos"); + int chunkX = CompoundTagUtil.getInt(tagLevel,"xPos"); + int chunkZ = CompoundTagUtil.getInt(tagLevel, "zPos"); ChunkPos actualPos = new ChunkPos(chunkX, chunkZ); if (!Objects.equals(chunkPos, actualPos)) @@ -165,18 +165,25 @@ public class ChunkFileReader chunkType = readChunkType(tagLevel); #if MC_VER < MC_1_18_2 - if (chunkType != ChunkStatus.ChunkType.LEVELCHUNK) - return null; + if (chunkType != ChunkStatus.ChunkType.LEVELCHUNK) + { + return null; + } #elif MC_VER < MC_1_21_6 BlendingData blendingData = readBlendingData(tagLevel); #if MC_VER < MC_1_19_2 if (chunkType == ChunkStatus.ChunkType.PROTOCHUNK && (blendingData == null || !blendingData.oldNoise())) + { return null; + } #else if (chunkType == #if MC_VER < MC_1_20_6 ChunkStatus.ChunkType.PROTOCHUNK #else ChunkType.PROTOCHUNK #endif && blendingData == null) + { return null; + } #endif + #else // ignore blending data, there appears to be an issue with parsing it in 1.21.6 @@ -188,31 +195,31 @@ public class ChunkFileReader } #endif - long inhabitedTime = tagGetLong(tagLevel, "InhabitedTime"); + long inhabitedTime = CompoundTagUtil.getLong(tagLevel, "InhabitedTime"); //================== Read params for making the LevelChunk ================== UpgradeData upgradeData = UpgradeData.EMPTY; // commented out 2025-06-04 as a test to see if the upgrade data // is actually necessary for DH or if it can be ignored - // (if it can't be ignored we'll need to handle null responses from tagGetCompoundTag()) + // (if it can't be ignored we'll need to handle null responses from CompoundTagUtil.getCompoundTag()) // //#if MC_VER < MC_1_17_1 //upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10) - // ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA)) + // ? new UpgradeData(CompoundTagUtil.getCompoundTag(tagLevel, TAG_UPGRADE_DATA)) // : UpgradeData.EMPTY; //#elif MC_VER < MC_1_21_5 //upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10) - // ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level) + // ? new UpgradeData(CompoundTagUtil.getCompoundTag(tagLevel, TAG_UPGRADE_DATA), level) // : UpgradeData.EMPTY; //#else //upgradeData = tagLevel.contains(TAG_UPGRADE_DATA) - // ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level) + // ? new UpgradeData(CompoundTagUtil.getCompoundTag(tagLevel, TAG_UPGRADE_DATA), level) // : UpgradeData.EMPTY; //#endif - boolean isLightOn = tagGetBoolean(tagLevel, "isLightOn"); + boolean isLightOn = CompoundTagUtil.getBoolean(tagLevel, "isLightOn"); #if MC_VER < MC_1_18_2 ChunkBiomeContainer chunkBiomeContainer = new ChunkBiomeContainer( level.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY)#if MC_VER >= MC_1_17_1 , level #endif , @@ -301,10 +308,10 @@ public class ChunkFileReader int sectionYIndex = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYIndex]; - ListTag tagSections = tagGetListTag(chunkData, "Sections", 10); + ListTag tagSections = CompoundTagUtil.getListTag(chunkData, "Sections", 10); if (tagSections == null || tagSections.isEmpty()) { - tagSections = tagGetListTag(chunkData, "sections", 10); + tagSections = CompoundTagUtil.getListTag(chunkData, "sections", 10); } @@ -312,13 +319,13 @@ public class ChunkFileReader { for (int j = 0; j < tagSections.size(); ++j) { - CompoundTag tagSection = tagGetCompoundTag(tagSections, j); + CompoundTag tagSection = CompoundTagUtil.getCompoundTag(tagSections, j); if (tagSection == null) { continue; } - final int sectionYPos = tagGetByte(tagSection, "Y"); + final int sectionYPos = CompoundTagUtil.getByte(tagSection, "Y"); #if MC_VER < MC_1_18_2 if (tagSection.contains("Palette", 9) && tagSection.contains("BlockStates", 12)) @@ -353,11 +360,11 @@ public class ChunkFileReader if (containsBlockStates) { #if MC_VER < MC_1_20_6 - blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states")) + blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "block_states")) .promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string)) .getOrThrow(false, (message) -> logParsingWarningOnce(message)); #else - blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "block_states")) + blockStateContainer = BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "block_states")) .promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string)) .getOrThrow((message) -> logErrorAndReturnException(message)); #endif @@ -389,11 +396,11 @@ public class ChunkFileReader if (containsBiomes) { #if MC_VER < MC_1_20_6 - biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes")) + biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "biomes")) .promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string)) .getOrThrow(false, (message) -> logParsingWarningOnce(message)); #else - biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(tagSection, "biomes")) + biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "biomes")) .promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string)) .getOrThrow((message) -> logErrorAndReturnException(message)); #endif @@ -434,7 +441,7 @@ public class ChunkFileReader #else ChunkType #endif readChunkType(CompoundTag tagLevel) { - String statusString = tagGetString(tagLevel,"Status"); + String statusString = CompoundTagUtil.getString(tagLevel,"Status"); if (statusString != null) { ChunkStatus chunkStatus = ChunkStatus.byName(statusString); @@ -452,7 +459,7 @@ public class ChunkFileReader } private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData) { - CompoundTag tagHeightmaps = tagGetCompoundTag(chunkData, "Heightmaps"); + CompoundTag tagHeightmaps = CompoundTagUtil.getCompoundTag(chunkData, "Heightmaps"); if (tagHeightmaps != null) { for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter()) @@ -482,18 +489,18 @@ public class ChunkFileReader // DH probably doesn't need any chunk post-processing data //private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData) //{ - // ListTag tagPostProcessings = tagGetListTag(chunkData,"PostProcessing", 9); + // ListTag tagPostProcessings = CompoundTagUtil.getListTag(chunkData,"PostProcessing", 9); // if (tagPostProcessings != null) // { // for (int i = 0; i < tagPostProcessings.size(); ++i) // { - // ListTag listTag3 = tagGetListTag(tagPostProcessings, i); + // ListTag listTag3 = CompoundTagUtil.getListTag(tagPostProcessings, i); // for (int j = 0; j < listTag3.size(); ++j) // { // #if MC_VER < MC_1_21_3 // chunk.addPackedPostProcess(listTag3.getShort(j), i); // #else - // chunk.addPackedPostProcess(ShortList.of(tagGetShort(listTag3, j)), i); + // chunk.addPackedPostProcess(ShortList.of(CompoundTagUtil.getShort(listTag3, j)), i); // #endif // } // } @@ -547,9 +554,7 @@ public class ChunkFileReader // read chunk lighting // //=====================// - /** - * https://minecraft.wiki/w/Chunk_format - */ + /** https://minecraft.wiki/w/Chunk_format */ public static CombinedChunkLightStorage readLight(ChunkAccess chunk, CompoundTag chunkData) { #if MC_VER <= MC_1_17_1 @@ -612,8 +617,8 @@ public class ChunkFileReader // if null all lights = 0 - byte[] blockLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "BlockLight"); - byte[] skyLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "SkyLight"); + byte[] blockLightNibbleArray = CompoundTagUtil.getByteArray(chunkSectionCompoundTag, "BlockLight"); + byte[] skyLightNibbleArray = CompoundTagUtil.getByteArray(chunkSectionCompoundTag, "SkyLight"); if (blockLightNibbleArray != null && skyLightNibbleArray != null) @@ -725,137 +730,6 @@ public class ChunkFileReader - //====================// - // tag helper methods // - //====================// - - // TODO move into separate file (this file is getting long) - // these tag helpers are to simplify tag accessing between MC versions - - /** defaults to "false" if the tag isn't present */ - private static boolean tagGetBoolean(CompoundTag tag, String key) - { - #if MC_VER < MC_1_21_5 - return tag.getBoolean(key); - #else - return tag.getBoolean(key).orElse(false); - #endif - } - - /** defaults to "0" if the tag isn't present */ - private static byte tagGetByte(CompoundTag tag, String key) - { - #if MC_VER < MC_1_21_5 - return tag.getByte(key); - #else - return tag.getByte(key).orElse((byte)0); - #endif - } - - /** defaults to "0" if the tag isn't present */ - private static short tagGetShort(ListTag tag, int index) - { - #if MC_VER < MC_1_21_5 - return tag.getShort(index); - #else - return tag.getShort(index).orElse((short)0); - #endif - } - - /** defaults to "0" if the tag isn't present */ - private static int tagGetInt(CompoundTag tag, String key) - { - #if MC_VER < MC_1_21_5 - return tag.getInt(key); - #else - return tag.getInt(key).orElse(0); - #endif - } - - /** defaults to "0" if the tag isn't present */ - private static long tagGetLong(CompoundTag tag, String key) - { - #if MC_VER < MC_1_21_5 - return tag.getInt(key); - #else - return tag.getLong(key).orElse(0L); - #endif - } - - - - /** defaults to null if the tag isn't present */ - @Nullable - private static String tagGetString(CompoundTag tag, String key) - { - #if MC_VER < MC_1_21_5 - return tag.getString(key); - #else - return tag.getString(key).orElse(null); - #endif - } - - /** defaults to null if the tag isn't present */ - @Nullable - private static byte[] tagGetByteArray(CompoundTag tag, String key) - { - #if MC_VER < MC_1_21_5 - return tag.getByteArray(key); - #else - return tag.getByteArray(key).orElse(null); - #endif - } - - - - /** defaults to null if the tag isn't present */ - @Nullable - private static CompoundTag tagGetCompoundTag(CompoundTag tag, String key) - { - #if MC_VER < MC_1_21_5 - return tag.getCompound(key); - #else - return tag.getCompound(key).orElse(null); - #endif - } - /** defaults to null if the tag isn't present */ - @Nullable - private static CompoundTag tagGetCompoundTag(ListTag tag, int index) - { - #if MC_VER < MC_1_21_5 - return tag.getCompound(index); - #else - return tag.getCompound(index).orElse(null); - #endif - } - - /** - * defaults to null if the tag isn't present - * @param elementType unused after MC 1.21.5 - */ - @Nullable - private static ListTag tagGetListTag(CompoundTag tag, String key, int elementType) - { - #if MC_VER < MC_1_21_5 - return tag.getList(key, elementType); - #else - return tag.getList(key).orElse(null); - #endif - } - - /** defaults to null if the tag isn't present */ - @Nullable - private static ListTag tagGetListTag(ListTag tag, int index) - { - #if MC_VER < MC_1_21_5 - return tag.getList(index); - #else - return tag.getList(index).orElse(null); - #endif - } - - - //================// // helper classes // //================// diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkFileReader.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkFileReader.java new file mode 100644 index 000000000..688688f88 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkFileReader.java @@ -0,0 +1,339 @@ +package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling; + +import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.GlobalWorldGenParams; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.RegionFileStorageExternalCache; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.pos.DhChunkPos; +import com.seibel.distanthorizons.core.util.ExceptionUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.PalettedContainerFactory; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.storage.IOWorker; +import net.minecraft.world.level.chunk.storage.RegionFileStorage; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ClosedChannelException; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.atomic.AtomicReference; + +public class ChunkFileReader implements AutoCloseable +{ + + public static final DhLogger LOGGER = new DhLoggerBuilder() + .name("LOD World Gen") + .fileLevelConfig(Config.Common.Logging.logWorldGenEventToFile) + .build(); + + public static final DhLogger CHUNK_LOAD_LOGGER = new DhLoggerBuilder() + .name("LOD Chunk Loading") + .fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile) + .build(); + + private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class); + + public final GlobalWorldGenParams params; + + /** + * will be true if C2ME is installed (since they require us to + * pull chunks using their async method), or if there + * was an issue with the sync pulling method. + */ + private boolean pullExistingChunkUsingMcAsyncMethod = false; + + private final AtomicReference regionFileStorageCacheRef = new AtomicReference<>(); + public RegionFileStorageExternalCache getOrCreateRegionFileCache(RegionFileStorage storage) + { + RegionFileStorageExternalCache cache = this.regionFileStorageCacheRef.get(); + if (cache == null) + { + cache = new RegionFileStorageExternalCache(storage); + if (!this.regionFileStorageCacheRef.compareAndSet(null, cache)) + { + cache = this.regionFileStorageCacheRef.get(); + } + } + return cache; + } + + + + //=============// + // constructor // + //=============// + + public ChunkFileReader(GlobalWorldGenParams params) + { + this.params = params; + + if (MOD_CHECKER.isModLoaded("c2me")) + { + LOGGER.info("C2ME detected: DH's pre-existing chunk accessing will use methods handled by C2ME."); + this.pullExistingChunkUsingMcAsyncMethod = true; + } + + } + + + + //=============// + // constructor // + //=============// + + /** + * If the given chunk pos already exists in the world, that chunk will be returned, + * otherwise this will return an empty chunk. + */ + public CompletableFuture createEmptyOrPreExistingChunkAsync( + int chunkX, int chunkZ, + Map chunkSkyLightingByDhPos, + Map chunkBlockLightingByDhPos, + Map generatedChunkByDhPos) + { + ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); + DhChunkPos dhChunkPos = new DhChunkPos(chunkX, chunkZ); + + if (generatedChunkByDhPos.containsKey(dhChunkPos)) + { + return CompletableFuture.completedFuture(generatedChunkByDhPos.get(dhChunkPos)); + } + + return this.getChunkNbtDataAsync(chunkPos) + .thenApply((CompoundTag chunkData) -> + { + ChunkAccess newChunk = this.loadOrMakeChunk(chunkPos, chunkData); + + if (Config.Common.LodBuilding.pullLightingForPregeneratedChunks.get()) + { + // attempt to get chunk lighting + ChunkCompoundTagParser.CombinedChunkLightStorage combinedLights = ChunkCompoundTagParser.readLight(newChunk, chunkData); + if (combinedLights != null) + { + chunkSkyLightingByDhPos.put(dhChunkPos, combinedLights.skyLightStorage); + chunkBlockLightingByDhPos.put(dhChunkPos, combinedLights.blockLightStorage); + } + } + + return newChunk; + }) + // separate handle so we can cleanly handle missing chunks and/or thrown errors + .handle((newChunk, throwable) -> + { + if (newChunk != null) + { + return newChunk; + } + else + { + return CreateEmptyChunk(this.params.level, chunkPos); + } + }) + .thenApply((newChunk) -> + { + generatedChunkByDhPos.put(dhChunkPos, newChunk); + return newChunk; + }); + } + // TODO FIXME this method can be called up to 25 times for the same chunk position, why? + private CompletableFuture getChunkNbtDataAsync(ChunkPos chunkPos) + { + ServerLevel level = this.params.level; + + //if (true) + // return CompletableFuture.completedFuture(null); + + // TODO disabling drastically reduces GC overhead (2Gb/s -> 1GB/s) + + try + { + IOWorker ioWorker = level.getChunkSource().chunkMap.worker; + + #if MC_VER <= MC_1_18_2 + return CompletableFuture.completedFuture(ioWorker.load(chunkPos)); + #else + + // storage will be null if C2ME is installed + if (!this.pullExistingChunkUsingMcAsyncMethod + && ioWorker.storage != null) + { + try + { + RegionFileStorage storage = this.params.level.getChunkSource().chunkMap.worker.storage; + RegionFileStorageExternalCache cache = this.getOrCreateRegionFileCache(storage); + return CompletableFuture.completedFuture(cache.read(chunkPos)); + } + catch (NullPointerException e) + { + // this shouldn't happen, if anything is null it should be + // ioWorker.storage + // but just in case + LOGGER.error("Unexpected issue pulling pre-existing chunk ["+chunkPos+"], falling back to async chunk pulling. This may cause server-tick lag.", e); + this.pullExistingChunkUsingMcAsyncMethod = true; + + // try again now using the async method + return this.getChunkNbtDataAsync(chunkPos); + } + } + else + { + // log if we unexpectedly weren't able to run the sync chunk pulling + if (!this.pullExistingChunkUsingMcAsyncMethod) + { + // this shouldn't happen, but just in case + LOGGER.info("Unable to pull pre-existing chunk using synchronous method. Falling back to async method. this may cause server-tick lag."); + this.pullExistingChunkUsingMcAsyncMethod = true; + } + + //GET_CHUNK_COUNT_REF.incrementAndGet(); + + // When running in vanilla MC on versions before 1.21.4, + // DH would attempt to run loadAsync on this same thread via a threading mixin, + // to prevent causing lag on the server thread. + // However, if a mod like C2ME is installed this will run on a C2ME thread instead. + return ioWorker.loadAsync(chunkPos) + .thenApply(optional -> + { + // Debugging note: + // If there are reports of extreme memory use when C2ME is installed, that probably means + // this method is queuing a lot of tasks (1,000+), which causes C2ME to explode. + + //GET_CHUNK_COUNT_REF.decrementAndGet(); + //PREF_LOGGER.info("chunk getter count ["+F3Screen.NUMBER_FORMAT.format(GET_CHUNK_COUNT_REF.get())+"]"); + return optional.orElse(null); + }) + .exceptionally((throwable) -> + { + // unwrap the CompletionException if necessary + Throwable actualThrowable = throwable; + while (actualThrowable instanceof CompletionException completionException) + { + actualThrowable = completionException.getCause(); + } + + boolean isShutdownException = ExceptionUtil.isShutdownException(actualThrowable); + if (!isShutdownException) + { + CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load or make chunk ["+chunkPos+"], error: ["+actualThrowable.getMessage()+"].", actualThrowable); + } + + return null; + }); + } + #endif + } + catch (ClosedByInterruptException ignore) + { + // this just means the world generator is being shut down + return CompletableFuture.completedFuture(null); + } + catch (Exception e) + { + CHUNK_LOAD_LOGGER.warn("Couldn't load or make chunk [" + chunkPos + "]. Error: [" + e.getMessage() + "].", e); + return CompletableFuture.completedFuture(null); + } + } + private ChunkAccess loadOrMakeChunk(ChunkPos chunkPos, CompoundTag chunkTagData) + { + ServerLevel level = this.params.level; + + if (chunkTagData == null) + { + return CreateEmptyChunk(level, chunkPos); + } + else + { + try + { + CHUNK_LOAD_LOGGER.debug("DistantHorizons: Loading chunk [" + chunkPos + "] from disk."); + + @Nullable + ChunkAccess chunk = ChunkCompoundTagParser.createFromTag(level, chunkPos, chunkTagData); + if (chunk != null) + { + if (Config.Common.LodBuilding.assumePreExistingChunksAreFinished.get()) + { + // Sometimes the chunk status is wrong + // (this might be an issue with some versions of chunky) + // which can cause issues with some world gen steps re-running and locking up + ChunkWrapper.trySetStatus(chunk, ChunkStatus.FULL); + } + } + else + { + chunk = CreateEmptyChunk(level, chunkPos); + } + return chunk; + } + catch (Exception e) + { + CHUNK_LOAD_LOGGER.error( + "DistantHorizons: couldn't load or make chunk at [" + chunkPos + "]." + + "Please try optimizing your world to fix this issue. \n" + + "World optimization can be done from the singleplayer world selection screen.\n" + + "Error: [" + e.getMessage() + "]." + , e); + + return CreateEmptyChunk(level, chunkPos); + } + } + } + private static ProtoChunk CreateEmptyChunk(ServerLevel level, ChunkPos chunkPos) + { + #if MC_VER <= MC_1_16_5 + return new ProtoChunk(chunkPos, UpgradeData.EMPTY); + #elif MC_VER <= MC_1_17_1 + return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level); + #elif MC_VER <= MC_1_19_2 + return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), null); + #elif MC_VER <= MC_1_19_4 + return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null); + #elif MC_VER < MC_1_21_3 + return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().registryOrThrow(Registries.BIOME), null); + #elif MC_VER < MC_1_21_9 + return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, level.registryAccess().lookupOrThrow(Registries.BIOME), null); + #else + return new ProtoChunk(chunkPos, UpgradeData.EMPTY, level, PalettedContainerFactory.create(level.registryAccess()), null); + #endif + } + + + + //================// + // base overrides // + //================// + + @Override + public void close() + { + RegionFileStorageExternalCache regionStorage = this.regionFileStorageCacheRef.get(); + if (regionStorage != null) + { + try + { + regionStorage.close(); + } + catch (ClosedChannelException ignore) { /* world generator is being shut down */ } + catch (IOException e) + { + LOGGER.error("Failed to close region file storage cache, error: ["+e.getMessage()+"].", e); + } + } + } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/CompoundTagUtil.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/CompoundTagUtil.java new file mode 100644 index 000000000..9e442cfd1 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/CompoundTagUtil.java @@ -0,0 +1,137 @@ +package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import org.jetbrains.annotations.Nullable; + +/** + * these tag helpers are usedd to simplify tag accessing between MC versions + */ +public class CompoundTagUtil +{ + + /** defaults to "false" if the tag isn't present */ + public static boolean getBoolean(CompoundTag tag, String key) + { + #if MC_VER < MC_1_21_5 + return tag.getBoolean(key); + #else + return tag.getBoolean(key).orElse(false); + #endif + } + + /** defaults to "0" if the tag isn't present */ + public static byte getByte(CompoundTag tag, String key) + { + #if MC_VER < MC_1_21_5 + return tag.getByte(key); + #else + return tag.getByte(key).orElse((byte)0); + #endif + } + + /** defaults to "0" if the tag isn't present */ + public static short getShort(ListTag tag, int index) + { + #if MC_VER < MC_1_21_5 + return tag.getShort(index); + #else + return tag.getShort(index).orElse((short)0); + #endif + } + + /** defaults to "0" if the tag isn't present */ + public static int getInt(CompoundTag tag, String key) + { + #if MC_VER < MC_1_21_5 + return tag.getInt(key); + #else + return tag.getInt(key).orElse(0); + #endif + } + + /** defaults to "0" if the tag isn't present */ + public static long getLong(CompoundTag tag, String key) + { + #if MC_VER < MC_1_21_5 + return tag.getInt(key); + #else + return tag.getLong(key).orElse(0L); + #endif + } + + + + /** defaults to null if the tag isn't present */ + @Nullable + public static String getString(CompoundTag tag, String key) + { + #if MC_VER < MC_1_21_5 + return tag.getString(key); + #else + return tag.getString(key).orElse(null); + #endif + } + + /** defaults to null if the tag isn't present */ + @Nullable + public static byte[] getByteArray(CompoundTag tag, String key) + { + #if MC_VER < MC_1_21_5 + return tag.getByteArray(key); + #else + return tag.getByteArray(key).orElse(null); + #endif + } + + + + /** defaults to null if the tag isn't present */ + @Nullable + public static CompoundTag getCompoundTag(CompoundTag tag, String key) + { + #if MC_VER < MC_1_21_5 + return tag.getCompound(key); + #else + return tag.getCompound(key).orElse(null); + #endif + } + /** defaults to null if the tag isn't present */ + @Nullable + public static CompoundTag getCompoundTag(ListTag tag, int index) + { + #if MC_VER < MC_1_21_5 + return tag.getCompound(index); + #else + return tag.getCompound(index).orElse(null); + #endif + } + + /** + * defaults to null if the tag isn't present + * @param elementType unused after MC 1.21.5 + */ + @Nullable + public static ListTag getListTag(CompoundTag tag, String key, int elementType) + { + #if MC_VER < MC_1_21_5 + return tag.getList(key, elementType); + #else + return tag.getList(key).orElse(null); + #endif + } + + /** defaults to null if the tag isn't present */ + @Nullable + public static ListTag getListTag(ListTag tag, int index) + { + #if MC_VER < MC_1_21_5 + return tag.getList(index); + #else + return tag.getList(index).orElse(null); + #endif + } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/RegionFileStorageExternalCache.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/RegionFileStorageExternalCache.java index 3b8fb6980..d7b2df37d 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/RegionFileStorageExternalCache.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/RegionFileStorageExternalCache.java @@ -1,6 +1,6 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling.ChunkFileReader; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; @@ -55,7 +55,7 @@ public class RegionFileStorageExternalCache implements AutoCloseable public RegionFileStorageExternalCache(RegionFileStorage storage) { this.storage = storage; } @Nullable - public RegionFile getRegionFile(ChunkPos pos) throws IOException + public RegionFile getRegionFile(ChunkPos chunkPos) throws IOException { if (this.storage == null) { @@ -70,8 +70,8 @@ public class RegionFileStorageExternalCache implements AutoCloseable - long posLong = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()); - RegionFile rFile = null; + long chunkPosLong = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ()); + RegionFile regionFile = null; // Check vanilla cache int retryCount = 0; @@ -85,7 +85,7 @@ public class RegionFileStorageExternalCache implements AutoCloseable this.getRegionFileLock.lock(); #if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 - rFile = this.storage.getRegionFile(pos); + regionFile = this.storage.getRegionFile(chunkPos); // keeping the region cache size low helps prevent concurrency issues if (this.storage.regionCache.size() > 150) // max 256 @@ -97,7 +97,7 @@ public class RegionFileStorageExternalCache implements AutoCloseable } } #else - rFile = this.storage.regionCache.getOrDefault(posLong, null); + regionFile = this.storage.regionCache.getOrDefault(chunkPosLong, null); #endif break; @@ -139,19 +139,19 @@ public class RegionFileStorageExternalCache implements AutoCloseable if (retryCount >= maxRetryCount) { - BatchGenerationEnvironment.CHUNK_LOAD_LOGGER.warn("Concurrency issue detected when getting region file for chunk at [" + pos + "]."); + ChunkFileReader.CHUNK_LOAD_LOGGER.warn("Concurrency issue detected when getting region file for chunk at [" + chunkPos + "]."); } - if (rFile != null) + if (regionFile != null) { - return rFile; + return regionFile; } // Then check our custom cache for (RegionFileCache cache : this.regionFileCache) { - if (cache.pos == posLong) + if (cache.pos == chunkPosLong) { return cache.file; } @@ -170,22 +170,22 @@ public class RegionFileStorageExternalCache implements AutoCloseable return null; } - Path regionFilePath = storageFolderPath.resolve("r." + pos.getRegionX() + "." + pos.getRegionZ() + ".mca"); + Path regionFilePath = storageFolderPath.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca"); #if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 - rFile = new RegionFile(regionFilePath.toFile(), storageFolderPath.toFile(), false); + regionFile = new RegionFile(regionFilePath.toFile(), storageFolderPath.toFile(), false); #elif MC_VER <= MC_1_20_4 - rFile = new RegionFile(regionFilePath, storageFolderPath, false); + regionFile = new RegionFile(regionFilePath, storageFolderPath, false); #else - rFile = new RegionFile(new RegionStorageInfo("level", null, "level type"), regionFilePath, storageFolderPath, false); + regionFile = new RegionFile(new RegionStorageInfo("level", null, "level type"), regionFilePath, storageFolderPath, false); #endif - this.regionFileCache.add(new RegionFileCache(ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ()), rFile)); + this.regionFileCache.add(new RegionFileCache(ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ()), regionFile)); while (this.regionFileCache.size() > MAX_CACHE_SIZE) { this.regionFileCache.poll().file.close(); } - return rFile; + return regionFile; } diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepStructureStart.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepStructureStart.java index d94318267..5485e757a 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepStructureStart.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepStructureStart.java @@ -71,6 +71,8 @@ public final class StepStructureStart extends AbstractWorldGenStep { ArrayList chunksToDo = this.getChunkWrappersToGenerate(chunkWrappers); + // TODO should be put in wrapped environment so we can skip some other world gen steps + // SURFACE wouldn't need structure generation either #if MC_VER < MC_1_19_2 if (!this.environment.params.worldGenSettings.generateFeatures()) #elif MC_VER < MC_1_19_4