Fix DH world gen missing structures

This commit is contained in:
James Seibel
2025-09-14 08:11:47 -05:00
parent 2c38401637
commit 5ce7eae7c0
8 changed files with 186 additions and 86 deletions
@@ -32,6 +32,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.EventTimer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.gridList.ArrayGridList;
@@ -84,6 +85,7 @@ import net.minecraft.world.level.chunk.ChunkStatus;
import org.jetbrains.annotations.Nullable;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
#endif
@@ -174,6 +176,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
// constructors //
//==============//
@NotNull
public static final ImmutableMap<EDhApiWorldGenerationStep, Integer> WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP;
public static final int MAX_WORLD_GEN_CHUNK_BORDER_NEEDED;
@@ -205,11 +208,12 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
builder.put(EDhApiWorldGenerationStep.LIGHT, 0);
WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP = builder.build();
// TODO this is a test to see if the additional boarder is actually necessary or not.
// If world generators end up having infinite loops or other unexplained issues,
// this should be set back to the commented out logic below
// in James' testing as of 2025-09-13 a border here of 2
// and a getChunkPosToGenerateStream() radius of 14 provided more accurate
// structure generation, however it also caused extreme server lag
// a border of 0 here and a getChunkPosToGenerateStream() radius of 8 provided
// good-enough structure generation while not lagging the server
MAX_WORLD_GEN_CHUNK_BORDER_NEEDED = 0;
//MAX_WORLD_GEN_CHUNK_BORDER_NEEDED = WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP.values().stream().mapToInt(Integer::intValue).max().getAsInt();
}
public BatchGenerationEnvironment(IDhServerLevel serverlevel)
@@ -387,7 +391,9 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
CompletableFuture.allOf(readFutures).join();
// future chain for generation
return CompletableFuture.runAsync(() ->
return CompletableFuture.runAsync(() ->
{
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
@@ -410,10 +416,10 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
int centerZ = refPosZ + radius + zOffset;
// get/create the list of chunks we're going to generate
IEmptyChunkRetrievalFunc fallbackFunc =
IEmptyChunkRetrievalFunc fallbackFunc =
(chunkPosX, chunkPosZ) -> Objects.requireNonNull(
generatedChunkByDhPos.get(new DhChunkPos(chunkPosX, chunkPosZ)),
() -> String.format("Requested chunk [%d, %d] unavailable during world generation", chunkPosX, chunkPosZ));
generatedChunkByDhPos.get(new DhChunkPos(chunkPosX, chunkPosZ)),
() -> String.format("Requested chunk [%d, %d] unavailable during world generation", chunkPosX, chunkPosZ));
ArrayGridList<ChunkAccess> regionChunks = new ArrayGridList<>(
refSize,
@@ -435,7 +441,7 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
// 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);
@@ -524,7 +530,12 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
genEvent.threadedParam.perf.recordEvent(genEvent.timer);
PREF_LOGGER.debugInc(genEvent.timer.toString());
}
}, executor);
}
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<ChunkPos> getChunkPosToGenerateStream(int genMinX, int genMinZ, int width, int extraRadius)
@@ -1078,8 +1089,8 @@ public final class BatchGenerationEnvironment extends AbstractBatchGenerationEnv
}
}
private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, int border) { return new ArrayGridList<>(total, border, total.gridSize - border); }
//private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, EDhApiWorldGenerationStep step) { return GetCutoutFrom(total, MaxBorderNeeded - WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP.get(step)); }
private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, EDhApiWorldGenerationStep step) { return GetCutoutFrom(total, 0); }
private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, EDhApiWorldGenerationStep step) { return GetCutoutFrom(total, WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP.get(step)); }
//private static <T> ArrayGridList<T> GetCutoutFrom(ArrayGridList<T> total, EDhApiWorldGenerationStep step) { return GetCutoutFrom(total, 0); }
@Override
@@ -0,0 +1,54 @@
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.mimicObject.DhLitWorldGenRegion;
import com.seibel.distanthorizons.core.util.gridList.ArrayGridList;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ProtoChunk;
import java.util.ArrayList;
import java.util.List;
#if MC_VER <= MC_1_20_4
import net.minecraft.world.level.chunk.ChunkStatus;
#else
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public abstract class AbstractWorldGenStep
{
public abstract void generateGroup(
ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion,
ArrayGridList<ChunkWrapper> chunkWrappers);
public abstract ChunkStatus getChunkStatus();
/** @return the list of chunks that have an earlier status and can be generated */
protected ArrayList<ChunkAccess> getChunksToGenerate(List<ChunkWrapper> chunkWrappers)
{
ArrayList<ChunkAccess> chunksToGenerate = new ArrayList<>();
for (ChunkWrapper chunkWrapper : chunkWrappers)
{
ChunkAccess chunk = chunkWrapper.getChunk();
if (chunkWrapper.getStatus().isOrAfter(this.getChunkStatus()))
{
// this chunk has already generated this step
continue;
}
else if (chunk instanceof ProtoChunk)
{
chunkWrapper.trySetStatus(this.getChunkStatus());
chunksToGenerate.add(chunk);
}
}
return chunksToGenerate;
}
}
@@ -20,12 +20,13 @@
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.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;
@@ -40,39 +41,35 @@ import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepBiomes
public final class StepBiomes extends AbstractWorldGenStep
{
public static final ChunkStatus STATUS = ChunkStatus.BIOMES;
private final BatchGenerationEnvironment environment;
public static final ChunkStatus STATUS = ChunkStatus.BIOMES;
//=============//
// constructor //
//=============//
public StepBiomes(BatchGenerationEnvironment batchGenerationEnvironment) { this.environment = batchGenerationEnvironment; }
//==================//
// abstract methods //
//==================//
@Override
public ChunkStatus getChunkStatus() { return STATUS; }
@Override
public void generateGroup(
ThreadedParameters tParams, WorldGenRegion worldGenRegion,
List<ChunkWrapper> chunkWrappers)
ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion,
ArrayGridList<ChunkWrapper> chunkWrappers)
{
ArrayList<ChunkAccess> chunksToDo = new ArrayList<>();
for (ChunkWrapper chunkWrapper : chunkWrappers)
{
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);
}
}
ArrayList<ChunkAccess> chunksToDo = this.getChunksToGenerate(chunkWrappers);
for (ChunkAccess chunk : chunksToDo)
{
#if MC_VER < MC_1_18_2
@@ -37,8 +37,10 @@ import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
import java.util.ConcurrentModificationException;
public final class StepFeatures
public final class StepFeatures extends AbstractWorldGenStep
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -48,10 +50,22 @@ public final class StepFeatures
//=============//
// constructor //
//=============//
public StepFeatures(BatchGenerationEnvironment batchGenerationEnvironment) { this.environment = batchGenerationEnvironment; }
//==================//
// abstract methods //
//==================//
@Override
public ChunkStatus getChunkStatus() { return STATUS; }
@Override
public void generateGroup(
ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion,
ArrayGridList<ChunkWrapper> chunkWrappers)
@@ -88,6 +102,10 @@ public final class StepFeatures
Heightmap.primeHeightmaps(chunk, STATUS.heightmapsAfter());
}
catch (ConcurrentModificationException e) // ReportedException
{
// TODO
}
catch (Exception e)
{
LOGGER.warn("Unexpected issue when generating features for chunk at pos ["+chunkWrapper.getChunkPos()+"], error: ["+e.getMessage()+"].", e);
@@ -26,6 +26,8 @@ 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.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;
@@ -41,7 +43,7 @@ import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepNoise
public final class StepNoise extends AbstractWorldGenStep
{
private static final ChunkStatus STATUS = ChunkStatus.NOISE;
@@ -49,15 +51,26 @@ public final class StepNoise
//=============//
// constructor //
//=============//
public StepNoise(BatchGenerationEnvironment batchGenerationEnvironment) { this.environment = batchGenerationEnvironment; }
//==================//
// abstract methods //
//==================//
@Override
public ChunkStatus getChunkStatus() { return STATUS; }
@Override
public void generateGroup(
ThreadedParameters tParams, WorldGenRegion worldGenRegion,
List<ChunkWrapper> chunkWrappers)
ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion,
ArrayGridList<ChunkWrapper> chunkWrappers)
{
ArrayList<ChunkAccess> chunksToDo = new ArrayList<>();
for (ChunkWrapper chunkWrapper : chunkWrappers)
@@ -26,6 +26,8 @@ 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.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;
@@ -37,7 +39,7 @@ import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepStructureReference
public final class StepStructureReference extends AbstractWorldGenStep
{
private static final ChunkStatus STATUS = ChunkStatus.STRUCTURE_REFERENCES;
@@ -45,15 +47,26 @@ public final class StepStructureReference
//=============//
// constructor //
//=============//
public StepStructureReference(BatchGenerationEnvironment batchGenerationEnvironment) { this.environment = batchGenerationEnvironment; }
//==================//
// abstract methods //
//==================//
@Override
public ChunkStatus getChunkStatus() { return STATUS; }
@Override
public void generateGroup(
ThreadedParameters tParams, WorldGenRegion worldGenRegion,
List<ChunkWrapper> chunkWrappers)
ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion,
ArrayGridList<ChunkWrapper> chunkWrappers)
{
ArrayList<ChunkAccess> chunksToDo = new ArrayList<ChunkAccess>();
for (ChunkWrapper chunkWrapper : chunkWrappers)
@@ -67,6 +80,7 @@ public final class StepStructureReference
else if (chunk instanceof ProtoChunk)
{
chunkWrapper.trySetStatus(STATUS);
chunksToDo.add(chunk);
}
}
@@ -27,7 +27,9 @@ 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.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;
@@ -42,7 +44,7 @@ import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepStructureStart
public final class StepStructureStart extends AbstractWorldGenStep
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final ChunkStatus STATUS = ChunkStatus.STRUCTURE_STARTS;
@@ -52,42 +54,27 @@ public final class StepStructureStart
//=============//
// constructor //
//=============//
public StepStructureStart(BatchGenerationEnvironment batchGenerationEnvironment) { this.environment = batchGenerationEnvironment; }
public static class StructStartCorruptedException extends RuntimeException
{
private static final long serialVersionUID = -8987434342051563358L;
public StructStartCorruptedException(ArrayIndexOutOfBoundsException e)
{
super("StructStartCorruptedException");
super.initCause(e);
fillInStackTrace();
}
}
//==================//
// abstract methods //
//==================//
@Override
public ChunkStatus getChunkStatus() { return STATUS; }
@Override
public void generateGroup(
ThreadedParameters tParams, WorldGenRegion worldGenRegion,
List<ChunkWrapper> chunkWrappers) throws InterruptedException
ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion,
ArrayGridList<ChunkWrapper> chunkWrappers)
{
ArrayList<ChunkAccess> chunksToDo = new ArrayList<>();
for (ChunkWrapper chunkWrapper : chunkWrappers)
{
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);
}
}
ArrayList<ChunkAccess> chunksToDo = this.getChunksToGenerate(chunkWrappers);
#if MC_VER < MC_1_19_2
if (this.environment.params.worldGenSettings.generateFeatures())
@@ -101,12 +88,6 @@ public final class StepStructureStart
#endif
for (ChunkAccess chunk : chunksToDo)
{
// System.out.println("StepStructureStart: "+chunk.getPos());
// there are a few cases where the structure generator call may lock up (either due to teleporting or leaving the world).
// hopefully allowing interrupts here will prevent that from happening.
BatchGenerationEnvironment.throwIfThreadInterrupted();
// 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();
@@ -151,8 +132,6 @@ public final class StepStructureStart
{
// 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
//throw new StepStructureStart.StructStartCorruptedException(secondEx);
}
}
@@ -26,6 +26,8 @@ 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.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;
@@ -37,7 +39,7 @@ import net.minecraft.world.level.chunk.status.ChunkStatus;
#endif
public final class StepSurface
public final class StepSurface extends AbstractWorldGenStep
{
private static final ChunkStatus STATUS = ChunkStatus.SURFACE;
@@ -45,13 +47,25 @@ public final class StepSurface
//=============//
// constructor //
//=============//
public StepSurface(BatchGenerationEnvironment batchGenerationEnvironment) { this.environment = batchGenerationEnvironment; }
//==================//
// abstract methods //
//==================//
@Override
public ChunkStatus getChunkStatus() { return STATUS; }
@Override
public void generateGroup(
ThreadedParameters tParams, WorldGenRegion worldGenRegion,
List<ChunkWrapper> chunkWrappers)
ThreadedParameters tParams, DhLitWorldGenRegion worldGenRegion,
ArrayGridList<ChunkWrapper> chunkWrappers)
{
ArrayList<ChunkAccess> chunksToDo = new ArrayList<>();