diff --git a/.editorconfig b/.editorconfig index c9290aae9..0c9c384c3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,7 @@ insert_final_newline = false max_line_length = 1000 tab_width = 4 trim_trailing_whitespace = false -ij_continuation_indent_size = 8 +ij_continuation_indent_size = 4 ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on ij_formatter_tags_enabled = true diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetup.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetup.java index c7b851f36..b8fb6a99f 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetup.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetup.java @@ -56,7 +56,6 @@ public class DependencySetup SingletonInjector.INSTANCE.bind(IVersionConstants.class, VersionConstants.INSTANCE); SingletonInjector.INSTANCE.bind(IWrapperFactory.class, WrapperFactory.INSTANCE); SingletonInjector.INSTANCE.bind(IKeyedClientLevelManager.class, KeyedClientLevelManager.INSTANCE); - DependencySetupDoneCheck.isDone = true; } public static void createServerBindings() diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/WrapperFactory.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/WrapperFactory.java index b692b9c11..ce789634a 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/WrapperFactory.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/WrapperFactory.java @@ -38,7 +38,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvironmentWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper; import net.minecraft.client.multiplayer.ClientLevel; #if MC_VER > MC_1_17_1 import net.minecraft.core.Holder; @@ -67,7 +67,7 @@ public class WrapperFactory implements IWrapperFactory //==============// @Override - public AbstractBatchGenerationEnvironmentWrapper createBatchGenerator(IDhLevel targetLevel) + public IBatchGeneratorEnvironmentWrapper createBatchGenerator(IDhLevel targetLevel) { if (targetLevel instanceof IDhServerLevel) { diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/AbstractDhTintGetter.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/AbstractDhTintGetter.java index cb7a334ac..1d7ccb579 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/AbstractDhTintGetter.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/AbstractDhTintGetter.java @@ -124,10 +124,10 @@ public abstract class AbstractDhTintGetter implements BlockAndTintGetter int rollingBlue = 0; int xMin = mutableBlockPos.getX() - this.smoothingRadiusInBlocks; - int xMax = mutableBlockPos.getX() + this.smoothingRadiusInBlocks; + int xMax = mutableBlockPos.getX() + this.smoothingRadiusInBlocks + 1; // +1 to account for the center block int zMin = mutableBlockPos.getZ() - this.smoothingRadiusInBlocks; - int zMax = mutableBlockPos.getZ() + this.smoothingRadiusInBlocks; + int zMax = mutableBlockPos.getZ() + this.smoothingRadiusInBlocks + 1; int levelMinY = this.clientLevelWrapper.getMinHeight(); @@ -141,7 +141,7 @@ public abstract class AbstractDhTintGetter implements BlockAndTintGetter // this can return the same position/datapoint for larger LODs duplicating work, // however for small smoothing ranges that isn't a big deal and for large LODs // we ignore smoothing anyway - long dataPoint = this.fullDataSource.getDataPointAtBlockPos(mutableBlockPos.getX(), mutableBlockPos.getY(), mutableBlockPos.getZ()); + long dataPoint = this.fullDataSource.getDataPointAtBlockPos(mutableBlockPos.getX(), mutableBlockPos.getY(), mutableBlockPos.getZ(), levelMinY); if (dataPoint == FullDataPointUtil.EMPTY_DATA_POINT) { continue; diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/BlockStateWrapper.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/BlockStateWrapper.java index 216f7452e..8829c8bb0 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/BlockStateWrapper.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/block/BlockStateWrapper.java @@ -274,7 +274,7 @@ public class BlockStateWrapper implements IBlockStateWrapper HashSet baseIgnoredBlock = new HashSet<>(); baseIgnoredBlock.add(AIR_STRING); - rendererIgnoredBlocks = getBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderBlockCsv, baseIgnoredBlock, levelWrapper); + rendererIgnoredBlocks = getAllBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderBlockCsv, baseIgnoredBlock, levelWrapper); return rendererIgnoredBlocks; } /** @@ -291,7 +291,7 @@ public class BlockStateWrapper implements IBlockStateWrapper HashSet baseIgnoredBlock = new HashSet<>(); baseIgnoredBlock.add(AIR_STRING); - rendererIgnoredCaveBlocks = getBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderCaveBlockCsv, baseIgnoredBlock, levelWrapper); + rendererIgnoredCaveBlocks = getAllBlockWrappers(Config.Client.Advanced.Graphics.Culling.ignoredRenderCaveBlockCsv, baseIgnoredBlock, levelWrapper); return rendererIgnoredCaveBlocks; } @@ -302,7 +302,7 @@ public class BlockStateWrapper implements IBlockStateWrapper // lod builder helpers // - private static HashSet getBlockWrappers(ConfigEntry config, HashSet baseResourceLocations, ILevelWrapper levelWrapper) + private static HashSet getAllBlockWrappers(ConfigEntry config, HashSet baseResourceLocations, ILevelWrapper levelWrapper) { // get the base blocks HashSet blockStringList = new HashSet<>(); @@ -318,9 +318,9 @@ public class BlockStateWrapper implements IBlockStateWrapper blockStringList.addAll(Arrays.asList(ignoreBlockCsv.split(","))); } - return getBlockWrappers(blockStringList, levelWrapper); + return getAllBlockWrappers(blockStringList, levelWrapper); } - private static HashSet getBlockWrappers(HashSet blockResourceLocationSet, ILevelWrapper levelWrapper) + private static HashSet getAllBlockWrappers(HashSet blockResourceLocationSet, ILevelWrapper levelWrapper) { // deserialize each of the given resource locations HashSet blockStateWrappers = new HashSet<>(); @@ -587,7 +587,8 @@ public class BlockStateWrapper implements IBlockStateWrapper // we need the final string for the concurrent hash map later final String finalResourceStateString = resourceStateString; - if (finalResourceStateString.equals(AIR_STRING) || finalResourceStateString.equals("")) // the empty string shouldn't normally happen, but just in case + if (finalResourceStateString.equals(AIR_STRING) + || finalResourceStateString.equals("")) // the empty string shouldn't normally happen, but just in case { return AIR; } diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkWrapper.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkWrapper.java index e04273faf..0e4eca66b 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkWrapper.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/chunk/ChunkWrapper.java @@ -21,7 +21,6 @@ package com.seibel.distanthorizons.common.wrappers.chunk; import com.seibel.distanthorizons.common.wrappers.block.BiomeWrapper; import com.seibel.distanthorizons.common.wrappers.block.BlockStateWrapper; import com.seibel.distanthorizons.common.wrappers.misc.MutableBlockPosWrapper; -import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhChunkPos; @@ -98,9 +97,9 @@ public class ChunkWrapper implements IChunkWrapper private int maxNonEmptyHeight = Integer.MAX_VALUE; /** will be null if we are using MC heightmaps */ - private final int[][] solidHeightMap; + private int[][] solidHeightMap = null; /** will be null if we are using MC heightmaps */ - private final int[][] lightBlockingHeightMap; + private int[][] lightBlockingHeightMap = null; @@ -109,23 +108,18 @@ public class ChunkWrapper implements IChunkWrapper //=============// public ChunkWrapper(ChunkAccess chunk, ILevelWrapper wrappedLevel) + { + this(chunk, wrappedLevel, true); + } + public ChunkWrapper(ChunkAccess chunk, ILevelWrapper wrappedLevel, boolean recreateHeightmaps) { this.chunk = chunk; this.wrappedLevel = wrappedLevel; this.chunkPos = new DhChunkPos(chunk.getPos().x, chunk.getPos().z); - // use DH heightmaps if requested - if (Config.Common.LodBuilding.recalculateChunkHeightmaps.get()) + if (recreateHeightmaps) { - this.solidHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH]; - this.lightBlockingHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH]; - - this.recalculateDhHeightMapsIfNeeded(); - } - else - { - this.solidHeightMap = null; - this.lightBlockingHeightMap = null; + this.createDhHeightMaps(); } } @@ -248,56 +242,59 @@ public class ChunkWrapper implements IChunkWrapper } private int getChunkSectionMinHeight(int index) { return (index * 16) + this.getInclusiveMinBuildHeight(); } - /** Will only run if the config says the MC heightmaps shouldn't be trusted. */ - public void recalculateDhHeightMapsIfNeeded() + public void createDhHeightMaps() { // re-calculate the min/max heights for consistency (during world gen these may be wrong) this.minNonEmptyHeight = Integer.MIN_VALUE; this.maxNonEmptyHeight = Integer.MAX_VALUE; - // recalculate heightmaps if needed - if (this.solidHeightMap != null) + this.solidHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH]; + this.lightBlockingHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH]; + + for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++) { - for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++) + for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++) { - for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++) + int minInclusiveBuildHeight = this.getMinNonEmptyHeight(); + // if no blocks are found the height map will be at the bottom of the world + int solidHeight = minInclusiveBuildHeight; + int lightBlockingHeight = minInclusiveBuildHeight; + + + int y = this.getMaxNonEmptyHeight(); //this.getExclusiveMaxBuildHeight(); + IBlockStateWrapper block = this.getBlockState(x, y, z); + while (// go down until we reach the minimum build height + y > minInclusiveBuildHeight + // keep going until we find both height map values + && + ( + solidHeight == minInclusiveBuildHeight + || lightBlockingHeight == minInclusiveBuildHeight + ) + ) { - int minInclusiveBuildHeight = this.getMinNonEmptyHeight(); - // if no blocks are found the height map will be at the bottom of the world - int solidHeight = minInclusiveBuildHeight; - int lightBlockingHeight = minInclusiveBuildHeight; - - - int y = this.getMaxNonEmptyHeight(); //this.getExclusiveMaxBuildHeight(); - IBlockStateWrapper block = this.getBlockState(x, y, z); - while (// go down until we reach the minimum build height - y > minInclusiveBuildHeight - // keep going until we find both height map values - && (solidHeight == minInclusiveBuildHeight || lightBlockingHeight == minInclusiveBuildHeight)) + // is this block solid? + if (solidHeight == minInclusiveBuildHeight + && block.isSolid()) { - // is this block solid? - if (solidHeight == minInclusiveBuildHeight - && block.isSolid()) - { - solidHeight = y; - } - - // is this block light blocking? - if (lightBlockingHeight == minInclusiveBuildHeight - && block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT) - { - lightBlockingHeight = y; - } - - // get the next block down - y--; - block = this.getBlockState(x, y, z); + solidHeight = y; } - this.solidHeightMap[x][z] = solidHeight; - this.lightBlockingHeightMap[x][z] = lightBlockingHeight; + // is this block light blocking? + if (lightBlockingHeight == minInclusiveBuildHeight + && block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT) + { + lightBlockingHeight = y; + } + + // get the next block down + y--; + block = this.getBlockState(x, y, z); } + + this.solidHeightMap[x][z] = solidHeight; + this.lightBlockingHeightMap[x][z] = lightBlockingHeight; } } } 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 fc7967440..e58be1b81 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 @@ -21,43 +21,36 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration; import com.google.common.collect.ImmutableMap; +import com.seibel.distanthorizons.api.DhApi; 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.mimicObject.*; -import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.GlobalWorldGenParams; import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.level.IDhServerLevel; 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.pos.DhChunkPos; +import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.util.ExceptionUtil; -import com.seibel.distanthorizons.core.util.objects.EventTimer; import com.seibel.distanthorizons.core.util.LodUtil; 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.AbstractBatchGenerationEnvironmentWrapper; +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 java.util.function.Function; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; +import com.seibel.distanthorizons.coreapi.ModInfo; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck; import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepBiomes; import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepFeatures; import com.seibel.distanthorizons.common.wrappers.worldGeneration.step.StepNoise; @@ -65,16 +58,11 @@ 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 @@ -93,91 +81,51 @@ import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.status.ChunkStatus; #endif -/* -Total: 3.135214124s -===================================== -Empty Chunks: 0.000558328s -StructureStart Step: 0.025177207s -StructureReference Step: 0.00189559s -Biome Step: 0.13789155s -Noise Step: 1.570347555s -Surface Step: 0.741238194s -Carver Step: 0.000009923s -Feature Step: 0.389072425s -Lod Generation: 0.269023348s -*/ -public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnvironmentWrapper +public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironmentWrapper { - public static final DhLogger PREF_LOGGER = new DhLoggerBuilder() - .name("LOD World Gen") - .fileLevelConfig(Config.Common.Logging.logWorldGenPerformanceToFile) - .maxCountPerSecond(1) - .build(); - public static final DhLogger EVENT_LOGGER = new DhLoggerBuilder() + 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() + + public static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder() .name("LOD World Gen") - .fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile) + .maxCountPerSecond(1) .build(); - #if MC_VER < MC_1_21_5 - private static final TicketType DH_SERVER_GEN_TICKET = TicketType.create("dh_server_gen_ticket", Comparator.comparingLong(ChunkPos::toLong)); - #elif MC_VER < MC_1_21_9 - private static final TicketType DH_SERVER_GEN_TICKET = new TicketType(/* timeout, 0 = disabled*/0L, /* persist */ false, TicketType.TicketUse.LOADING); - #else - private static final TicketType DH_SERVER_GEN_TICKET = new TicketType(/* timeout, 0 = disabled*/0L, /* flags */TicketType.FLAG_LOADING); - #endif + @NotNull + public static final ImmutableMap WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP; + public static final int MAX_WORLD_GEN_CHUNK_BORDER_NEEDED; - private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class); - - - private final IDhServerLevel serverLevel; - - /** - * 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 static final long EXCEPTION_TIMER_RESET_TIME = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS); + public static final int EXCEPTION_COUNTER_TRIGGER = 20; - //=================Generation Step=================== + private final IDhServerLevel dhServerLevel; + + public final InternalServerGenerator internalServerGenerator; + public final ChunkFileReader chunkFileReader; + + + + public final LinkedBlockingQueue generationEventQueue = new LinkedBlockingQueue<>(); + public final GlobalWorldGenParams globalParams; - public final LinkedBlockingQueue generationEventList = new LinkedBlockingQueue<>(); - public final GlobalParameters params; public final StepStructureStart stepStructureStart = new StepStructureStart(this); public final StepStructureReference stepStructureReference = new StepStructureReference(this); public final StepBiomes stepBiomes = new StepBiomes(this); public final StepNoise stepNoise = new StepNoise(this); public final StepSurface stepSurface = new StepSurface(this); public final StepFeatures stepFeatures = new StepFeatures(this); + public boolean unsafeThreadingRecorded = false; - public static final long EXCEPTION_TIMER_RESET_TIME = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS); - public static final int EXCEPTION_COUNTER_TRIGGER = 20; - public static final int RANGE_TO_RANGE_EMPTY_EXTENSION = 1; + public boolean generatedChunkWithoutBiomeWarningLogged = false; 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 isDistantGeneratorThread = new ThreadLocal<>(); - public static boolean isCurrentThreadDistantGeneratorThread() { return (isDistantGeneratorThread.get() != null); } + public static ThreadLocal isDhWorldGenThreadRef = new ThreadLocal<>(); + public static boolean isThisDhWorldGenThread() { return (isDhWorldGenThreadRef.get() != null); } @@ -185,31 +133,24 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv // constructors // //==============// - @NotNull - public static final ImmutableMap WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP; - public static final int MAX_WORLD_GEN_CHUNK_BORDER_NEEDED; - static { - DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread = BatchGenerationEnvironment::isCurrentThreadDistantGeneratorThread; - - boolean isTerraFirmaCraft = false; + boolean isTerraFirmaCraftPresent = false; try { Class.forName("net.dries007.tfc.world.TFCChunkGenerator"); - isTerraFirmaCraft = true; + isTerraFirmaCraftPresent = true; + LOGGER.info("TerraFirmaCraft detected."); } - catch (ClassNotFoundException e) - { - //Ignore - } - EVENT_LOGGER.info("DH TerraFirmaCraft detection: " + isTerraFirmaCraft); + catch (ClassNotFoundException ignore) { } + + ImmutableMap.Builder builder = ImmutableMap.builder(); builder.put(EDhApiWorldGenerationStep.EMPTY, 1); builder.put(EDhApiWorldGenerationStep.STRUCTURE_START, 0); builder.put(EDhApiWorldGenerationStep.STRUCTURE_REFERENCE, 0); - builder.put(EDhApiWorldGenerationStep.BIOMES, isTerraFirmaCraft ? 1 : 0); - builder.put(EDhApiWorldGenerationStep.NOISE, isTerraFirmaCraft ? 1 : 0); + builder.put(EDhApiWorldGenerationStep.BIOMES, isTerraFirmaCraftPresent ? 1 : 0); + builder.put(EDhApiWorldGenerationStep.NOISE, isTerraFirmaCraftPresent ? 1 : 0); builder.put(EDhApiWorldGenerationStep.SURFACE, 0); builder.put(EDhApiWorldGenerationStep.CARVERS, 0); builder.put(EDhApiWorldGenerationStep.LIQUID_CARVERS, 0); @@ -225,44 +166,37 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv MAX_WORLD_GEN_CHUNK_BORDER_NEEDED = 0; } - public BatchGenerationEnvironment(IDhServerLevel serverLevel) + public BatchGenerationEnvironment(IDhServerLevel dhServerLevel) { - super(serverLevel); - this.serverLevel = serverLevel; + this.dhServerLevel = dhServerLevel; + this.globalParams = new GlobalWorldGenParams(dhServerLevel); + this.internalServerGenerator = new InternalServerGenerator(this.globalParams, this.dhServerLevel); + this.chunkFileReader = new ChunkFileReader(this.globalParams); - EVENT_LOGGER.info("================WORLD_GEN_STEP_INITING============="); - - serverLevel.getServerLevelWrapper().getDimensionType(); - - ChunkGenerator generator = ((ServerLevelWrapper) (serverLevel.getServerLevelWrapper())).getLevel().getChunkSource().getGenerator(); - if (!(generator instanceof NoiseBasedChunkGenerator || - generator instanceof DebugLevelSource || - generator instanceof FlatLevelSource)) + ChunkGenerator generator = ((ServerLevelWrapper) (dhServerLevel.getServerLevelWrapper())).getLevel().getChunkSource().getGenerator(); + boolean isMcGenerator = + generator instanceof NoiseBasedChunkGenerator + || generator instanceof DebugLevelSource + || generator instanceof FlatLevelSource; + if (!isMcGenerator) { if (generator.getClass().toString().equals("class com.terraforged.mod.chunk.TFChunkGenerator")) { - EVENT_LOGGER.info("TerraForge Chunk Generator detected: [" + generator.getClass() + "], Distant Generation will try its best to support it."); - EVENT_LOGGER.info("If it does crash, turn Distant Generation off or set it to to [" + EDhApiDistantGeneratorMode.PRE_EXISTING_ONLY + "]."); + LOGGER.info("TerraForge Chunk Generator detected: [" + generator.getClass() + "], Distant Generation will try its best to support it."); + LOGGER.info("If it does crash, turn Distant Generation off or set it to to [" + EDhApiDistantGeneratorMode.PRE_EXISTING_ONLY + "]."); } else if (generator.getClass().toString().equals("class net.dries007.tfc.world.TFCChunkGenerator")) { - EVENT_LOGGER.info("TerraFirmaCraft Chunk Generator detected: [" + generator.getClass() + "], Distant Generation will try its best to support it."); - EVENT_LOGGER.info("If it does crash, turn Distant Generation off or set it to to [" + EDhApiDistantGeneratorMode.PRE_EXISTING_ONLY + "]."); + LOGGER.info("TerraFirmaCraft Chunk Generator detected: [" + generator.getClass() + "], Distant Generation will try its best to support it."); + LOGGER.info("If it does crash, turn Distant Generation off or set it to to [" + EDhApiDistantGeneratorMode.PRE_EXISTING_ONLY + "]."); } else { - EVENT_LOGGER.warn("Unknown Chunk Generator detected: [" + generator.getClass() + "], Distant Generation May Fail!"); - EVENT_LOGGER.warn("If it does crash, disable Distant Generation or set the Generation Mode to [" + EDhApiDistantGeneratorMode.PRE_EXISTING_ONLY + "]."); + LOGGER.warn("Unknown Chunk Generator detected: [" + generator.getClass() + "], Distant Generation May Fail!"); + LOGGER.warn("If it does crash, disable Distant Generation or set the Generation Mode to [" + EDhApiDistantGeneratorMode.PRE_EXISTING_ONLY + "]."); } } - if (MOD_CHECKER.isModLoaded("c2me")) - { - EVENT_LOGGER.info("C2ME detected: DH's pre-existing chunk accessing will use methods handled by C2ME."); - this.pullExistingChunkUsingMcAsyncMethod = true; - } - - this.params = new GlobalParameters(serverLevel); } @@ -283,7 +217,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv // operation to be done synchronously if (!this.unsafeThreadingRecorded && !future.isDone()) { - EVENT_LOGGER.warn( + LOGGER.warn( "Unsafe MultiThreading in Distant Horizons Chunk Generator. \n" + "This can happen if world generation is run on one of Minecraft's thread pools " + "instead of the thread DH provided. \n" + @@ -311,7 +245,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv // Update all current out standing jobs - Iterator iter = this.generationEventList.iterator(); + Iterator iter = this.generationEventQueue.iterator(); while (iter.hasNext()) { GenerationEvent event = iter.next(); @@ -328,7 +262,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv { this.unknownExceptionCount++; this.lastExceptionTriggerTime = System.nanoTime(); - EVENT_LOGGER.error("Batching World Generator event ["+event+"] threw an exception: "+e.getMessage(), e); + LOGGER.error("Batching World Generator event ["+event+"] threw an exception: "+e.getMessage(), e); } } @@ -338,7 +272,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv if (this.unknownExceptionCount > EXCEPTION_COUNTER_TRIGGER) { - EVENT_LOGGER.error("Too many exceptions in Batching World Generator! Disabling the generator."); + LOGGER.error("Too many exceptions in Batching World Generator! Disabling the generator."); this.unknownExceptionCount = 0; Config.Common.WorldGenerator.enableDistantGeneration.set(false); } @@ -350,639 +284,278 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv // world generation // //==================// - // TODO this is already being run on a generator thread, - // why are we passing in an executor? /** @throws RejectedExecutionException if the given {@link Executor} is cancelled. */ - public CompletableFuture generateLodFromListAsync(GenerationEvent genEvent, Executor executor) throws RejectedExecutionException, InterruptedException + public void generateEvent(GenerationEvent genEvent) throws RejectedExecutionException { - EVENT_LOGGER.debug("Lod Generate Event: " + genEvent.minPos); - // Minecraft's generation events expect odd chunk width areas (3x3, 7x7, or 11x11), // but DH submits square generation events (4x4). // We handle this later, although that handling would need to change if the gen size ever changes. - LodUtil.assertTrue(genEvent.size % 2 == 0, "Generation events are expected to be an evan number of chunks wide."); + LodUtil.assertTrue(genEvent.widthInChunks % 2 == 0, "Generation events are expected to be an evan number of chunks wide."); - if (genEvent.generatorMode == EDhApiDistantGeneratorMode.INTERNAL_SERVER) + if (!DhApi.isDhThread() + && ModInfo.IS_DEV_BUILD) { - return this.generateChunksViaInternalServerAsync(genEvent); + throw new IllegalStateException("Batch world generation should be called from one of DH's world gen thread. Current thread: ["+Thread.currentThread().getName()+"]"); } + + + //================// + // 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.size - 1) + (borderSize * 2); + int refSize = (genEvent.widthInChunks - 1) + (borderSize * 2); int refPosX = genEvent.minPos.getX() - borderSize; int refPosZ = genEvent.minPos.getZ() - borderSize; - LightGetterAdaptor lightGetterAdaptor = new LightGetterAdaptor(this.params.level); + LightGetterAdaptor lightGetterAdaptor = new LightGetterAdaptor(this.globalParams.mcServerLevel); 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 - getChunkPosToGenerateStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.size, 8) - .map((chunkPos) -> this.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(); - // future chain for generation - return CompletableFuture.runAsync(() -> + //================================// + // 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()) { - try - { - // offset 1 chunk in both X and Z direction so we can generate an even number of chunks wide - // while still submitting an odd number width to MC's internal generators - for (int xOffset = 0; xOffset < 2; xOffset++) - { - // final is so the offset can be used in lambdas - final int xOffsetFinal = xOffset; - for (int zOffset = 0; zOffset < 2; zOffset++) - { - final int zOffsetFinal = zOffset; - - - - //================// - // variable setup // - //================// - - int radius = refSize / 2; - int centerX = refPosX + radius + xOffset; - 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)); - - ArrayGridList regionChunks = new ArrayGridList<>( - refSize, - (relX, relZ) -> fallbackFunc.getChunk( - relX + refPosX + xOffsetFinal, - relZ + refPosZ + zOffsetFinal)); - - ChunkAccess centerChunk = regionChunks.stream() - .filter((chunk) -> chunk.getPos().x == centerX && chunk.getPos().z == centerZ) - .findFirst() - .orElseGet(() -> regionChunks.getFirst()); - - genEvent.refreshTimeout(); - DhLitWorldGenRegion region = new DhLitWorldGenRegion( - centerX, centerZ, - centerChunk, - this.params.level, dummyLightEngine, regionChunks, - 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 - ); - lightGetterAdaptor.setRegion(region); - genEvent.threadedParam.makeStructFeat(region, this.params); - - - - //=============================// - // create chunk wrappers // - // and process existing chunks // - //=============================// - - ArrayGridList chunkWrapperList = new ArrayGridList<>(regionChunks.gridSize); - regionChunks.forEachPos((relX, relZ) -> - { - // ArrayGridList's use relative positions and don't have a center position - // so we need to use the offsetFinal to select the correct position - DhChunkPos chunkPos = new DhChunkPos(relX + refPosX + xOffsetFinal, relZ + refPosZ + zOffsetFinal); - ChunkAccess chunk = regionChunks.get(relX, relZ); - - if (chunkWrappersByDhPos.containsKey(chunkPos)) - { - chunkWrapperList.set(relX, relZ, chunkWrappersByDhPos.get(chunkPos)); - } - else if (chunk != null) - { - // wrap the chunk - ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, this.serverLevel.getLevelWrapper()); - chunkWrapperList.set(relX, relZ, chunkWrapper); - - // try setting the wrapper's lighting - if (chunkBlockLightingByDhPos.containsKey(chunkWrapper.getChunkPos())) - { - chunkWrapper.setBlockLightStorage(chunkBlockLightingByDhPos.get(chunkWrapper.getChunkPos())); - chunkWrapper.setSkyLightStorage(chunkSkyLightingByDhPos.get(chunkWrapper.getChunkPos())); - chunkWrapper.setIsDhBlockLightCorrect(true); - chunkWrapper.setIsDhSkyLightCorrect(true); - } - - chunkWrappersByDhPos.put(chunkPos, chunkWrapper); - } - else //if (chunk == null) - { - LodUtil.assertNotReach("Programmer Error: No chunk found in grid list, position offset is likely wrong."); - } - }); - - - - //=================// - // generate chunks // - //=================// - - try - { - this.generateDirect(genEvent, chunkWrapperList, region); - } - catch (InterruptedException e) - { - throw new CompletionException(e); - } - - genEvent.timer.nextEvent("cleanup"); - } - } - - genEvent.timer.nextEvent("cleanup"); - - - - //=========================// - // submit generated chunks // - //=========================// - - Iterator iterator = getChunkPosToGenerateStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.size, 0).iterator(); - while (iterator.hasNext()) - { - ChunkPos pos = iterator.next(); - DhChunkPos dhPos = new DhChunkPos(pos.x, pos.z); - ChunkWrapper wrappedChunk = chunkWrappersByDhPos.get(dhPos); - genEvent.resultConsumer.accept(wrappedChunk); - } - - genEvent.timer.complete(); - genEvent.refreshTimeout(); - if (PREF_LOGGER.canLog()) - { - genEvent.threadedParam.perf.recordEvent(genEvent.timer); - PREF_LOGGER.debug(genEvent.timer.toString()); - } - } - catch (CompletionException | UncheckedInterruptedException e) - { - // interrupts mean the world generator is being shut down, no need to log that - boolean isShutdownException = ExceptionUtil.isShutdownException(e); - if (!isShutdownException) - { - EVENT_LOGGER.error("Completion error during world gen for min chunk pos ["+genEvent.minPos+"], error: ["+e.getMessage()+"].", e); - } - } - catch (Exception e) - { - EVENT_LOGGER.error("Unexpected error during world gen for min chunk pos ["+genEvent.minPos+"], error: ["+e.getMessage()+"].", e); - } - }, executor); - } - /** @param extraRadius in both the positive and negative directions */ - private static Stream getChunkPosToGenerateStream(int genMinX, int genMinZ, int width, int extraRadius) - { - return StreamSupport.stream(new InclusiveChunkPosStream(genMinX, genMinZ, width, extraRadius), false); - - // method this is replacing - //return ChunkPos.rangeClosed( - // new ChunkPos(genMinX - extraRadius, genMinZ - extraRadius), - // new ChunkPos(genMinX + (width - 1) + extraRadius, genMinZ + (width - 1) + extraRadius) - //); - } - /** - * 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 x, int z, - Map chunkSkyLightingByDhPos, - Map chunkBlockLightingByDhPos, - Map generatedChunkByDhPos) - { - ChunkPos chunkPos = new ChunkPos(x, z); - DhChunkPos dhChunkPos = new DhChunkPos(x, z); - - if (generatedChunkByDhPos.containsKey(dhChunkPos)) - { - return CompletableFuture.completedFuture(generatedChunkByDhPos.get(dhChunkPos)); + 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.createEmptyOrPreExistingChunkWrapperAsync( + chunkPos.x, chunkPos.z, + chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, chunkWrappersByDhPos); + + readFutureByDhChunkPos.put(dhChunkPos, getExistingChunkFuture); } - 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; + // 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(); + } - //if (true) - // return CompletableFuture.completedFuture(null); - // TODO disabling drastically reduces GC overhead (2Gb/s -> 1GB/s) + + //===================================// + // 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)) + { + ChunkWrapper chunkWrapper = this.chunkFileReader.CreateProtoChunkWrapper(this.globalParams.mcServerLevel, chunkPos); + chunkWrappersByDhPos.put(dhChunkPos, chunkWrapper); + } + } + + + + //=================// + // generate chunks // + //=================// 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) + // offset 1 chunk in both X and Z direction so we can generate an even number of chunks wide + // while still submitting an odd number width to MC's internal generators + for (int xOffset = 0; xOffset < 2; xOffset++) { - try + // final is so the offset can be used in lambdas + final int xOffsetFinal = xOffset; + for (int zOffset = 0; zOffset < 2; zOffset++) { - 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 - EVENT_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; + final int zOffsetFinal = zOffset; - // 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 - EVENT_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("DistantHorizons: 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()) + + + //================// + // variable setup // + //================// + + int radius = refSize / 2; + int centerX = refPosX + radius + xOffset; + int centerZ = refPosZ + radius + zOffset; + + // get/create the list of chunks we're going to generate + IEmptyChunkRetrievalFunc fallbackChunkGetterFunc = + (chunkPosX, chunkPosZ) -> Objects.requireNonNull( + chunkWrappersByDhPos.get(new DhChunkPos(chunkPosX, chunkPosZ)).getChunk(), + () -> String.format("Requested chunk [%d, %d] unavailable during world generation", chunkPosX, chunkPosZ)); + + ArrayGridList regionChunks = new ArrayGridList<>( + refSize, + (relX, relZ) -> fallbackChunkGetterFunc.getChunk( + relX + refPosX + xOffsetFinal, + relZ + refPosZ + zOffsetFinal)); + + ChunkAccess centerChunk = regionChunks.stream() + .filter((chunk) -> chunk.getPos().x == centerX && chunk.getPos().z == centerZ) + .findFirst() + .orElseGet(() -> regionChunks.getFirst()); + + DhLitWorldGenRegion region = new DhLitWorldGenRegion( + centerX, centerZ, + centerChunk, + this.globalParams.mcServerLevel, dummyLightEngine, regionChunks, + 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 + fallbackChunkGetterFunc + ); + lightGetterAdaptor.setRegion(region); + genEvent.threadedParam.makeStructFeatManager(region, this.globalParams); + + + + //=============================// + // process existing chunks // + // + //=============================// + + ArrayGridList chunkWrapperList = new ArrayGridList<>(regionChunks.gridSize); + regionChunks.forEachPos((relX, relZ) -> { - // 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); + // ArrayGridList's use relative positions and don't have a center position + // so we need to use the offsetFinal to select the correct position + DhChunkPos chunkPos = new DhChunkPos(relX + refPosX + xOffsetFinal, relZ + refPosZ + zOffsetFinal); + ChunkAccess chunk = regionChunks.get(relX, relZ); + + if (chunkWrappersByDhPos.containsKey(chunkPos)) + { + chunkWrapperList.set(relX, relZ, chunkWrappersByDhPos.get(chunkPos)); + } + else if (chunk != null) + { + // + ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, this.dhServerLevel.getLevelWrapper()); + chunkWrapperList.set(relX, relZ, chunkWrapper); + + // try setting the wrapper's lighting + if (chunkBlockLightingByDhPos.containsKey(chunkWrapper.getChunkPos())) + { + // block + ChunkLightStorage blockLightStorage = chunkBlockLightingByDhPos.get(chunkWrapper.getChunkPos()); + // if the light storage is empty then we should try generating the lighting + // ourselves, the light data is probably missing + if (blockLightStorage != null + && !blockLightStorage.isEmpty()) + { + chunkWrapper.setBlockLightStorage(blockLightStorage); + chunkWrapper.setIsDhBlockLightCorrect(true); + } + + // sky + ChunkLightStorage skyLightStorage = chunkSkyLightingByDhPos.get(chunkWrapper.getChunkPos()); + if (skyLightStorage != null + && !skyLightStorage.isEmpty()) + { + chunkWrapper.setSkyLightStorage(skyLightStorage); + chunkWrapper.setIsDhSkyLightCorrect(true); + } + } + + chunkWrappersByDhPos.put(chunkPos, chunkWrapper); + } + else //if (chunk == null) + { + LodUtil.assertNotReach("Programmer Error: No chunk found in grid list, position offset is likely wrong."); + } + }); + + + + //=================// + // generate chunks // + //=================// + + try + { + this.generateDirect(genEvent, chunkWrapperList, region); } + catch (InterruptedException e) + { + throw new CompletionException(e); + } + } + } + + + + //=========================// + // submit generated chunks // + //=========================// + + Iterator iterator = ChunkPosGenStream.getIterator(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0); + while (iterator.hasNext()) + { + ChunkPos pos = iterator.next(); + DhChunkPos dhPos = new DhChunkPos(pos.x, pos.z); + ChunkWrapper wrappedChunk = chunkWrappersByDhPos.get(dhPos); + + // only pass along chunks that have been generated up to BIOMES + // this is to prevent issues with generating existing + if (wrappedChunk.getStatus().isOrAfter(ChunkStatus.BIOMES)) + { + genEvent.resultConsumer.accept(wrappedChunk); } else { - chunk = CreateEmptyChunk(level, chunkPos); + // this shouldn't happen, but if it does log it + if (!this.generatedChunkWithoutBiomeWarningLogged) + { + this.generatedChunkWithoutBiomeWarningLogged = true; + LOGGER.warn("Chunk [" + dhPos + "] wasn't generated up to BIOMES, world gen may appear empty."); + } } - 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 - } - - - - private CompletableFuture generateChunksViaInternalServerAsync(GenerationEvent genEvent) throws InterruptedException - { - genEvent.timer.nextEvent("requestFromServer"); - LinkedBlockingQueue runnableQueue = new LinkedBlockingQueue<>(); - - Map chunkWrappersByDhPos = Collections.synchronizedMap(new HashMap<>()); - - - - //===================================// - // create generation queue runnables // - //===================================// - - // request each chunk pos from the server - CompletableFuture[] requestFutures = - getChunkPosToGenerateStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.size, 0) - .map(chunkPos -> - { - return requestChunkFromServerAsync(this.params.level, chunkPos, true) - .whenCompleteAsync((chunk, throwable) -> - { - // unwrap the CompletionException if necessary - Throwable actualThrowable = throwable; - while (actualThrowable instanceof CompletionException) - { - actualThrowable = actualThrowable.getCause(); - } - - if (throwable != null) - { - CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load chunk [" + chunkPos + "] from server, error: [" + actualThrowable.getMessage() + "].", actualThrowable); - } - - if (chunk != null) - { - ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, this.serverLevel.getLevelWrapper()); - chunkWrappersByDhPos.put(new DhChunkPos(chunkPos.x, chunkPos.z), chunkWrapper); - } - }, runnableQueue::add); - }) - .toArray(CompletableFuture[]::new); - - // handle each generated chunk - CompletableFuture processGeneratedChunksFuture = - CompletableFuture.allOf(requestFutures) - .whenCompleteAsync((voidObj, throwable) -> - { - // generate chunk lighting using DH's lighting engine - genEvent.timer.nextEvent("light"); - int maxSkyLight = this.serverLevel.getServerLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT; - - ArrayList generatedChunks = new ArrayList<>(chunkWrappersByDhPos.values()); - for (IChunkWrapper iChunkWrapper : generatedChunks) - { - ((ChunkWrapper) iChunkWrapper).recalculateDhHeightMapsIfNeeded(); - - // pre-generated chunks should have lighting but new ones won't - if (!iChunkWrapper.isDhBlockLightingCorrect()) - { - DhLightingEngine.INSTANCE.bakeChunkBlockLighting(iChunkWrapper, generatedChunks, maxSkyLight); - } - - this.serverLevel.updateBeaconBeamsForChunk(iChunkWrapper, generatedChunks); - } - - genEvent.timer.nextEvent("cleanup"); - for (IChunkWrapper iChunkWrapper : generatedChunks) - { - genEvent.resultConsumer.accept(iChunkWrapper); - } - }, runnableQueue::add) - .whenCompleteAsync((unused, throwable) -> - { - // cleanup - // release the generated chunks - - Iterator iterator = getChunkPosToGenerateStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.size, 0).iterator(); - while (iterator.hasNext()) - { - ChunkPos chunkPos = iterator.next(); - releaseChunkToServer(this.params.level, chunkPos, true); - } - - genEvent.timer.complete(); - genEvent.refreshTimeout(); - if (PREF_LOGGER.canLog()) - { - genEvent.threadedParam.perf.recordEvent(genEvent.timer); - PREF_LOGGER.debug(genEvent.timer.toString()); - } - }); - - processGeneratedChunksFuture.whenCompleteAsync((unused, throwable) -> { }, runnableQueue::add); // trigger wakeup - - - - //===============// - // run each step // - //===============// - - while (!processGeneratedChunksFuture.isDone()) + catch (CompletionException | UncheckedInterruptedException e) { - try + // interrupts mean the world generator is being shut down, no need to log that + boolean isShutdownException = ExceptionUtil.isShutdownException(e); + if (!isShutdownException) { - Runnable command = runnableQueue.poll(1, TimeUnit.SECONDS); - if (command != null) - { - command.run(); - } - } - catch (InterruptedException e) - { - // interrupted, release chunk to server - Iterator iterator = getChunkPosToGenerateStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.size, 0).iterator(); - while (iterator.hasNext()) - { - ChunkPos chunkPos = iterator.next(); - releaseChunkToServer(this.params.level, chunkPos, true); - } - - throw e; + LOGGER.error("Completion error during world gen for min chunk pos ["+genEvent.minPos+"], error: ["+e.getMessage()+"].", e); } } - - return processGeneratedChunksFuture; - } - /** @param generateUpToFeatures if false this generate the chunk up to "FULL" status */ - private static CompletableFuture requestChunkFromServerAsync(ServerLevel level, ChunkPos pos, boolean generateUpToFeatures) - { - return CompletableFuture.supplyAsync(() -> + catch (Exception e) { - int chunkLevel; - #if MC_VER <= MC_1_19_4 - // 33 is equivalent to FULL Chunk - chunkLevel = generateUpToFeatures ? 33 + ChunkStatus.getDistance(ChunkStatus.FEATURES) : 33; - #else - // 33 is equivalent to FULL Chunk - chunkLevel = generateUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33; - #endif - - #if MC_VER < MC_1_21_5 - level.getChunkSource().distanceManager.addTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos); - #else - level.getChunkSource().addTicketWithRadius(DH_SERVER_GEN_TICKET, pos, 0); - #endif - level.getChunkSource().distanceManager.runAllUpdates(level.getChunkSource().chunkMap); // probably not the most optimal to run updates here, but fast enough - ChunkHolder holder = level.getChunkSource().chunkMap.getUpdatingChunkIfPresent(pos.toLong()); - if (holder == null) - { - throw new IllegalStateException("No chunk holder after ticket has been added"); - } - - #if MC_VER <= MC_1_20_4 - return holder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap) - .thenApply(result -> result.left().orElseThrow(() -> new RuntimeException(result.right().get().toString()))); // can throw if the server is shutting down - #elif MC_VER <= MC_1_20_6 - return holder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap) - .thenApply(result -> result.orElseThrow(() -> new RuntimeException(result.toString()))); // can throw if the server is shutting down - #else - return holder.scheduleChunkGenerationTask(ChunkStatus.FEATURES, level.getChunkSource().chunkMap) - .thenApply(result -> result.orElseThrow(() -> new RuntimeException(result.getError()))); // can throw if the server is shutting down - #endif - - }, level.getChunkSource().chunkMap.mainThreadExecutor).thenCompose(Function.identity()); - } - /** @param chunkWasGeneratedUpToFeatures if false this assumes the chunk was generated to "FULL" status */ - private static void releaseChunkToServer(ServerLevel level, ChunkPos pos, boolean chunkWasGeneratedUpToFeatures) - { - level.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> - { - try - { - int chunkLevel; - #if MC_VER <= MC_1_19_4 - // 33 is equivalent to FULL Chunk - chunkLevel = chunkWasGeneratedUpToFeatures ? 33 + ChunkStatus.getDistance(ChunkStatus.FEATURES) : 33; - #else - // 33 is equivalent to FULL Chunk - chunkLevel = chunkWasGeneratedUpToFeatures ? ChunkLevel.byStatus(ChunkStatus.FEATURES) : 33; - #endif - - #if MC_VER < MC_1_21_5 - level.getChunkSource().distanceManager.removeTicket(DH_SERVER_GEN_TICKET, pos, chunkLevel, pos); - #else - level.getChunkSource().removeTicketWithRadius(DH_SERVER_GEN_TICKET, pos, 0); - #endif - - // mitigate OOM issues in vanilla chunk system: see https://github.com/pop4959/Chunky/pull/383 - level.getChunkSource().chunkMap.tick(() -> false); - #if MC_VER > MC_1_16_5 - level.entityManager.tick(); - #endif - } - catch (Exception e) - { - EVENT_LOGGER.warn("Failed to release chunk back to internal server. Error: ["+e.getMessage()+"]", e); - } - }); + LOGGER.error("Unexpected error during world gen for min chunk pos ["+genEvent.minPos+"], error: ["+e.getMessage()+"].", e); + } } + + + // direct generation // + public void generateDirect( GenerationEvent genEvent, ArrayGridList chunkWrappersToGenerate, DhLitWorldGenRegion region) throws InterruptedException @@ -1000,11 +573,20 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv if (chunk instanceof ProtoChunk) { ProtoChunk protoChunk = ((ProtoChunk) chunk); - protoChunk.setLightEngine(region.getLightEngine()); } }); + + // if we're only working with pre-existing chunks, + // biomes need to be initialized but no other steps should be done + if (genEvent.generatorMode == EDhApiDistantGeneratorMode.PRE_EXISTING_ONLY) + { + this.stepBiomes.generateGroup(genEvent.threadedParam, region, GetCutoutFrom(chunkWrappersToGenerate, EDhApiWorldGenerationStep.BIOMES)); + return; + } + + EDhApiWorldGenerationStep step = genEvent.targetGenerationStep; if (step == EDhApiWorldGenerationStep.EMPTY) { @@ -1012,71 +594,56 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv return; } - genEvent.timer.nextEvent("structStart"); throwIfThreadInterrupted(); this.stepStructureStart.generateGroup(genEvent.threadedParam, region, GetCutoutFrom(chunkWrappersToGenerate, EDhApiWorldGenerationStep.STRUCTURE_START)); - genEvent.refreshTimeout(); if (step == EDhApiWorldGenerationStep.STRUCTURE_START) { return; } - genEvent.timer.nextEvent("structRef"); throwIfThreadInterrupted(); this.stepStructureReference.generateGroup(genEvent.threadedParam, region, GetCutoutFrom(chunkWrappersToGenerate, EDhApiWorldGenerationStep.STRUCTURE_REFERENCE)); - genEvent.refreshTimeout(); if (step == EDhApiWorldGenerationStep.STRUCTURE_REFERENCE) { return; } - genEvent.timer.nextEvent("biome"); throwIfThreadInterrupted(); this.stepBiomes.generateGroup(genEvent.threadedParam, region, GetCutoutFrom(chunkWrappersToGenerate, EDhApiWorldGenerationStep.BIOMES)); - genEvent.refreshTimeout(); if (step == EDhApiWorldGenerationStep.BIOMES) { return; } - genEvent.timer.nextEvent("noise"); throwIfThreadInterrupted(); this.stepNoise.generateGroup(genEvent.threadedParam, region, GetCutoutFrom(chunkWrappersToGenerate, EDhApiWorldGenerationStep.NOISE)); - genEvent.refreshTimeout(); if (step == EDhApiWorldGenerationStep.NOISE) { return; } - genEvent.timer.nextEvent("surface"); throwIfThreadInterrupted(); this.stepSurface.generateGroup(genEvent.threadedParam, region, GetCutoutFrom(chunkWrappersToGenerate, EDhApiWorldGenerationStep.SURFACE)); - genEvent.refreshTimeout(); if (step == EDhApiWorldGenerationStep.SURFACE) { return; } - genEvent.timer.nextEvent("carver"); - throwIfThreadInterrupted(); - // caves can generally be ignored since they aren't generally visible from far away - if (step == EDhApiWorldGenerationStep.CARVERS) - { - return; - } - - genEvent.timer.nextEvent("feature"); +// throwIfThreadInterrupted(); +// // caves can generally be ignored since they aren't generally visible from far away +// if (step == EDhApiWorldGenerationStep.CARVERS) +// { +// return; +// } + throwIfThreadInterrupted(); this.stepFeatures.generateGroup(genEvent.threadedParam, region, GetCutoutFrom(chunkWrappersToGenerate, EDhApiWorldGenerationStep.FEATURES)); - genEvent.refreshTimeout(); } finally { - genEvent.timer.nextEvent("light"); - // generate lighting using DH's lighting engine - int maxSkyLight = this.serverLevel.getServerLevelWrapper().hasSkyLight() ? 15 : 0; + int maxSkyLight = this.dhServerLevel.getServerLevelWrapper().hasSkyLight() ? 15 : 0; // only light generated chunks, // attempting to light un-generated chunks will cause lighting issues on bordering generated chunks @@ -1093,87 +660,81 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv // light each chunk in the list for (int i = 0; i < iChunkWrapperList.size(); i++) { - ChunkWrapper centerChunk = (ChunkWrapper) iChunkWrapperList.get(i); - if (centerChunk == null) + ChunkWrapper centerChunkWrapper = (ChunkWrapper) iChunkWrapperList.get(i); + if (centerChunkWrapper == null) { continue; } throwIfThreadInterrupted(); - // make sure the height maps are all properly generated - // if this isn't done everything else afterward may fail - Heightmap.primeHeightmaps(centerChunk.getChunk(), ChunkStatus.FEATURES.heightmapsAfter()); - centerChunk.recalculateDhHeightMapsIfNeeded(); + // not always necessary, but sometimes MC heightmap is wrong + // and can cause LODs to generate incorrectly + centerChunkWrapper.createDhHeightMaps(); // pre-generated chunks should have lighting but new ones won't - if (!centerChunk.isDhBlockLightingCorrect()) + if (!centerChunkWrapper.isDhBlockLightingCorrect()) { - DhLightingEngine.INSTANCE.bakeChunkBlockLighting(centerChunk, iChunkWrapperList, maxSkyLight); + DhLightingEngine.INSTANCE.bakeChunkBlockLighting(centerChunkWrapper, iChunkWrapperList, maxSkyLight); } - this.serverLevel.updateBeaconBeamsForChunk(centerChunk, iChunkWrapperList); + List activeBeamList = centerChunkWrapper.getAllActiveBeacons(iChunkWrapperList); + if (!activeBeamList.isEmpty()) + { + this.dhServerLevel.updateBeaconBeamsForChunkPos(centerChunkWrapper.getChunkPos(), activeBeamList); + } } - - genEvent.refreshTimeout(); } } private static ArrayGridList GetCutoutFrom(ArrayGridList total, int border) { return new ArrayGridList<>(total, border, total.gridSize - border); } private static ArrayGridList GetCutoutFrom(ArrayGridList total, EDhApiWorldGenerationStep step) { return GetCutoutFrom(total, WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP.get(step)); } - //private static ArrayGridList GetCutoutFrom(ArrayGridList total, EDhApiWorldGenerationStep step) { return GetCutoutFrom(total, 0); } + + // queue task // + @Override - public int getEventCount() { return this.generationEventList.size(); } - - @Override - public void stop() - { - EVENT_LOGGER.info(BatchGenerationEnvironment.class.getSimpleName() + " shutting down..."); - - EVENT_LOGGER.info("Canceling in progress generation event futures..."); - Iterator iter = this.generationEventList.iterator(); - while (iter.hasNext()) - { - GenerationEvent event = iter.next(); - event.future.cancel(true); - iter.remove(); - } - - // 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) - { - EVENT_LOGGER.error("Failed to close region file storage cache, error: ["+e.getMessage()+"].", e); - } - } - - EVENT_LOGGER.info(BatchGenerationEnvironment.class.getSimpleName() + " shutdown complete."); - } - - @Override - public CompletableFuture generateChunks( - int minX, int minZ, int genSize, + public CompletableFuture queueGenEvent( + int minX, int minZ, int chunkWidthCount, EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep targetStep, ExecutorService worldGeneratorThreadPool, Consumer resultConsumer) { - //System.out.println("GenerationEvent: "+genSize+"@"+minX+","+minZ+" "+targetStep); - - // TODO: Check event overlap via e.tooClose() - GenerationEvent genEvent = GenerationEvent.startEvent(new DhChunkPos(minX, minZ), genSize, this, generatorMode, targetStep, resultConsumer, worldGeneratorThreadPool); - this.generationEventList.add(genEvent); + GenerationEvent genEvent = GenerationEvent.start( + new DhChunkPos(minX, minZ), chunkWidthCount, this, + generatorMode, targetStep, resultConsumer, + worldGeneratorThreadPool); + this.generationEventQueue.add(genEvent); return genEvent.future; } + //================// + // base overrides // + //================// + + @Override + public void close() + { + LOGGER.info("Closing [" +BatchGenerationEnvironment.class.getSimpleName() + "]"); + + + // cancel in-progress tasks + Iterator genEventIter = this.generationEventQueue.iterator(); + while (genEventIter.hasNext()) + { + GenerationEvent event = genEventIter.next(); + event.future.cancel(true); + genEventIter.remove(); + } + + + this.chunkFileReader.close(); + + } + + + //================// // helper methods // //================// @@ -1187,7 +748,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv { if (Thread.interrupted()) { - throw new InterruptedException(BatchGenerationEnvironment.class.getSimpleName() + " task interrupted."); + throw new InterruptedException("["+BatchGenerationEnvironment.class.getSimpleName()+"] task interrupted."); } } @@ -1203,128 +764,6 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv ChunkAccess getChunk(int chunkPosX, int chunkPosZ); } - private static class InclusiveChunkPosStream extends Spliterators.AbstractSpliterator - { - private final int minX; - private final int minZ; - - private final int maxX; - private final int maxZ; - - - /** current X pos */ - int x; - /** current Z pos */ - private int z; - - - - //=============// - // constructor // - //=============// - - protected InclusiveChunkPosStream(int genMinX, int genMinZ, int width, int extraRadius) - { - super(getCount(width, extraRadius), Spliterator.SIZED); - - this.minX = genMinX - extraRadius; - this.minZ = genMinZ - extraRadius; - - this.maxX = genMinX + (width - 1) + extraRadius; - this.maxZ = genMinZ + (width - 1) + extraRadius; - - // X starts at 1 minus the minX so we can immediately re-add 1 in the tryAdvance() loop - this.x = this.minX - 1; - this.z = this.minZ; - } - private static int getCount(int width, int extraRadius) - { - int widthPlusExtra = width + (extraRadius * 2); - return widthPlusExtra * widthPlusExtra; - } - - - - //=================// - // iterator method // - //=================// - - public boolean tryAdvance(Consumer consumer) - { - if (this.x == this.maxX && this.z == this.maxZ) - { - // the last returned position was the final valid position - return false; - } - - if (this.x == this.maxX) - { - // we reached the max X position, loop back around in the next Z row - this.x = this.minX; - this.z++; - } - else - { - this.x++; - } - - consumer.accept(new ChunkPos(this.x, this.z)); - return true; - } - } - - public static class PerfCalculator - { - private static final String[] TIME_NAMES = { - "total", - "setup", - "structStart", - "structRef", - "biome", - "noise", - "surface", - "carver", - "feature", - "light", - "cleanup", - //"lodCreation" (No longer used) - }; - - public static final int SIZE = 50; - ArrayList times = new ArrayList<>(); - - public PerfCalculator() - { - for (int i = 0; i < 11; i++) - { - times.add(new Rolling(SIZE)); - } - } - - public void recordEvent(EventTimer event) - { - for (EventTimer.Event e : event.events) - { - String name = e.name; - int index = Arrays.asList(TIME_NAMES).indexOf(name); - if (index == -1) continue; - times.get(index).add(e.timeNs); - } - times.get(0).add(event.getTotalTimeNs()); - } - - public String toString() - { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < times.size(); i++) - { - if (times.get(i).getAverage() == 0) continue; - sb.append(TIME_NAMES[i]).append(": ").append(times.get(i).getAverage()).append("\n"); - } - return sb.toString(); - } - - } } \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ChunkPosGenStream.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ChunkPosGenStream.java new file mode 100644 index 000000000..a4e92348c --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ChunkPosGenStream.java @@ -0,0 +1,92 @@ +package com.seibel.distanthorizons.common.wrappers.worldGeneration; + +import net.minecraft.world.level.ChunkPos; + +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class ChunkPosGenStream +{ + + public static Iterator getIterator(int genMinX, int genMinZ, int width, int extraRadius) + { return getStream(genMinX, genMinZ, width, extraRadius).iterator(); } + /** @param extraRadius in both the positive and negative directions */ + public static Stream getStream(int genMinX, int genMinZ, int width, int extraRadius) + { return StreamSupport.stream(new InclusiveChunkPosIterator(genMinX, genMinZ, width, extraRadius), false); } + + private static class InclusiveChunkPosIterator extends Spliterators.AbstractSpliterator + { + private final int minX; + private final int minZ; + + private final int maxX; + private final int maxZ; + + + /** current X pos */ + int x; + /** current Z pos */ + private int z; + + + + //=============// + // constructor // + //=============// + + protected InclusiveChunkPosIterator(int genMinX, int genMinZ, int width, int extraRadius) + { + super(getCount(width, extraRadius), Spliterator.SIZED); + + this.minX = genMinX - extraRadius; + this.minZ = genMinZ - extraRadius; + + this.maxX = genMinX + (width - 1) + extraRadius; + this.maxZ = genMinZ + (width - 1) + extraRadius; + + // X starts at 1 minus the minX so we can immediately re-add 1 in the tryAdvance() loop + this.x = this.minX - 1; + this.z = this.minZ; + } + private static int getCount(int width, int extraRadius) + { + int widthPlusExtra = width + (extraRadius * 2); + return widthPlusExtra * widthPlusExtra; + } + + + + //=================// + // iterator method // + //=================// + + @Override + public boolean tryAdvance(Consumer consumer) + { + if (this.x == this.maxX && this.z == this.maxZ) + { + // the last returned position was the final valid position + return false; + } + + if (this.x == this.maxX) + { + // we reached the max X position, loop back around in the next Z row + this.x = this.minX; + this.z++; + } + else + { + this.x++; + } + + consumer.accept(new ChunkPos(this.x, this.z)); + return true; + } + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/GenerationEvent.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/GenerationEvent.java index 193fad255..603318235 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/GenerationEvent.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/GenerationEvent.java @@ -19,65 +19,71 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration; -import java.lang.invoke.MethodHandles; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.ThreadWorldGenParams; import com.seibel.distanthorizons.core.util.ExceptionUtil; -import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhChunkPos; -import com.seibel.distanthorizons.core.util.objects.EventTimer; -import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.logging.DhLogger; -import org.jetbrains.annotations.Nullable; public final class GenerationEvent { private static final DhLogger LOGGER = new DhLoggerBuilder().build();; - private static int generationFutureDebugIDs = 0; + private static final AtomicInteger DEBUG_ID_REF = new AtomicInteger(0); + + + /** can be used for troubleshooting */ public final int id; - public final ThreadedParameters threadedParam; + + public final ThreadWorldGenParams threadedParam; public final DhChunkPos minPos; - /** the number of chunks wide this event is */ - public final int size; + public final int widthInChunks; public final EDhApiWorldGenerationStep targetGenerationStep; public final EDhApiDistantGeneratorMode generatorMode; - public EventTimer timer = null; - public long inQueueTime; - public long timeoutTime = -1; - public final CompletableFuture future = new CompletableFuture<>(); + public final CompletableFuture future; public final Consumer resultConsumer; - public GenerationEvent( - DhChunkPos minPos, int size, BatchGenerationEnvironment generationGroup, + //=============// + // constructor // + //=============// + + private GenerationEvent( + DhChunkPos minPos, int widthInChunks, BatchGenerationEnvironment generationGroup, EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep targetGenerationStep, Consumer resultConsumer) { - this.inQueueTime = System.nanoTime(); - this.id = generationFutureDebugIDs++; + this.id = DEBUG_ID_REF.getAndIncrement(); + this.minPos = minPos; - this.size = size; - this.generatorMode = generatorMode; + this.widthInChunks = widthInChunks; this.targetGenerationStep = targetGenerationStep; - this.threadedParam = ThreadedParameters.getOrMake(generationGroup.params); + this.generatorMode = generatorMode; + this.threadedParam = ThreadWorldGenParams.getOrMake(generationGroup.globalParams); + this.future = new CompletableFuture<>(); this.resultConsumer = resultConsumer; } - public static GenerationEvent startEvent( - DhChunkPos minPos, int size, BatchGenerationEnvironment genEnvironment, + //=======// + // start // + //=======// + + public static GenerationEvent start( + DhChunkPos minPos, int widthInChunks, BatchGenerationEnvironment genEnvironment, EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep target, Consumer resultConsumer, ExecutorService worldGeneratorThreadPool) { - GenerationEvent generationEvent = new GenerationEvent(minPos, size, genEnvironment, generatorMode, target, resultConsumer); + GenerationEvent genEvent = new GenerationEvent(minPos, widthInChunks, genEnvironment, generatorMode, target, resultConsumer); try { @@ -85,60 +91,46 @@ public final class GenerationEvent { try { - long runStartTime = System.nanoTime(); - generationEvent.timeoutTime = runStartTime; - generationEvent.inQueueTime = runStartTime - generationEvent.inQueueTime; - generationEvent.timer = new EventTimer("setup"); - - BatchGenerationEnvironment.isDistantGeneratorThread.set(true); + BatchGenerationEnvironment.isDhWorldGenThreadRef.set(true); - genEnvironment.generateLodFromListAsync(generationEvent, (runnable) -> + if (genEvent.generatorMode == EDhApiDistantGeneratorMode.INTERNAL_SERVER) { - worldGeneratorThreadPool.execute(() -> + genEnvironment.internalServerGenerator.generateChunksViaInternalServer(genEvent); + genEvent.future.complete(null); + } + else + { + try { - boolean alreadyMarked = BatchGenerationEnvironment.isCurrentThreadDistantGeneratorThread(); - if (!alreadyMarked) - { - BatchGenerationEnvironment.isDistantGeneratorThread.set(true); - } - - try - { - runnable.run(); - } - catch (Throwable throwable) - { - handleWorldGenThrowable(generationEvent, throwable); - } - finally - { - if (!alreadyMarked) - { - BatchGenerationEnvironment.isDistantGeneratorThread.set(false); - } - } - }); - }); - - generationEvent.future.complete(null); + genEnvironment.generateEvent(genEvent); + } + catch (Throwable throwable) + { + handleWorldGenThrowable(genEvent, throwable); + } + finally + { + genEvent.future.complete(null); + } + } } catch (Throwable initialThrowable) { - handleWorldGenThrowable(generationEvent, initialThrowable); + handleWorldGenThrowable(genEvent, initialThrowable); } finally { - BatchGenerationEnvironment.isDistantGeneratorThread.remove(); + BatchGenerationEnvironment.isDhWorldGenThreadRef.remove(); } }); } catch (RejectedExecutionException e) { - generationEvent.future.completeExceptionally(e); + genEvent.future.completeExceptionally(e); } - return generationEvent; + return genEvent; } /** There's probably a better way to handle this, but it'll work for now */ private static void handleWorldGenThrowable(GenerationEvent generationEvent, Throwable initialThrowable) @@ -165,35 +157,15 @@ public final class GenerationEvent } } - public boolean isComplete() { return this.future.isDone(); } - public boolean hasTimeout(int duration, TimeUnit unit) - { - if (this.timeoutTime == -1) - { - return false; - } - - long currentTime = System.nanoTime(); - long delta = currentTime - this.timeoutTime; - return (delta > TimeUnit.NANOSECONDS.convert(duration, unit)); - } - public boolean terminate() - { - LOGGER.info("======================DUMPING ALL THREADS FOR WORLD GEN======================="); - ThreadPoolUtil.WORLD_GEN_THREAD_FACTORY.dumpAllThreadStacks(); - this.future.cancel(true); - return this.future.isCancelled(); - } - - public void refreshTimeout() - { - this.timeoutTime = System.nanoTime(); - UncheckedInterruptedException.throwIfInterrupted(); - } + //================// + // base overrides // + //================// @Override - public String toString() { return this.id + ":" + this.size + "@" + this.minPos + "(" + this.targetGenerationStep + ")"; } + public String toString() { return this.id + ":" + this.widthInChunks + "@" + this.minPos + "(" + this.targetGenerationStep + ")"; } + + } \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/InternalServerGenerator.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/InternalServerGenerator.java new file mode 100644 index 000000000..eb7fa0b25 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/InternalServerGenerator.java @@ -0,0 +1,310 @@ +package com.seibel.distanthorizons.common.wrappers.worldGeneration; + +import com.seibel.distanthorizons.api.DhApi; +import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.GlobalWorldGenParams; +import com.seibel.distanthorizons.core.api.internal.ClientApi; +import com.seibel.distanthorizons.core.api.internal.SharedApi; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; +import com.seibel.distanthorizons.core.generation.DhLightingEngine; +import com.seibel.distanthorizons.core.level.IDhServerLevel; +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.util.LodUtil; +import com.seibel.distanthorizons.core.util.TimerUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor; +import com.seibel.distanthorizons.coreapi.ModInfo; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkAccess; + +#if MC_VER <= MC_1_20_4 +import net.minecraft.world.level.chunk.ChunkStatus; +#else +import net.minecraft.world.level.chunk.status.ChunkStatus; +#endif + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Function; + +public class InternalServerGenerator +{ + public static final DhLogger LOGGER = new DhLoggerBuilder() + .name("LOD World Gen - Internal Server") + .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 IC2meAccessor C2ME_ACCESSOR = ModAccessorInjector.INSTANCE.get(IC2meAccessor.class); + + /** + * Used to revert the ignore logic in {@link SharedApi} so + * that given chunk pos can be handled again. + * A timer is used so we don't have to inject into MC's code and it works sell enough + * most of the time. + * If a chunk does get through due the timeout not being long enough that isn't the end of the world. + */ + private static final int MS_TO_IGNORE_CHUNK_AFTER_COMPLETION = 5_000; + + #if MC_VER < MC_1_21_5 + private static final TicketType DH_SERVER_GEN_TICKET = TicketType.create("dh_server_gen_ticket", Comparator.comparingLong(ChunkPos::toLong)); + #elif MC_VER < MC_1_21_9 + private static final TicketType DH_SERVER_GEN_TICKET = new TicketType(/* timeout, 0 = disabled*/0L, /* persist */ false, TicketType.TicketUse.LOADING); + #else + private static final TicketType DH_SERVER_GEN_TICKET = new TicketType(/* timeout, 0 = disabled*/0L, /* flags */TicketType.FLAG_LOADING); + #endif + + private static boolean c2meMissingWarningLogged = false; + + + private final GlobalWorldGenParams params; + private final IDhServerLevel dhServerLevel; + private final Timer chunkSaveIgnoreTimer = TimerUtil.CreateTimer("ChunkSaveIgnoreTimer"); + + + + //=============// + // constructor // + //=============// + + public InternalServerGenerator(GlobalWorldGenParams params, IDhServerLevel dhServerLevel) + { + this.params = params; + this.dhServerLevel = dhServerLevel; + } + + + + //============// + // generation // + //============// + + public void generateChunksViaInternalServer(GenerationEvent genEvent) + { + this.runValidation(); + + try + { + //=====================// + // create gen requests // + //=====================// + + ArrayList> getChunkFutureList = new ArrayList<>(); + { + Iterator chunkPosIterator = ChunkPosGenStream.getIterator(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0); + while (chunkPosIterator.hasNext()) + { + ChunkPos chunkPos = chunkPosIterator.next(); + + CompletableFuture requestChunkFuture = + this.requestChunkFromServerAsync(chunkPos) + // log errors if necessary + .whenCompleteAsync( + (chunk, throwable) -> + { + // unwrap the CompletionException if necessary + Throwable actualThrowable = throwable; + while (actualThrowable instanceof CompletionException) + { + actualThrowable = actualThrowable.getCause(); + } + + if (actualThrowable != null) + { + // ignore expected shutdown exceptions + boolean isShutdownException = + ExceptionUtil.isShutdownException(actualThrowable) + || actualThrowable.getMessage().contains("Unloaded chunk"); + if (!isShutdownException) + { + CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load chunk [" + chunkPos + "] from server, error: [" + actualThrowable.getMessage() + "].", actualThrowable); + } + } + }); + + getChunkFutureList.add(requestChunkFuture); + } + } + + + + //==============================// + // wait for generation requests // + //==============================// + + // Join-ing each thread will prevent DH from working on anything else + // but will also prevent over-queuing world gen tasks. + // If C2ME is present the CPU will still be well utilized. + + ArrayList chunkWrappers = new ArrayList<>(); + for (int i = 0; i < getChunkFutureList.size(); i++) + { + CompletableFuture getChunkFuture = getChunkFutureList.get(i); + ChunkAccess chunk = getChunkFuture.join(); + if (chunk != null) + { + ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, this.dhServerLevel.getLevelWrapper()); + chunkWrappers.add(chunkWrapper); + } + } + + + + //==========================// + // process generated chunks // + //==========================// + + int maxSkyLight = this.dhServerLevel.getServerLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT; + for (int i = 0; i < chunkWrappers.size(); i++) + { + ChunkWrapper chunkWrapper = (ChunkWrapper)chunkWrappers.get(i); + + // pre-generated chunks should have lighting but new ones won't + if (!chunkWrapper.isDhBlockLightingCorrect()) + { + DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, chunkWrappers, maxSkyLight); + } + + this.dhServerLevel.updateBeaconBeamsForChunk(chunkWrapper, chunkWrappers); + genEvent.resultConsumer.accept(chunkWrapper); + } + } + finally + { + // release all chunks from the server to prevent out of memory issues + Iterator chunkPosIterator = ChunkPosGenStream.getIterator(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0); + while (chunkPosIterator.hasNext()) + { + ChunkPos chunkPos = chunkPosIterator.next(); + this.releaseChunkFromServer(this.params.mcServerLevel, chunkPos); + } + } + } + private void runValidation() + { + // DH thread check + if (!DhApi.isDhThread() + && ModInfo.IS_DEV_BUILD) + { + throw new IllegalStateException("Internal server generation should be called from one of DH's world gen thread. Current thread: ["+Thread.currentThread().getName()+"]"); + } + + + // C2ME present? + if (C2ME_ACCESSOR == null + && !c2meMissingWarningLogged) + { + c2meMissingWarningLogged = true; + + String c2meWarning = "C2ME missing, \n" + + "low CPU usage and slow world gen speeds expected. \n" + + "DH is set to use MC's internal server for world gen \n" + + "this mode is less efficient unless a mod like C2ME is present." + ; + + if (Config.Common.Logging.Warning.showSlowWorldGenSettingWarnings.get()) + { + String message = + // orange text + "\u00A76" + "Distant Horizons: slow world gen." + "\u00A7r\n" + + c2meWarning; + ClientApi.INSTANCE.showChatMessageNextFrame(message); + } + + LOGGER.warn(c2meWarning); + } + } + private CompletableFuture requestChunkFromServerAsync(ChunkPos chunkPos) + { + return CompletableFuture.supplyAsync(() -> + { + ServerLevel level = this.params.mcServerLevel; + + // ignore chunk update events for this position + SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.addPosToIgnore(new DhChunkPos(chunkPos.x, chunkPos.z)); + + #if MC_VER < MC_1_21_5 + int chunkLevel = 33; // 33 is equivalent to FULL Chunk + level.getChunkSource().distanceManager.addTicket(DH_SERVER_GEN_TICKET, chunkPos, chunkLevel, chunkPos); + #else + level.getChunkSource().addTicketWithRadius(DH_SERVER_GEN_TICKET, chunkPos, 0); + #endif + + // probably not the most optimal to run updates here, but fast enough + level.getChunkSource().distanceManager.runAllUpdates(level.getChunkSource().chunkMap); + + ChunkHolder chunkHolder = level.getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()); + if (chunkHolder == null) + { + throw new IllegalStateException("No chunk chunkHolder for pos ["+chunkPos+"] after ticket has been added."); + } + + #if MC_VER <= MC_1_20_4 + return chunkHolder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap) + .thenApply(result -> result.left().orElseThrow(() -> new RuntimeException(result.right().get().toString()))); // can throw if the server is shutting down + #elif MC_VER <= MC_1_20_6 + return chunkHolder.getOrScheduleFuture(ChunkStatus.FEATURES, level.getChunkSource().chunkMap) + .thenApply(result -> result.orElseThrow(() -> new RuntimeException(result.toString()))); // can throw if the server is shutting down + #else + return chunkHolder.scheduleChunkGenerationTask(ChunkStatus.FEATURES, level.getChunkSource().chunkMap) + .thenApply(result -> result.orElseThrow(() -> new RuntimeException(result.getError()))); // can throw if the server is shutting down + #endif + + }, this.params.mcServerLevel.getChunkSource().chunkMap.mainThreadExecutor) + .thenCompose(Function.identity()); + } + /** + * mitigates out of memory issues in the vanilla chunk system.
+ * See: https://github.com/pop4959/Chunky/pull/383 + */ + private void releaseChunkFromServer(ServerLevel level, ChunkPos chunkPos) + { + level.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> + { + try + { + #if MC_VER < MC_1_21_5 + int chunkLevel = 33; // 33 is equivalent to FULL Chunk + level.getChunkSource().distanceManager.removeTicket(DH_SERVER_GEN_TICKET, chunkPos, chunkLevel, chunkPos); + #else + level.getChunkSource().removeTicketWithRadius(DH_SERVER_GEN_TICKET, chunkPos, 0); + #endif + + level.getChunkSource().chunkMap.tick(() -> false); + + #if MC_VER > MC_1_16_5 + level.entityManager.tick(); + #endif + + + // give MC a few seconds to save the chunk before + // we can process update events there again + this.chunkSaveIgnoreTimer.schedule(new TimerTask() + { + @Override + public void run() { SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.removePosToIgnore(new DhChunkPos(chunkPos.x, chunkPos.z)); } + }, MS_TO_IGNORE_CHUNK_AFTER_COMPLETION); + + } + catch (Exception e) + { + LOGGER.warn("Failed to release chunk back to internal server. Error: ["+e.getMessage()+"]", e); + } + }); + } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ThreadedParameters.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ThreadedParameters.java deleted file mode 100644 index 88001881b..000000000 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ThreadedParameters.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - - -package com.seibel.distanthorizons.common.wrappers.worldGeneration; - -import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment.PerfCalculator; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.WorldGenStructFeatManager; - -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.WorldGenLevel; -#if MC_VER >= MC_1_18_2 -import net.minecraft.world.level.levelgen.structure.StructureCheck; -#endif - -public final class ThreadedParameters -{ - private static final ThreadLocal LOCAL_PARAM = new ThreadLocal<>(); - - final ServerLevel level; - public WorldGenStructFeatManager structFeat = null; - #if MC_VER >= MC_1_18_2 - public StructureCheck structCheck; - #endif - boolean isValid = true; - public final PerfCalculator perf = new PerfCalculator(); - - private static GlobalParameters previousGlobalParameters = null; - - - - public static ThreadedParameters getOrMake(GlobalParameters param) - { - ThreadedParameters tParam = LOCAL_PARAM.get(); - if (tParam != null && tParam.isValid && tParam.level == param.level) - { - return tParam; - } - - tParam = new ThreadedParameters(param); - LOCAL_PARAM.set(tParam); - return tParam; - } - - private ThreadedParameters(GlobalParameters param) - { - previousGlobalParameters = param; - - this.level = param.level; - #if MC_VER < MC_1_18_2 - this.structFeat = new WorldGenStructFeatManager(param.worldGenSettings, level); - #elif MC_VER < MC_1_19_2 - this.structCheck = this.createStructureCheck(param); - #else - this.structCheck = new StructureCheck(param.chunkScanner, param.registry, param.structures, - param.level.dimension(), param.generator, param.randomState, level, param.generator.getBiomeSource(), param.worldSeed, - param.fixerUpper); - #endif - } - - - - public void markAsInvalid() { isValid = false; } - - public void makeStructFeat(WorldGenLevel genLevel, GlobalParameters param) - { - #if MC_VER < MC_1_19_4 - structFeat = new WorldGenStructFeatManager(param.worldGenSettings, genLevel #if MC_VER >= MC_1_18_2 , structCheck #endif ); - #else - structFeat = new WorldGenStructFeatManager(param.worldOptions, genLevel, structCheck); - #endif - } - - - #if MC_VER >= MC_1_18_2 && MC_VER < MC_1_19_2 - public void recreateStructureCheck() - { - if (previousGlobalParameters != null) - { - this.structCheck = createStructureCheck(previousGlobalParameters); - } - } - private StructureCheck createStructureCheck(GlobalParameters param) - { - return new StructureCheck(param.chunkScanner, param.registry, param.structures, - param.level.dimension(), param.generator, this.level, param.generator.getBiomeSource(), param.worldSeed, - param.fixerUpper); - } - #else - public void recreateStructureCheck() { /* do nothing */ } - #endif - -} \ No newline at end of file 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 new file mode 100644 index 000000000..9ad4cabb3 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkCompoundTagParser.java @@ -0,0 +1,744 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling; + +import com.mojang.serialization.Codec; +import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; + +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.level.IDhServerLevel; +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; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + + +import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; +import net.minecraft.core.Registry; +#if MC_VER >= MC_1_19_4 +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.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.state.BlockState; +import net.minecraft.world.level.chunk.*; + +#if MC_VER < MC_1_21_3 +import net.minecraft.world.level.chunk.storage.ChunkSerializer; +#else +#endif + +#if MC_VER < MC_1_21_9 +import net.minecraft.world.level.block.Blocks; +#else +#endif + +import net.minecraft.world.level.levelgen.Heightmap; +#if MC_VER >= MC_1_18_2 +#if MC_VER < MC_1_19_2 +import net.minecraft.world.level.levelgen.feature.StructureFeature; +#endif +import net.minecraft.world.ticks.LevelChunkTicks; +#endif +#if MC_VER >= MC_1_18_2 +import net.minecraft.core.Holder; +#if MC_VER < MC_1_19_2 +import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature; +#endif +#endif + +#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 +import net.minecraft.world.level.material.Fluids; +#endif + +#if MC_VER == MC_1_20_6 +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.chunk.status.ChunkType; +#elif MC_VER >= MC_1_21_1 +import net.minecraft.world.level.chunk.status.ChunkStatus; +#endif + +import net.minecraft.world.level.material.Fluid; + + +public class ChunkCompoundTagParser +{ + public static final DhLogger LOGGER = new DhLoggerBuilder() + .name("LOD Chunk Reader") + .fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile) + .build(); + + 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; + + + + + //============// + // read chunk // + //============// + + public static ChunkWrapper createFromTag( + WorldGenLevel mcWorldGenLevel, IDhServerLevel dhServerLevel, + ChunkPos chunkPos, CompoundTag chunkData) + { + #if MC_VER < MC_1_18_2 + CompoundTag tagLevel = chunkData.getCompound("Level"); + #else + CompoundTag tagLevel = chunkData; + #endif + + + + //=======================// + // validate the chunkPos // + //=======================// + + int chunkX = CompoundTagUtil.getInt(tagLevel,"xPos"); + int chunkZ = CompoundTagUtil.getInt(tagLevel, "zPos"); + ChunkPos actualChunkPos = new ChunkPos(chunkX, chunkZ); + + // confirm chunk pos is correct + if (!Objects.equals(chunkPos, actualChunkPos)) + { + if (chunkX == 0 && chunkZ == 0) + { + if (!ZERO_CHUNK_POS_ERROR_LOGGED_REF.getAndSet(true)) + { + // explicit chunkPos toString is necessary otherwise the JDK 17 compiler breaks + LOGGER.warn("Chunk file at ["+chunkPos.toString()+"] doesn't have a chunk pos. \n" + + "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." + + " "); + } + } + else + { + 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; + } + } + + + + //===========// + // get ticks // + //===========// + + #if MC_VER < MC_1_18_2 + ChunkBiomeContainer chunkBiomeContainer = new ChunkBiomeContainer( + mcWorldGenLevel.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), #if MC_VER >= MC_1_17_1 mcWorldGenLevel, #endif + chunkPos, mcWorldGenLevel.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 , mcWorldGenLevel #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 , mcWorldGenLevel #endif ); + #else + // 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 // + //=====================// + + int sectionYCount = #if MC_VER < MC_1_17_1 16; #else mcWorldGenLevel.getSectionsCount(); #endif + LevelChunkSection[] chunkSections = new LevelChunkSection[sectionYCount]; + boolean hasBlocks = readAndPopulateSections(mcWorldGenLevel, chunkPos, tagLevel, chunkSections); + if (!hasBlocks) + { + return null; + } + + + long inhabitedTime = CompoundTagUtil.getLong(tagLevel, "InhabitedTime"); + boolean isLightOn = CompoundTagUtil.getBoolean(tagLevel, "isLightOn"); + + + + //============// + // make chunk // + //============// + + #if MC_VER < MC_1_18_2 + LevelChunk chunk = new LevelChunk((Level) mcWorldGenLevel.getLevel(), chunkPos, chunkBiomeContainer, UpgradeData.EMPTY, blockTicks, + fluidTicks, inhabitedTime, chunkSections, null); + #else + LevelChunk chunk = new LevelChunk((Level) mcWorldGenLevel, chunkPos, UpgradeData.EMPTY, blockTicks, + fluidTicks, inhabitedTime, chunkSections, null, null); + #endif + + // Set some states after object creation + chunk.setLightCorrect(isLightOn); + boolean hasHeightmapData = readHeightmaps(chunk, chunkData); + + // chunk wrapper so we can pass along extra data more easily + ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, dhServerLevel.getServerLevelWrapper(), !hasHeightmapData); + + + + //===========================// + // check if chunk has blocks // + //===========================// + + // in some MC versions all the NBT data will be there + // but the chunk will be totally empty, + // usually this means the chunk was only partially generated. + // If that happens we should try to generate the chunk from scratch + // otherwise we can end up with large empty holes in the world. + + // walking through the heightmap (recreated by DH if missing) + // is a fast way to check if there are any blocks in the chunk + boolean chunkHasBlocks = false; + int serverMinHeight = dhServerLevel.getServerLevelWrapper().getMinHeight(); + for (int x = 0; x < 16 && !chunkHasBlocks; x++) + { + for (int z = 0; z < 16 && !chunkHasBlocks; z++) + { + int heightMap = Math.max( + // max between both heightmaps just in case there's a discrepancy + chunkWrapper.getLightBlockingHeightMapValue(x, z), + chunkWrapper.getSolidHeightMapValue(x, z) + ); + if (heightMap != serverMinHeight) + { + chunkHasBlocks = true; + } + } + } + + + if (chunkHasBlocks) + { + return chunkWrapper; + } + else + { + // no blocks detected, this chunk should be generated from scratch + return null; + } + } + + + + //=================// + // chunk sections // + // (Blocks/biomes) // + //=================// + + /** handles both blocks and biomes */ + private static boolean readAndPopulateSections( + LevelAccessor level, ChunkPos chunkPos, CompoundTag chunkData, + LevelChunkSection[] chunkSections) + { + int sectionYCount = #if MC_VER < MC_1_17_1 16; #else level.getSectionsCount(); #endif + + 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); + } + + + boolean blocksFound = false; + if (tagSections != null) + { + for (int i = 0; i < tagSections.size(); ++i) + { + CompoundTag tagSection = CompoundTagUtil.getCompoundTag(tagSections, i); + 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 + + blocksFound = true; + } + 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_19_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 + && !biomeTag.isEmpty()) + { + #if MC_VER < MC_1_20_6 + biomeContainer = new PalettedContainer>( + biomeRegistry.asHolderIdMap(), + biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES); + #else + biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, biomeTag) + .promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYCount, (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 blocksFound; + } + + private static Codec> getBlockStateCodec(LevelAccessor level) + { + #if MC_VER < MC_1_18_2 + return null; // unused for older MC versions + #elif MC_VER < MC_1_19_2 + return PalettedContainer.codec(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState()); + #elif MC_VER <= MC_1_21_8 + 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_19_2 Codec> + #else Codec>> + #endif + getBiomeCodec(LevelAccessor level, Registry biomeRegistry) + { + #if MC_VER < MC_1_18_2 + return null; // unused for older MC versions + #elif MC_VER < MC_1_19_2 + return 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 boolean readHeightmaps(LevelChunk chunk, CompoundTag chunkData) + { + CompoundTag tagHeightmaps = CompoundTagUtil.getCompoundTag(chunkData, "Heightmaps"); + if (tagHeightmaps == null) + { + return false; + } + + + for (Heightmap.Types type : ChunkStatus.FULL.heightmapsAfter()) + { + String heightmapKey = type.getSerializationKey(); + + #if MC_VER < MC_1_21_5 + if (tagHeightmaps.contains(heightmapKey, 12)) + { + chunk.setHeightmap(type, tagHeightmaps.getLongArray(heightmapKey)); + } + #else + if (tagHeightmaps.contains(heightmapKey)) + { + Optional optionalHeightmap = tagHeightmaps.getLongArray(heightmapKey); + if (optionalHeightmap.isPresent()) + { + chunk.setHeightmap(type, optionalHeightmap.get()); + } + } + #endif + } + + Heightmap.primeHeightmaps(chunk, ChunkStatus.FULL.heightmapsAfter()); + return true; + } + + + + //================// + // chunk lighting // + //================// + + /** source: https://minecraft.wiki/w/Chunk_format */ + public static CombinedChunkLightStorage readLight(ChunkAccess chunk, CompoundTag chunkData) + { + #if MC_VER <= MC_1_17_1 + // MC 1.16 and 1.17 doesn't have the necessary NBT info + return null; + #else + + CombinedChunkLightStorage combinedStorage = new CombinedChunkLightStorage(ChunkWrapper.getInclusiveMinBuildHeight(chunk), ChunkWrapper.getExclusiveMaxBuildHeight(chunk)); + ChunkLightStorage blockLightStorage = combinedStorage.blockLightStorage; + ChunkLightStorage skyLightStorage = combinedStorage.skyLightStorage; + + boolean foundSkyLight = false; + + + + //===================// + // get NBT tags info // + //===================// + + Tag chunkSectionTags = chunkData.get("sections"); + if (chunkSectionTags == null) + { + if (!lightingSectionErrorLogged) + { + lightingSectionErrorLogged = true; + LOGGER.error("No sections found for chunk at pos ["+chunk.getPos()+"] chunk data may be out of date."); + } + return null; + } + else if (!(chunkSectionTags instanceof ListTag)) + { + if (!lightingSectionErrorLogged) + { + lightingSectionErrorLogged = true; + LOGGER.error("Chunk section tag list have unexpected type ["+chunkSectionTags.getClass().getName()+"], expected ["+ListTag.class.getName()+"]."); + } + return null; + } + ListTag chunkSectionListTag = (ListTag) chunkSectionTags; + + + + //===================// + // get lighting info // + //===================// + + for (int sectionIndex = 0; sectionIndex < chunkSectionListTag.size(); sectionIndex++) + { + Tag chunkSectionTag = chunkSectionListTag.get(sectionIndex); + if (!(chunkSectionTag instanceof CompoundTag)) + { + if (!lightingSectionErrorLogged) + { + lightingSectionErrorLogged = true; + LOGGER.error("Chunk section tag has an unexpected type ["+chunkSectionTag.getClass().getName()+"], expected ["+CompoundTag.class.getName()+"]."); + } + return null; + } + CompoundTag chunkSectionCompoundTag = (CompoundTag) chunkSectionTag; + + + // if null all lights = 0 + byte[] blockLightNibbleArray = CompoundTagUtil.getByteArray(chunkSectionCompoundTag, "BlockLight"); + byte[] skyLightNibbleArray = CompoundTagUtil.getByteArray(chunkSectionCompoundTag, "SkyLight"); + + if (blockLightNibbleArray != null + && skyLightNibbleArray != null) + { + // if any sky light was found then all lights above will be max brightness + if (skyLightNibbleArray.length != 0) + { + foundSkyLight = true; + } + + for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) + { + for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) + { + // chunk sections are also 16 blocks tall + for (int relY = 0; relY < LodUtil.CHUNK_WIDTH; relY++) + { + int blockPosIndex = relY*16*16 + relZ*16 + relX; + byte blockLight = (blockLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(blockLightNibbleArray, blockPosIndex); + byte skyLight = (skyLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(skyLightNibbleArray, blockPosIndex); + if (skyLightNibbleArray.length == 0 && foundSkyLight) + { + skyLight = LodUtil.MAX_MC_LIGHT; + } + + int y = relY + (sectionIndex * LodUtil.CHUNK_WIDTH) + ChunkWrapper.getInclusiveMinBuildHeight(chunk); + blockLightStorage.set(relX, y, relZ, blockLight); + skyLightStorage.set(relX, y, relZ, skyLight); + } + } + } + } + } + + return combinedStorage; + #endif + } + /** source: https://minecraft.wiki/w/Chunk_format#Block_Format */ + private static byte getNibbleAtIndex(byte[] arr, int index) + { + if (index % 2 == 0) + { + return (byte)(arr[index/2] & 0x0F); + } + else + { + return (byte)((arr[index/2]>>4) & 0x0F); + } + } + + + + //=========// + // logging // + //=========// + + private static void logBlockDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message) + { + LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) -> + { + LOGGER.warn("Unable to deserialize blocks for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+newMessage+"]. " + + "This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem."); + + return newMessage; + }); + } + private static void logBiomeDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message) + { + LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) -> + { + LOGGER.warn("Unable to deserialize biomes for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+newMessage+"]. " + + "This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem."); + + return newMessage; + }); + } + + private static void logParsingWarningOnce(String message) { logParsingWarningOnce(message, null); } + private static void logParsingWarningOnce(String message, Exception e) + { + if (message == null) + { + return; + } + + LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) -> + { + LOGGER.warn("Parsing error: ["+newMessage+"]. " + + "This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.", + e); + + return newMessage; + }); + } + + private static RuntimeException logErrorAndReturnException(String message) + { + LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) -> + { + LOGGER.warn("Parsing error: ["+newMessage+"]. " + + "This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem."); + + return newMessage; + }); + + // Currently we want to ignore these errors, if returning null is a problem, we can change this later + return null; //new RuntimeException(message); + } + + + + //================// + // helper classes // + //================// + + public static class CombinedChunkLightStorage + { + public ChunkLightStorage blockLightStorage; + public ChunkLightStorage skyLightStorage; + + public CombinedChunkLightStorage(int minY, int maxY) + { + this.blockLightStorage = ChunkLightStorage.createBlockLightStorage(minY, maxY); + this.skyLightStorage = ChunkLightStorage.createSkyLightStorage(minY, maxY); + } + } + + + +} 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..cc51b392d --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/ChunkFileReader.java @@ -0,0 +1,343 @@ +package com.seibel.distanthorizons.common.wrappers.worldGeneration.chunkFileHandling; + +import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.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.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.storage.IOWorker; +import net.minecraft.world.level.chunk.storage.RegionFileStorage; + +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; + +#if MC_VER <= MC_1_17_1 +import net.minecraft.world.level.chunk.ChunkStatus; +#elif MC_VER <= MC_1_19_2 +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.core.Registry; +#elif MC_VER <= MC_1_19_4 +import net.minecraft.core.registries.Registries; +import net.minecraft.world.level.chunk.ChunkStatus; +#elif MC_VER <= MC_1_20_6 +import net.minecraft.core.registries.Registries; +#elif MC_VER <= MC_1_21_3 +import net.minecraft.core.registries.Registries; +import net.minecraft.world.level.chunk.status.ChunkStatus; +#elif MC_VER <= MC_1_21_8 +import net.minecraft.core.registries.Registries; +import net.minecraft.world.level.chunk.status.ChunkStatus; +#elif MC_VER <= MC_1_21_9 +import net.minecraft.world.level.chunk.PalettedContainerFactory; +#else +import net.minecraft.world.level.chunk.PalettedContainerFactory; +#endif + +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 createEmptyOrPreExistingChunkWrapperAsync( + int chunkX, int chunkZ, + Map chunkSkyLightingByDhPos, + Map chunkBlockLightingByDhPos, + Map generatedChunkWrapperByDhPos) + { + ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); + DhChunkPos dhChunkPos = new DhChunkPos(chunkX, chunkZ); + + if (generatedChunkWrapperByDhPos.containsKey(dhChunkPos)) + { + return CompletableFuture.completedFuture(generatedChunkWrapperByDhPos.get(dhChunkPos)); + } + + return this.getChunkNbtDataAsync(chunkPos) + .thenApply((CompoundTag chunkData) -> + { + ChunkWrapper newChunkWrapper = this.loadOrMakeChunkWrapper(chunkPos, chunkData); + + // attempt to get chunk lighting + ChunkCompoundTagParser.CombinedChunkLightStorage combinedLights = ChunkCompoundTagParser.readLight(newChunkWrapper.getChunk(), chunkData); + if (combinedLights != null) + { + // may be empty, empty checks are handled later + chunkSkyLightingByDhPos.put(dhChunkPos, combinedLights.skyLightStorage); + chunkBlockLightingByDhPos.put(dhChunkPos, combinedLights.blockLightStorage); + } + + return newChunkWrapper; + }) + // separate handle so we can cleanly handle missing chunks and/or thrown errors + .handle((ChunkWrapper newChunkWrapper, Throwable throwable) -> + { + if (newChunkWrapper != null) + { + return newChunkWrapper; + } + else + { + return this.CreateProtoChunkWrapper(this.params.mcServerLevel, chunkPos); + } + }) + .thenApply((ChunkWrapper newChunkWrapper) -> + { + generatedChunkWrapperByDhPos.put(dhChunkPos, newChunkWrapper); + return newChunkWrapper; + }); + } + private CompletableFuture getChunkNbtDataAsync(ChunkPos chunkPos) + { + ServerLevel level = this.params.mcServerLevel; + + 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.mcServerLevel.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 ChunkWrapper loadOrMakeChunkWrapper(ChunkPos chunkPos, CompoundTag chunkTagData) + { + ServerLevel mcServerLevel = this.params.mcServerLevel; + + if (chunkTagData == null) + { + return this.CreateProtoChunkWrapper(mcServerLevel, chunkPos); + } + else + { + try + { + ChunkWrapper chunkWrapper = ChunkCompoundTagParser.createFromTag(mcServerLevel, this.params.dhServerLevel, chunkPos, chunkTagData); + if (chunkWrapper == null) + { + chunkWrapper = this.CreateProtoChunkWrapper(mcServerLevel, chunkPos); + } + return chunkWrapper; + } + 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 this.CreateProtoChunkWrapper(mcServerLevel, chunkPos); + } + } + } + + public ChunkWrapper CreateProtoChunkWrapper(ServerLevel level, ChunkPos chunkPos) + { + ProtoChunk chunk = CreateProtoChunk(level, chunkPos); + return new ChunkWrapper(chunk, this.params.dhServerLevel.getLevelWrapper(), false); + } + public static ProtoChunk CreateProtoChunk(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..f175815c8 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/chunkFileHandling/CompoundTagUtil.java @@ -0,0 +1,148 @@ +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 + } + + + + 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/mimicObject/ChunkFileReader.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/ChunkFileReader.java deleted file mode 100644 index b9aa41169..000000000 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/mimicObject/ChunkFileReader.java +++ /dev/null @@ -1,876 +0,0 @@ -/* - * This file is part of the Distant Horizons mod - * licensed under the GNU GPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject; - -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.logging.DhLogger; -import com.seibel.distanthorizons.core.util.LodUtil; -import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage; - -import java.util.Objects; -import java.util.Optional; -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.*; - -#if MC_VER < MC_1_21_3 -import net.minecraft.world.level.chunk.storage.ChunkSerializer; -#else -#endif - -import net.minecraft.world.level.levelgen.Heightmap; -#if MC_VER >= MC_1_18_2 -import net.minecraft.world.level.levelgen.blending.BlendingData; -#if MC_VER < MC_1_19_2 -import net.minecraft.world.level.levelgen.feature.StructureFeature; -#endif -import net.minecraft.world.ticks.LevelChunkTicks; -#endif -#if MC_VER >= MC_1_18_2 -import net.minecraft.core.Holder; -#if MC_VER < MC_1_19_2 -import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature; -#endif -#endif - -#if MC_VER == MC_1_16_5 || MC_VER == MC_1_17_1 -import net.minecraft.world.level.material.Fluids; -#endif - -#if MC_VER == MC_1_20_6 -import net.minecraft.world.level.chunk.status.ChunkStatus; -import net.minecraft.world.level.chunk.status.ChunkType; -#elif MC_VER >= MC_1_21_1 -import net.minecraft.world.level.chunk.status.ChunkStatus; -import net.minecraft.world.level.chunk.status.ChunkType; -#endif - -import net.minecraft.world.level.material.Fluid; -import org.jetbrains.annotations.Nullable; - - -public class ChunkFileReader -{ - private static final AtomicBoolean ZERO_CHUNK_POS_ERROR_LOGGED_REF = new AtomicBoolean(false); - - private static final DhLogger LOGGER = BatchGenerationEnvironment.CHUNK_LOAD_LOGGER; - - - #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 boolean lightingSectionErrorLogged = false; - - private static final ConcurrentHashMap LOGGED_ERROR_MESSAGE_MAP = new ConcurrentHashMap<>(); - - - - //============// - // read chunk // - //============// - - public static LevelChunk read(WorldGenLevel level, ChunkPos chunkPos, CompoundTag chunkData) - { - #if MC_VER < MC_1_18_2 - CompoundTag tagLevel = chunkData.getCompound("Level"); - #else - CompoundTag tagLevel = chunkData; - #endif - - int chunkX = tagGetInt(tagLevel,"xPos"); - int chunkZ = tagGetInt(tagLevel, "zPos"); - ChunkPos actualPos = new ChunkPos(chunkX, chunkZ); - - if (!Objects.equals(chunkPos, actualPos)) - { - if (chunkX == 0 && chunkZ == 0) - { - if (!ZERO_CHUNK_POS_ERROR_LOGGED_REF.getAndSet(true)) - { - // explicit chunkPos toString is necessary otherwise the JDK 17 compiler breaks - LOGGER.warn("Chunk file at ["+chunkPos.toString()+"] doesn't have a chunk pos. \n" + - "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."+ - ""); - } - } - 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()+"])"); - return null; - } - } - - #if MC_VER < MC_1_20_6 - ChunkStatus.ChunkType chunkType; - #else - ChunkType chunkType; - #endif - chunkType = readChunkType(tagLevel); - - #if MC_VER < MC_1_18_2 - 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 - BlendingData blendingData = null; - - if (chunkType == ChunkType.PROTOCHUNK) - { - return null; - } - #endif - - long inhabitedTime = tagGetLong(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 MC_VER < MC_1_17_1 - //upgradeData = tagLevel.contains(TAG_UPGRADE_DATA, 10) - // ? new UpgradeData(tagGetCompoundTag(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) - // : UpgradeData.EMPTY; - //#else - //upgradeData = tagLevel.contains(TAG_UPGRADE_DATA) - // ? new UpgradeData(tagGetCompoundTag(tagLevel, TAG_UPGRADE_DATA), level) - // : UpgradeData.EMPTY; - //#endif - - - boolean isLightOn = tagGetBoolean(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 , - chunkPos, level.getLevel().getChunkSource().getGenerator().getBiomeSource(), - tagLevel.contains("Biomes", 11) ? tagLevel.getIntArray("Biomes") : null); - - 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 ); - - 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 ); - #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 - #endif - - LevelChunkSection[] levelChunkSections = readSections(level, chunkPos, tagLevel); - - // ====================== Make the chunk ========================= - #if MC_VER < MC_1_18_2 - LevelChunk chunk = new LevelChunk((Level) level.getLevel(), chunkPos, chunkBiomeContainer, upgradeData, blockTicks, - fluidTicks, inhabitedTime, levelChunkSections, null); - #else - LevelChunk chunk = new LevelChunk((Level) level, chunkPos, upgradeData, blockTicks, - fluidTicks, inhabitedTime, levelChunkSections, null, blendingData); - #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 = tagGetListTag(chunkData, "Sections", 10); - if (tagSections == null || tagSections.isEmpty()) - { - tagSections = tagGetListTag(chunkData, "sections", 10); - } - - - if (tagSections != null) - { - for (int j = 0; j < tagSections.size(); ++j) - { - CompoundTag tagSection = tagGetCompoundTag(tagSections, j); - if (tagSection == null) - { - continue; - } - - final int sectionYPos = tagGetByte(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, tagGetCompoundTag(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")) - .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, tagGetCompoundTag(tagSection, "biomes")) - .promotePartial(string -> logBiomeDeserializationWarning(chunkPos, sectionYIndex, (String) string)) - .getOrThrow(false, (message) -> logParsingWarningOnce(message)); - #else - biomeContainer = biomeCodec.parse(NbtOps.INSTANCE, tagGetCompoundTag(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; - } - 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) - { - String statusString = tagGetString(tagLevel,"Status"); - if (statusString != null) - { - ChunkStatus chunkStatus = ChunkStatus.byName(statusString); - if (chunkStatus != null) - { - return chunkStatus.getChunkType(); - } - } - - #if MC_VER <= MC_1_20_4 - return ChunkStatus.ChunkType.PROTOCHUNK; - #else - return ChunkType.PROTOCHUNK; - #endif - } - private static void readHeightmaps(LevelChunk chunk, CompoundTag chunkData) - { - CompoundTag tagHeightmaps = tagGetCompoundTag(chunkData, "Heightmaps"); - 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()); - } - } - // 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 = tagGetListTag(chunkData,"PostProcessing", 9); - // if (tagPostProcessings != null) - // { - // for (int i = 0; i < tagPostProcessings.size(); ++i) - // { - // ListTag listTag3 = tagGetListTag(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); - // #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) - { - @SuppressWarnings({"unchecked", "rawtypes"}) - Dynamic blendingDataTag = new Dynamic(NbtOps.INSTANCE, chunkData.getCompound("blending_data")); - - try - { - #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 - } - catch (Exception e) - { - String message = e.getMessage(); - if (message == null || message.trim().isEmpty()) - { - message = "Failed to parse blending data"; - } - - logParsingWarningOnce(message, e); - } - } - return blendingData; - } - #endif - - - - //=====================// - // read chunk lighting // - //=====================// - - /** - * https://minecraft.wiki/w/Chunk_format - */ - public static CombinedChunkLightStorage readLight(ChunkAccess chunk, CompoundTag chunkData) - { - #if MC_VER <= MC_1_17_1 - // MC 1.16 and 1.17 doesn't have the necessary NBT info - return null; - #else - - CombinedChunkLightStorage combinedStorage = new CombinedChunkLightStorage(ChunkWrapper.getInclusiveMinBuildHeight(chunk), ChunkWrapper.getExclusiveMaxBuildHeight(chunk)); - ChunkLightStorage blockLightStorage = combinedStorage.blockLightStorage; - ChunkLightStorage skyLightStorage = combinedStorage.skyLightStorage; - - boolean foundSkyLight = false; - - - - //===================// - // get NBT tags info // - //===================// - - Tag chunkSectionTags = chunkData.get("sections"); - if (chunkSectionTags == null) - { - if (!lightingSectionErrorLogged) - { - lightingSectionErrorLogged = true; - LOGGER.error("No sections found for chunk at pos ["+chunk.getPos()+"] chunk data may be out of date."); - } - return null; - } - else if (!(chunkSectionTags instanceof ListTag)) - { - if (!lightingSectionErrorLogged) - { - lightingSectionErrorLogged = true; - LOGGER.error("Chunk section tag list have unexpected type ["+chunkSectionTags.getClass().getName()+"], expected ["+ListTag.class.getName()+"]."); - } - return null; - } - ListTag chunkSectionListTag = (ListTag) chunkSectionTags; - - - - //===================// - // get lighting info // - //===================// - - for (int sectionIndex = 0; sectionIndex < chunkSectionListTag.size(); sectionIndex++) - { - Tag chunkSectionTag = chunkSectionListTag.get(sectionIndex); - if (!(chunkSectionTag instanceof CompoundTag)) - { - if (!lightingSectionErrorLogged) - { - lightingSectionErrorLogged = true; - LOGGER.error("Chunk section tag has an unexpected type ["+chunkSectionTag.getClass().getName()+"], expected ["+CompoundTag.class.getName()+"]."); - } - return null; - } - CompoundTag chunkSectionCompoundTag = (CompoundTag) chunkSectionTag; - - - // if null all lights = 0 - byte[] blockLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "BlockLight"); - byte[] skyLightNibbleArray = tagGetByteArray(chunkSectionCompoundTag, "SkyLight"); - - if (blockLightNibbleArray != null - && skyLightNibbleArray != null) - { - // if any sky light was found then all lights above will be max brightness - if (skyLightNibbleArray.length != 0) - { - foundSkyLight = true; - } - - for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) - { - for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) - { - // chunk sections are also 16 blocks tall - for (int relY = 0; relY < LodUtil.CHUNK_WIDTH; relY++) - { - int blockPosIndex = relY*16*16 + relZ*16 + relX; - byte blockLight = (blockLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(blockLightNibbleArray, blockPosIndex); - byte skyLight = (skyLightNibbleArray.length == 0) ? 0 : getNibbleAtIndex(skyLightNibbleArray, blockPosIndex); - if (skyLightNibbleArray.length == 0 && foundSkyLight) - { - skyLight = LodUtil.MAX_MC_LIGHT; - } - - int y = relY + (sectionIndex * LodUtil.CHUNK_WIDTH) + ChunkWrapper.getInclusiveMinBuildHeight(chunk); - blockLightStorage.set(relX, y, relZ, blockLight); - skyLightStorage.set(relX, y, relZ, skyLight); - } - } - } - } - } - - return combinedStorage; - #endif - } - /** source: https://minecraft.wiki/w/Chunk_format#Block_Format */ - private static byte getNibbleAtIndex(byte[] arr, int index) - { - if (index % 2 == 0) - { - return (byte)(arr[index/2] & 0x0F); - } - else - { - return (byte)((arr[index/2]>>4) & 0x0F); - } - } - - - - //=========// - // logging // - //=========// - - private static void logBlockDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message) - { - LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) -> - { - LOGGER.warn("Unable to deserialize blocks for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+newMessage+"]. " + - "This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem."); - - return newMessage; - }); - } - private static void logBiomeDeserializationWarning(ChunkPos chunkPos, int sectionYIndex, String message) - { - LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) -> - { - LOGGER.warn("Unable to deserialize biomes for chunk section [" + chunkPos.x + ", " + sectionYIndex + ", " + chunkPos.z + "], error: ["+newMessage+"]. " + - "This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem."); - - return newMessage; - }); - } - - private static void logParsingWarningOnce(String message) { logParsingWarningOnce(message, null); } - private static void logParsingWarningOnce(String message, Exception e) - { - if (message == null) - { - return; - } - - LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) -> - { - LOGGER.warn("Parsing error: ["+newMessage+"]. " + - "This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem.", - e); - - return newMessage; - }); - } - - private static RuntimeException logErrorAndReturnException(String message) - { - LOGGED_ERROR_MESSAGE_MAP.computeIfAbsent(message, (newMessage) -> - { - LOGGER.warn("Parsing error: ["+newMessage+"]. " + - "This can probably be ignored, although if your world looks wrong, optimizing it via the single player menu then deleting your DH database(s) should fix the problem."); - - return newMessage; - }); - - // Currently we want to ignore these errors, if returning null is a problem, we can change this later - return null; //new RuntimeException(message); - } - - - - //====================// - // 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 // - //================// - - public static class CombinedChunkLightStorage - { - public ChunkLightStorage blockLightStorage; - public ChunkLightStorage skyLightStorage; - - public CombinedChunkLightStorage(int minY, int maxY) - { - this.blockLightStorage = ChunkLightStorage.createBlockLightStorage(minY, maxY); - this.skyLightStorage = ChunkLightStorage.createSkyLightStorage(minY, maxY); - } - } - -} - 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/GlobalParameters.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/params/GlobalWorldGenParams.java similarity index 71% rename from common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/GlobalParameters.java rename to common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/params/GlobalWorldGenParams.java index a9c90a45f..2f5b729d0 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/GlobalParameters.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/params/GlobalWorldGenParams.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package com.seibel.distanthorizons.common.wrappers.worldGeneration; +package com.seibel.distanthorizons.common.wrappers.worldGeneration.params; import com.mojang.datafixers.DataFixer; import com.seibel.distanthorizons.common.wrappers.world.ServerLevelWrapper; @@ -27,35 +27,50 @@ import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ThreadedLevelLightEngine; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.chunk.ChunkGenerator; #if MC_VER >= MC_1_18_2 import net.minecraft.world.level.chunk.storage.ChunkScanAccess; #endif -import net.minecraft.world.level.levelgen.WorldGenSettings; + #if MC_VER < MC_1_19_2 import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; +#elif MC_VER < MC_1_19_2 +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager; #else -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; import net.minecraft.world.level.levelgen.RandomState; -#if MC_VER >= MC_1_19_4 -import net.minecraft.world.level.levelgen.WorldOptions; -import net.minecraft.core.registries.Registries; -#endif +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; #endif import net.minecraft.world.level.storage.WorldData; -public final class GlobalParameters +#if MC_VER < MC_1_19_4 +#elif MC_VER < MC_1_21_3 +import net.minecraft.core.registries.Registries; +#else +import net.minecraft.core.registries.Registries; +#endif + +#if MC_VER < MC_1_19_4 +import net.minecraft.world.level.levelgen.WorldGenSettings; +#else +import net.minecraft.world.level.levelgen.WorldOptions; +#endif + +/** + * Handles parameters that are relevant for the entire MC world. + * + * @see ThreadWorldGenParams + */ +public final class GlobalWorldGenParams { public final ChunkGenerator generator; - public final IDhServerLevel lodLevel; - public final ServerLevel level; + public final IDhServerLevel dhServerLevel; + public final ServerLevel mcServerLevel; public final Registry biomes; public final RegistryAccess registry; public final long worldSeed; - public final DataFixer fixerUpper; + public final DataFixer dataFixer; #if MC_VER < MC_1_19_2 public final StructureManager structures; @@ -72,18 +87,24 @@ public final class GlobalParameters #if MC_VER >= MC_1_18_2 public final BiomeManager biomeManager; - public final ChunkScanAccess chunkScanner; // FIXME: Figure out if this is actually needed + public final ChunkScanAccess chunkScanner; #endif - public GlobalParameters(IDhServerLevel lodLevel) + + + //=============// + // constructor // + //=============// + + public GlobalWorldGenParams(IDhServerLevel dhServerLevel) { - this.lodLevel = lodLevel; + this.dhServerLevel = dhServerLevel; - this.level = ((ServerLevelWrapper) lodLevel.getServerLevelWrapper()).getWrappedMcObject(); - MinecraftServer server = this.level.getServer(); + this.mcServerLevel = ((ServerLevelWrapper) dhServerLevel.getServerLevelWrapper()).getWrappedMcObject(); + MinecraftServer server = this.mcServerLevel.getServer(); WorldData worldData = server.getWorldData(); this.registry = server.registryAccess(); - + #if MC_VER < MC_1_19_4 this.worldGenSettings = worldData.worldGenSettings(); this.biomes = registry.registryOrThrow(Registry.BIOME_REGISTRY); @@ -99,15 +120,19 @@ public final class GlobalParameters #endif #if MC_VER >= MC_1_18_2 - this.biomeManager = new BiomeManager(this.level, BiomeManager.obfuscateSeed(this.worldSeed)); - this.chunkScanner = this.level.getChunkSource().chunkScanner(); + this.biomeManager = new BiomeManager(this.mcServerLevel, BiomeManager.obfuscateSeed(this.worldSeed)); + this.chunkScanner = this.mcServerLevel.getChunkSource().chunkScanner(); #endif + this.structures = server.getStructureManager(); - this.generator = this.level.getChunkSource().getGenerator(); - this.fixerUpper = server.getFixerUpper(); + this.generator = this.mcServerLevel.getChunkSource().getGenerator(); + this.dataFixer = server.getFixerUpper(); + #if MC_VER >= MC_1_19_2 - this.randomState = this.level.getChunkSource().randomState(); + this.randomState = this.mcServerLevel.getChunkSource().randomState(); #endif } + + } \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/params/ThreadWorldGenParams.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/params/ThreadWorldGenParams.java new file mode 100644 index 000000000..00944a550 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/params/ThreadWorldGenParams.java @@ -0,0 +1,124 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + + +package com.seibel.distanthorizons.common.wrappers.worldGeneration.params; + +import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.WorldGenStructFeatManager; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.WorldGenLevel; +#if MC_VER >= MC_1_18_2 +import net.minecraft.world.level.levelgen.structure.StructureCheck; +#endif + +public final class ThreadWorldGenParams +{ + private static final ThreadLocal LOCAL_PARAM_REF = new ThreadLocal<>(); + + + final ServerLevel level; + public WorldGenStructFeatManager structFeatManager = null; + + #if MC_VER >= MC_1_18_2 + public StructureCheck structCheck; + #endif + + boolean isValid = true; + + // used for some older MC versions + private static GlobalWorldGenParams previousGlobalWorldGenParams = null; + + + + //=============// + // constructor // + //=============// + + public static ThreadWorldGenParams getOrMake(GlobalWorldGenParams globalParams) + { + ThreadWorldGenParams threadParam = LOCAL_PARAM_REF.get(); + if (threadParam != null + && threadParam.isValid + && threadParam.level == globalParams.mcServerLevel) + { + return threadParam; + } + + threadParam = new ThreadWorldGenParams(globalParams); + LOCAL_PARAM_REF.set(threadParam); + return threadParam; + } + + private ThreadWorldGenParams(GlobalWorldGenParams param) + { + previousGlobalWorldGenParams = param; + + this.level = param.mcServerLevel; + + #if MC_VER < MC_1_18_2 + this.structFeatManager = new WorldGenStructFeatManager(param.worldGenSettings, this.level); + #elif MC_VER < MC_1_19_2 + this.structCheck = this.createStructureCheck(param); + #else + this.structCheck = new StructureCheck(param.chunkScanner, param.registry, param.structures, + param.mcServerLevel.dimension(), param.generator, param.randomState, this.level, param.generator.getBiomeSource(), param.worldSeed, + param.dataFixer); + #endif + } + + + + //==========// + // builders // + //==========// + + public void makeStructFeatManager(WorldGenLevel genLevel, GlobalWorldGenParams param) + { + #if MC_VER < MC_1_18_2 + this.structFeatManager = new WorldGenStructFeatManager(param.worldGenSettings, genLevel); + #elif MC_VER < MC_1_19_4 + this.structFeatManager = new WorldGenStructFeatManager(param.worldGenSettings, genLevel, this.structCheck); + #else + this.structFeatManager = new WorldGenStructFeatManager(param.worldOptions, genLevel, this.structCheck); + #endif + } + + #if MC_VER < MC_1_18_2 + #elif MC_VER < MC_1_19_2 + public void recreateStructureCheck() + { + if (previousGlobalWorldGenParams != null) + { + this.structCheck = this.createStructureCheck(previousGlobalWorldGenParams); + } + } + private StructureCheck createStructureCheck(GlobalWorldGenParams param) + { + return new StructureCheck(param.chunkScanner, param.registry, param.structures, + param.mcServerLevel.dimension(), param.generator, this.level, param.generator.getBiomeSource(), param.worldSeed, + param.dataFixer); + } + #else + public void recreateStructureCheck() { /* do nothing */ } + #endif + + + +} \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/AbstractWorldGenStep.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/AbstractWorldGenStep.java index fb8b41977..4cf169062 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/AbstractWorldGenStep.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/AbstractWorldGenStep.java @@ -1,7 +1,7 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration.step; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParameters; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.ThreadWorldGenParams; import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; import net.minecraft.world.level.chunk.ChunkAccess; @@ -19,7 +19,7 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; public abstract class AbstractWorldGenStep { public abstract void generateGroup( - ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion, + ThreadWorldGenParams tParams, DhLitWorldGenRegion worldGenRegion, ArrayGridList chunkWrappers); public abstract ChunkStatus getChunkStatus(); @@ -27,26 +27,26 @@ public abstract class AbstractWorldGenStep /** @return the list of chunks that have an earlier status and can be generated */ - protected ArrayList getChunksToGenerate(List chunkWrappers) + protected ArrayList getChunkWrappersToGenerate(List chunkWrappers) { - ArrayList chunksToGenerate = new ArrayList<>(); + ArrayList chunkWrappersToGenerate = new ArrayList<>(chunkWrappers.size()); for (ChunkWrapper chunkWrapper : chunkWrappers) { ChunkAccess chunk = chunkWrapper.getChunk(); if (chunkWrapper.getStatus().isOrAfter(this.getChunkStatus())) { - // this chunk has already generated this step + // this chunk has already been generated up to this step continue; } else if (chunk instanceof ProtoChunk) { chunkWrapper.trySetStatus(this.getChunkStatus()); - chunksToGenerate.add(chunk); + chunkWrappersToGenerate.add(chunkWrapper); } } - return chunksToGenerate; + return chunkWrappersToGenerate; } diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepBiomes.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepBiomes.java index 8a0bcb819..a4fa04f80 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepBiomes.java +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/step/StepBiomes.java @@ -23,13 +23,11 @@ import java.util.ArrayList; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParameters; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.ThreadWorldGenParams; import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; -import net.minecraft.server.level.WorldGenRegion; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ProtoChunk; #if MC_VER >= MC_1_18_2 import net.minecraft.world.level.levelgen.blending.Blender; @@ -66,47 +64,50 @@ public final class StepBiomes extends AbstractWorldGenStep @Override public void generateGroup( - ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion, + ThreadWorldGenParams tParams, DhLitWorldGenRegion worldGenRegion, ArrayGridList chunkWrappers) { - ArrayList chunksToDo = this.getChunksToGenerate(chunkWrappers); - for (ChunkAccess chunk : chunksToDo) + ArrayList chunksToDo = this.getChunkWrappersToGenerate(chunkWrappers); + for (ChunkWrapper chunkWrapper : chunksToDo) { + ChunkAccess chunk = chunkWrapper.getChunk(); + + #if MC_VER < MC_1_18_2 - this.environment.params.generator.createBiomes(this.environment.params.biomes, chunk); + this.environment.globalParams.generator.createBiomes(this.environment.globalParams.biomes, chunk); #elif MC_VER < MC_1_19_2 chunk = this.environment.confirmFutureWasRunSynchronously( - this.environment.params.generator.createBiomes( - this.environment.params.biomes, + this.environment.globalParams.generator.createBiomes( + this.environment.globalParams.biomes, Runnable::run, Blender.of(worldGenRegion), - tParams.structFeat.forWorldGenRegion(worldGenRegion), + tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk) ); #elif MC_VER < MC_1_19_4 chunk = this.environment.confirmFutureWasRunSynchronously( - this.environment.params.generator.createBiomes( - this.environment.params.biomes, + this.environment.globalParams.generator.createBiomes( + this.environment.globalParams.biomes, Runnable::run, - this.environment.params.randomState, Blender.of(worldGenRegion), - tParams.structFeat.forWorldGenRegion(worldGenRegion), + this.environment.globalParams.randomState, Blender.of(worldGenRegion), + tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk) ); #elif MC_VER < MC_1_21_1 chunk = this.environment.confirmFutureWasRunSynchronously( - this.environment.params.generator.createBiomes( + this.environment.globalParams.generator.createBiomes( Runnable::run, - this.environment.params.randomState, + this.environment.globalParams.randomState, Blender.of(worldGenRegion), - tParams.structFeat.forWorldGenRegion(worldGenRegion), + tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk) ); #else chunk = this.environment.confirmFutureWasRunSynchronously( - this.environment.params.generator.createBiomes( - this.environment.params.randomState, + this.environment.globalParams.generator.createBiomes( + this.environment.globalParams.randomState, Blender.of(worldGenRegion), - tParams.structFeat.forWorldGenRegion(worldGenRegion), + tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk) ); #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 ffe8fe346..24542b106 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 @@ -21,13 +21,12 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration.step; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParameters; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.ThreadWorldGenParams; import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion; 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; @@ -37,6 +36,7 @@ import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.status.ChunkStatus; #endif +import java.util.ArrayList; import java.util.ConcurrentModificationException; @@ -67,32 +67,24 @@ public final class StepFeatures extends AbstractWorldGenStep @Override public void generateGroup( - ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion, + ThreadWorldGenParams tParams, DhLitWorldGenRegion worldGenRegion, ArrayGridList chunkWrappers) { - for (ChunkWrapper chunkWrapper : chunkWrappers) + ArrayList chunksToDo = this.getChunkWrappersToGenerate(chunkWrappers); + for (ChunkWrapper chunkWrapper : chunksToDo) { ChunkAccess chunk = chunkWrapper.getChunk(); - if (chunkWrapper.getStatus().isOrAfter(STATUS)) - { - // this chunk has already generated this step - continue; - } - else if (chunk instanceof ProtoChunk) - { - chunkWrapper.trySetStatus(STATUS); - } try { #if MC_VER < MC_1_18_2 worldGenRegion.setOverrideCenter(chunk.getPos()); - environment.params.generator.applyBiomeDecoration(worldGenRegion, tParams.structFeat); + environment.globalParams.generator.applyBiomeDecoration(worldGenRegion, tParams.structFeatManager); #else if (worldGenRegion.hasChunk(chunkWrapper.getChunkPos().getX(), chunkWrapper.getChunkPos().getZ())) { - this.environment.params.generator.applyBiomeDecoration(worldGenRegion, chunk, tParams.structFeat.forWorldGenRegion(worldGenRegion)); + this.environment.globalParams.generator.applyBiomeDecoration(worldGenRegion, chunk, tParams.structFeatManager.forWorldGenRegion(worldGenRegion)); } else { 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 49a1b5bcf..f6b7625cd 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 @@ -20,18 +20,14 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration.step; import java.util.ArrayList; -import java.util.List; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParameters; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.ThreadWorldGenParams; 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.server.level.WorldGenRegion; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ProtoChunk; #if MC_VER >= MC_1_18_2 import net.minecraft.world.level.levelgen.blending.Blender; @@ -68,56 +64,45 @@ public final class StepNoise extends AbstractWorldGenStep @Override public void generateGroup( - ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion, + ThreadWorldGenParams tParams, DhLitWorldGenRegion worldGenRegion, ArrayGridList chunkWrappers) { - ArrayList chunksToDo = new ArrayList<>(); - - for (ChunkWrapper chunkWrapper : chunkWrappers) + ArrayList chunksToDo = this.getChunkWrappersToGenerate(chunkWrappers); + for (ChunkWrapper chunkWrapper : chunksToDo) { ChunkAccess chunk = chunkWrapper.getChunk(); - if (chunkWrapper.getStatus().isOrAfter(STATUS)) - { - continue; - } - chunkWrapper.trySetStatus(STATUS); - chunksToDo.add(chunk); - } - - for (ChunkAccess chunk : chunksToDo) - { + #if MC_VER < MC_1_17_1 - this.environment.params.generator.fillFromNoise(worldGenRegion, tParams.structFeat, chunk); + this.environment.globalParams.generator.fillFromNoise(worldGenRegion, tParams.structFeatManager, chunk); #elif MC_VER < MC_1_18_2 chunk = this.environment.confirmFutureWasRunSynchronously( - this.environment.params.generator.fillFromNoise( + this.environment.globalParams.generator.fillFromNoise( Runnable::run, - tParams.structFeat.forWorldGenRegion(worldGenRegion), + tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk)); #elif MC_VER < MC_1_19_2 chunk = this.environment.confirmFutureWasRunSynchronously( - this.environment.params.generator.fillFromNoise( + this.environment.globalParams.generator.fillFromNoise( Runnable::run, Blender.of(worldGenRegion), - tParams.structFeat.forWorldGenRegion(worldGenRegion), + tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk)); #elif MC_VER < MC_1_21_1 chunk = this.environment.confirmFutureWasRunSynchronously( - this.environment.params.generator.fillFromNoise( + this.environment.globalParams.generator.fillFromNoise( Runnable::run, Blender.of(worldGenRegion), - this.environment.params.randomState, - tParams.structFeat.forWorldGenRegion(worldGenRegion), + this.environment.globalParams.randomState, + tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk)); #else chunk = this.environment.confirmFutureWasRunSynchronously( - this.environment.params.generator.fillFromNoise( + this.environment.globalParams.generator.fillFromNoise( Blender.of(worldGenRegion), - this.environment.params.randomState, - tParams.structFeat.forWorldGenRegion(worldGenRegion), + this.environment.globalParams.randomState, + tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk)); #endif - UncheckedInterruptedException.throwIfInterrupted(); } } 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 cba8dc7cc..4a0bf6ddd 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 @@ -20,17 +20,14 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration.step; import java.util.ArrayList; -import java.util.List; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParameters; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.ThreadWorldGenParams; import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; -import net.minecraft.server.level.WorldGenRegion; 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; @@ -64,30 +61,14 @@ public final class StepStructureReference extends AbstractWorldGenStep @Override public void generateGroup( - ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion, + ThreadWorldGenParams tParams, DhLitWorldGenRegion worldGenRegion, ArrayGridList chunkWrappers) { - ArrayList chunksToDo = new ArrayList(); - - for (ChunkWrapper chunkWrapper : chunkWrappers) + ArrayList chunksToDo = this.getChunkWrappersToGenerate(chunkWrappers); + for (ChunkWrapper chunkWrapper : chunksToDo) { ChunkAccess chunk = chunkWrapper.getChunk(); - if (chunkWrapper.getStatus().isOrAfter(STATUS)) - { - // this chunk has already generated this step - continue; - } - else if (chunk instanceof ProtoChunk) - { - chunkWrapper.trySetStatus(STATUS); - chunksToDo.add(chunk); - } - } - - for (ChunkAccess chunk : chunksToDo) - { - // System.out.println("StepStructureReference: "+chunk.getPos()); - this.environment.params.generator.createReferences(worldGenRegion, tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk); + this.environment.globalParams.generator.createReferences(worldGenRegion, tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk); } } 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 ce5ff81f4..340bbf683 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 @@ -20,21 +20,16 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration.step; import java.util.ArrayList; -import java.util.List; import java.util.concurrent.locks.ReentrantLock; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParameters; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.ThreadWorldGenParams; import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.level.WorldGenRegion; -import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ProtoChunk; import com.seibel.distanthorizons.core.logging.DhLogger; #if MC_VER <= MC_1_20_4 @@ -71,74 +66,80 @@ public final class StepStructureStart extends AbstractWorldGenStep @Override public void generateGroup( - ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion, + ThreadWorldGenParams tParams, DhLitWorldGenRegion worldGenRegion, ArrayGridList chunkWrappers) { - ArrayList chunksToDo = this.getChunksToGenerate(chunkWrappers); + 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()) - { + if (!this.environment.globalParams.worldGenSettings.generateFeatures()) #elif MC_VER < MC_1_19_4 - if (this.environment.params.worldGenSettings.generateStructures()) - { + if (!this.environment.globalParams.worldGenSettings.generateStructures()) #else - if (this.environment.params.worldOptions.generateStructures()) - { + if (!this.environment.globalParams.worldOptions.generateStructures()) #endif - for (ChunkAccess chunk : chunksToDo) + { + return; + } + + + + for (ChunkWrapper chunkWrapper : chunksToDo) + { + ChunkAccess chunk = chunkWrapper.getChunk(); + + // hopefully this shouldn't cause any performance issues (this step is generally quite quick so hopefully it should be fine) + // and should prevent some concurrency issues + STRUCTURE_PLACEMENT_LOCK.lock(); + + #if MC_VER < MC_1_19_2 + this.environment.globalParams.generator.createStructures(this.environment.globalParams.registry, tParams.structFeatManager, chunk, this.environment.globalParams.structures, + this.environment.globalParams.worldSeed); + #elif MC_VER < MC_1_19_4 + this.environment.globalParams.generator.createStructures(this.environment.globalParams.registry, this.environment.globalParams.randomState, tParams.structFeatManager, chunk, this.environment.globalParams.structures, + this.environment.globalParams.worldSeed); + #elif MC_VER <= MC_1_21_3 + this.environment.globalParams.generator.createStructures(this.environment.globalParams.registry, + this.environment.globalParams.mcServerLevel.getChunkSource().getGeneratorState(), + tParams.structFeatManager, chunk, this.environment.globalParams.structures); + #else + this.environment.globalParams.generator.createStructures(this.environment.globalParams.registry, + this.environment.globalParams.mcServerLevel.getChunkSource().getGeneratorState(), + tParams.structFeatManager, chunk, this.environment.globalParams.structures, + this.environment.globalParams.mcServerLevel.dimension()); + #endif + + #if MC_VER >= MC_1_18_2 + try { - // hopefully this shouldn't cause any performance issues (this step is generally quite quick so hopefully it should be fine) - // and should prevent some concurrency issues - STRUCTURE_PLACEMENT_LOCK.lock(); + tParams.structCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts()); + } + catch (ArrayIndexOutOfBoundsException firstEx) + { + // There's a rare issue with StructStart where it throws ArrayIndexOutOfBounds + // This means the structFeat is corrupted (For some reason) and I need to reset it. + // TODO: Figure out in the future why this happens even though I am using new structFeat - OLD - #if MC_VER < MC_1_19_2 - this.environment.params.generator.createStructures(this.environment.params.registry, tParams.structFeat, chunk, this.environment.params.structures, - this.environment.params.worldSeed); - #elif MC_VER < MC_1_19_4 - this.environment.params.generator.createStructures(this.environment.params.registry, this.environment.params.randomState, tParams.structFeat, chunk, this.environment.params.structures, - this.environment.params.worldSeed); - #elif MC_VER <= MC_1_21_3 - this.environment.params.generator.createStructures(this.environment.params.registry, - this.environment.params.level.getChunkSource().getGeneratorState(), - tParams.structFeat, chunk, this.environment.params.structures); - #else - this.environment.params.generator.createStructures(this.environment.params.registry, - this.environment.params.level.getChunkSource().getGeneratorState(), - tParams.structFeat, chunk, this.environment.params.structures, - this.environment.params.level.dimension()); - #endif + // reset the structureStart + tParams.recreateStructureCheck(); - #if MC_VER >= MC_1_18_2 try { + // try running the structure logic again tParams.structCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts()); } - catch (ArrayIndexOutOfBoundsException firstEx) + catch (ArrayIndexOutOfBoundsException secondEx) { - // There's a rare issue with StructStart where it throws ArrayIndexOutOfBounds - // This means the structFeat is corrupted (For some reason) and I need to reset it. - // TODO: Figure out in the future why this happens even though I am using new structFeat - OLD - - // reset the structureStart - tParams.recreateStructureCheck(); - - try - { - // try running the structure logic again - tParams.structCheck.onStructureLoad(chunk.getPos(), chunk.getAllStarts()); - } - catch (ArrayIndexOutOfBoundsException secondEx) - { - // the structure logic failed again, log it and move on - LOGGER.error("Unable to create structure starts for " + chunk.getPos() + ". This is an error with MC's world generation. Ignoring and continuing generation. Error: " + secondEx.getMessage()); // don't log the full stack trace since it is long and will generally end up in MC's code - } + // the structure logic failed again, log it and move on + LOGGER.error("Unable to create structure starts for " + chunk.getPos() + ". This is an error with MC's world generation. Ignoring and continuing generation. Error: " + secondEx.getMessage()); // don't log the full stack trace since it is long and will generally end up in MC's code } - - #endif - - STRUCTURE_PLACEMENT_LOCK.unlock(); } + + #endif + + STRUCTURE_PLACEMENT_LOCK.unlock(); } } 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 4f6ef551a..92c5bd1da 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 @@ -20,17 +20,14 @@ package com.seibel.distanthorizons.common.wrappers.worldGeneration.step; import java.util.ArrayList; -import java.util.List; import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; -import com.seibel.distanthorizons.common.wrappers.worldGeneration.ThreadedParameters; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.params.ThreadWorldGenParams; import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.DhLitWorldGenRegion; import com.seibel.distanthorizons.core.util.gridList.ArrayGridList; -import net.minecraft.server.level.WorldGenRegion; 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; @@ -64,37 +61,24 @@ public final class StepSurface extends AbstractWorldGenStep @Override public void generateGroup( - ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion, + ThreadWorldGenParams tParams, DhLitWorldGenRegion worldGenRegion, ArrayGridList chunkWrappers) { - ArrayList chunksToDo = new ArrayList<>(); - - for (ChunkWrapper chunkWrapper : chunkWrappers) + ArrayList chunksToDo = this.getChunkWrappersToGenerate(chunkWrappers); + for (ChunkWrapper chunkWrapper : chunksToDo) { ChunkAccess chunk = chunkWrapper.getChunk(); - if (chunkWrapper.getStatus().isOrAfter(STATUS)) - { - // this chunk has already generated this step - continue; - } - else if (chunk instanceof ProtoChunk) - { - chunkWrapper.trySetStatus(STATUS); - chunksToDo.add(chunk); - } - } - - for (ChunkAccess chunk : chunksToDo) - { - // System.out.println("StepSurface: "+chunk.getPos()); + #if MC_VER < MC_1_18_2 - environment.params.generator.buildSurfaceAndBedrock(worldGenRegion, chunk); + this.environment.globalParams.generator.buildSurfaceAndBedrock(worldGenRegion, chunk); #elif MC_VER < MC_1_19_2 - environment.params.generator.buildSurface(worldGenRegion, tParams.structFeat.forWorldGenRegion(worldGenRegion), chunk); + this.environment.globalParams.generator.buildSurface(worldGenRegion, tParams.structFeatManager.forWorldGenRegion(worldGenRegion), chunk); #else - environment.params.generator.buildSurface(worldGenRegion, tParams.structFeat.forWorldGenRegion(worldGenRegion), environment.params.randomState, chunk); + this.environment.globalParams.generator.buildSurface(worldGenRegion, tParams.structFeatManager.forWorldGenRegion(worldGenRegion), this.environment.globalParams.randomState, chunk); #endif } } + + } \ No newline at end of file diff --git a/coreSubProjects b/coreSubProjects index 4e9559f23..149fbccfa 160000 --- a/coreSubProjects +++ b/coreSubProjects @@ -1 +1 @@ -Subproject commit 4e9559f23043f8f3568362374025ff7c2c77c4df +Subproject commit 149fbccfa58972a3390281e26c21691acba6b212 diff --git a/fabric/src/main/java/com/seibel/distanthorizons/fabric/FabricClientProxy.java b/fabric/src/main/java/com/seibel/distanthorizons/fabric/FabricClientProxy.java index 30cc79c5e..fee42ffd6 100644 --- a/fabric/src/main/java/com/seibel/distanthorizons/fabric/FabricClientProxy.java +++ b/fabric/src/main/java/com/seibel/distanthorizons/fabric/FabricClientProxy.java @@ -223,7 +223,7 @@ public class FabricClientProxy implements AbstractModInitializer.IEventProxy // render event // //==============// - // TODO wait for fabric to re-add their rendering API + #if MC_VER < MC_1_21_9 WorldRenderEvents.AFTER_SETUP.register((renderContext) -> { diff --git a/fabric/src/main/java/com/seibel/distanthorizons/fabric/FabricMain.java b/fabric/src/main/java/com/seibel/distanthorizons/fabric/FabricMain.java index a23e7b7dc..e2fec7267 100644 --- a/fabric/src/main/java/com/seibel/distanthorizons/fabric/FabricMain.java +++ b/fabric/src/main/java/com/seibel/distanthorizons/fabric/FabricMain.java @@ -47,8 +47,6 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; #endif -import javax.swing.*; -import java.awt.*; import java.util.function.Consumer; /** @@ -110,6 +108,7 @@ public class FabricMain extends AbstractModInitializer implements ClientModIniti this.tryCreateModCompatAccessor("starlight", IStarlightAccessor.class, StarlightAccessor::new); this.tryCreateModCompatAccessor("optifine", IOptifineAccessor.class, OptifineAccessor::new); this.tryCreateModCompatAccessor("bclib", IBCLibAccessor.class, BCLibAccessor::new); + this.tryCreateModCompatAccessor("c2me", IC2meAccessor.class, C2meAccessor::new); #if MC_VER >= MC_1_19_4 // 1.19.4 is the lowest version Iris supports DH this.tryCreateModCompatAccessor("iris", IIrisAccessor.class, IrisAccessor::new); diff --git a/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinLevelTicks.java b/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinLevelTicks.java index 68db84b96..4df7a270f 100644 --- a/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinLevelTicks.java +++ b/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinLevelTicks.java @@ -13,8 +13,7 @@ public class MixinLevelTicks #else -import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck; - +import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import net.minecraft.world.ticks.LevelTicks; import net.minecraft.world.ticks.ScheduledTick; @@ -26,18 +25,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(LevelTicks.class) // available in 1.18.2+, but only needed in 1.21.4+ public class MixinLevelTicks { - // TODO put in a common location - private static boolean isWorldGenThread() - { return DependencySetupDoneCheck.isDone && DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread.get(); } - - - @Inject(method = "schedule", at = @At(value = "HEAD"), cancellable = true) private void onChunkSave(ScheduledTick tick, CallbackInfo ci) { // In MC 1.21.4 an error check was added to log attempting to schedule ticks for unloaded chunks // this caused a lot of unnecessary errors when generating sand (FallingBlock.class). - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { ci.cancel(); } diff --git a/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinTracingExecutor.java b/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinTracingExecutor.java index 8d9637a0a..a35e4e759 100644 --- a/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinTracingExecutor.java +++ b/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinTracingExecutor.java @@ -19,6 +19,7 @@ package com.seibel.distanthorizons.fabric.mixins.server; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import org.spongepowered.asm.mixin.Mixin; #if MC_VER < MC_1_21_3 @@ -35,10 +36,8 @@ public class MixinTracingExecutor } #else -import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck; import com.seibel.distanthorizons.core.util.objects.RunOnThisThreadExecutorService; import net.minecraft.TracingExecutor; -import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -55,16 +54,11 @@ import java.util.concurrent.Executor; @Mixin(TracingExecutor.class) public class MixinTracingExecutor { - // TODO put in a common location - private static boolean isWorldGenThread() - { return DependencySetupDoneCheck.isDone && DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread.get(); } - - // replaced with TracingExecutor in MC 1.21.3+ @Inject(method = "forName(Ljava/lang/String;)Ljava/util/concurrent/Executor;", at = @At("HEAD"), cancellable = true) private void forName(String executorName, CallbackInfoReturnable ci) { - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { // run this task on the current DH thread instead of a new MC thread ci.setReturnValue(new RunOnThisThreadExecutorService()); diff --git a/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinUtilBackgroundThread.java b/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinUtilBackgroundThread.java index f8c5c7642..80ea7f5df 100644 --- a/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinUtilBackgroundThread.java +++ b/fabric/src/main/java/com/seibel/distanthorizons/fabric/mixins/server/MixinUtilBackgroundThread.java @@ -19,19 +19,19 @@ package com.seibel.distanthorizons.fabric.mixins.server; -import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import com.seibel.distanthorizons.core.util.objects.RunOnThisThreadExecutorService; import org.spongepowered.asm.mixin.Mixin; + +import net.minecraft.Util; + import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import net.minecraft.Util; - -import java.util.concurrent.ExecutorService; - #if MC_VER < MC_1_16_5 #elif MC_VER < MC_1_21_3 +import java.util.concurrent.ExecutorService; import java.util.function.Supplier; #else #endif @@ -46,15 +46,11 @@ import java.util.function.Supplier; @Mixin(Util.class) public class MixinUtilBackgroundThread { - private static boolean isWorldGenThread() - { return DependencySetupDoneCheck.isDone && DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread.get(); } - - #if MC_VER < MC_1_21_3 @Inject(method = "backgroundExecutor", at = @At("HEAD"), cancellable = true) private static void overrideUtil$backgroundExecutor(CallbackInfoReturnable ci) { - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { // run this task on the current DH thread instead of a new MC thread ci.setReturnValue(new RunOnThisThreadExecutorService()); @@ -70,7 +66,7 @@ public class MixinUtilBackgroundThread at = @At("HEAD"), cancellable = true) private static void overrideUtil$wrapThreadWithTaskName(String string, Runnable r, CallbackInfoReturnable ci) { - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { //ApiShared.LOGGER.info("util wrapThreadWithTaskName(Runnable) triggered"); ci.setReturnValue(r); @@ -86,7 +82,7 @@ public class MixinUtilBackgroundThread at = @At("HEAD"), cancellable = true) private static void overrideUtil$wrapThreadWithTaskNameForSupplier(String string, Supplier r, CallbackInfoReturnable> ci) { - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { //ApiShared.LOGGER.info("util wrapThreadWithTaskName(Supplier) triggered"); ci.setReturnValue(r); diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetupDoneCheck.java b/fabric/src/main/java/com/seibel/distanthorizons/fabric/wrappers/modAccessor/C2meAccessor.java similarity index 61% rename from common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetupDoneCheck.java rename to fabric/src/main/java/com/seibel/distanthorizons/fabric/wrappers/modAccessor/C2meAccessor.java index c508d5321..313e71ae7 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/DependencySetupDoneCheck.java +++ b/fabric/src/main/java/com/seibel/distanthorizons/fabric/wrappers/modAccessor/C2meAccessor.java @@ -17,20 +17,16 @@ * along with this program. If not, see . */ -package com.seibel.distanthorizons.common.wrappers; +package com.seibel.distanthorizons.fabric.wrappers.modAccessor; -import java.util.function.Supplier; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor; -public class DependencySetupDoneCheck +public class C2meAccessor implements IC2meAccessor { - // TODO move to DependencySetup - public static boolean isDone = false; - /** - * This is used so we can override some MC logic when running - * in DH's world generator. - * Specifically so we can redirect threads to run on DH threads instead - * of MC threads. - */ - public static Supplier getIsCurrentThreadDistantGeneratorThread = (() -> { return false; }); + @Override + public String getModName() + { + return "c2me"; + } } diff --git a/fabric/src/main/java/com/seibel/distanthorizons/fabric/wrappers/modAccessor/IrisAccessor.java b/fabric/src/main/java/com/seibel/distanthorizons/fabric/wrappers/modAccessor/IrisAccessor.java index 84f666cd5..40790483d 100644 --- a/fabric/src/main/java/com/seibel/distanthorizons/fabric/wrappers/modAccessor/IrisAccessor.java +++ b/fabric/src/main/java/com/seibel/distanthorizons/fabric/wrappers/modAccessor/IrisAccessor.java @@ -34,22 +34,14 @@ import net.irisshaders.iris.api.v0.IrisApi; public class IrisAccessor implements IIrisAccessor { @Override - public String getModName() - { - return Iris.MODID; - } + public String getModName() { return Iris.MODID; } @Override - public boolean isShaderPackInUse() - { - return IrisApi.getInstance().isShaderPackInUse(); - } + public boolean isShaderPackInUse() { return IrisApi.getInstance().isShaderPackInUse(); } @Override - public boolean isRenderingShadowPass() - { - return IrisApi.getInstance().isRenderingShadowPass(); - } + public boolean isRenderingShadowPass() { return IrisApi.getInstance().isRenderingShadowPass(); } + } #endif diff --git a/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/server/MixinUtilBackgroundThread.java b/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/server/MixinUtilBackgroundThread.java index 194114ad0..dfaf7de41 100644 --- a/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/server/MixinUtilBackgroundThread.java +++ b/forge/src/main/java/com/seibel/distanthorizons/forge/mixins/server/MixinUtilBackgroundThread.java @@ -22,12 +22,15 @@ package com.seibel.distanthorizons.forge.mixins.server; import java.util.concurrent.ExecutorService; import java.util.function.Supplier; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck; import com.seibel.distanthorizons.core.util.objects.RunOnThisThreadExecutorService; import net.minecraft.Util; @@ -35,17 +38,17 @@ import net.minecraft.Util; @Mixin(Util.class) public class MixinUtilBackgroundThread { - private static boolean shouldApplyOverride() - { - return DependencySetupDoneCheck.isDone && DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread.get(); - } + @Unique + private static final DhLogger LOGGER = new DhLoggerBuilder().name("MixinUtilBackgroundThread").build(); + + @Inject(method = "backgroundExecutor", at = @At("HEAD"), cancellable = true) private static void overrideUtil$backgroundExecutor(CallbackInfoReturnable ci) { - if (shouldApplyOverride()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { - //ApiShared.LOGGER.info("util backgroundExecutor triggered"); + //LOGGER.info("util backgroundExecutor triggered"); ci.setReturnValue(new RunOnThisThreadExecutorService()); } } @@ -55,21 +58,22 @@ public class MixinUtilBackgroundThread at = @At("HEAD"), cancellable = true) private static void overrideUtil$wrapThreadWithTaskName(String string, Runnable r, CallbackInfoReturnable ci) { - if (shouldApplyOverride()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { - //ApiShared.LOGGER.info("util wrapThreadWithTaskName(Runnable) triggered"); + //LOGGER.info("util wrapThreadWithTaskName(Runnable) triggered"); ci.setReturnValue(r); } } #endif + #if MC_VER >= MC_1_18_2 @Inject(method = "wrapThreadWithTaskName(Ljava/lang/String;Ljava/util/function/Supplier;)Ljava/util/function/Supplier;", at = @At("HEAD"), cancellable = true) private static void overrideUtil$wrapThreadWithTaskNameForSupplier(String string, Supplier r, CallbackInfoReturnable> ci) { - if (shouldApplyOverride()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { - //ApiShared.LOGGER.info("util wrapThreadWithTaskName(Supplier) triggered"); + //LOGGER.info("util wrapThreadWithTaskName(Supplier) triggered"); ci.setReturnValue(r); } } diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 89b5b2f6d..20c8f3f27 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -65,7 +65,10 @@ dependencies { // Neoforge neoForge "net.neoforged:neoforge:${rootProject.neoforge_version}" - addMod("curse.maven:TerraFirmaCraft-302973:4616004", rootProject.enable_terrafirmacraft) + + // Iris + addMod("maven.modrinth:iris:${rootProject.neo_iris_version}", rootProject.neo_enable_iris) + } diff --git a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/NeoforgeMain.java b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/NeoforgeMain.java index 37c56f8f6..0fd84bd81 100644 --- a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/NeoforgeMain.java +++ b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/NeoforgeMain.java @@ -30,9 +30,13 @@ import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IPluginPacketSender; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IModChecker; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor; import com.seibel.distanthorizons.coreapi.ModInfo; +import com.seibel.distanthorizons.neoforge.wrappers.modAccessor.C2meAccessor; +import com.seibel.distanthorizons.neoforge.wrappers.modAccessor.IrisAccessor; import com.seibel.distanthorizons.neoforge.wrappers.NeoforgeMinecraftRenderWrapper; import com.seibel.distanthorizons.neoforge.wrappers.modAccessor.ModChecker; import com.seibel.distanthorizons.neoforge.wrappers.modAccessor.OptifineAccessor; @@ -144,6 +148,12 @@ public class NeoforgeMain extends AbstractModInitializer protected void initializeModCompat() { this.tryCreateModCompatAccessor("optifine", IOptifineAccessor.class, OptifineAccessor::new); + this.tryCreateModCompatAccessor("c2me", IC2meAccessor.class, C2meAccessor::new); + + #if MC_VER >= MC_1_20_6 + // 1.20.6 is the lowest version Iris supports Neoforge + this.tryCreateModCompatAccessor("iris", IIrisAccessor.class, IrisAccessor::new); + #endif #if MC_VER < MC_1_20_6 ModLoadingContext.get().registerExtensionPoint(ConfigScreenHandler.ConfigScreenFactory.class, diff --git a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/NeoforgeServerProxy.java b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/NeoforgeServerProxy.java index da9fbf0b8..a91dcc863 100644 --- a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/NeoforgeServerProxy.java +++ b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/NeoforgeServerProxy.java @@ -30,8 +30,7 @@ import java.util.function.Supplier; #if MC_VER < MC_1_20_6 import net.neoforged.neoforge.event.TickEvent; -#else -import net.neoforged.neoforge.event.tick.ServerTickEvent; +#else #endif @@ -52,7 +51,7 @@ public class NeoforgeServerProxy implements AbstractModInitializer.IEventProxy public NeoforgeServerProxy(boolean isDedicated) { this.isDedicated = isDedicated; - isGenerationThreadChecker = BatchGenerationEnvironment::isCurrentThreadDistantGeneratorThread; + isGenerationThreadChecker = BatchGenerationEnvironment::isThisDhWorldGenThread; } @Override diff --git a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinLevelTicks.java b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinLevelTicks.java index c01c8b503..15c5c8423 100644 --- a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinLevelTicks.java +++ b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinLevelTicks.java @@ -13,7 +13,7 @@ public class MixinLevelTicks #else -import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import net.minecraft.world.ticks.LevelTicks; import net.minecraft.world.ticks.ScheduledTick; import org.spongepowered.asm.mixin.Mixin; @@ -24,18 +24,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(LevelTicks.class) // available in 1.18.2+, but only needed in 1.21.4+ public class MixinLevelTicks { - // TODO put in a common location - private static boolean isWorldGenThread() - { return DependencySetupDoneCheck.isDone && DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread.get(); } - - - @Inject(method = "schedule", at = @At(value = "HEAD"), cancellable = true) private void onChunkSave(ScheduledTick tick, CallbackInfo ci) { // In MC 1.21.4 an error check was added to log attempting to schedule ticks for unloaded chunks // this caused a lot of unnecessary errors when generating sand (FallingBlock.class). - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { ci.cancel(); } diff --git a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinTracingExecutor.java b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinTracingExecutor.java index 100d2e90e..51abf0a5f 100644 --- a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinTracingExecutor.java +++ b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinTracingExecutor.java @@ -35,7 +35,7 @@ public class MixinTracingExecutor } #else -import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import com.seibel.distanthorizons.core.util.objects.RunOnThisThreadExecutorService; import net.minecraft.TracingExecutor; import org.spongepowered.asm.mixin.Mixin; @@ -55,11 +55,6 @@ import java.util.concurrent.Executor; @Mixin(TracingExecutor.class) public class MixinTracingExecutor { - // TODO put in a common location - private static boolean isWorldGenThread() - { return DependencySetupDoneCheck.isDone && DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread.get(); } - - // Util.backgroundExecutor().forName("init_biomes") // needed for world gen @@ -67,7 +62,7 @@ public class MixinTracingExecutor @Inject(method = "forName(Ljava/lang/String;)Ljava/util/concurrent/Executor;", at = @At("HEAD"), cancellable = true) private void forName(String executorName, CallbackInfoReturnable ci) { - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { // run this task on the current DH thread instead of a new MC thread ci.setReturnValue(new RunOnThisThreadExecutorService()); diff --git a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinUtilBackgroundThread.java b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinUtilBackgroundThread.java index a6f488cc7..0974498ee 100644 --- a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinUtilBackgroundThread.java +++ b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/mixins/server/MixinUtilBackgroundThread.java @@ -19,18 +19,20 @@ package com.seibel.distanthorizons.neoforge.mixins.server; -import java.util.concurrent.ExecutorService; -import java.util.function.Supplier; - +import com.seibel.distanthorizons.common.wrappers.worldGeneration.BatchGenerationEnvironment; import com.seibel.distanthorizons.core.util.objects.RunOnThisThreadExecutorService; import org.spongepowered.asm.mixin.Mixin; + +import net.minecraft.Util; + +#if MC_VER < MC_1_21_3 import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import com.seibel.distanthorizons.common.wrappers.DependencySetupDoneCheck; - -import net.minecraft.Util; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; +#endif /** * This is needed for DH's world gen so we can run @@ -42,16 +44,13 @@ import net.minecraft.Util; @Mixin(Util.class) public class MixinUtilBackgroundThread { - private static boolean isWorldGenThread() - { return DependencySetupDoneCheck.isDone && DependencySetupDoneCheck.getIsCurrentThreadDistantGeneratorThread.get(); } - #if MC_VER < MC_1_21_3 @Inject(method = "backgroundExecutor", at = @At("HEAD"), cancellable = true) private static void overrideUtil$backgroundExecutor(CallbackInfoReturnable ci) { - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { // run this task on the current DH thread instead of a new MC thread ci.setReturnValue(new RunOnThisThreadExecutorService()); @@ -67,7 +66,7 @@ public class MixinUtilBackgroundThread at = @At("HEAD"), cancellable = true) private static void overrideUtil$wrapThreadWithTaskName(String string, Runnable r, CallbackInfoReturnable ci) { - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { //ApiShared.LOGGER.info("util wrapThreadWithTaskName(Runnable) triggered"); ci.setReturnValue(r); @@ -83,7 +82,7 @@ public class MixinUtilBackgroundThread at = @At("HEAD"), cancellable = true) private static void overrideUtil$wrapThreadWithTaskNameForSupplier(String string, Supplier r, CallbackInfoReturnable> ci) { - if (isWorldGenThread()) + if (BatchGenerationEnvironment.isThisDhWorldGenThread()) { //ApiShared.LOGGER.info("util wrapThreadWithTaskName(Supplier) triggered"); ci.setReturnValue(r); diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/Rolling.java b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/wrappers/modAccessor/C2meAccessor.java similarity index 55% rename from common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/Rolling.java rename to neoforge/src/main/java/com/seibel/distanthorizons/neoforge/wrappers/modAccessor/C2meAccessor.java index 64a37a287..85811c706 100644 --- a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/Rolling.java +++ b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/wrappers/modAccessor/C2meAccessor.java @@ -17,40 +17,16 @@ * along with this program. If not, see . */ +package com.seibel.distanthorizons.neoforge.wrappers.modAccessor; -package com.seibel.distanthorizons.common.wrappers.worldGeneration; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IC2meAccessor; -//FIXME: Move this outside the WorldGenerationStep thingy -public class Rolling +public class C2meAccessor implements IC2meAccessor { - - private final int size; - private double total = 0d; - private int index = 0; - private final double[] samples; - - public Rolling(int size) + @Override + public String getModName() { - this.size = size; - samples = new double[size]; - for (int i = 0; i < size; i++) - { - samples[i] = 0d; - } + return "c2me"; } - public void add(double x) - { - total -= samples[index]; - samples[index] = x; - total += x; - if (++index == size) - index = 0; // cheaper than modulus - } - - public double getAverage() - { - return size == 0 ? 0 : total / size; - } - -} \ No newline at end of file +} diff --git a/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/wrappers/modAccessor/IrisAccessor.java b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/wrappers/modAccessor/IrisAccessor.java new file mode 100644 index 000000000..26e9148a6 --- /dev/null +++ b/neoforge/src/main/java/com/seibel/distanthorizons/neoforge/wrappers/modAccessor/IrisAccessor.java @@ -0,0 +1,67 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.neoforge.wrappers.modAccessor; + +// 1.20.6 is the lowest version Iris supports Neoforge +#if MC_VER >= MC_1_20_6 + +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor; + +#if MC_VER != MC_1_21_9 +import net.irisshaders.iris.Iris; +import net.irisshaders.iris.api.v0.IrisApi; +#endif + +public class IrisAccessor implements IIrisAccessor +{ + @Override + public String getModName() + { + #if MC_VER == MC_1_21_9 + return "iris"; // Iris doesn't support this MC version + #else + return Iris.MODID; + #endif + } + + @Override + public boolean isShaderPackInUse() + { + #if MC_VER == MC_1_21_9 + return true; // Iris doesn't support this MC version + #else + return IrisApi.getInstance().isShaderPackInUse(); + #endif + } + + @Override + public boolean isRenderingShadowPass() + { + #if MC_VER == MC_1_21_9 + return false; // Iris doesn't support this MC version + #else + return IrisApi.getInstance().isRenderingShadowPass(); + #endif + } + +} + +#endif + diff --git a/versionProperties/1.16.5.properties b/versionProperties/1.16.5.properties index 01d2bac2b..1c1751740 100644 --- a/versionProperties/1.16.5.properties +++ b/versionProperties/1.16.5.properties @@ -48,7 +48,6 @@ forge_version=36.2.39 neoforge_version_range=[*,) # Forge mod versions - starlight_version_forge= terraforged_version=4044290 # Forge mod run diff --git a/versionProperties/1.17.1.properties b/versionProperties/1.17.1.properties index ff748417c..e289396c6 100644 --- a/versionProperties/1.17.1.properties +++ b/versionProperties/1.17.1.properties @@ -47,7 +47,6 @@ forge_version=37.1.1 neoforge_version_range=[*,) # Forge mod versions - starlight_version_forge=3457784 terraforged_version= # Forge mod run diff --git a/versionProperties/1.18.2.properties b/versionProperties/1.18.2.properties index a3d752b49..0690b97bb 100644 --- a/versionProperties/1.18.2.properties +++ b/versionProperties/1.18.2.properties @@ -56,7 +56,6 @@ forge_version=40.2.10 neoforge_version_range=[*,) # Forge mod versions - starlight_version_forge= terraforged_version= # Forge mod run diff --git a/versionProperties/1.19.2.properties b/versionProperties/1.19.2.properties index 36daf1491..e3d228d3a 100644 --- a/versionProperties/1.19.2.properties +++ b/versionProperties/1.19.2.properties @@ -47,7 +47,6 @@ forge_version=43.3.2 neoforge_version_range=[*,) # Forge mod versions - starlight_version_forge= terraforged_version= # Forge mod run diff --git a/versionProperties/1.19.4.properties b/versionProperties/1.19.4.properties index 64a2048a5..ddddb1129 100644 --- a/versionProperties/1.19.4.properties +++ b/versionProperties/1.19.4.properties @@ -46,7 +46,6 @@ forge_version=45.2.4 neoforge_version_range=[*,) # Forge mod versions - starlight_version_forge= terraforged_version= # Forge mod run diff --git a/versionProperties/1.20.1.properties b/versionProperties/1.20.1.properties index 5c0d75b60..ae501f639 100644 --- a/versionProperties/1.20.1.properties +++ b/versionProperties/1.20.1.properties @@ -46,7 +46,6 @@ forge_version=47.2.1 neoforge_version_range=[*,) # Forge mod versions - starlight_version_forge= terraforged_version= # Forge mod run diff --git a/versionProperties/1.20.2.properties b/versionProperties/1.20.2.properties index b72f17a3b..625a901ba 100644 --- a/versionProperties/1.20.2.properties +++ b/versionProperties/1.20.2.properties @@ -46,7 +46,6 @@ forge_version=48.0.13 neoforge_version_range=[*,) # Forge mod versions - starlight_version_forge= terraforged_version= # Forge mod run diff --git a/versionProperties/1.20.4.properties b/versionProperties/1.20.4.properties index 47152d13f..f996087a3 100644 --- a/versionProperties/1.20.4.properties +++ b/versionProperties/1.20.4.properties @@ -41,16 +41,15 @@ fabric_api_version=0.91.2+1.20.4 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader +# Forge loader forge_version=49.1.13 -neoforge_version=20.4.246 +neoforge_version= neoforge_version_range=[*,) - # (Neo)Forge mod versions - starlight_version_forge= + # Forge mod versions terraforged_version= - # (Neo)Forge mod run + # Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client diff --git a/versionProperties/1.20.6.properties b/versionProperties/1.20.6.properties index cda4dca1a..33b8a9c40 100644 --- a/versionProperties/1.20.6.properties +++ b/versionProperties/1.20.6.properties @@ -41,19 +41,16 @@ fabric_api_version=0.97.8+1.20.6 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader -forge_version=50.0.19 +# NeoForge loader +forge_version= neoforge_version=20.6.136 neoforge_version_range=[*,) - - # (Neo)Forge mod versions - starlight_version_forge= - terraforged_version= - + + # NeoForge mod versions + neo_iris_version=1.8.12+1.21.1-neoforge + # (Neo)Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client - enable_starlight_forge=0 - enable_terraforged=0 - enable_terrafirmacraft=0 + neo_enable_iris=1 diff --git a/versionProperties/1.21.1.properties b/versionProperties/1.21.1.properties index b68287f2c..5d54e32c4 100644 --- a/versionProperties/1.21.1.properties +++ b/versionProperties/1.21.1.properties @@ -41,19 +41,17 @@ fabric_api_version=0.115.0+1.21.1 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader +# NeoForge loader forge_version= -neoforge_version=21.1.192 +neoforge_version=21.1.216 neoforge_version_range=[*,) - # (Neo)Forge mod versions - starlight_version_forge= - terraforged_version= + # NeoForge mod versions + neo_iris_version=1.8.12+1.21.1-neoforge # (Neo)Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client - enable_starlight_forge=0 - enable_terraforged=0 - enable_terrafirmacraft=0 + neo_enable_iris=1 + diff --git a/versionProperties/1.21.10.properties b/versionProperties/1.21.10.properties index 02238be09..dc30ac02b 100644 --- a/versionProperties/1.21.10.properties +++ b/versionProperties/1.21.10.properties @@ -12,7 +12,7 @@ netty_version=4.1.97.Final # Fabric loader fabric_loader_version=0.17.3 -fabric_api_version=0.135.0+1.21.10 +fabric_api_version=0.138.3+1.21.10 modmenu_version=16.0.0-rc.1 starlight_version_fabric= phosphor_version_fabric= @@ -39,19 +39,16 @@ fabric_api_version=0.135.0+1.21.10 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader +# NeoForge loader forge_version= -neoforge_version=21.10.6-beta +neoforge_version=21.10.56-beta neoforge_version_range=[21.10.6-beta,) - # (Neo)Forge mod versions - starlight_version_forge= - terraforged_version= - + # NeoForge mod versions + neo_iris_version=1.9.6+1.21.10-neoforge + # (Neo)Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client - enable_starlight_forge=0 - enable_terraforged=0 - enable_terrafirmacraft=0 + neo_enable_iris=1 diff --git a/versionProperties/1.21.3.properties b/versionProperties/1.21.3.properties index a719956f5..5dec76fc8 100644 --- a/versionProperties/1.21.3.properties +++ b/versionProperties/1.21.3.properties @@ -41,19 +41,16 @@ fabric_api_version=0.110.0+1.21.3 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader +# NeoForge loader forge_version= neoforge_version=21.3.86 neoforge_version_range=[*,) - # (Neo)Forge mod versions - starlight_version_forge= - terraforged_version= - + # NeoForge mod versions + neo_iris_version=1.8.1+1.21.3-neoforge + # (Neo)Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client - enable_starlight_forge=0 - enable_terraforged=0 - enable_terrafirmacraft=0 + neo_enable_iris=1 diff --git a/versionProperties/1.21.4.properties b/versionProperties/1.21.4.properties index b221459dc..deaa58a70 100644 --- a/versionProperties/1.21.4.properties +++ b/versionProperties/1.21.4.properties @@ -40,20 +40,17 @@ fabric_api_version=0.110.5+1.21.4 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader +# NeoForge loader forge_version= neoforge_version=21.4.147 # version range may not be necessary, but having compiled DH for an older version caused issues with shaders neoforge_version_range=[21.4.147,) - # (Neo)Forge mod versions - starlight_version_forge= - terraforged_version= - + # NeoForge mod versions + neo_iris_version=1.8.8+1.21.4-neoforge + # (Neo)Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client - enable_starlight_forge=0 - enable_terraforged=0 - enable_terrafirmacraft=0 + neo_enable_iris=1 \ No newline at end of file diff --git a/versionProperties/1.21.5.properties b/versionProperties/1.21.5.properties index 289fbe64e..365920e6a 100644 --- a/versionProperties/1.21.5.properties +++ b/versionProperties/1.21.5.properties @@ -40,19 +40,16 @@ fabric_api_version=0.119.5+1.21.5 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader +# NeoForge loader forge_version= neoforge_version=21.5.87 neoforge_version_range=[*,) - # (Neo)Forge mod versions - starlight_version_forge= - terraforged_version= - + # NeoForge mod versions + neo_iris_version=1.8.11+1.21.5-neoforge + # (Neo)Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client - enable_starlight_forge=0 - enable_terraforged=0 - enable_terrafirmacraft=0 + neo_enable_iris=1 \ No newline at end of file diff --git a/versionProperties/1.21.6.properties b/versionProperties/1.21.6.properties index 647df0aa1..ebbd0fc9c 100644 --- a/versionProperties/1.21.6.properties +++ b/versionProperties/1.21.6.properties @@ -39,20 +39,17 @@ fabric_api_version=0.127.0+1.21.6 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader +# NeoForge loader forge_version= neoforge_version=21.6.20-beta # around 6.19 neo changed how their render API works, failing to meet this causes the game to crash neoforge_version_range=[21.6.19-beta,) - - # (Neo)Forge mod versions - starlight_version_forge= - terraforged_version= + # NeoForge mod versions + neo_iris_version=1.9.6+1.21.8-neoforge + # (Neo)Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client - enable_starlight_forge=0 - enable_terraforged=0 - enable_terrafirmacraft=0 + neo_enable_iris=1 diff --git a/versionProperties/1.21.8.properties b/versionProperties/1.21.8.properties index c00be8478..714227dea 100644 --- a/versionProperties/1.21.8.properties +++ b/versionProperties/1.21.8.properties @@ -39,20 +39,18 @@ fabric_api_version=0.133.4+1.21.8 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader +# NeoForge loader forge_version= -neoforge_version=21.8.2-beta +neoforge_version=21.8.52 # around 6.19 neo changed how their render API works, failing to meet this causes the game to crash neoforge_version_range=[*,) - - # (Neo)Forge mod versions - starlight_version_forge= - terraforged_version= + # NeoForge mod versions + neo_iris_version=1.9.6+1.21.8-neoforge + # (Neo)Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client - enable_starlight_forge=0 - enable_terraforged=0 - enable_terrafirmacraft=0 + neo_enable_iris=1 + diff --git a/versionProperties/1.21.9.properties b/versionProperties/1.21.9.properties index 3d5437bde..6f9f0ca76 100644 --- a/versionProperties/1.21.9.properties +++ b/versionProperties/1.21.9.properties @@ -39,21 +39,19 @@ fabric_api_version=0.134.0+1.21.9 enable_immersive_portals=0 enable_canvas=0 -# (Neo)Forge loader +# NeoForge loader forge_version= neoforge_version=21.9.15-beta # sometime before 21.9.15-beta Neoforge changed how their rendering API events handle levels # so we can't support both versions at once neoforge_version_range=[21.9.15-beta,) - - # (Neo)Forge mod versions - starlight_version_forge= - terraforged_version= + # NeoForge mod versions + # Iris doesn't exist for this MC version + neo_iris_version= + # (Neo)Forge mod run # 0 = Don't enable and don't run # 1 = Can be referenced in code but doesn't run # 2 = Can be referenced in code and runs in client - enable_starlight_forge=0 - enable_terraforged=0 - enable_terrafirmacraft=0 + neo_enable_iris=0