minor world gen related refactoring

This commit is contained in:
James Seibel
2025-12-04 07:39:09 -06:00
parent 6ea864ef6b
commit 385bd326cf
5 changed files with 73 additions and 105 deletions
@@ -1415,49 +1415,6 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> recalculateChunkHeightmaps = new ConfigEntry.Builder<Boolean>()
.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<Boolean> pullLightingForPregeneratedChunks = new ConfigEntry.Builder<Boolean>()
.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<Boolean> assumePreExistingChunksAreFinished = new ConfigEntry.Builder<Boolean>()
.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();
@@ -98,18 +98,14 @@ public class BatchGenerator implements IDhApiWorldGenerator
ExecutorService worldGeneratorThreadPool,
Consumer<Object[]> 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)
@@ -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<DhBlockPos> blockLightPosList = chunk.getWorldBlockLightPosList();
ArrayList<DhBlockPos> 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);
}
}
}
@@ -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. <br><br>
*
* 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<IChunkWrapper> 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<IChunkWrapper> 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))
{
@@ -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)