From a709ab60715433942c810771c9f4ec97d4cf413c Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 22 Nov 2025 11:02:13 -0600 Subject: [PATCH] move internal server into its own file --- .../BatchGenerationEnvironment.java | 352 ++---------------- .../worldGeneration/ChunkPosGenStream.java | 88 +++++ .../worldGeneration/GenerationEvent.java | 63 ++-- .../InternalServerGenerator.java | 262 +++++++++++++ coreSubProjects | 2 +- 5 files changed, 420 insertions(+), 347 deletions(-) create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ChunkPosGenStream.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/internalServer/InternalServerGenerator.java 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 166c2d769..f7ad847f5 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 @@ -24,6 +24,7 @@ import com.google.common.collect.ImmutableMap; 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.internalServer.InternalServerGenerator; import com.seibel.distanthorizons.common.wrappers.worldGeneration.mimicObject.*; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.generation.DhLightingEngine; @@ -49,9 +50,6 @@ 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 org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -99,27 +97,22 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm .build(); public static final DhLogger CHUNK_LOAD_LOGGER = new DhLoggerBuilder() - .name("LOD World Gen") + .name("LOD Chunk Loading") .fileLevelConfig(Config.Common.Logging.logWorldGenChunkLoadEventToFile) .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 - private static final IModChecker MOD_CHECKER = SingletonInjector.INSTANCE.get(IModChecker.class); @NotNull public static final ImmutableMap WORLD_GEN_CHUNK_BORDER_NEEDED_BY_GEN_STEP; public static final int MAX_WORLD_GEN_CHUNK_BORDER_NEEDED; + public static final long EXCEPTION_TIMER_RESET_TIME = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS); + public static final int EXCEPTION_COUNTER_TRIGGER = 20; - private final IDhServerLevel serverLevel; + + private final IDhServerLevel dhServerLevel; /** * will be true if C2ME is installed (since they require us to @@ -128,9 +121,11 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm */ private boolean pullExistingChunkUsingMcAsyncMethod = false; + public final InternalServerGenerator internalServerGenerator; - public final LinkedBlockingQueue generationEventList = new LinkedBlockingQueue<>(); + + public final LinkedBlockingQueue generationEventQueue = new LinkedBlockingQueue<>(); public final GlobalWorldGenParams params; public final StepStructureStart stepStructureStart = new StepStructureStart(this); @@ -141,8 +136,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm 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 int unknownExceptionCount = 0; public long lastExceptionTriggerTime = 0; @@ -203,15 +196,13 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm MAX_WORLD_GEN_CHUNK_BORDER_NEEDED = 0; } - public BatchGenerationEnvironment(IDhServerLevel serverLevel) + public BatchGenerationEnvironment(IDhServerLevel dhServerLevel) { - this.serverLevel = serverLevel; + this.dhServerLevel = dhServerLevel; + this.params = new GlobalWorldGenParams(dhServerLevel); + this.internalServerGenerator = new InternalServerGenerator(this.params, this.dhServerLevel); - LOGGER.info("Creating Batch Generator"); - - serverLevel.getServerLevelWrapper().getDimensionType(); - - ChunkGenerator generator = ((ServerLevelWrapper) (serverLevel.getServerLevelWrapper())).getLevel().getChunkSource().getGenerator(); + ChunkGenerator generator = ((ServerLevelWrapper) (dhServerLevel.getServerLevelWrapper())).getLevel().getChunkSource().getGenerator(); boolean isMcGenerator = generator instanceof NoiseBasedChunkGenerator || generator instanceof DebugLevelSource @@ -241,7 +232,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm this.pullExistingChunkUsingMcAsyncMethod = true; } - this.params = new GlobalWorldGenParams(serverLevel); } @@ -290,7 +280,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm // Update all current out standing jobs - Iterator iter = this.generationEventList.iterator(); + Iterator iter = this.generationEventQueue.iterator(); while (iter.hasNext()) { GenerationEvent event = iter.next(); @@ -339,11 +329,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm // We handle this later, although that handling would need to change if the gen size ever changes. LodUtil.assertTrue(genEvent.widthInChunks % 2 == 0, "Generation events are expected to be an evan number of chunks wide."); - if (genEvent.generatorMode == EDhApiDistantGeneratorMode.INTERNAL_SERVER) - { - return this.generateChunksViaInternalServerAsync(genEvent); - } - int borderSize = MAX_WORLD_GEN_CHUNK_BORDER_NEEDED; // genEvent.size - 1 converts the even width size to an odd number for MC compatability int refSize = (genEvent.widthInChunks - 1) + (borderSize * 2); @@ -368,7 +353,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm // 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.widthInChunks, 8) + ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 8) .map((chunkPos) -> this.createEmptyOrPreExistingChunkAsync(chunkPos.x, chunkPos.z, chunkSkyLightingByDhPos, chunkBlockLightingByDhPos, generatedChunkByDhPos)) .toArray(CompletableFuture[]::new); @@ -452,7 +437,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm else if (chunk != null) { // wrap the chunk - ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, this.serverLevel.getLevelWrapper()); + ChunkWrapper chunkWrapper = new ChunkWrapper(chunk, this.dhServerLevel.getLevelWrapper()); chunkWrapperList.set(relX, relZ, chunkWrapper); // try setting the wrapper's lighting @@ -495,7 +480,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm // submit generated chunks // //=========================// - Iterator iterator = getChunkPosToGenerateStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0).iterator(); + Iterator iterator = ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0).iterator(); while (iterator.hasNext()) { ChunkPos pos = iterator.next(); @@ -519,17 +504,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm } }, 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) - //); - } @@ -682,7 +656,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm } catch (Exception e) { - CHUNK_LOAD_LOGGER.warn("DistantHorizons: Couldn't load or make chunk [" + chunkPos + "]. Error: [" + e.getMessage() + "].", e); + CHUNK_LOAD_LOGGER.warn("Couldn't load or make chunk [" + chunkPos + "]. Error: [" + e.getMessage() + "].", e); return CompletableFuture.completedFuture(null); } } @@ -752,199 +726,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm - // internal server generation // - - private CompletableFuture generateChunksViaInternalServerAsync(GenerationEvent genEvent) throws InterruptedException - { - 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.widthInChunks, 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 - 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); - } - - 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.widthInChunks, 0).iterator(); - while (iterator.hasNext()) - { - ChunkPos chunkPos = iterator.next(); - releaseChunkToServer(this.params.level, chunkPos, true); - } - }); - - processGeneratedChunksFuture.whenCompleteAsync((unused, throwable) -> { }, runnableQueue::add); // trigger wakeup - - - - //===============// - // run each step // - //===============// - - while (!processGeneratedChunksFuture.isDone()) - { - try - { - 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.widthInChunks, 0).iterator(); - while (iterator.hasNext()) - { - ChunkPos chunkPos = iterator.next(); - releaseChunkToServer(this.params.level, chunkPos, true); - } - - throw 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(() -> - { - 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) - { - LOGGER.warn("Failed to release chunk back to internal server. Error: ["+e.getMessage()+"]", e); - } - }); - } - - - // direct generation // public void generateDirect( @@ -1025,7 +806,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm { // 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 @@ -1061,7 +842,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm DhLightingEngine.INSTANCE.bakeChunkBlockLighting(centerChunk, iChunkWrapperList, maxSkyLight); } - this.serverLevel.updateBeaconBeamsForChunk(centerChunk, iChunkWrapperList); + this.dhServerLevel.updateBeaconBeamsForChunk(centerChunk, iChunkWrapperList); } } } @@ -1070,8 +851,10 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm + // queue task // + @Override - public CompletableFuture generateChunks( + public CompletableFuture queueGenEvent( int minX, int minZ, int chunkWidthCount, EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep targetStep, ExecutorService worldGeneratorThreadPool, Consumer resultConsumer) @@ -1080,7 +863,7 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm new DhChunkPos(minX, minZ), chunkWidthCount, this, generatorMode, targetStep, resultConsumer, worldGeneratorThreadPool); - this.generationEventList.add(genEvent); + this.generationEventQueue.add(genEvent); return genEvent.future; } @@ -1093,17 +876,19 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm @Override public void close() { - LOGGER.info(BatchGenerationEnvironment.class.getSimpleName() + " shutting down..."); + LOGGER.info("Closing [" +BatchGenerationEnvironment.class.getSimpleName() + "]"); - LOGGER.info("Canceling in progress generation event futures..."); - Iterator iter = this.generationEventList.iterator(); - while (iter.hasNext()) + + // cancel in-progress tasks + Iterator genEventIter = this.generationEventQueue.iterator(); + while (genEventIter.hasNext()) { - GenerationEvent event = iter.next(); + GenerationEvent event = genEventIter.next(); event.future.cancel(true); - iter.remove(); + genEventIter.remove(); } + // clear the chunk cache RegionFileStorageExternalCache regionStorage = this.regionFileStorageCacheRef.get(); if (regionStorage != null) @@ -1118,8 +903,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm LOGGER.error("Failed to close region file storage cache, error: ["+e.getMessage()+"].", e); } } - - LOGGER.info(BatchGenerationEnvironment.class.getSimpleName() + " shutdown complete."); } @@ -1153,75 +936,6 @@ public final class BatchGenerationEnvironment implements IBatchGeneratorEnvironm 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; - } - } } \ 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..b923ff1a6 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/ChunkPosGenStream.java @@ -0,0 +1,88 @@ +package com.seibel.distanthorizons.common.wrappers.worldGeneration; + +import net.minecraft.world.level.ChunkPos; + +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 +{ + + /** @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); } + public 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 313ae3ca9..f898655c0 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 @@ -59,6 +59,7 @@ public final class GenerationEvent EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep targetGenerationStep, Consumer resultConsumer) { this.id = generationFutureDebugIDs++; + this.minPos = minPos; this.widthInChunks = widthInChunks; this.generatorMode = generatorMode; @@ -78,7 +79,7 @@ public final class GenerationEvent EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep target, Consumer resultConsumer, ExecutorService worldGeneratorThreadPool) { - GenerationEvent generationEvent = new GenerationEvent(minPos, widthInChunks, genEnvironment, generatorMode, target, resultConsumer); + GenerationEvent genEvent = new GenerationEvent(minPos, widthInChunks, genEnvironment, generatorMode, target, resultConsumer); try { @@ -89,40 +90,48 @@ public final class GenerationEvent 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 + { + genEnvironment.generateLodFromListAsync(genEvent, (runnable) -> { - // TODO why not just always set this each time? - boolean alreadyMarked = BatchGenerationEnvironment.isThisDhWorldGenThread(); - if (!alreadyMarked) - { - BatchGenerationEnvironment.isDhWorldGenThreadRef.set(true); - } - - try - { - runnable.run(); - } - catch (Throwable throwable) - { - handleWorldGenThrowable(generationEvent, throwable); - } - finally + worldGeneratorThreadPool.execute(() -> { + // TODO why not just always set this each time? + boolean alreadyMarked = BatchGenerationEnvironment.isThisDhWorldGenThread(); if (!alreadyMarked) { - BatchGenerationEnvironment.isDhWorldGenThreadRef.set(false); + BatchGenerationEnvironment.isDhWorldGenThreadRef.set(true); } - } + + try + { + runnable.run(); + } + catch (Throwable throwable) + { + handleWorldGenThrowable(genEvent, throwable); + } + finally + { + if (!alreadyMarked) + { + BatchGenerationEnvironment.isDhWorldGenThreadRef.set(false); + } + } + }); }); - }); - - generationEvent.future.complete(null); + + genEvent.future.complete(null); + } } catch (Throwable initialThrowable) { - handleWorldGenThrowable(generationEvent, initialThrowable); + handleWorldGenThrowable(genEvent, initialThrowable); } finally { @@ -132,10 +141,10 @@ public final class GenerationEvent } 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) diff --git a/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/internalServer/InternalServerGenerator.java b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/internalServer/InternalServerGenerator.java new file mode 100644 index 000000000..3c0f88947 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/wrappers/worldGeneration/internalServer/InternalServerGenerator.java @@ -0,0 +1,262 @@ +package com.seibel.distanthorizons.common.wrappers.worldGeneration.internalServer; + +import com.seibel.distanthorizons.common.wrappers.chunk.ChunkWrapper; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.ChunkPosGenStream; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.GenerationEvent; +import com.seibel.distanthorizons.common.wrappers.worldGeneration.GlobalWorldGenParams; +import com.seibel.distanthorizons.core.config.Config; +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.LodUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkLevel; +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; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +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(); + + #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 final GlobalWorldGenParams params; + private final IDhServerLevel dhServerLevel; + + + + //=============// + // constructor // + //=============// + + public InternalServerGenerator(GlobalWorldGenParams params, IDhServerLevel dhServerLevel) + { + this.params = params; + this.dhServerLevel = dhServerLevel; + } + + + + //============// + // generation // + //============// + + public void generateChunksViaInternalServer(GenerationEvent genEvent) throws InterruptedException + { + LinkedBlockingQueue runnableQueue = new LinkedBlockingQueue<>(); + + Map chunkWrappersByDhPos = Collections.synchronizedMap(new HashMap<>()); + + + + //===================================// + // create generation queue runnables // + //===================================// + + // request each chunk pos from the server + CompletableFuture[] requestFutures = + ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 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.dhServerLevel.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 + int maxSkyLight = this.dhServerLevel.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.dhServerLevel.updateBeaconBeamsForChunk(iChunkWrapper, generatedChunks); + } + + for (IChunkWrapper iChunkWrapper : generatedChunks) + { + genEvent.resultConsumer.accept(iChunkWrapper); + } + }, runnableQueue::add) + .whenCompleteAsync((unused, throwable) -> + { + // cleanup + // release the generated chunks + + Iterator iterator = ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0).iterator(); + while (iterator.hasNext()) + { + ChunkPos chunkPos = iterator.next(); + releaseChunkFromServer(this.params.level, chunkPos, true); + } + }); + + processGeneratedChunksFuture.whenCompleteAsync((unused, throwable) -> { }, runnableQueue::add); // trigger wakeup + + + + //===============// + // run each step // + //===============// + + while (!processGeneratedChunksFuture.isDone()) + { + try + { + Runnable command = runnableQueue.poll(1, TimeUnit.SECONDS); + if (command != null) + { + command.run(); + } + } + catch (InterruptedException e) + { + // interrupted, release chunk to server + Iterator iterator = ChunkPosGenStream.getStream(genEvent.minPos.getX(), genEvent.minPos.getZ(), genEvent.widthInChunks, 0).iterator(); + while (iterator.hasNext()) + { + ChunkPos chunkPos = iterator.next(); + releaseChunkFromServer(this.params.level, chunkPos, true); + } + + throw e; + } + } + } + /** @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(() -> + { + 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 releaseChunkFromServer(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) + { + LOGGER.warn("Failed to release chunk back to internal server. Error: ["+e.getMessage()+"]", e); + } + }); + } + + + +} diff --git a/coreSubProjects b/coreSubProjects index 33a55dc7c..47a4d1535 160000 --- a/coreSubProjects +++ b/coreSubProjects @@ -1 +1 @@ -Subproject commit 33a55dc7cdae68979d304b335eef103e0bc4a65f +Subproject commit 47a4d1535f1ad38850bf8db1230184c5e5314bcd