From 385bd326cf2488bcef7be82194e55743a141e3cf Mon Sep 17 00:00:00 2001 From: James Seibel Date: Thu, 4 Dec 2025 07:39:09 -0600 Subject: [PATCH] minor world gen related refactoring --- .../distanthorizons/core/config/Config.java | 43 ---------- .../core/generation/BatchGenerator.java | 13 ++- .../core/generation/DhLightingEngine.java | 85 +++++++++++-------- .../core/generation/WorldGenerationQueue.java | 31 +++---- .../chunk/ChunkLightStorage.java | 6 ++ 5 files changed, 73 insertions(+), 105 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index f604ef9e9..29738afb1 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -1415,49 +1415,6 @@ public class Config + "") .build(); - public static ConfigEntry recalculateChunkHeightmaps = new ConfigEntry.Builder() - .set(false) - .comment("" - + "True: Recalculate chunk height maps before chunks can be used by DH.\n" - + " This can fix problems with worlds created by World Painter or \n" - + " other external tools where the heightmap format may be incorrect. \n" - + "False: Assume any height maps handled by Minecraft are correct. \n" - + "\n" - + "Fastest: False\n" - + "Most Compatible: True\n" - + "") - .build(); - - public static ConfigEntry pullLightingForPregeneratedChunks = new ConfigEntry.Builder() - .set(false) - .comment("" - + "If true LOD generation for pre-existing chunks will attempt to pull the lighting data \n" - + "saved in Minecraft's Region files. \n" - + "If false DH will pull in chunks without lighting and re-light them. \n" - + " \n" - + "Setting this to true will result in faster LOD generation \n" - + "for already generated worlds, but is broken by most lighting mods. \n" - + " \n" - + "Set this to false if LODs are black. \n" - + "") - .build(); - - public static ConfigEntry assumePreExistingChunksAreFinished = new ConfigEntry.Builder() - .set(false) - .comment("" - + "When DH pulls in pre-existing chunks it will attempt to \n" - + "run any missing world generation steps; for example: \n" - + "if a chunk has the status SURFACE, DH will skip BIOMES \n" - + "and SURFACE, but will run FEATURES. \n" - + " \n" - + "However if for some reason the chunks are malformed \n" - + "or there's some other issue that causes the status \n" - + "to be incorrect that can either cause world gen \n" - + "lock-ups and/or crashes. \n" - + "If either of those happen try setting this to True. \n" - + "") - .build(); - public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/BatchGenerator.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/BatchGenerator.java index 9d5d365ac..b3d417e23 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/BatchGenerator.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/BatchGenerator.java @@ -98,18 +98,14 @@ public class BatchGenerator implements IDhApiWorldGenerator ExecutorService worldGeneratorThreadPool, Consumer resultConsumer) { - EDhApiWorldGenerationStep targetStep = null; + EDhApiWorldGenerationStep targetStep; switch (generatorMode) { case PRE_EXISTING_ONLY: // Only load in existing chunks. - //case BIOME_ONLY: // No blocks. Require fake height in LodBuilder - targetStep = EDhApiWorldGenerationStep.EMPTY; + targetStep = EDhApiWorldGenerationStep.EMPTY; // special logic break; - //case BIOME_ONLY_SIMULATE_HEIGHT: - // targetStep = EDhApiWorldGenerationStep.NOISE; // Stone only. Requires a fake surface - // break; case SURFACE: - targetStep = EDhApiWorldGenerationStep.SURFACE; // TODO could we ignore adjacent chunks for a speedup? + targetStep = EDhApiWorldGenerationStep.SURFACE; break; case FEATURES: targetStep = EDhApiWorldGenerationStep.FEATURES; @@ -117,6 +113,9 @@ public class BatchGenerator implements IDhApiWorldGenerator case INTERNAL_SERVER: targetStep = EDhApiWorldGenerationStep.LIGHT; break; + + default: + throw new IllegalArgumentException("no target step defined for generator mode: ["+generatorMode+"]."); } // the consumer needs to be wrapped like this because the API can't use DH core objects (and IChunkWrapper can't be easily put into the API project) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java index 2e1632acb..f5a3a15cc 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java @@ -154,14 +154,15 @@ public class DhLightingEngine // and get any necessary info from them for (int chunkIndex = 0; chunkIndex < nearbyChunkList.size(); chunkIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead { - IChunkWrapper chunk = nearbyChunkList.get(chunkIndex); - if (chunk != null && requestedAdjacentPositions.contains(chunk.getChunkPos())) + IChunkWrapper neighborChunk = nearbyChunkList.get(chunkIndex); + if (neighborChunk != null + && requestedAdjacentPositions.contains(neighborChunk.getChunkPos())) { // remove the newly found position - requestedAdjacentPositions.remove(chunk.getChunkPos()); + requestedAdjacentPositions.remove(neighborChunk.getChunkPos()); // add the adjacent chunk - adjacentChunkHolder.add(chunk); + adjacentChunkHolder.add(neighborChunk); // get and set the adjacent chunk's initial block lights final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get(); @@ -174,19 +175,19 @@ public class DhLightingEngine if (updateBlockLight) { - ArrayList blockLightPosList = chunk.getWorldBlockLightPosList(); + ArrayList blockLightPosList = neighborChunk.getWorldBlockLightPosList(); for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead { DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex); blockLightPos.mutateToChunkRelativePos(relLightBlockPos); // get the light - IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos); + IBlockStateWrapper blockState = neighborChunk.getBlockState(relLightBlockPos); int lightValue = blockState.getLightEmission(); blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue); // set the light - chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue); + neighborChunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue); } } @@ -198,23 +199,24 @@ public class DhLightingEngine // get and set the adjacent chunk's initial skylights, // if the dimension has skylights - if (updateSkyLight && maxSkyLight > 0) + if (updateSkyLight + && maxSkyLight > 0) { - IMutableBlockPosWrapper mcBlockPos = chunk.getMutableBlockPosWrapper(); + IMutableBlockPosWrapper mcBlockPos = neighborChunk.getMutableBlockPosWrapper(); IBlockStateWrapper previousBlockState = null; - int maxY = chunk.getMaxNonEmptyHeight(); - int minY = chunk.getInclusiveMinBuildHeight(); + int maxY = neighborChunk.getMaxNonEmptyHeight(); + int minY = neighborChunk.getInclusiveMinBuildHeight(); // get the adjacent chunk's sky lights for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) // relative block pos { for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) { - // set each pos' sky light all the way down until an opaque block is hit + // set each pos sky light all the way down until an opaque block is hit for (int y = maxY; y >= minY; y--) { - IBlockStateWrapper block = previousBlockState = chunk.getBlockState(relX, y, relZ, mcBlockPos, previousBlockState); + IBlockStateWrapper block = previousBlockState = neighborChunk.getBlockState(relX, y, relZ, mcBlockPos, previousBlockState); if (block != null && block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT) { // keep moving down until we find a non-transparent block @@ -223,12 +225,12 @@ public class DhLightingEngine // add sky light to the queue - DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ); + DhBlockPos skyLightPos = new DhBlockPos(neighborChunk.getMinBlockX() + relX, y, neighborChunk.getMinBlockZ() + relZ); skyLightWorldPosQueue.push(skyLightPos.getX(), skyLightPos.getY(), skyLightPos.getZ(), maxSkyLight); // set the chunk's sky light skyLightPos.mutateToChunkRelativePos(relLightBlockPos); - chunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight); + neighborChunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight); } } } @@ -247,13 +249,12 @@ public class DhLightingEngine if (updateBlockLight) { // done to prevent a rare issue where the light values are incorrectly set to -1 - // TODO why could that happen? centerChunk.clearDhBlockLighting(); this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder, - (neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), - (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), - true); + (neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), + (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), + true); } // sky light @@ -262,9 +263,9 @@ public class DhLightingEngine centerChunk.clearDhSkyLighting(); this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder, - (neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), - (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), - false); + (neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), + (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), + false); } } catch (Exception e) @@ -300,9 +301,25 @@ public class DhLightingEngine final DhBlockPosMutable neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get(); final DhBlockPosMutable relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get(); + // it doesn't matter what chunk we get the mutable block pos from IMutableBlockPosWrapper mcBlockPos = null; + for (int i = 0; i < adjacentChunkHolder.chunkArray.length; i++) + { + IChunkWrapper chunkWrapper = adjacentChunkHolder.chunkArray[i]; + if (chunkWrapper != null) + { + mcBlockPos = chunkWrapper.getMutableBlockPosWrapper(); + break; + } + } + if (mcBlockPos == null) + { + LodUtil.assertNotReach("How did we try to light a chunk with no chunks?"); + } + IBlockStateWrapper previousBlockState = null; + // update each light position while (!lightPosQueue.isEmpty()) { @@ -346,15 +363,9 @@ public class DhLightingEngine } - if (mcBlockPos == null) - { - // it doesn't matter what chunk we get the position object from - // TODO move this getter logic out of ChunkWrapper - mcBlockPos = neighbourChunk.getMutableBlockPosWrapper(); - } + IBlockStateWrapper neighbourBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState); + previousBlockState = neighbourBlockState; - - IBlockStateWrapper neighbourBlockState = previousBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState); // Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles. int targetLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity()); if (targetLevel > currentBlockLight) @@ -370,12 +381,14 @@ public class DhLightingEngine } - // can be enable if troubleshooting lighting issues - if (RENDER_BLOCK_LIGHT_WIREFRAME && propagatingBlockLights) + // can be enabled if troubleshooting lighting issues + if (RENDER_BLOCK_LIGHT_WIREFRAME + && propagatingBlockLights) { RenderDhLightValuesAsWireframe(adjacentChunkHolder, true); } - else if (RENDER_SKY_LIGHT_WIREFRAME && !propagatingBlockLights) + else if (RENDER_SKY_LIGHT_WIREFRAME + && !propagatingBlockLights) { RenderDhLightValuesAsWireframe(adjacentChunkHolder, false); } @@ -462,7 +475,7 @@ public class DhLightingEngine point = FullDataPointUtil.setSkyLight(point, skylight); dataPoints.set(index, point); // now for the propagation. - recursivelyLightAdjacentDataPoints(dataSource, airIDs, x, z, point); + this.recursivelyLightAdjacentDataPoints(dataSource, airIDs, x, z, point); } } } @@ -596,7 +609,7 @@ public class DhLightingEngine else if (!airIDs.get(FullDataPointUtil.getId(adjacentDataPoint))) { // assume for now that we cannot propagate into non-transparent data points. - continue; // TODO how does this work with water? Do we care? + continue; } else { @@ -610,7 +623,7 @@ public class DhLightingEngine adjacentDataPoint = FullDataPointUtil.setSkyLight(adjacentDataPoint, lightLevel - 1); adjacentDataPoints.set(adjacentIndex, adjacentDataPoint); // if propagation succeeded, recursively propagate again starting at the adjacent data point. - recursivelyLightAdjacentDataPoints(chunk, airIDs, adjacentX, adjacentZ, adjacentDataPoint); + this.recursivelyLightAdjacentDataPoints(chunk, airIDs, adjacentX, adjacentZ, adjacentDataPoint); } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index 92b943b0f..a864cea83 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -66,15 +66,6 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); - /** - * Defines how many tasks can be queued per thread.

- * - * TODO the multiplier here should change dynamically based on how fast the generator is vs the queuing thread, - * if this is too high it may cause issues when moving, - * but if it is too low the generator threads won't have enough tasks to work on - */ - private static final int MAX_QUEUED_TASKS_PER_THREAD = 1; - private final IDhApiWorldGenerator generator; private final IDhServerLevel level; @@ -241,9 +232,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb return true; } + // queue more tasks if any of the threads are available int worldGenThreadCount = Math.max(Config.Common.MultiThreading.numberOfThreads.get(), 1); - int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD; - return this.inProgressGenTasksByLodPos.size() > maxWorldGenTaskCount; + return this.inProgressGenTasksByLodPos.size() > worldGenThreadCount; } /** * @param targetPos the position to center the generation around @@ -412,15 +403,17 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb try { IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray); - - //// TODO light data should be pulled (if possible) from the ChunkAccess object itself via ChunkFileReader.readLight - //// but this should work for now - //ArrayList nearbyChunkList = new ArrayList<>(); - //nearbyChunkList.add(chunkWrapper); - //byte maxSkyLight = this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT; - //DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, maxSkyLight); - chunkWrapper.setIsDhBlockLightCorrect(true); + // only light the chunk here if necessary, + // lighting before this point is preferred but for potenial legacy API uses this + // check should be done + if (!chunkWrapper.isDhBlockLightingCorrect()) + { + ArrayList nearbyChunkList = new ArrayList<>(); + nearbyChunkList.add(chunkWrapper); + byte maxSkyLight = this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT; + DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, maxSkyLight); + } try (FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper)) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java index 54c95f7dc..fe8b14744 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/ChunkLightStorage.java @@ -158,6 +158,12 @@ public class ChunkLightStorage lightSection.set(x, y, z, lightLevel); } + public boolean isEmpty() + { + return this.lightSections == null + || this.lightSections.length == 0; + } + public void clear() { if (this.lightSections != null)