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 369d657ab..590550713 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 @@ -37,10 +37,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; -import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; -import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; -import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper; @@ -302,6 +299,10 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm + //================// + // variable setup // + //================// + int borderSize = MAX_WORLD_GEN_CHUNK_BORDER_NEEDED; // genEvent.size - 1 converts the even width size to an odd number for MC compatability int refSize = (genEvent.widthInChunks - 1) + (borderSize * 2); @@ -311,29 +312,75 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm LightGetterAdaptor lightGetterAdaptor = new LightGetterAdaptor(this.params.level); DummyLightEngine dummyLightEngine = new DummyLightEngine(lightGetterAdaptor); - - - //====================================// - // offset and generate odd width area // - //====================================// - // reused data between each offset Map chunkSkyLightingByDhPos = Collections.synchronizedMap(new HashMap<>()); Map chunkBlockLightingByDhPos = Collections.synchronizedMap(new HashMap<>()); Map generatedChunkByDhPos = Collections.synchronizedMap(new HashMap<>()); Map chunkWrappersByDhPos = Collections.synchronizedMap(new HashMap<>()); - // futures to handle getting empty chunks - 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)// TODO - .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(?) - // also allows file IO to run in parallel so no one thread is waiting on disk IO (this is only an issue when C2ME is present) - CompletableFuture.allOf(readFutures).join(); + //================================// + // read existing chunks from file // + //================================// + + HashMap> readFutureByDhChunkPos = new HashMap<>(); + + Iterator existingChunkPosIterator = ChunkPosGenStream.getIterator( + genEvent.minPos.getX(), genEvent.minPos.getZ(), + genEvent.widthInChunks, + // 0 radius -> only pull existing chunks from disk + 0); + while (existingChunkPosIterator.hasNext()) + { + ChunkPos chunkPos = existingChunkPosIterator.next(); + DhChunkPos dhChunkPos = new DhChunkPos(chunkPos.x, chunkPos.z); + + CompletableFuture getExistingChunkFuture + // running async allows file IO to run in parallel when C2ME is present + = this.chunkFileReader.createEmptyOrPreExistingChunkAsync( + chunkPos.x, chunkPos.z, + chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos); + + readFutureByDhChunkPos.put(dhChunkPos, getExistingChunkFuture); + } + + // normally DH will handle each of these futures serially + // but if C2ME is present these will be completed in parallel + for (CompletableFuture readChunkFuture : readFutureByDhChunkPos.values()) + { + readChunkFuture.join(); + } + + + + //===================================// + // create empty chunks for world gen // + //===================================// + + Iterator emptyChunkPosIterator = ChunkPosGenStream.getIterator( + genEvent.minPos.getX(), genEvent.minPos.getZ(), + genEvent.widthInChunks, + // the extra radius of 8 is to account for structure references which need a chunk radius of 8 + 8); + while (emptyChunkPosIterator.hasNext()) + { + ChunkPos chunkPos = emptyChunkPosIterator.next(); + DhChunkPos dhChunkPos = new DhChunkPos(chunkPos.x, chunkPos.z); + + // create empty chunks outside the generation radius + if (!readFutureByDhChunkPos.containsKey(dhChunkPos)) + { + ChunkAccess chunk = ChunkFileReader.CreateEmptyChunk(this.params.level, chunkPos); + generatedChunkByDhPos.put(dhChunkPos, chunk); + } + } + + + + //=================// + // generate chunks // + //=================// try { @@ -358,14 +405,14 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm int centerZ = refPosZ + radius + zOffset; // get/create the list of chunks we're going to generate - IEmptyChunkRetrievalFunc fallbackFunc = - (chunkPosX, chunkPosZ) -> Objects.requireNonNull( - generatedChunkByDhPos.get(new DhChunkPos(chunkPosX, chunkPosZ)), - () -> String.format("Requested chunk [%d, %d] unavailable during world generation", chunkPosX, chunkPosZ)); + IEmptyChunkRetrievalFunc fallbackChunkGetterFunc = + (chunkPosX, chunkPosZ) -> Objects.requireNonNull( + generatedChunkByDhPos.get(new DhChunkPos(chunkPosX, chunkPosZ)), + () -> String.format("Requested chunk [%d, %d] unavailable during world generation", chunkPosX, chunkPosZ)); ArrayGridList regionChunks = new ArrayGridList<>( refSize, - (relX, relZ) -> fallbackFunc.getChunk( + (relX, relZ) -> fallbackChunkGetterFunc.getChunk( relX + refPosX + xOffsetFinal, relZ + refPosZ + zOffsetFinal)); @@ -381,10 +428,10 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm ChunkStatus.STRUCTURE_STARTS, radius, // this method shouldn't be necessary since we're passing in a pre-populated // list of chunks, but just in case - fallbackFunc + fallbackChunkGetterFunc ); lightGetterAdaptor.setRegion(region); - genEvent.threadedParam.makeStructFeat(region, this.params); + genEvent.threadedParam.makeStructFeatManager(region, this.params); diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ThreadWorldGenParams.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ThreadWorldGenParams.java index 60cf1b698..cd54bac95 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ThreadWorldGenParams.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ThreadWorldGenParams.java @@ -89,7 +89,7 @@ public final class ThreadWorldGenParams // builders // //==========// - public void makeStructFeat(WorldGenLevel genLevel, GlobalWorldGenParams param) + public void makeStructFeatManager(WorldGenLevel genLevel, GlobalWorldGenParams param) { #if MC_VER < MC_1_18_2 this.structFeat = new WorldGenStructFeatManager(param.worldGenSettings, genLevel); diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkCompoundTagParser.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkCompoundTagParser.java index d7b73644c..652096195 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkCompoundTagParser.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkCompoundTagParser.java @@ -88,32 +88,16 @@ import net.minecraft.world.level.material.Fluid; public class ChunkCompoundTagParser { - private static final AtomicBoolean ZERO_CHUNK_POS_ERROR_LOGGED_REF = new AtomicBoolean(false); - public static final DhLogger LOGGER = new DhLoggerBuilder() .name("LOD Chunk Reader") .fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile) .build(); - - #if MC_VER >= MC_1_21_9 - // BLOCK_STATE_CODEC can no longer be statically created since - // it needs a level reference - #elif MC_VER >= MC_1_19_2 - private static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); - #elif MC_VER >= MC_1_18_2 - private static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codec(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); - #endif - - private static final String TAG_UPGRADE_DATA = "UpgradeData"; - private static final String BLOCK_TICKS_TAG_18 = "block_ticks"; - private static final String FLUID_TICKS_TAG_18 = "fluid_ticks"; - private static final String BLOCK_TICKS_TAG_PRE18 = "TileTicks"; - private static final String FLUID_TICKS_TAG_PRE18 = "LiquidTicks"; + private static final AtomicBoolean ZERO_CHUNK_POS_ERROR_LOGGED_REF = new AtomicBoolean(false); + private static final ConcurrentHashMap LOGGED_ERROR_MESSAGE_MAP = new ConcurrentHashMap<>(); private static boolean lightingSectionErrorLogged = false; - private static final ConcurrentHashMap LOGGED_ERROR_MESSAGE_MAP = new ConcurrentHashMap<>(); @@ -129,11 +113,18 @@ public class ChunkCompoundTagParser CompoundTag tagLevel = chunkData; #endif + + + //=======================// + // validate the chunkPos // + //=======================// + int chunkX = CompoundTagUtil.getInt(tagLevel,"xPos"); int chunkZ = CompoundTagUtil.getInt(tagLevel, "zPos"); - ChunkPos actualPos = new ChunkPos(chunkX, chunkZ); + ChunkPos actualChunkPos = new ChunkPos(chunkX, chunkZ); - if (!Objects.equals(chunkPos, actualPos)) + // confirm chunk pos is correct + if (!Objects.equals(chunkPos, actualChunkPos)) { if (chunkX == 0 && chunkZ == 0) { @@ -144,19 +135,27 @@ public class ChunkCompoundTagParser "This might happen if the world was created using an external program. \n" + "DH will attempt to parse the chunk anyway and won't log this message again.\n" + "If issues arise please try optimizing your world to fix this issue. \n" + - "World optimization can be done from the singleplayer world selection screen."+ - ""); + "World optimization can be done from the singleplayer world selection screen." + + " "); } } else { - // everything is on one line to fix a JDK 17 compiler issue - // if the issue is ever resolved, feel free to make this multi-line for readability - LOGGER.error("Chunk file at ["+chunkPos.toString()+"] is in the wrong location. \nPlease try optimizing your world to fix this issue. \nWorld optimization can be done from the singleplayer world selection screen. \n(Expected pos: ["+chunkPos.toString()+"], actual ["+actualPos.toString()+"])"); + LOGGER.error("Chunk file at ["+chunkPos.toString()+"] is in the wrong location. \n" + + "Please try optimizing your world to fix this issue. \n" + + "World optimization can be done from the singleplayer world selection screen. \n" + + "(Expected pos: ["+chunkPos.toString()+"], actual ["+actualChunkPos.toString()+"])" + + " "); return null; } } + + + //==========================// + // ignore incomplete chunks // + //==========================// + #if MC_VER < MC_1_20_6 ChunkStatus.ChunkType chunkType; #else @@ -169,277 +168,90 @@ public class ChunkCompoundTagParser { 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 - + #elif MC_VER < MC_1_19_2 + if (chunkType == ChunkStatus.ChunkType.PROTOCHUNK) + { + return null; + } #else - - // ignore blending data, there appears to be an issue with parsing it in 1.21.6 - BlendingData blendingData = null; - if (chunkType == ChunkType.PROTOCHUNK) { return null; } #endif - 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 CompoundTagUtil.getCompoundTag()) - // - //#if MC_VER < MC_1_17_1 - //upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10) - // ? 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(CompoundTagUtil.getCompoundTag(tagLevel, TAG_UPGRADE_DATA), level) - // : UpgradeData.EMPTY; - //#else - //upgradeData = tagLevel.contains(TAG_UPGRADE_DATA) - // ? new UpgradeData(CompoundTagUtil.getCompoundTag(tagLevel, TAG_UPGRADE_DATA), level) - // : UpgradeData.EMPTY; - //#endif - boolean isLightOn = CompoundTagUtil.getBoolean(tagLevel, "isLightOn"); + //===========// + // get ticks // + //===========// + #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 , + level.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), #if MC_VER >= MC_1_17_1 level, #endif chunkPos, level.getLevel().getChunkSource().getGenerator().getBiomeSource(), tagLevel.contains("Biomes", 11) ? tagLevel.getIntArray("Biomes") : null); + String BLOCK_TICKS_TAG_PRE18 = "TileTicks"; TickList blockTicks = tagLevel.contains(BLOCK_TICKS_TAG_PRE18, 9) ? ChunkTickList.create(tagLevel.getList(BLOCK_TICKS_TAG_PRE18, 10), Registry.BLOCK::getKey, Registry.BLOCK::get) : new ProtoTickList(block -> (block == null || block.defaultBlockState().isAir()), chunkPos, - tagLevel.getList("ToBeTicked", 9)#if MC_VER >= MC_1_17_1 , level #endif ); + tagLevel.getList("ToBeTicked", 9) #if MC_VER >= MC_1_17_1 , level #endif ); + String FLUID_TICKS_TAG_PRE18 = "LiquidTicks"; TickList fluidTicks = tagLevel.contains(FLUID_TICKS_TAG_PRE18, 9) ? ChunkTickList.create(tagLevel.getList(FLUID_TICKS_TAG_PRE18, 10), Registry.FLUID::getKey, Registry.FLUID::get) : new ProtoTickList(fluid -> (fluid == null || fluid == Fluids.EMPTY), chunkPos, - tagLevel.getList("LiquidsToBeTicked", 9)#if MC_VER >= MC_1_17_1 , level #endif ); + tagLevel.getList("LiquidsToBeTicked", 9) #if MC_VER >= MC_1_17_1 , level #endif ); #else - #if MC_VER < MC_1_19_4 - LevelChunkTicks blockTicks = LevelChunkTicks.load(tagLevel.getList(BLOCK_TICKS_TAG_18, 10), - string -> Registry.BLOCK.getOptional(ResourceLocation.tryParse(string)), chunkPos); - LevelChunkTicks fluidTicks = LevelChunkTicks.load(tagLevel.getList(FLUID_TICKS_TAG_18, 10), - string -> Registry.FLUID.getOptional(ResourceLocation.tryParse(string)), chunkPos); - #elif MC_VER < MC_1_21_4 - LevelChunkTicks blockTicks = LevelChunkTicks.load(tagLevel.getList(BLOCK_TICKS_TAG_18, 10), - (string -> BuiltInRegistries.BLOCK.getOptional(ResourceLocation.tryParse(string))), chunkPos); - LevelChunkTicks fluidTicks = LevelChunkTicks.load(tagLevel.getList(FLUID_TICKS_TAG_18, 10), - string -> BuiltInRegistries.FLUID.getOptional(ResourceLocation.tryParse(string)), chunkPos); - #else - // do we need the ticks for what we're doing? - LevelChunkTicks blockTicks = new LevelChunkTicks<>(); - LevelChunkTicks fluidTicks = new LevelChunkTicks<>(); - #endif + // ticks shouldn't be needed so ignore them for MC versions after 1.18.2 + LevelChunkTicks blockTicks = new LevelChunkTicks<>(); + LevelChunkTicks fluidTicks = new LevelChunkTicks<>(); #endif + + + //=====================// + // get misc properties // + //=====================// + LevelChunkSection[] levelChunkSections = readSections(level, chunkPos, tagLevel); + long inhabitedTime = CompoundTagUtil.getLong(tagLevel, "InhabitedTime"); + boolean isLightOn = CompoundTagUtil.getBoolean(tagLevel, "isLightOn"); + + + + //============// + // make chunk // + //============// - // ====================== Make the chunk ========================= #if MC_VER < MC_1_18_2 - LevelChunk chunk = new LevelChunk((Level) level.getLevel(), chunkPos, chunkBiomeContainer, upgradeData, blockTicks, + LevelChunk chunk = new LevelChunk((Level) level.getLevel(), chunkPos, chunkBiomeContainer, UpgradeData.EMPTY, blockTicks, fluidTicks, inhabitedTime, levelChunkSections, null); #else - LevelChunk chunk = new LevelChunk((Level) level, chunkPos, upgradeData, blockTicks, - fluidTicks, inhabitedTime, levelChunkSections, null, blendingData); + LevelChunk chunk = new LevelChunk((Level) level, chunkPos, UpgradeData.EMPTY, blockTicks, + fluidTicks, inhabitedTime, levelChunkSections, null, null); #endif + // Set some states after object creation chunk.setLightCorrect(isLightOn); readHeightmaps(chunk, chunkData); - //readPostPocessings(chunk, chunkData); + return chunk; } - private static LevelChunkSection[] readSections(LevelAccessor level, ChunkPos chunkPos, CompoundTag chunkData) - { - #if MC_VER < MC_1_21_9 - // BLOCK_STATE_CODEC is created statically - // TODO clean up this code separation - #else - final Codec> BLOCK_STATE_CODEC = PalettedContainerFactory.create(level.registryAccess()).blockStatesContainerCodec(); - #endif - - - #if MC_VER >= MC_1_18_2 - #if MC_VER < MC_1_19_4 - Registry biomes = level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY); - #elif MC_VER < MC_1_21_3 - Registry biomes = level.registryAccess().registryOrThrow(Registries.BIOME); - #else - Registry biomes = level.registryAccess().lookupOrThrow(Registries.BIOME); - #endif - #if MC_VER < MC_1_18_2 - Codec> biomeCodec = PalettedContainer.codec( - biomes, biomes.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getOrThrow(Biomes.PLAINS)); - #elif MC_VER < MC_1_19_2 - Codec>> biomeCodec = PalettedContainer.codec( - biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getHolderOrThrow(Biomes.PLAINS)); - #elif MC_VER < MC_1_21_3 - Codec>> biomeCodec = PalettedContainer.codecRW( - biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getHolderOrThrow(Biomes.PLAINS)); - #elif MC_VER < MC_1_21_9 - Codec>> biomeCodec = PalettedContainer.codecRW( - biomes.asHolderIdMap(), biomes.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomes.getOrThrow(Biomes.PLAINS)); - #else - Codec>> biomeCodec = PalettedContainer.codecRW( - biomes.holderByNameCodec(), PalettedContainerFactory.create(level.registryAccess()).biomeStrategy(), biomes.getOrThrow(Biomes.PLAINS)); - #endif - #endif - - int sectionYIndex = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif - LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYIndex]; - - ListTag tagSections = CompoundTagUtil.getListTag(chunkData, "Sections", 10); - if (tagSections == null || tagSections.isEmpty()) - { - tagSections = CompoundTagUtil.getListTag(chunkData, "sections", 10); - } - - - if (tagSections != null) - { - for (int j = 0; j < tagSections.size(); ++j) - { - CompoundTag tagSection = CompoundTagUtil.getCompoundTag(tagSections, j); - if (tagSection == null) - { - continue; - } - - final int sectionYPos = CompoundTagUtil.getByte(tagSection, "Y"); - - #if MC_VER < MC_1_18_2 - if (tagSection.contains("Palette", 9) && tagSection.contains("BlockStates", 12)) - { - LevelChunkSection levelChunkSection = new LevelChunkSection(sectionYPos << 4); - levelChunkSection.getStates().read(tagSection.getList("Palette", 10), - tagSection.getLongArray("BlockStates")); - levelChunkSection.recalcBlockCounts(); - if (!levelChunkSection.isEmpty()) - chunkSections[#if MC_VER < MC_1_17_1 sectionYPos #else level.getSectionIndexFromSectionY(sectionYPos) #endif ] - = levelChunkSection; - } - #else - int sectionId = level.getSectionIndexFromSectionY(sectionYPos); - if (sectionId >= 0 && sectionId < chunkSections.length) - { - PalettedContainer blockStateContainer; - #if MC_VER < MC_1_18_2 - PalettedContainer biomeContainer; - #else - PalettedContainer> biomeContainer; - #endif - - - boolean containsBlockStates; - #if MC_VER < MC_1_21_5 - containsBlockStates = tagSection.contains("block_states", 10); - #else - containsBlockStates = tagSection.contains("block_states"); - #endif - - if (containsBlockStates) - { - #if MC_VER < MC_1_20_6 - 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, CompoundTagUtil.getCompoundTag(tagSection, "block_states")) - .promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string)) - .getOrThrow((message) -> logErrorAndReturnException(message)); - #endif - } - else - { - #if MC_VER < MC_1_21_9 - blockStateContainer = new PalettedContainer(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); - #else - blockStateContainer = PalettedContainerFactory.create(level.registryAccess()).createForBlockStates(); - #endif - } - - - #if MC_VER < MC_1_18_2 - biomeContainer = tagSection.contains("biomes", 10) - ? biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomes")).promotePartial(string -> logErrors(chunkPos, sectionYPos, string)).getOrThrow(false, (message) -> logWarningOnce(message)) - : new PalettedContainer(biomes, biomes.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); - #else - - - boolean containsBiomes; - #if MC_VER < MC_1_21_5 - containsBiomes = tagSection.contains("biomes", 10); - #else - containsBiomes = tagSection.contains("biomes"); - #endif - - if (containsBiomes) - { - #if MC_VER < MC_1_20_6 - 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, CompoundTagUtil.getCompoundTag(tagSection, "biomes")) - .promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string)) - .getOrThrow((message) -> logErrorAndReturnException(message)); - #endif - } - else - { - #if MC_VER < MC_1_21_3 - biomeContainer = new PalettedContainer>( - biomes.asHolderIdMap(), - biomes.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); - #elif MC_VER < MC_1_21_9 - biomeContainer = new PalettedContainer>(biomes.asHolderIdMap(), - biomes.getOrThrow(Biomes.PLAINS), - PalettedContainer.Strategy.SECTION_BIOMES); - #else - biomeContainer = PalettedContainerFactory.create(level.registryAccess()).createForBiomes(); - #endif - - } - - #endif - - #if MC_VER < MC_1_20_1 - chunkSections[sectionId] = new LevelChunkSection(sectionYPos, blockStateContainer, biomeContainer); - #else - chunkSections[sectionId] = new LevelChunkSection(blockStateContainer, biomeContainer); - #endif - } - #endif - - } - } - return chunkSections; - } + + + + //==========================// + // chunk type // + // (incomplete chunk check) // + //==========================// + private static #if MC_VER < MC_1_20_6 ChunkStatus.ChunkType #elif MC_VER < MC_1_21_1 ChunkType - #else ChunkType #endif - readChunkType(CompoundTag tagLevel) + #else ChunkType #endif + readChunkType(CompoundTag tagLevel) { String statusString = CompoundTagUtil.getString(tagLevel,"Status"); if (statusString != null) @@ -457,104 +269,277 @@ public class ChunkCompoundTagParser return ChunkType.PROTOCHUNK; #endif } + + + + //=================// + // chunk sections // + // (Blocks/biomes) // + //=================// + + /** handles both blocks and biomes */ + private static LevelChunkSection[] readSections(LevelAccessor level, ChunkPos chunkPos, CompoundTag chunkData) + { + int sectionYIndex = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif + LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYIndex]; + + ListTag tagSections = CompoundTagUtil.getListTag(chunkData, "Sections", 10); + // try lower-case "sections" if capital "Sections" is missing + if (tagSections == null + || tagSections.isEmpty()) + { + tagSections = CompoundTagUtil.getListTag(chunkData, "sections", 10); + } + + + if (tagSections != null) + { + for (int j = 0; j < tagSections.size(); ++j) + { + CompoundTag tagSection = CompoundTagUtil.getCompoundTag(tagSections, j); + if (tagSection == null) + { + continue; + } + + final int sectionYPos = CompoundTagUtil.getByte(tagSection, "Y"); + + + + //===================// + // get blocks/biomes // + //===================// + + #if MC_VER < MC_1_18_2 + if (tagSection.contains("Palette", 9) + && tagSection.contains("BlockStates", 12)) + { + LevelChunkSection levelChunkSection = new LevelChunkSection(sectionYPos << 4); + levelChunkSection.getStates().read(tagSection.getList("Palette", 10), tagSection.getLongArray("BlockStates")); + levelChunkSection.recalcBlockCounts(); + if (!levelChunkSection.isEmpty()) + { + int sectionIndex; + #if MC_VER < MC_1_17_1 + sectionIndex = sectionYPos; + #else + sectionIndex = level.getSectionIndexFromSectionY(sectionYPos); + #endif + + chunkSections[sectionIndex] = levelChunkSection; + } + } + + #else + + int sectionId = level.getSectionIndexFromSectionY(sectionYPos); + if (sectionId >= 0 + && sectionId < chunkSections.length) + { + //========// + // blocks // + //========// + + PalettedContainer blockStateContainer; + + boolean containsBlockStates = CompoundTagUtil.contains(tagSection, "block_states", 10); + if (containsBlockStates) + { + Codec> blockStateCodec = getBlockStateCodec(level); + + #if MC_VER < MC_1_20_6 + blockStateContainer = blockStateCodec + .parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "block_states")) + .promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string)) + .getOrThrow(false, (message) -> logParsingWarningOnce(message)); + #else + blockStateContainer = blockStateCodec + .parse(NbtOps.INSTANCE, CompoundTagUtil.getCompoundTag(tagSection, "block_states")) + .promotePartial(string -> logBlockDeserializationWarning(chunkPos, sectionYPos, string)) + .getOrThrow((message) -> logErrorAndReturnException(message)); + #endif + } + else + { + #if MC_VER < MC_1_21_9 + blockStateContainer = new PalettedContainer(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES); + #else + blockStateContainer = PalettedContainerFactory.create(level.registryAccess()).createForBlockStates(); + #endif + } + + + + //========// + // biomes // + //========// + + Registry biomeRegistry = getBiomeRegistry(level); + + #if MC_VER < MC_1_18_2 + Codec> biomeCodec; + #else + Codec>> biomeCodec; + #endif + biomeCodec = getBiomeCodec(level, biomeRegistry); + + #if MC_VER < MC_1_18_2 + PalettedContainer biomeContainer; + #else + PalettedContainer> biomeContainer; + #endif + + #if MC_VER < MC_1_18_2 + biomeContainer = tagSection.contains("biomeRegistry", 10) + ? biomeCodec.parse(NbtOps.INSTANCE, tagSection.getCompound("biomeRegistry")).promotePartial(string -> logErrors(chunkPos, sectionYPos, string)).getOrThrow(false, (message) -> logWarningOnce(message)) + : new PalettedContainer(biomeRegistry, biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); + #else + { + CompoundTag biomeTag = CompoundTagUtil.getCompoundTag(tagSection, "biomeRegistry"); + if (biomeTag == null) + { + biomeTag = CompoundTagUtil.getCompoundTag(tagSection, "biomes"); + } + + if (biomeTag != null) + { + #if MC_VER < MC_1_20_6 + biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, biomeTag) + .promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string)) + .getOrThrow(false, (message) -> logParsingWarningOnce(message)); + #else + biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, biomeTag) + .promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string)) + .getOrThrow((message) -> logErrorAndReturnException(message)); + #endif + } + else + { + // no biomes found, use the default (probably plains) + + #if MC_VER < MC_1_21_3 + biomeContainer = new PalettedContainer>( + biomeRegistry.asHolderIdMap(), + biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); + #elif MC_VER < MC_1_21_9 + biomeContainer = new PalettedContainer>(biomeRegistry.asHolderIdMap(), + biomeRegistry.getOrThrow(Biomes.PLAINS), + PalettedContainer.Strategy.SECTION_BIOMES); + #else + biomeContainer = PalettedContainerFactory.create(level.registryAccess()).createForBiomes(); + #endif + } + } + #endif + + #if MC_VER < MC_1_20_1 + chunkSections[sectionId] = new LevelChunkSection(sectionYPos, blockStateContainer, biomeContainer); + #else + chunkSections[sectionId] = new LevelChunkSection(blockStateContainer, biomeContainer); + #endif + } + #endif + + } + } + + return chunkSections; + } + + private static Codec> getBlockStateCodec(LevelAccessor level) + { + #if MC_VER <= MC_1_18_2 + return PalettedContainer.codec(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); + #elif MC_VER <= MC_1_19_2 + return PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); + #else + return PalettedContainerFactory.create(level.registryAccess()).blockStatesContainerCodec(); + #endif + } + + private static Registry getBiomeRegistry(LevelAccessor level) + { + #if MC_VER < MC_1_18_2 + // not needed + return null; + #elif MC_VER < MC_1_19_4 + return level.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY); + #elif MC_VER < MC_1_21_3 + return level.registryAccess().registryOrThrow(Registries.BIOME); + #else + return level.registryAccess().lookupOrThrow(Registries.BIOME); + #endif + } + private static + #if MC_VER < MC_1_18_2 Codec> + #else Codec>> + #endif + getBiomeCodec(LevelAccessor level, Registry biomeRegistry) + { + #if MC_VER < MC_1_18_2 + Codec> biomeCodec = PalettedContainer.codec( + biomeRegistry, biomeRegistry.byNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getOrThrow(Biomes.PLAINS)); + #elif MC_VER < MC_1_19_2 + return PalettedContainer.codec( + biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS)); + #elif MC_VER < MC_1_21_3 + return PalettedContainer.codecRW( + biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getHolderOrThrow(Biomes.PLAINS)); + #elif MC_VER < MC_1_21_9 + return PalettedContainer.codecRW( + biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getOrThrow(Biomes.PLAINS)); + #else + return PalettedContainer.codecRW( + biomeRegistry.holderByNameCodec(), PalettedContainerFactory.create(level.registryAccess()).biomeStrategy(), biomeRegistry.getOrThrow(Biomes.PLAINS)); + #endif + } + + + + //============// + // heightmaps // + //============// + private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData) { CompoundTag tagHeightmaps = CompoundTagUtil.getCompoundTag(chunkData, "Heightmaps"); - if (tagHeightmaps != null) + if (tagHeightmaps == null) { - for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter()) - { - String heightmap = type.getSerializationKey(); - #if MC_VER < MC_1_21_5 - if (tagHeightmaps.contains(heightmap, 12)) - { - chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmap)); - } - #else - if (tagHeightmaps.contains(heightmap)) - { - Optional optionalHeightmap = tagHeightmaps.getLongArray(heightmap); - if (optionalHeightmap.isPresent()) - { - chunk.setHeightmap(type, optionalHeightmap.get()); - } - } - #endif - } - - Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter()); + return; } - } - // commented out as a test as of 2025-06-04 to see if this is actually necessary for DH - // DH probably doesn't need any chunk post-processing data - //private static void readPostPocessings(LevelChunk chunk, CompoundTag chunkData) - //{ - // ListTag tagPostProcessings = CompoundTagUtil.getListTag(chunkData,"PostProcessing", 9); - // if (tagPostProcessings != null) - // { - // for (int i = 0; i < tagPostProcessings.size(); ++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(CompoundTagUtil.getShort(listTag3, j)), i); - // #endif - // } - // } - // } - //} - #if MC_VER >= MC_1_18_2 - private static BlendingData readBlendingData(CompoundTag chunkData) - { - BlendingData blendingData = null; - boolean containsBlendingData; - #if MC_VER < MC_1_21_5 - containsBlendingData = chunkData.contains("blending_data", 10); - #else - containsBlendingData = chunkData.contains("blending_data"); - #endif - - if (containsBlendingData) + for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter()) { - @SuppressWarnings({"unchecked", "rawtypes"}) - Dynamic blendingDataTag = new Dynamic(NbtOps.INSTANCE, chunkData.getCompound("blending_data")); + String heightmapKey = type.getSerializationKey(); - try + #if MC_VER < MC_1_21_5 + if (tagHeightmaps.contains(heightmapKey, 12)) { - #if MC_VER < MC_1_21_3 - blendingData = BlendingData.CODEC.parse(blendingDataTag).resultOrPartial((message) -> logParsingWarningOnce(message)).orElse(null); - #else - // blending data appears to have changed as of 1.21.6 causing a class cast exception here due to it being wrapped in a Java.Optional - blendingData = BlendingData.unpack(BlendingData.Packed.CODEC.parse(blendingDataTag).resultOrPartial((message) -> logParsingWarningOnce(message)).orElse(null)); - #endif + chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmapKey)); } - catch (Exception e) + #else + if (tagHeightmaps.contains(heightmapKey)) { - String message = e.getMessage(); - if (message == null || message.trim().isEmpty()) + Optional optionalHeightmap = tagHeightmaps.getLongArray(heightmapKey); + if (optionalHeightmap.isPresent()) { - message = "Failed to parse blending data"; + chunk.setHeightmap(type, optionalHeightmap.get()); } - - logParsingWarningOnce(message, e); } + #endif } - return blendingData; + + Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter()); } - #endif - //=====================// - // read chunk lighting // - //=====================// + //================// + // chunk lighting // + //================// - /** https://minecraft.wiki/w/Chunk_format */ + /** source: https://minecraft.wiki/w/Chunk_format */ public static CombinedChunkLightStorage readLight(ChunkAccess chunk, CompoundTag chunkData) { #if MC_VER <= MC_1_17_1 @@ -746,5 +731,6 @@ public class ChunkCompoundTagParser } } + + } - 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 index 46e0b2343..e12f64042 100644 --- 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 @@ -107,13 +107,6 @@ public class ChunkFileReader implements AutoCloseable ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); DhChunkPos dhChunkPos = new DhChunkPos(chunkX, chunkZ); -// if (true) -// { -// ChunkAccess newChunk = CreateEmptyChunk(this.params.level, chunkPos); -// generatedChunkByDhPos.put(dhChunkPos, newChunk); -// return CompletableFuture.completedFuture(newChunk); -// } - if (generatedChunkByDhPos.containsKey(dhChunkPos)) { return CompletableFuture.completedFuture(generatedChunkByDhPos.get(dhChunkPos)); @@ -155,7 +148,6 @@ public class ChunkFileReader implements AutoCloseable 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; @@ -265,8 +257,6 @@ public class ChunkFileReader implements AutoCloseable { try { - CHUNK_LOAD_LOGGER.debug("DistantHorizons: Loading chunk [" + chunkPos + "] from disk."); - @Nullable ChunkAccess chunk = ChunkCompoundTagParser.createFromTag(level, chunkPos, chunkTagData); if (chunk != null) @@ -298,7 +288,8 @@ public class ChunkFileReader implements AutoCloseable } } } - private static ProtoChunk CreateEmptyChunk(ServerLevel level, ChunkPos chunkPos) + + public static ProtoChunk CreateEmptyChunk(ServerLevel level, ChunkPos chunkPos) { #if MC_VER <= MC_1_16_5 return new ProtoChunk(chunkPos, UpgradeData.EMPTY); 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 index 9e442cfd1..f175815c8 100644 --- 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 @@ -134,4 +134,15 @@ public class CompoundTagUtil + public static boolean contains(CompoundTag tag, String key, int index) + { + #if MC_VER < MC_1_21_5 + return tag.contains(key, index); + #else + return tag.contains(key); + #endif + } + + + } diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepFeatures.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepFeatures.java index fecc60e9e..141c0e64c 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepFeatures.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepFeatures.java @@ -27,7 +27,6 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ProtoChunk; import net.minecraft.world.level.levelgen.Heightmap; import com.seibel.distanthorizons.core.logging.DhLogger; diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepNoise.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepNoise.java index d421543a2..3aad819cb 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepNoise.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepNoise.java @@ -27,7 +27,6 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadWorldGen import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; -import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import net.minecraft.world.level.chunk.ChunkAccess; #if MC_VER >= MC_1_18_2 diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepStructureReference.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepStructureReference.java index a96965c48..70fdb6f6f 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepStructureReference.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepStructureReference.java @@ -28,7 +28,6 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadWorldGen import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ProtoChunk; #if MC_VER <= MC_1_20_4 import net.minecraft.world.level.chunk.ChunkStatus; diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepSurface.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepSurface.java index f56521085..a13ba63f2 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepSurface.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepSurface.java @@ -28,7 +28,6 @@ import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadWorldGen import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ProtoChunk; #if MC_VER <= MC_1_20_4 import net.minecraft.world.level.chunk.ChunkStatus;