From 60232e713b77dd6163e85234586d120017a0d851 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 19 Dec 2025 16:54:07 -0600 Subject: [PATCH] refactor world gen queue --- .../worldGenerator/IDhApiWorldGenerator.java | 2 +- .../fullData/sources/FullDataSourceV2.java | 6 +- .../GeneratedFullDataSourceProvider.java | 115 +--- .../RemoteFullDataSourceProvider.java | 23 +- .../V2/FullDataSourceProviderV2.java | 12 +- .../IFullDataSourceRetrievalQueue.java | 6 +- .../core/generation/PregenManager.java | 2 +- .../generation/RemoteWorldRetrievalQueue.java | 108 +++- .../core/generation/WorldGenerationQueue.java | 603 ++++++++++-------- ...lt.java => DataSourceRetrievalResult.java} | 32 +- ...Task.java => DataSourceRetrievalTask.java} | 30 +- .../tasks/InProgressWorldGenTaskGroup.java | 39 -- .../generation/tasks/WorldGenTaskGroup.java | 67 -- .../core/level/AbstractDhServerLevel.java | 3 +- .../core/level/DhClientLevel.java | 5 +- .../core/level/DhServerLevel.java | 2 +- .../AbstractFullDataNetworkRequestQueue.java | 329 +++++----- .../client/ClientNetworkState.java | 1 + .../multiplayer/client/ENetRequestState.java | 15 + .../client/NetRequestResult.java} | 33 +- .../client/SyncOnLoadRequestQueue.java | 4 +- .../fullData/FullDataPayloadReceiver.java | 3 +- .../server/FullDataSourceRequestHandler.java | 2 +- .../pooling/AbstractPhantomArrayList.java | 4 +- .../core/pos/blockPos/DhBlockPos2D.java | 24 + .../core/render/LodQuadTree.java | 256 +++++--- .../core/render/LodRenderSection.java | 121 +--- 27 files changed, 908 insertions(+), 939 deletions(-) rename core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/{WorldGenResult.java => DataSourceRetrievalResult.java} (51%) rename core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/{WorldGenTask.java => DataSourceRetrievalTask.java} (53%) delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/InProgressWorldGenTaskGroup.java delete mode 100644 core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenTaskGroup.java create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ENetRequestState.java rename core/src/main/java/com/seibel/distanthorizons/core/{generation/tasks/IWorldGenTaskTracker.java => multiplayer/client/NetRequestResult.java} (50%) diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/IDhApiWorldGenerator.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/IDhApiWorldGenerator.java index f97dc86e5..e7e095447 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/IDhApiWorldGenerator.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/override/worldGenerator/IDhApiWorldGenerator.java @@ -169,7 +169,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable * * After the {@link IDhApiWorldGenerator} has been generated, it should be passed into the * resultConsumer's {@link Consumer#accept(Object)} method. - * Note: if air blocks aren't included in the with the {@link DhApiChunk} with proper lighting, lower detail levels will appear as black/unlit. + * Note: if air blocks aren't included in the {@link IDhApiFullDataSource} with proper lighting, lower detail levels will appear as black/unlit. * * @implNote the default implementation of this method throws an {@link UnsupportedOperationException}, * and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}. diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java index c4bae8725..c8b2f34c2 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java @@ -121,7 +121,7 @@ public class FullDataSourceV2 public Boolean applyToChildren = null; /** should only be used by methods exposed via the DH API */ - private boolean runApiChunkValidation = false; + private boolean runApiSetterValidation = false; @@ -1296,7 +1296,7 @@ public class FullDataSourceV2 // API methods // //=============// - public void setRunApiChunkValidation(boolean runValidation) { this.runApiChunkValidation = runValidation; } + public void setRunApiSetterValidation(boolean runValidation) { this.runApiSetterValidation = runValidation; } @Override public int getWidthInDataColumns() { return WIDTH; } @@ -1308,7 +1308,7 @@ public class FullDataSourceV2 try { LodDataBuilder.putListInTopDownOrder(columnDataPoints); - if (this.runApiChunkValidation) + if (this.runApiSetterValidation) { LodDataBuilder.validateOrThrowApiDataColumn(columnDataPoints); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java index c6fc3785b..7c4d5ab87 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java @@ -28,8 +28,7 @@ import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataUpdatePropag import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue; -import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; -import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; +import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; @@ -38,12 +37,12 @@ import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; +import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -68,7 +67,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im * TODO this should be dynamically allocated based on CPU load * and abilities. */ - public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20; + public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20; + + public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Generated Provider"); private final AtomicReference worldGenQueueRef = new AtomicReference<>(null); @@ -85,15 +86,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException { this(level, saveStructure, null); } public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException - { - super(level, saveStructure, saveDirOverride); - - this.addDataSourceUpdateListener((@NotNull FullDataSourceV2 updatedData) -> - { - this.onWorldGenTaskComplete(WorldGenResult.CreateSuccess(updatedData.getPos()), null); - }); - - } + { super(level, saveStructure, saveDirOverride); } @@ -122,30 +115,35 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im // events // //========// - private void onWorldGenTaskComplete(WorldGenResult genTaskResult, Throwable exception) + private void onWorldGenTaskComplete(DataSourceRetrievalResult genTaskResult, Throwable exception) { if (exception != null) { // don't log shutdown exceptions - if (!(exception instanceof CancellationException || exception.getCause() instanceof CancellationException)) + if (!ExceptionUtil.isInterruptOrReject(exception)) { LOGGER.error("Uncaught Gen Task Exception at ["+genTaskResult.pos+"], error: ["+exception.getMessage()+"].", exception); } } - else if (genTaskResult.success) + else if (genTaskResult.generatedDataSource != null) { + this.dataUpdater.updateDataSource(genTaskResult.generatedDataSource); this.fireOnGenPosSuccessListeners(genTaskResult.pos); - return; + } + else if (exception == null && !genTaskResult.success) // TODO use enum to check type + { + // task was split } else { - // generation didn't complete - LOGGER.debug("Gen Task Failed at " + genTaskResult.pos); + // shouldn't happen, but just in case + // TODO is definitely happening + LOGGER.warn("Unexpected gen Task state at: [" + DhSectionPos.toString(genTaskResult.pos) + "], success: ["+genTaskResult.success+"], datasource: NULL, exception: NULL."); } // if the generation task was split up into smaller positions, add the on-complete event to them - for (CompletableFuture siblingFuture : genTaskResult.childFutures) + for (CompletableFuture siblingFuture : genTaskResult.childFutures) { siblingFuture.whenComplete((siblingGenTaskResult, siblingEx) -> this.onWorldGenTaskComplete(siblingGenTaskResult, siblingEx)); } @@ -272,6 +270,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount(); if (availableTaskSlots <= 0) { + //if (false) if (pruneWaitingTasksAboveLimit) { AtomicInteger tasksToCancel = new AtomicInteger(-availableTaskSlots + 1); @@ -288,7 +287,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im } @Override - public CompletableFuture queuePositionForRetrieval(Long genPos) + public CompletableFuture queuePositionForRetrieval(Long genPos) { IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get(); if (worldGenQueue == null) @@ -296,13 +295,8 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im return null; } - WorldGenTaskTracker genTaskTracker = new WorldGenTaskTracker(genPos); - CompletableFuture worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTaskTracker); - worldGenFuture.whenComplete((genTaskResult, ex) -> - { - //LOGGER.info("gen task complete ["+DhSectionPos.toString(genPos)+"]"); - //this.onWorldGenTaskComplete(genTaskResult, ex); - }); + CompletableFuture worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)); + worldGenFuture.whenComplete(this::onWorldGenTaskComplete); return worldGenFuture; } @@ -321,22 +315,20 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im public void clearRetrievalQueue() { this.worldGenQueueRef.set(null); } - public boolean isFullyGenerated(ByteArrayList columnGenerationSteps) + public boolean generationStepsAreFullyGenerated(ByteArrayList columnGenerationSteps) { return IntStream.range(0, columnGenerationSteps.size()) - .noneMatch(i -> - { - byte value = columnGenerationSteps.getByte(i); - return value == EDhApiWorldGenerationStep.EMPTY.value - || value == EDhApiWorldGenerationStep.DOWN_SAMPLED.value; - }); + .noneMatch((int intValue) -> + { + byte value = columnGenerationSteps.getByte(intValue); + return value == EDhApiWorldGenerationStep.EMPTY.value + || value == EDhApiWorldGenerationStep.DOWN_SAMPLED.value; + }); } - public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Generated Provider"); - @Override - public LongArrayList getPositionsToRetrieve(Long pos) + public LongArrayList getPositionsToRetrieve(long pos) { IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get(); if (worldGenQueue == null) @@ -352,7 +344,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im { ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH); this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray); - if (!columnGenStepArray.isEmpty()) + if (columnGenStepArray.size() != 0) { boolean positionFullyGenerated = true; @@ -378,12 +370,11 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im // this section is missing one or more columns, queue the missing ones for generation. - // TODO speed up this logic by only checking ungenerated columns LongArrayList generationList = new LongArrayList(); byte lowestGeneratorDetailLevel = (byte) Math.min( - worldGenQueue.lowestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL, - DhSectionPos.getDetailLevel(pos)); + worldGenQueue.lowestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL, + DhSectionPos.getDetailLevel(pos)); DhSectionPos.forEachChildAtDetailLevel(pos, lowestGeneratorDetailLevel, (genPos) -> { @@ -471,48 +462,13 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im // helper classes // //================// - // TODO may not be needed - private class WorldGenTaskTracker implements IWorldGenTaskTracker - { - /** just used when debugging/troubleshooting */ - private final long pos; - - public WorldGenTaskTracker(long pos) { this.pos = pos; } - - - @Override - public Consumer getDataSourceConsumer() - { - return (dataSource) -> - { - GeneratedFullDataSourceProvider.this.delayedFullDataSourceSaveCache.writeDataSourceToMemoryAndQueueSave(dataSource); - }; - } - - @Override - public CompletableFuture shouldGenerateSplitChild(long pos) - { - return GeneratedFullDataSourceProvider.this.getAsync(pos).thenApply(fullDataSource -> - { - //noinspection TryFinallyCanBeTryWithResources - try - { - return !GeneratedFullDataSourceProvider.this.isFullyGenerated(fullDataSource.columnGenerationSteps); - } - finally - { - fullDataSource.close(); - } - }); - } - - } private CompletableFuture onDataSourceSaveAsync(FullDataSourceV2 fullDataSource) { // block lights should have been populated at the chunkWrapper stage // waiting to populate the data source's skylight at this stage prevents re-lighting and // allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once - DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, LodUtil.MAX_MC_LIGHT); + int skyLight = this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT; + DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, skyLight); return this.updateDataSourceAsync(fullDataSource); } @@ -524,7 +480,6 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im { boolean shouldDoWorldGen(); - @Nullable DhBlockPos2D getTargetPosForGeneration(); /** Fired whenever a section has completed generating */ diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/RemoteFullDataSourceProvider.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/RemoteFullDataSourceProvider.java index 93fa2711e..8a30c2763 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/RemoteFullDataSourceProvider.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/RemoteFullDataSourceProvider.java @@ -26,6 +26,8 @@ import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.LodRequestModule; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.multiplayer.client.ENetRequestState; +import com.seibel.distanthorizons.core.multiplayer.client.NetRequestResult; import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue; import com.seibel.distanthorizons.core.logging.DhLogger; import org.jetbrains.annotations.Nullable; @@ -102,10 +104,23 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide Long timestamp = this.getTimestampForPos(pos); if (timestamp != null) { - this.syncOnLoadRequestQueue.submitRequest(pos, timestamp, fullDataSource -> - { - this.updateDataSourceAsync(fullDataSource).whenComplete((result, throwable) -> fullDataSource.close()); - }); + this.syncOnLoadRequestQueue.submitRequest(pos, timestamp) + .thenAccept((NetRequestResult netRequestResult) -> + { + if (netRequestResult.state == ENetRequestState.SUCCESS) + { + FullDataSourceV2 fullDataSource = netRequestResult.receivedDataSource; + if (fullDataSource != null) + { + this.updateDataSourceAsync(fullDataSource) + .handle((voidObj, throwable) -> + { + fullDataSource.close(); + return null; + }); + } + } + }); } return super.get(pos); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataSourceProviderV2.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataSourceProviderV2.java index 06bb0df00..e26bff94b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataSourceProviderV2.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/V2/FullDataSourceProviderV2.java @@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc; import com.seibel.distanthorizons.core.file.structure.ISaveStructure; -import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; +import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; @@ -85,9 +85,9 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable protected final String levelId; - private final FullDataUpdaterV2 dataUpdater; - private final FullDataUpdatePropagatorV2 updatePropagator; - private final DataMigratorV1 dataMigratorV1; + protected final FullDataUpdaterV2 dataUpdater; + protected final FullDataUpdatePropagatorV2 updatePropagator; + protected final DataMigratorV1 dataMigratorV1; @@ -372,11 +372,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable * an empty array if all positions were generated */ @Nullable - public LongArrayList getPositionsToRetrieve(Long pos) { return null; } + public LongArrayList getPositionsToRetrieve(long pos) { return null; } /** @return null if the position couldn't be queued */ @Nullable - public CompletableFuture queuePositionForRetrieval(Long genPos) { return null; } + public CompletableFuture queuePositionForRetrieval(Long genPos) { return null; } /** does nothing if the given position isn't present in the queue */ public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/IFullDataSourceRetrievalQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/IFullDataSourceRetrievalQueue.java index 7cba3a227..06b959836 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/IFullDataSourceRetrievalQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/IFullDataSourceRetrievalQueue.java @@ -19,13 +19,11 @@ package com.seibel.distanthorizons.core.generation; -import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; -import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; +import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.render.LodQuadTree; import com.seibel.distanthorizons.core.util.objects.RollingAverage; -import org.jetbrains.annotations.Nullable; import java.io.Closeable; import java.util.List; @@ -92,7 +90,7 @@ public interface IFullDataSourceRetrievalQueue extends Closeable */ void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf); - CompletableFuture submitRetrievalTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker); + CompletableFuture submitRetrievalTask(long pos, byte requiredDataDetail); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/PregenManager.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/PregenManager.java index 518ad90c1..3eebe8afb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/PregenManager.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/PregenManager.java @@ -148,7 +148,7 @@ public class PregenManager this.fullDataSourceProvider.getAsync(nextSectionPos) .thenAccept(fullDataSource -> { - if (this.fullDataSourceProvider.isFullyGenerated(fullDataSource.columnGenerationSteps)) + if (this.fullDataSourceProvider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps)) { this.pendingGenerations.invalidate(fullDataSource.getPos()); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java index 49db5ae3b..1dc5e486e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/RemoteWorldRetrievalQueue.java @@ -1,24 +1,22 @@ package com.seibel.distanthorizons.core.generation; import com.seibel.distanthorizons.core.config.Config; -import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; -import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; +import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.multiplayer.client.AbstractFullDataNetworkRequestQueue; import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState; +import com.seibel.distanthorizons.core.multiplayer.client.NetRequestResult; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.WorldGenUtil; -import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.logging.DhLogger; import java.util.ArrayList; -import java.util.List; -import java.util.Objects; import java.util.concurrent.*; public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable @@ -55,48 +53,89 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; } @Override - public CompletableFuture submitRetrievalTask(long sectionPos, byte requiredDataDetail, IWorldGenTaskTracker tracker) + public CompletableFuture submitRetrievalTask(long sectionPos, byte requiredDataDetail) { long generationStartMsTime = System.currentTimeMillis(); - return super.submitRequest(sectionPos, /* client timestamp */null, - (fullDataSource) -> - { - tracker.getDataSourceConsumer().accept(fullDataSource); - fullDataSource.close(); // TODO can cause issues if the above is async - }) - .thenApply((ERequestResult requestResult) -> + CompletableFuture returnFuture = new CompletableFuture<>(); + + CompletableFuture netFuture = super.submitRequest(sectionPos, /* client timestamp */null); + netFuture.handle((NetRequestResult netResult, Throwable throwable) -> + { + try { + if (throwable != null) + { + return DataSourceRetrievalResult.CreateFail(); + } + long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime; int chunkWidth = DhSectionPos.getChunkWidth(sectionPos); int chunkCount = chunkWidth * chunkWidth; double timePerChunk = (double) totalGenTimeInMs / (double) chunkCount; - this.rollingAverageChunkGenTimeInMs.add(timePerChunk); - switch (requestResult) + switch (netResult.state) { - case SUCCEEDED: - return WorldGenResult.CreateSuccess(sectionPos); - case FAILED: - return WorldGenResult.CreateFail(); + case SUCCESS: + // only add the time on successes + // it won't be a perfect estimate but fails will often come back faster, skewing the time faster + this.rollingAverageChunkGenTimeInMs.add(timePerChunk); + + return DataSourceRetrievalResult.CreateSuccess(sectionPos, netResult.receivedDataSource); + case FAIL: + return DataSourceRetrievalResult.CreateFail(); case REQUIRES_SPLITTING: - List> childFutures = new ArrayList<>(4); - DhSectionPos.forEachChild(sectionPos, childPos -> { - tracker.shouldGenerateSplitChild(childPos).thenAccept(shouldGenerate -> { - if (shouldGenerate) + ArrayList> childFutures = new ArrayList<>(4); + DhSectionPos.forEachChild(sectionPos, (long childPos) -> + { + boolean shouldGenerate; + try (FullDataSourceV2 fullDataSource = this.level.remoteDataSourceProvider.get(childPos)) + { + if (fullDataSource != null) { - childFutures.add(this.submitRetrievalTask(childPos, requiredDataDetail, tracker)); + shouldGenerate = !this.level.remoteDataSourceProvider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps); } - }); + else + { + shouldGenerate = true; + } + } + + if (shouldGenerate) + { + childFutures.add(this.submitRetrievalTask(childPos, requiredDataDetail)); + } }); - return WorldGenResult.CreateSplit(childFutures); + return DataSourceRetrievalResult.CreateSplit(childFutures); } - LodUtil.assertNotReach("Unexpected and unhandled request response result: [" + requestResult + "]"); - return WorldGenResult.CreateFail(); - }); + LodUtil.assertNotReach("Unexpected and unhandled request response result: [" + netResult.state + "]"); + return DataSourceRetrievalResult.CreateFail(); + } + catch (Exception e) + { + LOGGER.error("Unexpected issue in submitRetrievalTask returned future, error: ["+e.getMessage()+"]", e); + return DataSourceRetrievalResult.CreateFail(); + } + }) + // convert the net result + .handle((DataSourceRetrievalResult retrievalResult, Throwable throwable) -> + { + if (throwable != null) + { + returnFuture.completeExceptionally(throwable); + } + else + { + returnFuture.complete(retrievalResult); + } + + return null; + }); + + return returnFuture; } @Override @@ -112,7 +151,7 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue @Override protected int getRequestRateLimit() { return this.networkState.sessionConfig.getGenerationRequestRateLimit(); } @Override - protected boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos) + protected boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos) { if (this.networkState.sessionConfig.getGenerationMaxChunkRadius() > 0) { @@ -130,12 +169,13 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxGenerationRequestDistance() * 16; } @Override - protected boolean onBeforeRequest(long sectionPos, CompletableFuture future) + protected boolean onBeforeRequest(long sectionPos, CompletableFuture future) { - if (DhSectionPos.getDetailLevel(sectionPos) > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL - && !Config.Server.Experimental.enableNSizedGeneration.get()) + // split up large requests if N-sized gen isn't enabled + if (!Config.Server.Experimental.enableNSizedGeneration.get() + && DhSectionPos.getDetailLevel(sectionPos) > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) { - future.complete(ERequestResult.REQUIRES_SPLITTING); + future.complete(NetRequestResult.CreateSplit()); return false; } 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 a11d81798..2482689fd 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 @@ -26,11 +26,8 @@ import com.seibel.distanthorizons.api.objects.data.DhApiChunk; import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; -import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; -import com.seibel.distanthorizons.core.generation.tasks.InProgressWorldGenTaskGroup; -import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; -import com.seibel.distanthorizons.core.generation.tasks.WorldGenTask; -import com.seibel.distanthorizons.core.generation.tasks.WorldGenTaskGroup; +import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; +import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalTask; import com.seibel.distanthorizons.core.level.IDhServerLevel; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; @@ -52,14 +49,12 @@ import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; -import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.core.logging.DhLogger; import java.awt.*; import java.util.*; import java.util.List; import java.util.concurrent.*; -import java.util.function.Consumer; public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable { @@ -71,9 +66,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb private final IDhServerLevel level; /** contains the positions that need to be generated */ - private final ConcurrentHashMap waitingTasks = new ConcurrentHashMap<>(); - - private final ConcurrentHashMap inProgressGenTasksByLodPos = new ConcurrentHashMap<>(); + private final ConcurrentHashMap waitingTasks = new ConcurrentHashMap<>(); + private final ConcurrentHashMap inProgressGenTasksByLodPos = new ConcurrentHashMap<>(); /** largest numerical detail level allowed */ public final byte lowestDataDetail; @@ -102,9 +96,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb - //==============// - // constructors // - //==============// + //=============// + // constructor // + //=============// + ///region constructor public WorldGenerationQueue(IDhApiWorldGenerator generator, IDhServerLevel level) { @@ -118,20 +113,29 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb LOGGER.info("Created world gen queue"); } + ///endregion constructor - //=================// - // world generator // - // task handling // - //=================// + + //===============// + // task handling // + //===============// + ///region task handling @Override - public CompletableFuture submitRetrievalTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker) + public CompletableFuture submitRetrievalTask(long pos, byte requiredDataDetail) { // the generator is shutting down, don't add new tasks if (this.generatorClosingFuture != null) { - return CompletableFuture.completedFuture(WorldGenResult.CreateFail()); + return CompletableFuture.completedFuture(DataSourceRetrievalResult.CreateFail()); + } + + // use the existing task if present + DataSourceRetrievalTask existingGenTask = this.waitingTasks.get(pos); + if (existingGenTask != null) + { + return existingGenTask.future; } @@ -145,13 +149,12 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb requiredDataDetail = this.lowestDataDetail; } - // Assert that the data at least can fill in 1 single ChunkSizedFullDataAccessor + // the request should be at least chunk-sized LodUtil.assertTrue(DhSectionPos.getDetailLevel(pos) > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL); - - CompletableFuture future = new CompletableFuture<>(); - this.waitingTasks.put(pos, new WorldGenTask(pos, requiredDataDetail, tracker, future)); - return future; + DataSourceRetrievalTask genTask = new DataSourceRetrievalTask(pos, requiredDataDetail); + this.waitingTasks.put(pos, genTask); + return genTask.future; } @Override @@ -161,7 +164,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb { if (removeIf.accept(genPos)) { - WorldGenTask removedTask = this.waitingTasks.remove(genPos); + DataSourceRetrievalTask removedTask = this.waitingTasks.remove(genPos); if (removedTask != null) { // cancel tasks so any waiting future steps can be triggered @@ -171,6 +174,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb }); } + ///endregion task handling @@ -253,18 +257,27 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb } - + // find the closest task TaskDistancePair closestTaskPair = this.waitingTasks.reduceEntries(1024, - entry -> new TaskDistancePair(entry.getValue(), DhSectionPos.getSectionBBoxPos(entry.getValue().pos).getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())), - (TaskDistancePair aTaskPair, TaskDistancePair bTaskPair) -> (aTaskPair.dist < bTaskPair.dist) ? aTaskPair : bTaskPair); + // get the target distance for each task + (Map.Entry entry) -> + { + DataSourceRetrievalTask task = entry.getValue(); + int distance = DhSectionPos.getCenterBlockPos(task.pos).chebyshevDist(targetPos); + return new TaskDistancePair(entry.getValue(), distance); + }, + // find the closest task + (TaskDistancePair aTaskPair, TaskDistancePair bTaskPair) -> + { + return (aTaskPair.dist < bTaskPair.dist) ? aTaskPair : bTaskPair; + }); if (closestTaskPair == null) { - // FIXME concurrency issue + // the waitingTasks was modified while this check was running return false; } - - WorldGenTask closestTask = closestTaskPair.task; + DataSourceRetrievalTask closestTask = closestTaskPair.task; // remove the task we found, we are going to start it and don't want to run it multiple times this.waitingTasks.remove(closestTask.pos, closestTask); @@ -274,27 +287,29 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb { // detail level is correct for generation, start generation - WorldGenTaskGroup closestTaskGroup = new WorldGenTaskGroup(closestTask.pos, (byte)(closestTask.pos - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)); - closestTaskGroup.worldGenTasks.add(closestTask); - - if (!this.inProgressGenTasksByLodPos.containsKey(closestTask.pos)) + DataSourceRetrievalTask existingTask = this.inProgressGenTasksByLodPos.get(closestTask.pos); + if (existingTask == null) { // no task exists for this position, start one - InProgressWorldGenTaskGroup newTaskGroup = new InProgressWorldGenTaskGroup(closestTaskGroup); - this.startWorldGenTaskGroup(newTaskGroup); + this.startWorldGenTaskGroup(closestTask); } else { - // TODO replace the previous inProgress task if one exists - // Note: Due to concurrency reasons, even if the currently running task is compatible with - // the newly selected task, we cannot use it, - // as some chunks may have already been written into. + // shouldn't normally happen, but if + // we somehow queued the same task twice: + // merge the two futures so they both complete - //LOGGER.warn("A task already exists for this position, todo: "+DhSectionPos.toString(closestTask.pos)); + existingTask.future.thenApply((DataSourceRetrievalResult result)-> + { + closestTask.future.complete(result); + return closestTask.future; // return value ignored + }); + existingTask.future.exceptionally((Throwable throwable)-> + { + closestTask.future.completeExceptionally(throwable); + return null; // return value ignored + }); } - - // a task has been started - return true; } else { @@ -302,48 +317,50 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb // split up the task - // split up the task and add each one to the tree - LinkedList> childFutures = new LinkedList<>(); - long sectionPos = closestTask.pos; - WorldGenTask finalClosestTask = closestTask; - DhSectionPos.forEachChild(sectionPos, (childDhSectionPos) -> + // split up the task and add each to the queue + ArrayList> childFutures = new ArrayList<>(4); + DhSectionPos.forEachChild(closestTask.pos, (childDhSectionPos) -> { - CompletableFuture newFuture = new CompletableFuture<>(); - childFutures.add(newFuture); - - WorldGenTask newGenTask = new WorldGenTask(childDhSectionPos, DhSectionPos.getDetailLevel(childDhSectionPos), finalClosestTask.taskTracker, newFuture); + DataSourceRetrievalTask newGenTask = new DataSourceRetrievalTask(childDhSectionPos, DhSectionPos.getDetailLevel(childDhSectionPos)); + childFutures.add(newGenTask.future); this.waitingTasks.put(newGenTask.pos, newGenTask); }); // send the child futures to the future recipient, to notify them of the new tasks - closestTask.future.complete(WorldGenResult.CreateSplit(childFutures)); - - // return true so we attempt to generate again - return true; + closestTask.future.complete(DataSourceRetrievalResult.CreateSplit(childFutures)); } - } - private void startWorldGenTaskGroup(InProgressWorldGenTaskGroup newTaskGroup) - { - byte taskDetailLevel = newTaskGroup.group.dataDetail; - long taskPos = newTaskGroup.group.pos; - LodUtil.assertTrue(taskDetailLevel >= this.highestDataDetail && taskDetailLevel <= this.lowestDataDetail); - int generationRequestChunkWidthCount = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(taskPos) - taskDetailLevel - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale + + // a task has been started or queued + // queue another task + return true; + } + private boolean canGenerateDetailLevel(byte taskDetailLevel) + { + byte requestedDetailLevel = (byte) (taskDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); + return (this.highestDataDetail <= requestedDetailLevel && requestedDetailLevel <= this.lowestDataDetail); + } + private void startWorldGenTaskGroup(DataSourceRetrievalTask worldGenTask) + { + long taskPos = worldGenTask.pos; + LodUtil.assertTrue( + worldGenTask.requestDetailLevel >= this.highestDataDetail + && worldGenTask.requestDetailLevel <= this.lowestDataDetail, + "World gen task started that isn't within the range that the generator can create."); long generationStartMsTime = System.currentTimeMillis(); - CompletableFuture generationFuture = this.startGenerationEvent(taskPos, taskDetailLevel, generationRequestChunkWidthCount, newTaskGroup.group::consumeDataSource); + CompletableFuture generationFuture = this.startGenerationEvent(worldGenTask); + + // calculate generation speed generationFuture.thenRun(() -> { long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime; - int chunkCount = generationRequestChunkWidthCount * generationRequestChunkWidthCount; + int chunkCount = worldGenTask.widthInChunks * worldGenTask.widthInChunks; double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount; this.rollingAverageChunkGenTimeInMs.add(timePerChunk); }); - newTaskGroup.genFuture = generationFuture; - LodUtil.assertTrue(newTaskGroup.genFuture != null); - - newTaskGroup.genFuture.whenComplete((voidObj, exception) -> + generationFuture.handle((fullDataSourceV2, exception) -> { try { @@ -355,157 +372,48 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception); } - newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateFail())); + worldGenTask.future.complete(DataSourceRetrievalResult.CreateFail()); } - else - { - newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(taskPos))); - } - boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, newTaskGroup); + + boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, worldGenTask); LodUtil.assertTrue(worked, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]"); + + worldGenTask.future.complete(DataSourceRetrievalResult.CreateSuccess(taskPos, fullDataSourceV2)); } catch (Exception e) { LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e); + worldGenTask.future.completeExceptionally(e); } finally { this.tryQueueNewWorldGenRequestsAsync(); } + + return null; }); - - this.inProgressGenTasksByLodPos.put(taskPos, newTaskGroup); } - private CompletableFuture startGenerationEvent( - long requestPos, - byte targetDataDetail, - int generationRequestChunkWidthCount, - Consumer dataSourceConsumer - ) + private CompletableFuture startGenerationEvent(DataSourceRetrievalTask task) { - DhChunkPos chunkPosMin = new DhChunkPos(DhSectionPos.getSectionBBoxPos(requestPos).getCornerBlockPos()); + this.inProgressGenTasksByLodPos.put(task.pos, task); + + DhChunkPos chunkPosMin = new DhChunkPos(new DhBlockPos2D(DhSectionPos.getMinCornerBlockX(task.pos), DhSectionPos.getMinCornerBlockZ(task.pos))); EDhApiDistantGeneratorMode generatorMode = Config.Common.WorldGenerator.distantGeneratorMode.get(); EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType(); switch (returnType) { case VANILLA_CHUNKS: - { - return this.generator.generateChunks( - chunkPosMin.getX(), chunkPosMin.getZ(), - generationRequestChunkWidthCount, - targetDataDetail, - generatorMode, - ThreadPoolUtil.getWorldGenExecutor(), - (Object[] generatedObjectArray) -> - { - try - { - IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray); - - // 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)) - { - LodUtil.assertTrue(dataSource != null); - dataSourceConsumer.accept(dataSource); - } - } - catch (ClassCastException e) - { - LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e); - Config.Common.WorldGenerator.enableDistantGeneration.set(false); - } - catch (Exception e) - { - LOGGER.error("Unexpected world generator error. Error: [" + e.getMessage() + "]. World generator disabled.", e); - Config.Common.WorldGenerator.enableDistantGeneration.set(false); - } - } - ); + { + return this.startVanillaChunkGenerationEvent(task, chunkPosMin, generatorMode); } case API_CHUNKS: { - return this.generator.generateApiChunks( - chunkPosMin.getX(), chunkPosMin.getZ(), - generationRequestChunkWidthCount, - targetDataDetail, - generatorMode, - ThreadPoolUtil.getWorldGenExecutor(), - (DhApiChunk dataPoints) -> - { - try(FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints, this.generator.runApiValidation())) - { - dataSourceConsumer.accept(dataSource); - } - catch (DataCorruptedException | IllegalArgumentException e) - { - LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e); - Config.Common.WorldGenerator.enableDistantGeneration.set(false); - } - catch (ClassCastException e) - { - LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e); - Config.Common.WorldGenerator.enableDistantGeneration.set(false); - } - } - ); + return this.startApiChunkGenerationEvent(task, chunkPosMin, generatorMode); } case API_DATA_SOURCES: { - // done to reduce GC overhead - FullDataSourceV2 pooledDataSource = FullDataSourceV2.createEmpty(requestPos); - // set here so the API user doesn't have to pass in this value anywhere themselves - pooledDataSource.setRunApiChunkValidation(this.generator.runApiValidation()); - - // only apply to children if we aren't at the bottom of the tree - - pooledDataSource.applyToChildren = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL; - pooledDataSource.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12; - - - return this.generator.generateLod( - chunkPosMin.getX(), chunkPosMin.getZ(), - DhSectionPos.getX(requestPos), DhSectionPos.getZ(requestPos), - (byte) (DhSectionPos.getDetailLevel(requestPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), - pooledDataSource, - generatorMode, - ThreadPoolUtil.getWorldGenExecutor(), - (IDhApiFullDataSource apiDataSource) -> - { - try - { - FullDataSourceV2 fullDataSource = (FullDataSourceV2) apiDataSource; - try - { - dataSourceConsumer.accept(fullDataSource); - } - finally - { - fullDataSource.close(); - } - } - catch (IllegalArgumentException e) - { - LOGGER.error("World generator returned a corrupt data source. Error: [" + e.getMessage() + "]. World generator disabled.", e); - Config.Common.WorldGenerator.enableDistantGeneration.set(false); - } - catch (ClassCastException e) - { - LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e); - Config.Common.WorldGenerator.enableDistantGeneration.set(false); - } - } - ); + return this.startApiDataSourceGenerationEvent(task, chunkPosMin, generatorMode); } default: { @@ -514,30 +422,181 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb } } } + private CompletableFuture startVanillaChunkGenerationEvent( + DataSourceRetrievalTask task, DhChunkPos chunkPosMin, EDhApiDistantGeneratorMode generatorMode) + { + final CompletableFuture returnFuture = new CompletableFuture<>(); + + ArrayList generatedChunks = new ArrayList<>(task.widthInChunks * task.widthInChunks); + + CompletableFuture chunkGenFuture = this.generator.generateChunks( + chunkPosMin.getX(), chunkPosMin.getZ(), + task.widthInChunks, + task.requestDetailLevel, + generatorMode, + ThreadPoolUtil.getWorldGenExecutor(), + (Object[] generatedObjectArray) -> + { + try + { + IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray); + generatedChunks.add(chunkWrapper); + } + catch (ClassCastException e) + { + LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e); + Config.Common.WorldGenerator.enableDistantGeneration.set(false); + } + catch (Exception e) + { + LOGGER.error("Unexpected world generator error. Error: [" + e.getMessage() + "]. World generator disabled.", e); + Config.Common.WorldGenerator.enableDistantGeneration.set(false); + } + } + ); + + chunkGenFuture.exceptionally((throwable) -> + { + returnFuture.completeExceptionally(throwable); + return null; + }); + chunkGenFuture.thenRun(() -> + { + FullDataSourceV2 requestedDataSource = FullDataSourceV2.createEmpty(task.pos); + + // process chunks // + for (int i = 0; i < generatedChunks.size(); i++) + { + IChunkWrapper chunkWrapper = generatedChunks.get(i); + + // only light the chunk here if necessary, + // lighting before this point is preferred but for legacy API use 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 generatedDataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper)) + { + LodUtil.assertTrue(generatedDataSource != null); + requestedDataSource.updateFromDataSource(generatedDataSource); + } + } + + DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(requestedDataSource, LodUtil.MAX_MC_LIGHT); + returnFuture.complete(requestedDataSource); + }); + + return returnFuture; + } + private CompletableFuture startApiChunkGenerationEvent( + DataSourceRetrievalTask task, DhChunkPos chunkPosMin, EDhApiDistantGeneratorMode generatorMode) + { + final CompletableFuture returnFuture = new CompletableFuture<>(); + + ArrayList generatedChunks = new ArrayList<>(task.widthInChunks * task.widthInChunks); + + CompletableFuture chunkGenFuture = this.generator.generateApiChunks( + chunkPosMin.getX(), chunkPosMin.getZ(), + task.widthInChunks, + task.requestDetailLevel, + generatorMode, + ThreadPoolUtil.getWorldGenExecutor(), + (DhApiChunk apiChunk) -> { generatedChunks.add(apiChunk); } + ); + + + chunkGenFuture.exceptionally((throwable) -> + { + returnFuture.completeExceptionally(throwable); + return null; + }); + chunkGenFuture.thenRun(() -> + { + FullDataSourceV2 requestedDataSource = FullDataSourceV2.createEmpty(task.pos); + + for (int i = 0; i < generatedChunks.size(); i++) + { + DhApiChunk apiChunk = generatedChunks.get(i); + + try(FullDataSourceV2 generatedDataSource = LodDataBuilder.createFromApiChunkData(apiChunk, this.generator.runApiValidation())) + { + requestedDataSource.updateFromDataSource(generatedDataSource); + } + catch (DataCorruptedException | IllegalArgumentException e) + { + LOGGER.error("World generator returned a corrupt API chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e); + Config.Common.WorldGenerator.enableDistantGeneration.set(false); + } + } + + returnFuture.complete(requestedDataSource); + }); + + return returnFuture; + } + private CompletableFuture startApiDataSourceGenerationEvent( + DataSourceRetrievalTask task, DhChunkPos chunkPosMin, EDhApiDistantGeneratorMode generatorMode) + { + final CompletableFuture returnFuture = new CompletableFuture<>(); + + + // done to reduce GC overhead + FullDataSourceV2 pooledDataSource = FullDataSourceV2.createEmpty(task.pos); + // set here so the API user doesn't have to pass in this value anywhere themselves + pooledDataSource.setRunApiSetterValidation(this.generator.runApiValidation()); + + // only apply to children if we aren't at the bottom of the tree + pooledDataSource.applyToChildren = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL; + pooledDataSource.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12; // TODO what does this 12 reference? + + CompletableFuture lodGenFuture = this.generator.generateLod( + chunkPosMin.getX(), chunkPosMin.getZ(), + DhSectionPos.getX(task.pos), DhSectionPos.getZ(task.pos), + (byte) (DhSectionPos.getDetailLevel(task.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), + pooledDataSource, + generatorMode, + ThreadPoolUtil.getWorldGenExecutor(), + (IDhApiFullDataSource apiDataSource) -> { } + ); + + + lodGenFuture.exceptionally((throwable) -> + { + returnFuture.completeExceptionally(throwable); + pooledDataSource.close(); + return null; + }); + lodGenFuture.thenRun(() -> + { + returnFuture.complete(pooledDataSource); + }); + + return returnFuture; + } //===================// // getters / setters // //===================// + ///region getters/setters @Override public int getWaitingTaskCount() { return this.waitingTasks.size(); } @Override public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); } - @Override - public byte lowestDataDetail() { return this.lowestDataDetail; } - @Override - public byte highestDataDetail() { return this.highestDataDetail; } + @Override public byte lowestDataDetail() { return this.lowestDataDetail; } + @Override public byte highestDataDetail() { return this.highestDataDetail; } - @Override - public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; } - @Override - public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; } + @Override public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; } + @Override public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; } - @Override - public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; } - @Override - public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; } + @Override public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; } + @Override public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; } @Override public void addDebugMenuStringsToList(List messageList) { } @@ -555,13 +614,54 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb return chunkCount; } + ///endregion getters/setters + + + + //=======// + // debug // + //=======// + ///region debug + + @Override + public void debugRender(DebugRenderer renderer) + { + int levelMinY = this.level.getLevelWrapper().getMinHeight(); + int levelMaxY = this.level.getLevelWrapper().getMaxHeight(); + + // show the wireframe a bit lower than world max height, + // since most worlds don't render all the way up to the max height + int levelHeightRange = (levelMaxY - levelMinY); + int maxY = levelMaxY - (levelHeightRange / 2); + + + // blue - queued + this.waitingTasks.keySet().forEach((Long pos) -> + { + renderer.renderBox( + new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue) + ); + }); + + // red - in progress + this.inProgressGenTasksByLodPos.forEach((Long pos, DataSourceRetrievalTask task) -> + { + renderer.renderBox( + new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red) + ); + }); + } + + ///endregion debug + //==========// // shutdown // //==========// + ///region shutdown - @Override + @Override public CompletableFuture startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning) { LOGGER.info("Closing world gen queue"); @@ -570,33 +670,31 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb // stop and remove any in progress tasks ArrayList> inProgressTasksCancelingFutures = new ArrayList<>(this.inProgressGenTasksByLodPos.size()); - this.inProgressGenTasksByLodPos.values().forEach(runningTaskGroup -> + this.inProgressGenTasksByLodPos.values().forEach((DataSourceRetrievalTask genTask) -> { - CompletableFuture genFuture = runningTaskGroup.genFuture; // Do this to prevent it getting swapped out - if (genFuture == null) - { - // genFuture's shouldn't be null, but sometimes they are... - LOGGER.info("Null gen future: "+runningTaskGroup.group.pos); - return; - } - + CompletableFuture genFuture = genTask.future; if (cancelCurrentGeneration) { genFuture.cancel(alsoInterruptRunning); } - inProgressTasksCancelingFutures.add(genFuture.handle((voidObj, exception) -> + inProgressTasksCancelingFutures.add(genFuture.handle((DataSourceRetrievalResult result, Throwable throwable) -> { - if (exception instanceof CompletionException) + if (throwable instanceof CompletionException) { - exception = exception.getCause(); + throwable = throwable.getCause(); } - if (!UncheckedInterruptedException.isInterrupt(exception) - && !(exception instanceof CancellationException)) + if (!UncheckedInterruptedException.isInterrupt(throwable) + && !(throwable instanceof CancellationException)) { - LOGGER.error("Error when terminating data generation for pos: ["+DhSectionPos.toString(runningTaskGroup.group.pos)+"], error: ["+exception.getMessage()+"].", exception); + LOGGER.error("Error when terminating data generation for pos: ["+DhSectionPos.toString(genTask.pos)+"], error: ["+throwable.getMessage()+"].", throwable); + } + + if (result.generatedDataSource != null) + { + result.generatedDataSource.close(); } return null; @@ -629,7 +727,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb LOGGER.info("World generator thread pool shutdown with [" + queueSize + "] incomplete tasks."); } - this.inProgressGenTasksByLodPos.values().forEach((inProgressWorldGenTaskGroup) -> inProgressWorldGenTaskGroup.genFuture.cancel(true)); + this.inProgressGenTasksByLodPos.values().forEach((inProgressWorldGenTaskGroup) -> inProgressWorldGenTaskGroup.future.cancel(true)); this.waitingTasks.values().forEach((worldGenTask) -> worldGenTask.future.cancel(true)); @@ -650,63 +748,22 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb LOGGER.info("Finished closing " + WorldGenerationQueue.class.getSimpleName()); } - - - //=======// - // debug // - //=======// - - @Override - public void debugRender(DebugRenderer renderer) - { - int levelMinY = this.level.getLevelWrapper().getMinHeight(); - int levelMaxY = this.level.getLevelWrapper().getMaxHeight(); - - // show the wireframe a bit lower than world max height, - // since most worlds don't render all the way up to the max height - int levelHeightRange = (levelMaxY - levelMinY); - int maxY = levelMaxY - (levelHeightRange / 2); - - - // blue - queued - this.waitingTasks.keySet().forEach((pos) -> - { - renderer.renderBox( - new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue)); - }); - - // red - in progress - this.inProgressGenTasksByLodPos.forEach((pos, t) -> - { - renderer.renderBox( - new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red)); - }); - } - - - - //================// - // helper methods // - //================// - - private boolean canGenerateDetailLevel(byte taskDetailLevel) - { - byte requestedDetailLevel = (byte) (taskDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); - return (this.highestDataDetail <= requestedDetailLevel && requestedDetailLevel <= this.lowestDataDetail); - } + ///endregion shutdown //================// // helper classes // //================// + ///region helper classes + /** Used during task starting to determine the closest task */ private static class TaskDistancePair { - public final WorldGenTask task; + public final DataSourceRetrievalTask task; public final int dist; - public TaskDistancePair(WorldGenTask task, int dist) + public TaskDistancePair(DataSourceRetrievalTask task, int dist) { this.task = task; this.dist = dist; @@ -714,4 +771,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb } + ///endregion helper classes + + + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenResult.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/DataSourceRetrievalResult.java similarity index 51% rename from core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenResult.java rename to core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/DataSourceRetrievalResult.java index dd30acd72..4fd7c3b97 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenResult.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/DataSourceRetrievalResult.java @@ -19,27 +19,41 @@ package com.seibel.distanthorizons.core.generation.tasks; -import java.util.Collection; -import java.util.LinkedList; +import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; import java.util.concurrent.CompletableFuture; -public class WorldGenResult +/** + * @see DataSourceRetrievalTask + */ +public class DataSourceRetrievalResult { /** true if terrain was generated */ - public final boolean success; + public final boolean success; // TODO reponse enum? /** the position that was generated, will be null if nothing was generated */ public final long pos; + @Nullable + public final FullDataSourceV2 generatedDataSource; + /** if a position is too high detail for world generator to handle it, these futures are for its 4 children positions after being split up. */ - public final LinkedList> childFutures = new LinkedList<>(); + public final ArrayList> childFutures = new ArrayList<>(4); - public static WorldGenResult CreateSplit(Collection> siblingFutures) { return new WorldGenResult(false, 0, siblingFutures); } - public static WorldGenResult CreateFail() { return new WorldGenResult(false, 0, null); } - public static WorldGenResult CreateSuccess(long pos) { return new WorldGenResult(true, pos, null); } - private WorldGenResult(boolean success, long pos, Collection> childFutures) + + //==============// + // constructors // + //==============// + + public static DataSourceRetrievalResult CreateSplit(ArrayList> siblingFutures) { return new DataSourceRetrievalResult(false, 0, null, siblingFutures); } + public static DataSourceRetrievalResult CreateFail() { return new DataSourceRetrievalResult(false, 0, null,null); } + public static DataSourceRetrievalResult CreateSuccess(long pos, FullDataSourceV2 generatedDataSource) { return new DataSourceRetrievalResult(true, pos, generatedDataSource, null); } + private DataSourceRetrievalResult(boolean success, long pos, @Nullable FullDataSourceV2 generatedDataSource, ArrayList> childFutures) { this.success = success; this.pos = pos; + this.generatedDataSource = generatedDataSource; if (childFutures != null) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenTask.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/DataSourceRetrievalTask.java similarity index 53% rename from core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenTask.java rename to core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/DataSourceRetrievalTask.java index 3442cf40f..720aec944 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenTask.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/DataSourceRetrievalTask.java @@ -19,29 +19,37 @@ package com.seibel.distanthorizons.core.generation.tasks; -import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider; +import com.seibel.distanthorizons.core.pos.DhSectionPos; +import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import java.util.concurrent.CompletableFuture; /** - * @author Leetom - * @version 2022-11-25 + * @see DataSourceRetrievalResult */ -public final class WorldGenTask +public final class DataSourceRetrievalTask { public final long pos; - public final byte dataDetailLevel; - public final IWorldGenTaskTracker taskTracker; - public final CompletableFuture future; + /** + * Usually the same as {@link DataSourceRetrievalTask#pos}, but + * can differ if the task needs something different. + */ + public final byte requestDetailLevel; + public final int widthInChunks; + + public final CompletableFuture future = new CompletableFuture<>(); - public WorldGenTask(long pos, byte dataDetail, IWorldGenTaskTracker taskTracker, CompletableFuture future) + //=============// + // constructor // + //=============// + + public DataSourceRetrievalTask(long pos, byte dataDetail) { - this.dataDetailLevel = dataDetail; this.pos = pos; - this.taskTracker = taskTracker; - this.future = future; + this.requestDetailLevel = dataDetail; + this.widthInChunks = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(this.pos) - this.requestDetailLevel - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/InProgressWorldGenTaskGroup.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/InProgressWorldGenTaskGroup.java deleted file mode 100644 index eca34aeb0..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/InProgressWorldGenTaskGroup.java +++ /dev/null @@ -1,39 +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.core.generation.tasks; - -import java.util.concurrent.CompletableFuture; - -/** - * @author Leetom - * @version 2022-11-25 - */ -public final class InProgressWorldGenTaskGroup -{ - public final WorldGenTaskGroup group; - public CompletableFuture genFuture = null; - - - public InProgressWorldGenTaskGroup(WorldGenTaskGroup group) - { - this.group = group; - } - -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenTaskGroup.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenTaskGroup.java deleted file mode 100644 index da71c7915..000000000 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/WorldGenTaskGroup.java +++ /dev/null @@ -1,67 +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.core.generation.tasks; - -import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; - -import java.util.Iterator; -import java.util.LinkedList; -import java.util.function.Consumer; - -/** - * @author Leetom - * @version 2022-11-25 - */ -@Deprecated // TODO look into how these are used and if they should continue to be used -public final class WorldGenTaskGroup -{ - public final long pos; - public byte dataDetail; - /** Only accessed by the generator polling thread */ - public final LinkedList worldGenTasks = new LinkedList<>(); - - - - public WorldGenTaskGroup(long pos, byte dataDetail) - { - this.pos = pos; - this.dataDetail = dataDetail; - } - - public void consumeDataSource(FullDataSourceV2 dataSource) - { - Iterator tasks = this.worldGenTasks.iterator(); - while (tasks.hasNext()) - { - WorldGenTask task = tasks.next(); - Consumer dataSourceConsumer = task.taskTracker.getDataSourceConsumer(); - if (dataSourceConsumer == null) - { - tasks.remove(); - task.future.complete(WorldGenResult.CreateFail()); - } - else - { - dataSourceConsumer.accept(dataSource); - } - } - } - -} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java index 9f0ea5c1c..4720fd552 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhServerLevel.java @@ -102,13 +102,12 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I { return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); } @Override - @Nullable public DhBlockPos2D getTargetPosForGeneration() { IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek(); if (firstPlayer == null) { - return null; + return DhBlockPos2D.ZERO; } // Put first player in back before removing from front, so it can be removed by other thread without blocking diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 307cb8e89..101f81f57 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -247,7 +247,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel } @Override - @Nullable public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); } @@ -372,9 +371,9 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel private static class LodRequestState extends LodRequestModule.AbstractLodRequestState { - LodRequestState(DhClientLevel level, ClientNetworkState networkState) + LodRequestState(DhClientLevel clientLevel, ClientNetworkState networkState) { - this.retrievalQueue = new RemoteWorldRetrievalQueue(networkState, level); + this.retrievalQueue = new RemoteWorldRetrievalQueue(networkState, clientLevel); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index 3f0b77112..dab89fdab 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -56,7 +56,7 @@ public class DhServerLevel extends AbstractDhServerLevel return true; //todo; } @Override - public @Nullable DhBlockPos2D getTargetPosForGeneration() + public DhBlockPos2D getTargetPosForGeneration() { DhBlockPos2D targetPos = super.getTargetPosForGeneration(); if (targetPos == null) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java index dc69d2627..8167ba73c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/AbstractFullDataNetworkRequestQueue.java @@ -23,7 +23,6 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter; -import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; @@ -34,7 +33,6 @@ import java.util.*; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRenderable, AutoCloseable { @@ -57,7 +55,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende private volatile CompletableFuture closingFuture = null; - protected final ConcurrentMap waitingTasksBySectionPos = new ConcurrentHashMap<>(); + protected final ConcurrentMap waitingTasksBySectionPos = new ConcurrentHashMap<>(); /** * This semaphore prevents a given thread from accidentally locking on the same group * multiple times, as the semaphore is tied to the given thread.
@@ -107,8 +105,8 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende //==================// protected abstract int getRequestRateLimit(); - protected abstract boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos); - protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture future); + protected abstract boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos); + protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture future); protected abstract String getQueueName(); @@ -118,44 +116,44 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende // request submitting // //====================// - public CompletableFuture submitRequest(long sectionPos, @Nullable Long clientTimestamp, Consumer dataSourceConsumer) + public CompletableFuture submitRequest(long sectionPos, @Nullable Long clientTimestamp) { if (this.succeededPositions.contains(sectionPos)) { - return CompletableFuture.completedFuture(ERequestResult.FAILED); + return CompletableFuture.completedFuture(NetRequestResult.CreateFail()); } if (this.requiresSplittingPositions.contains(sectionPos)) { - return CompletableFuture.completedFuture(ERequestResult.REQUIRES_SPLITTING); + return CompletableFuture.completedFuture(NetRequestResult.CreateSplit()); } - RequestQueueEntry entry = this.waitingTasksBySectionPos.compute(sectionPos, (pos, existingQueueEntry) -> + NetRequestTask requestEntry = this.waitingTasksBySectionPos.compute(sectionPos, (pos, existingNetTask) -> { // ignore already queued tasks - if (existingQueueEntry != null) + if (existingNetTask != null) { - return existingQueueEntry; + return existingNetTask; } - RequestQueueEntry newEntry = new RequestQueueEntry(dataSourceConsumer, clientTimestamp); - newEntry.future.whenComplete((requestResult, throwable) -> + NetRequestTask newRequestEntry = new NetRequestTask(pos, clientTimestamp); + newRequestEntry.future.whenComplete((requestResult, throwable) -> { - this.waitingTasksBySectionPos.remove(sectionPos); + this.waitingTasksBySectionPos.remove(pos); - switch (requestResult) + switch (requestResult.state) { - case SUCCEEDED: + case SUCCESS: this.finishedRequests.incrementAndGet(); this.succeededPositions.add(pos); - return; + break; case REQUIRES_SPLITTING: - this.requiresSplittingPositions.add(sectionPos); - return; - case FAILED: + this.requiresSplittingPositions.add(pos); + break; + case FAIL: this.failedRequests.incrementAndGet(); - return; + break; default: if (throwable != null && !(throwable instanceof CancellationException)) { @@ -165,20 +163,22 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende } }); - return newEntry; + return newRequestEntry; }); - return entry.future; + return requestEntry.future; } public synchronized boolean tick(DhBlockPos2D targetPos) { - if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly()) + if (DhApiWorldProxy.INSTANCE.worldLoaded() + && DhApiWorldProxy.INSTANCE.getReadOnly()) { return false; } - if (this.closingFuture != null || !this.networkState.isReady()) + if (this.closingFuture != null + || !this.networkState.isReady()) { return false; } @@ -201,146 +201,125 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende } private void sendNextRequest(DhBlockPos2D targetPos) { - Map.Entry mapEntry = this.waitingTasksBySectionPos.entrySet().stream() - .filter(task -> task.getValue().networkDataSourceFuture == null) - .min(Comparator.comparingInt(x -> DhSectionPos.getChebyshevSignedBlockDistance(x.getKey(), targetPos))) - .orElse(null); + Map.Entry nearestMapEntry = this.waitingTasksBySectionPos + .entrySet().stream() + .filter(task -> task.getValue().networkDataSourceFuture == null) + .min(Comparator.comparingInt(mapEntry -> DhSectionPos.getChebyshevSignedBlockDistance(mapEntry.getKey(), targetPos))) + .orElse(null); - if (mapEntry == null) + if (nearestMapEntry == null) { this.pendingTasksSemaphore.release(); return; } - long sectionPos = mapEntry.getKey(); - RequestQueueEntry requestEntry = mapEntry.getValue(); + long requestPos = nearestMapEntry.getKey(); + NetRequestTask requestTask = nearestMapEntry.getValue(); - if (!this.isSectionAllowedToGenerate(sectionPos, targetPos)) + if (!this.sectionInAllowedGenerationRadius(requestPos, targetPos)) { - requestEntry.future.cancel(false); + requestTask.future.cancel(false); this.pendingTasksSemaphore.release(); return; } - if (!this.onBeforeRequest(sectionPos, requestEntry.future)) + if (!this.onBeforeRequest(requestPos, requestTask.future)) { this.pendingTasksSemaphore.release(); return; } - Long offsetEntryTimestamp = requestEntry.updateTimestamp != null - ? requestEntry.updateTimestamp + this.networkState.getServerTimeOffset() + Long offsetEntryTimestamp = requestTask.updateTimestamp != null + ? requestTask.updateTimestamp + this.networkState.getServerTimeOffset() : null; CompletableFuture dataSourceNetworkFuture = this.networkState.getSession().sendRequest( - new FullDataSourceRequestMessage(this.level.getLevelWrapper(), sectionPos, offsetEntryTimestamp), + new FullDataSourceRequestMessage(this.level.getLevelWrapper(), requestPos, offsetEntryTimestamp), FullDataSourceResponseMessage.class ); - requestEntry.networkDataSourceFuture = dataSourceNetworkFuture; - dataSourceNetworkFuture.handle((response, throwable) -> + requestTask.networkDataSourceFuture = dataSourceNetworkFuture; + dataSourceNetworkFuture.handle((FullDataSourceResponseMessage response, Throwable throwable) -> { - this.pendingTasksSemaphore.release(); - - try - { - if (throwable != null) - { - throw throwable; - } - - if (response.payload != null) - { - FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(response.payload); - - // set application flags based on the received detail level, - // this is needed so the data sources propagate correctly - dataSourceDto.applyToChildren = DhSectionPos.getDetailLevel(dataSourceDto.pos) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL; - dataSourceDto.applyToParent = DhSectionPos.getDetailLevel(dataSourceDto.pos) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12; - - // TODO what thread is this currently running on? Does saving need to be run async? - AbstractExecutorService executor = ThreadPoolUtil.getNetworkCompressionExecutor(); - if (executor == null) - { - LOGGER.warn("Unable to handle FullDataPayload - getNetworkCompressionExecutor() is null"); - dataSourceDto.close(); - return null; - } - - CompletableFuture.runAsync(() -> - { - try - { - this.level.updateBeaconBeamsForSectionPos(dataSourceDto.pos, response.payload.beaconBeams); - - FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper(), null); - requestEntry.dataSourceConsumer.accept(fullDataSource); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - finally - { - dataSourceDto.close(); - } - }, executor); - } - else - { - LodUtil.assertTrue(this.changedOnly, "Received empty data source response for not changes-only request"); - } - } - catch (SectionRequiresSplittingException ignored) - { - return requestEntry.future.complete(ERequestResult.REQUIRES_SPLITTING); - } - catch (SessionClosedException | CancellationException ignored) - { - return requestEntry.future.cancel(false); - } - catch (RequestRejectedException e) - { - LOGGER.info("Request rejected by the server: " + e.getMessage()); - return requestEntry.future.complete(ERequestResult.FAILED); - } - catch (RateLimitedException e) - { - LOGGER.info("Rate limited by server, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage()); - - // Skip all requests for 1 second - this.rateLimiter.acquireAll(); - - requestEntry.networkDataSourceFuture = null; - return null; - } - catch (RequestOutOfRangeException e) - { - LOGGER.debug("Out of range, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage()); - - requestEntry.networkDataSourceFuture = null; - return null; - } - catch (Throwable e) - { - requestEntry.retryAttempts--; - LOGGER.error("Unexpected error ["+e.getMessage()+"] while fetching full data source, attempts left: ["+requestEntry.retryAttempts+"] / ["+MAX_RETRY_ATTEMPTS+"]", e); - - // Retry logic - if (requestEntry.retryAttempts > 0) - { - requestEntry.networkDataSourceFuture = null; - return null; - } - else - { - return requestEntry.future.complete(ERequestResult.FAILED); - } - } - - return requestEntry.future.complete(ERequestResult.SUCCEEDED); + this.handleNetResponse(requestTask, response, throwable); + return null; }); } - + private void handleNetResponse(NetRequestTask requestTask, FullDataSourceResponseMessage response, Throwable throwable) + { + this.pendingTasksSemaphore.release(); + + try + { + if (throwable != null) + { + throw throwable; + } + + if (response.payload == null) + { + LodUtil.assertTrue(this.changedOnly, "Received empty data source response for not changes-only request"); + return; + } + + + try(FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(response.payload)) + { + // set application flags based on the received detail level, + // this is needed so the data sources propagate correctly + dataSourceDto.applyToChildren = DhSectionPos.getDetailLevel(dataSourceDto.pos) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL; + dataSourceDto.applyToParent = DhSectionPos.getDetailLevel(dataSourceDto.pos) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12; + + + this.level.updateBeaconBeamsForSectionPos(dataSourceDto.pos, response.payload.beaconBeams); + + FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper(), null); + requestTask.future.complete(NetRequestResult.CreateSuccess(fullDataSource)); + } + } + catch (SectionRequiresSplittingException ignored) + { + requestTask.future.complete(NetRequestResult.CreateSplit()); + } + catch (SessionClosedException | CancellationException ignored) + { + requestTask.future.cancel(false); + } + catch (RequestRejectedException e) + { + LOGGER.info("Request rejected by the server, message: [" + e.getMessage() + "]."); + requestTask.future.complete(NetRequestResult.CreateFail()); + } + catch (RateLimitedException e) + { + LOGGER.info("Rate limited by server, re-queueing task [" + DhSectionPos.toString(requestTask.pos) + "], message: [" + e.getMessage() + "]."); + + // Skip all requests for 1 second + this.rateLimiter.acquireAll(); + + requestTask.networkDataSourceFuture = null; + } + catch (RequestOutOfRangeException e) + { + LOGGER.debug("Out of range, re-queueing task [" + DhSectionPos.toString(requestTask.pos) + "], message: [" + e.getMessage() + "]."); + + requestTask.networkDataSourceFuture = null; + } + catch (Throwable e) + { + requestTask.retryAttempts--; + LOGGER.error("Unexpected error: ["+e.getMessage()+"] while fetching full data source, attempts left: ["+requestTask.retryAttempts+"] / ["+MAX_RETRY_ATTEMPTS+"]", e); + + // Retry logic + if (requestTask.retryAttempts > 0) + { + requestTask.networkDataSourceFuture = null; + } + else + { + requestTask.future.complete(NetRequestResult.CreateFail()); + } + } + } @@ -350,12 +329,22 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { - for (Map.Entry mapEntry : (Iterable>) this.waitingTasksBySectionPos.entrySet().stream() - .sorted(Comparator.comparingInt((Map.Entry entry) -> DhSectionPos.getChebyshevSignedBlockDistance(entry.getKey(), Objects.requireNonNull(this.level.getTargetPosForGeneration()))).reversed()) - ::iterator) + // remove tasks furthest + Iterator> farestTaskIterator = this.waitingTasksBySectionPos + .entrySet().stream() + .sorted(Comparator.comparingInt((Map.Entry entry) -> + { + Long pos = entry.getKey(); + DhBlockPos2D targetPos = this.level.getTargetPosForGeneration(); + return DhSectionPos.getChebyshevSignedBlockDistance(pos, targetPos); + }).reversed()) + .iterator(); + + while (farestTaskIterator.hasNext()) { + Map.Entry mapEntry = farestTaskIterator.next(); long pos = mapEntry.getKey(); - RequestQueueEntry entry = mapEntry.getValue(); + NetRequestTask entry = mapEntry.getValue(); if (removeIf.accept(pos)) { @@ -391,7 +380,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende do { - for (RequestQueueEntry entry : this.waitingTasksBySectionPos.values()) + for (NetRequestTask entry : this.waitingTasksBySectionPos.values()) { entry.future.cancel(alsoInterruptRunning); if (entry.networkDataSourceFuture != null && entry.networkDataSourceFuture.cancel(alsoInterruptRunning)) @@ -429,13 +418,31 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende return; } - for (Map.Entry mapEntry : this.waitingTasksBySectionPos.entrySet()) + DhBlockPos2D targetPos = this.level.getTargetPosForGeneration(); + for (Map.Entry mapEntry : this.waitingTasksBySectionPos.entrySet()) { - renderer.renderBox(new DebugRenderer.Box(mapEntry.getKey(), -32f, 64f, 0.05f, - mapEntry.getValue().networkDataSourceFuture != null ? Color.red - : this.isSectionAllowedToGenerate(mapEntry.getKey(), Objects.requireNonNull(this.level.getTargetPosForGeneration())) ? Color.gray - : Color.darkGray - )); + long pos = mapEntry.getKey(); + NetRequestTask task = mapEntry.getValue(); + + Color color; + if (task.networkDataSourceFuture != null) + { + color = Color.RED; + } + else + { + boolean taskInAllowedGenRadius = this.sectionInAllowedGenerationRadius(pos, targetPos); + if (taskInAllowedGenRadius) + { + color = Color.GRAY; + } + else + { + color = Color.DARK_GRAY; + } + } + + renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, color)); } } @@ -445,11 +452,12 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende // helper classes // //================// - protected static class RequestQueueEntry + protected static class NetRequestTask { + public final long pos; + /** encapsulates the entire request, including client side queuing and the actual server request */ - public final CompletableFuture future = new CompletableFuture<>(); - public final Consumer dataSourceConsumer; + public final CompletableFuture future = new CompletableFuture<>(); /** will be null if we want to retrieve the LOD regardless of when it was last updated */ @Nullable public final Long updateTimestamp; @@ -468,23 +476,14 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende // constructor // //=============// - public RequestQueueEntry( - Consumer dataSourceConsumer, - @Nullable Long updateTimestamp) + public NetRequestTask(long pos, @Nullable Long updateTimestamp) { - this.dataSourceConsumer = dataSourceConsumer; + this.pos = pos; this.updateTimestamp = updateTimestamp; } } - public enum ERequestResult - { - SUCCEEDED, - REQUIRES_SPLITTING, - FAILED, - } - } \ No newline at end of file diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java index 3d51b2e48..bdcf5e8f2 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java @@ -129,6 +129,7 @@ public class ClientNetworkState implements Closeable { this.serverSupportStatus = EServerSupportStatus.FULL; + // TODO only log changes LOGGER.info("Connection config has been changed: [" + message.config + "]."); this.sessionConfig = message.config; this.configReceived = true; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ENetRequestState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ENetRequestState.java new file mode 100644 index 000000000..1f5f15572 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ENetRequestState.java @@ -0,0 +1,15 @@ +package com.seibel.distanthorizons.core.multiplayer.client; + +/** + * SUCCESS
+ * REQUIRES_SPLITTING
+ * FAIL
+ * + * @see NetRequestResult + */ +public enum ENetRequestState +{ + SUCCESS, + REQUIRES_SPLITTING, + FAIL, +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/IWorldGenTaskTracker.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/NetRequestResult.java similarity index 50% rename from core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/IWorldGenTaskTracker.java rename to core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/NetRequestResult.java index cf962e521..b0807d232 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/tasks/IWorldGenTaskTracker.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/NetRequestResult.java @@ -17,22 +17,31 @@ * along with this program. If not, see . */ -package com.seibel.distanthorizons.core.generation.tasks; +package com.seibel.distanthorizons.core.multiplayer.client; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -/** - * @author Leetom - * @version 2022-11-25 - */ -public interface IWorldGenTaskTracker +public class NetRequestResult { - Consumer getDataSourceConsumer(); + public final ENetRequestState state; + @Nullable + public final FullDataSourceV2 receivedDataSource; + + + + //==============// + // constructors // + //==============// + + public static NetRequestResult CreateFail() { return new NetRequestResult(ENetRequestState.FAIL, null); } + public static NetRequestResult CreateSuccess(FullDataSourceV2 receivedDataSource) { return new NetRequestResult(ENetRequestState.SUCCESS, receivedDataSource); } + public static NetRequestResult CreateSplit() { return new NetRequestResult(ENetRequestState.REQUIRES_SPLITTING, null); } + private NetRequestResult(ENetRequestState state, @Nullable FullDataSourceV2 receivedDataSource) + { + this.state = state; + this.receivedDataSource = receivedDataSource; + } - CompletableFuture shouldGenerateSplitChild(long pos); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/SyncOnLoadRequestQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/SyncOnLoadRequestQueue.java index fa46a502a..324968e5c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/SyncOnLoadRequestQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/SyncOnLoadRequestQueue.java @@ -35,12 +35,12 @@ public class SyncOnLoadRequestQueue extends AbstractFullDataNetworkRequestQueue @Override protected int getRequestRateLimit() { return this.networkState.sessionConfig.getSyncOnLoginRateLimit(); } @Override - protected boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos) + protected boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos) { return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxSyncOnLoadDistance() * 16; } @Override - protected boolean onBeforeRequest(long sectionPos, CompletableFuture future) { return true; } + protected boolean onBeforeRequest(long sectionPos, CompletableFuture future) { return true; } @Override protected String getQueueName() { return "Sync On Login Queue"; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadReceiver.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadReceiver.java index e80eff418..2b767bcd5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadReceiver.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadReceiver.java @@ -55,12 +55,11 @@ public class FullDataPayloadReceiver implements AutoCloseable public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload) { CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId); - LodUtil.assertTrue(compositeByteBuffer != null); + LodUtil.assertTrue(compositeByteBuffer != null, "decoded data source missing byte buffer"); try { FullDataSourceV2DTO dataSourceDto = INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer); - LOGGER.debug("Buffer {} DTO: {}", payload.dtoBufferId, dataSourceDto); return dataSourceDto; } finally diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/FullDataSourceRequestHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/FullDataSourceRequestHandler.java index 2ea17da30..f47250b70 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/FullDataSourceRequestHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/FullDataSourceRequestHandler.java @@ -231,7 +231,7 @@ public class FullDataSourceRequestHandler implements AutoCloseable { this.fullDataSourceProvider().getAsync(pos).thenAccept(fullDataSource -> { - if (this.fullDataSourceProvider().isFullyGenerated(fullDataSource.columnGenerationSteps)) + if (this.fullDataSourceProvider().generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps)) { requestGroup.fullDataSource = fullDataSource; return; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/pooling/AbstractPhantomArrayList.java b/core/src/main/java/com/seibel/distanthorizons/core/pooling/AbstractPhantomArrayList.java index 304a0c7b8..31e724299 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/pooling/AbstractPhantomArrayList.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/pooling/AbstractPhantomArrayList.java @@ -40,7 +40,9 @@ public abstract class AbstractPhantomArrayList implements AutoCloseable /** The Array counts can be 0 or greater. */ public AbstractPhantomArrayList(PhantomArrayListPool phantomArrayListPool, int byteArrayCount, int shortArrayCount, int longArrayCount) { - if (byteArrayCount < 0 || shortArrayCount < 0 || longArrayCount < 0) + if (byteArrayCount < 0 + || shortArrayCount < 0 + || longArrayCount < 0) { throw new IllegalArgumentException("Can't get a negative number of pooled arrays."); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/pos/blockPos/DhBlockPos2D.java b/core/src/main/java/com/seibel/distanthorizons/core/pos/blockPos/DhBlockPos2D.java index 32b4b0bfd..0226c3640 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/pos/blockPos/DhBlockPos2D.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/pos/blockPos/DhBlockPos2D.java @@ -75,6 +75,30 @@ public class DhBlockPos2D public long distSquared(DhBlockPos2D other) { return this.distSquared(other.x, other.z); } public long distSquared(int x, int z) { return MathUtil.pow2((long) this.x - x) + MathUtil.pow2((long) this.z - z); } + /** + * Returns the maximum distance along either the X or Z axis

+ * + * Example chebyshev distance between X and every point around it:
+ * + * 2 2 2 2 2
+ * 2 1 1 1 2
+ * 2 1 X 1 2
+ * 2 1 1 1 2
+ * 2 2 2 2 2
+ *
+ */ + public int chebyshevDist(DhBlockPos2D other) { return Math.max(Math.abs(this.x - other.x), Math.abs(this.z - other.z)); } + + /** + * Can be used to quickly determine the rough distance between two points
+ * or determine the taxi cab (manhattan) distance between two points.

+ * + * Manhattan distance is equivalent to determining the distance between two street intersections, + * where you can only drive along each street, instead of directly to the other point. + */ + public int manhattanDist(DhBlockPos2D other) { return Math.abs(this.x - other.x) + Math.abs(this.z - other.z); } + + //===========// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java index 00469c452..6d8744048 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java @@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer; import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2; +import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult; import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; @@ -34,19 +35,22 @@ import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandl import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.ThreadUtil; +import com.seibel.distanthorizons.core.util.WorldGenUtil; import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode; import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil; +import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import org.jetbrains.annotations.Nullable; import javax.annotation.WillNotClose; import java.awt.*; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; @@ -58,7 +62,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen { private static final DhLogger LOGGER = new DhLoggerBuilder().build(); /** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */ - private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleThreadPool("QuadTree Full Data Retrieval Queue Populator"); + private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleThreadPool("LodQuadTree Data Retrieval Queue"); public final int blockRenderDistanceDiameter; @@ -72,7 +76,6 @@ public class LodQuadTree extends QuadTree implements IDebugRen private final ConcurrentLinkedQueue sectionsToReload = new ConcurrentLinkedQueue<>(); private final IDhClientLevel level; private final ReentrantLock treeReadWriteLock = new ReentrantLock(); - private final AtomicBoolean fullDataRetrievalQueueRunning = new AtomicBoolean(false); private ArrayList debugRenderSections = new ArrayList<>(); private ArrayList altDebugRenderSections = new ArrayList<>(); @@ -101,6 +104,10 @@ public class LodQuadTree extends QuadTree implements IDebugRen /** used to calculate when a detail drop will occur */ private double detailDropOffLogBase; + /** the {@link DhSectionPos} that need to be retrieved/generated */ + public final LongOpenHashSet missingGenerationPosSet = new LongOpenHashSet(); + public final LongOpenHashSet queuedGenerationPosSet = new LongOpenHashSet(); + //=============// @@ -158,8 +165,18 @@ public class LodQuadTree extends QuadTree implements IDebugRen try { - // recenter if necessary, removing out of bounds sections - this.setCenterBlockPos(playerPos, LodRenderSection::close); + // recenter if necessary... + this.setCenterBlockPos(playerPos, (renderSection) -> + { + //...removing out of bounds sections + if (renderSection != null) + { + this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos)); + this.missingGenerationPosSet.remove(renderSection.pos); + this.queuedGenerationPosSet.remove(renderSection.pos); + renderSection.close(); + } + }); this.updateAllRenderSections(playerPos); } @@ -197,7 +214,6 @@ public class LodQuadTree extends QuadTree implements IDebugRen // walk through each root node - HashSet nodesNeedingRetrieval = new HashSet<>(); HashSet nodesNeedingLoading = new HashSet<>(); LongIterator rootPosIterator = this.rootNodePosIterator(); while (rootPosIterator.hasNext()) @@ -211,17 +227,22 @@ public class LodQuadTree extends QuadTree implements IDebugRen QuadNode rootNode = this.getNode(rootPos); LodUtil.assertTrue(rootNode != null, "All root nodes should have been created by this point."); - this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, rootNode, rootNode.sectionPos, false, nodesNeedingRetrieval, nodesNeedingLoading); + this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, rootNode, rootNode.sectionPos, false, nodesNeedingLoading); } // queue full data retrieval (world gen) requests if needed - if (nodesNeedingRetrieval.size() != 0 - && !this.fullDataRetrievalQueueRunning.get() + if (this.missingGenerationPosSet.size() != 0 && this.fullDataSourceProvider.canQueueRetrievalNow()) { - this.fullDataRetrievalQueueRunning.set(true); - FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() -> this.queueFullDataRetrievalTasks(playerPos, nodesNeedingRetrieval)); + try + { + this.queueFullDataRetrievalTasks(playerPos); + } + catch (Exception e) + { + LOGGER.error("Unexpected error queuing retrieval tasks, error: [" + e.getMessage() + "].", e); + } } @@ -237,7 +258,6 @@ public class LodQuadTree extends QuadTree implements IDebugRen DhBlockPos2D playerPos, QuadNode rootNode, QuadNode quadNode, long sectionPos, boolean parentSectionIsRendering, - HashSet nodesNeedingRetrieval, HashSet nodesNeedingLoading) { //=====================// @@ -246,7 +266,8 @@ public class LodQuadTree extends QuadTree implements IDebugRen //=====================// // create the node - if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance + if (quadNode == null + && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance { rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider, this.uploadTaskCountRef)); quadNode = rootNode.getNode(sectionPos); @@ -289,7 +310,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen for (int i = 0; i < 4; i++) { QuadNode childNode = quadNode.getChildByIndex(i); - boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), thisPosIsRendering || parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading); + boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), thisPosIsRendering || parentSectionIsRendering, nodesNeedingLoading); allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded; } @@ -348,7 +369,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen for (int i = 0; i < 4; i++) { QuadNode childNode = quadNode.getChildByIndex(i); - this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading); + this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingLoading); } // disabling rendering must be done after the children are enabled @@ -374,12 +395,6 @@ public class LodQuadTree extends QuadTree implements IDebugRen nodesNeedingLoading.add(renderSection); } - // queue world gen if needed - if (!renderSection.isFullyGenerated()) - { - nodesNeedingRetrieval.add(renderSection); - } - // update debug if needed if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get()) { @@ -389,7 +404,8 @@ public class LodQuadTree extends QuadTree implements IDebugRen // wait for the parent to disable before enabling this section, so we don't have a hole - if (!parentSectionIsRendering && renderSection.canRender()) + if (!parentSectionIsRendering + && renderSection.canRender()) { // if rendering is already enabled we don't have to re-enable it if (!renderSection.getRenderingEnabled()) @@ -420,6 +436,21 @@ public class LodQuadTree extends QuadTree implements IDebugRen // needs to be fired after the children are disabled so beacons render correctly renderSection.onRenderingEnabled(); + // since this section wants to render + // check if it needs any generation to do so + LongArrayList missingPosList = this.fullDataSourceProvider.getPositionsToRetrieve(renderSection.pos); + if (missingPosList != null) + { + for (int i = 0; i < missingPosList.size(); i++) + { + long missingPos = missingPosList.getLong(i); + if (!this.queuedGenerationPosSet.contains(missingPos)) + { + this.missingGenerationPosSet.add(missingPos); + } + } + } + } } @@ -480,7 +511,8 @@ public class LodQuadTree extends QuadTree implements IDebugRen for (int i = 0; i < loadSectionList.size(); i++) { LodRenderSection renderSection = loadSectionList.get(i); - if (!renderSection.gpuUploadInProgress() && renderSection.bufferContainer == null) + if (!renderSection.gpuUploadInProgress() + && renderSection.bufferContainer == null) { renderSection.uploadRenderDataToGpuAsync(); } @@ -563,34 +595,32 @@ public class LodQuadTree extends QuadTree implements IDebugRen */ public void clearRenderDataCache() { - if (this.treeReadWriteLock.tryLock()) // TODO make async, can lock render thread + try { - try + this.treeReadWriteLock.lock(); + LOGGER.info("Disposing render data..."); + + // clear the tree + Iterator> nodeIterator = this.nodeIterator(); + while (nodeIterator.hasNext()) { - LOGGER.info("Disposing render data..."); - - // clear the tree - Iterator> nodeIterator = this.nodeIterator(); - while (nodeIterator.hasNext()) + QuadNode quadNode = nodeIterator.next(); + if (quadNode.value != null) { - QuadNode quadNode = nodeIterator.next(); - if (quadNode.value != null) - { - quadNode.value.close(); - quadNode.value = null; - } + quadNode.value.close(); + quadNode.value = null; } - - LOGGER.info("Render data cleared, please wait a moment for everything to reload..."); - } - catch (Exception e) - { - LOGGER.error("Unexpected error when clearing LodQuadTree render cache: " + e.getMessage(), e); - } - finally - { - this.treeReadWriteLock.unlock(); } + + LOGGER.info("Render data cleared, please wait a moment for everything to reload..."); + } + catch (Exception e) + { + LOGGER.error("Unexpected error when clearing LodQuadTree render cache: " + e.getMessage(), e); + } + finally + { + this.treeReadWriteLock.unlock(); } } @@ -627,78 +657,96 @@ public class LodQuadTree extends QuadTree implements IDebugRen //=================================// //region world gen - private void queueFullDataRetrievalTasks(DhBlockPos2D playerPos, HashSet nodesNeedingRetrieval) + private void queueFullDataRetrievalTasks(DhBlockPos2D playerPos) { - try + // sort the nodes from nearest to farthest + LongArrayList sortedMissingPosList = new LongArrayList(this.missingGenerationPosSet); + sortedMissingPosList.sort((posA, posB) -> { - // sort the nodes from nearest to farthest - ArrayList renderSectionList = new ArrayList<>(nodesNeedingRetrieval); - renderSectionList.sort((renderSectionA, renderSectionB) -> + int aDist = DhSectionPos.getManhattanBlockDistance(posA, playerPos); + int bDist = DhSectionPos.getManhattanBlockDistance(posB, playerPos); + return Integer.compare(aDist, bDist); + }); + + + + //==================================// + // add retrieval tasks to the queue // + //==================================// + + for (int i = 0; i < sortedMissingPosList.size(); i++) + { + if (!this.fullDataSourceProvider.canQueueRetrievalNow()) { - int aDist = DhSectionPos.getManhattanBlockDistance(renderSectionA.pos, playerPos); - int bDist = DhSectionPos.getManhattanBlockDistance(renderSectionB.pos, playerPos); - return Integer.compare(aDist, bDist); - }); + break; + } + long missingPos = sortedMissingPosList.getLong(i); - - //==================================// - // add retrieval tasks to the queue // - //==================================// - - for (int i = 0; i < renderSectionList.size(); i++) + // is this position within acceptable generator range? + boolean posInRange = WorldGenUtil.isPosInWorldGenRange( + missingPos, + Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(), + Config.Common.WorldGenerator.generationMaxChunkRadius.get() + ); + if (!posInRange) { - LodRenderSection renderSection = renderSectionList.get(i); - if (!this.fullDataSourceProvider.canQueueRetrievalNow()) - { - break; - } + continue; + } + + CompletableFuture genFuture = this.fullDataSourceProvider.queuePositionForRetrieval(missingPos); + boolean positionQueued = (genFuture != null && !genFuture.isCompletedExceptionally()); + if (positionQueued) + { + this.queuedGenerationPosSet.add(missingPos); + this.missingGenerationPosSet.remove(missingPos); - renderSection.tryQueuingMissingLodRetrieval(); - } - - - - //==========================// - // calc task count estimate // - //==========================// - - // calculate an estimate for the max number of chunks for the queue - int totalWorldGenChunkCount = 0; - int totalWorldGenTaskCount = 0; - for (int i = 0; i < renderSectionList.size(); i++) - { - LodRenderSection renderSection = renderSectionList.get(i); - if (!renderSection.missingPositionsCalculated()) + genFuture.exceptionally((Throwable throwable) -> { - // chunk count - int sectionWidthInChunks = DhSectionPos.getChunkWidth(renderSection.pos); - totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks; + // gen task failed, + // requeue so we can try again in the future - // task count - totalWorldGenTaskCount += renderSection.ungeneratedPositionCount(); - } - else + this.queuedGenerationPosSet.remove(missingPos); + this.missingGenerationPosSet.add(missingPos); + return null; + }); + genFuture.thenAccept((DataSourceRetrievalResult result) -> { - totalWorldGenChunkCount += renderSection.ungeneratedChunkCount(); + // task finished + this.queuedGenerationPosSet.remove(missingPos); - // 1 since we assume the position can be generated in a single go - // TODO this is a bad assumption, can we determine what the world gen supports and determine it from that? - totalWorldGenTaskCount += 1; - } + // if the task failed re-queue so we can try again + if (!result.success) + { + this.missingGenerationPosSet.add(missingPos); + } + }); } + } + + + + //==========================// + // calc task count estimate // + //==========================// + + // calculate an estimate for the max number of chunks for the queue + int totalWorldGenChunkCount = 0; + int totalWorldGenTaskCount = 0; + for (int i = 0; i < sortedMissingPosList.size(); i++) + { + long missingPos = sortedMissingPosList.getLong(i); - this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount); - this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount); - } - catch (Exception e) - { - LOGGER.error("Unexpected error: "+e.getMessage(), e); - } - finally - { - this.fullDataRetrievalQueueRunning.set(false); + // chunk count + int sectionWidthInChunks = DhSectionPos.getChunkWidth(missingPos); + totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks; + + // task count + totalWorldGenTaskCount++; } + + this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount); + this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount); } //endregion world gen diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java index c64173c71..9bdfabb83 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java @@ -19,8 +19,6 @@ package com.seibel.distanthorizons.core.render; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; @@ -42,13 +40,10 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; -import com.seibel.distanthorizons.core.util.LodUtil; -import com.seibel.distanthorizons.core.util.WorldGenUtil; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; -import it.unimi.dsi.fastutil.longs.LongArrayList; import org.jetbrains.annotations.Nullable; import javax.annotation.WillNotClose; @@ -115,17 +110,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable * different threads (buffer uploading is on the MC render thread) and need to be canceled separately. */ private CompletableFuture bufferUploadFuture = null; - - /** - * should be an empty array if no positions need to be generated - * - * @deprecated see the comment where this variable is set - */ - @Nullable - @Deprecated - private Supplier missingGenerationPosFunc; - private LongArrayList getMissingGenerationPos() { return this.missingGenerationPosFunc != null ? this.missingGenerationPosFunc.get() : null; } - @@ -149,7 +133,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable this.beaconRenderHandler = this.quadTree.beaconRenderHandler; this.beaconBeamRepo = this.level.getBeaconBeamRepo(); - + DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus); } @@ -452,94 +436,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable - //=================================// - // full data retrieval (world gen) // - //=================================// - //region full data retrieval - - public boolean missingPositionsCalculated() { return this.getMissingGenerationPos() != null; } - public int ungeneratedPositionCount() - { - LongArrayList missingGenerationPos = this.getMissingGenerationPos(); - return missingGenerationPos != null ? missingGenerationPos.size() : 0; - } - public int ungeneratedChunkCount() - { - LongArrayList missingGenerationPos = this.getMissingGenerationPos(); - if (missingGenerationPos == null) - { - return 0; - } - - int chunkCount = 0; - // get the number of chunks each position contains - for (int i = 0; i < missingGenerationPos.size(); i++) - { - int chunkWidth = DhSectionPos.getChunkWidth(missingGenerationPos.getLong(i)); - chunkCount += (chunkWidth * chunkWidth); - } - return chunkCount; - } - - public void tryQueuingMissingLodRetrieval() - { - if (this.fullDataSourceProvider.canRetrieveMissingDataSources() - && this.fullDataSourceProvider.canQueueRetrievalNow()) - { - // calculate the missing positions if not already done - if (this.missingGenerationPosFunc == null) - { - // TODO memoization is needed for multiplayer, otherwise - // new retrieval requests won't be submitted. - // TODO why is that the case? Shouldn't the missing positions be un-changing? - // TODO setting this value to low can cause world gen to slow down significantly - // due to a race condition where the world gen thinks it is finished, but the results - // haven't been saved to file yet, causing the gen to fire again - this.missingGenerationPosFunc = Suppliers.memoizeWithExpiration( - () -> this.fullDataSourceProvider.getPositionsToRetrieve(this.pos), - 10, TimeUnit.MINUTES); - } - - LongArrayList missingGenerationPos = this.getMissingGenerationPos(); - if (missingGenerationPos != null) - { - // queue from last to first to prevent shifting the array unnecessarily - for (int i = missingGenerationPos.size() - 1; i >= 0; i--) - { - if (!this.fullDataSourceProvider.canQueueRetrievalNow()) - { - // the data source provider isn't accepting any more jobs - break; - } - - long pos = missingGenerationPos.removeLong(i); - - boolean posInRange = WorldGenUtil.isPosInWorldGenRange( - pos, - Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(), - Config.Common.WorldGenerator.generationMaxChunkRadius.get() - ); - if (!posInRange) - { - continue; - } - - - boolean positionQueued = (this.fullDataSourceProvider.queuePositionForRetrieval(pos) != null); - if (!positionQueued) - { - // shouldn't normally happen, but just in case - missingGenerationPos.add(pos); - } - } - } - } - } - - //endregion full data retrieval - - - //=================// // beacon handling // //=================// @@ -684,13 +580,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable { // remove the task from our executor if present // note: don't cancel the task since that prevents cleanup, we just don't want it to run - PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor(); - if (executor != null && !executor.isTerminated()) + PriorityTaskPicker.Executor renderLoaderExecutor = ThreadPoolUtil.getRenderLoadingExecutor(); + if (renderLoaderExecutor != null + && !renderLoaderExecutor.isTerminated()) { Runnable runnable = this.getAndBuildRenderDataRunnable; if (runnable != null) { - executor.remove(runnable); + renderLoaderExecutor.remove(runnable); } } } @@ -701,14 +598,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable uploadFuture.cancel(true); } - - - // remove any active world gen requests that may be for this position - ThreadPoolExecutor executor = ThreadPoolUtil.getCleanupExecutor(); - // while this should generally be a fast operation - // this is run on a separate thread to prevent lag on the render thread - executor.execute(() -> this.fullDataSourceProvider.removeRetrievalRequestIf((genPos) -> DhSectionPos.contains(this.pos, genPos))); - } //endregion base methods