From f09bfa7cfd6c91dd28b44e329881b5065be63edf Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 5 Mar 2023 18:38:03 -0600 Subject: [PATCH] Auto remove world gen tasks that are outside DH's render distance --- .../core/generation/WorldGenerationQueue.java | 119 +++++++++++++----- .../com/seibel/lod/core/pos/DhLodPos.java | 2 +- 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java index 5809ea096..e75c6b57f 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java @@ -29,7 +29,6 @@ import java.util.function.Consumer; */ public class WorldGenerationQueue implements Closeable { - public static final int SHUTDOWN_TIMEOUT_SEC = 10; public static final int MAX_TASKS_PROCESSED_PER_TICK = 10000; private static final Logger LOGGER = DhLoggerBuilder.getLogger(); @@ -46,32 +45,31 @@ public class WorldGenerationQueue implements Closeable // FIXME: Concurrency issue on close! // FIXME: This is using up a TONS of time to process! private final ConcurrentSkipListMap waitingTaskGroupsByLodPos = new ConcurrentSkipListMap<>( - (a, b) -> + (aLodPos, bLodPos) -> + { + // sort based on detail level, higher detailLevels first (less detailed sections first) + if (aLodPos.detailLevel != bLodPos.detailLevel) { - // sort based on detail level, higher detailLevels first (less detailed sections first) - if (a.detailLevel != b.detailLevel) - { - return a.detailLevel - b.detailLevel; - } - - // sort into layers (or sqaures) around the world origin, closer positions first // (look at the definition of chebyshev distance for an example of what this looks like) - // TODO shouldn't we sort based on the player's position, not the world center? Although doing that could potentially cause issues with having to constantly re-sort this list - int aDist = a.getCenterBlockPos().toPos2D().chebyshevDist(Pos2D.ZERO); - int bDist = b.getCenterBlockPos().toPos2D().chebyshevDist(Pos2D.ZERO); - if (aDist != bDist) - { - return aDist - bDist; - } - else if (a.x != b.x) - { - return a.x - b.x; - } - else - { - return a.z - b.z; - } + return aLodPos.detailLevel - bLodPos.detailLevel; } - ); // Accessed by poller only + + // sort into layers (or squares) around the world origin, closer positions first // (look at the definition of chebyshev distance for an example of what this looks like) + // TODO shouldn't we sort based on the player's position, not the world center? Although doing that could potentially cause issues with having to constantly re-sort this list + int aDist = aLodPos.getCenterBlockPos().toPos2D().chebyshevDist(Pos2D.ZERO); + int bDist = bLodPos.getCenterBlockPos().toPos2D().chebyshevDist(Pos2D.ZERO); + if (aDist != bDist) + { + return aDist - bDist; + } + else if (aLodPos.x != bLodPos.x) + { + return aLodPos.x - bLodPos.x; + } + else + { + return aLodPos.z - bLodPos.z; + } + }); // Accessed by poller only private final ConcurrentHashMap inProgressGenTasksByLodPos = new ConcurrentHashMap<>(); @@ -218,8 +216,11 @@ public class WorldGenerationQueue implements Closeable } + // done to prevent generating chunks where the player isn't + this.removeOutOfRangeTasks(targetPos); + // generate terrain until the generator is asked to stop (if the while loop wasn't done the world generator would run out of tasks and will end up idle) - while (!this.generator.isBusy())// && !this.waitingTaskGroupsByLodPos.isEmpty()) + while (!this.generator.isBusy())// && !this.waitingTaskGroupsByLodPos.isEmpty()) // TODO why is the isEmpty() commented out? { this.removeOutdatedTaskGroups(); this.processLooseTasks(); @@ -523,6 +524,8 @@ public class WorldGenerationQueue implements Closeable // Remove the selected task from the waiting list boolean taskRemoved = this.waitingTaskGroupsByLodPos.remove(closestTaskGroup.pos, closestTaskGroup); LodUtil.assertTrue(taskRemoved); + + //LOGGER.info("waiting world gen task count: "+this.waitingTaskGroupsByLodPos.size()); if (previousInProgressTask != null) { @@ -555,7 +558,7 @@ public class WorldGenerationQueue implements Closeable LodUtil.assertTrue(dataDetail >= this.minDataDetail && dataDetail <= this.maxDataDetail); DhChunkPos chunkPosMin = new DhChunkPos(pos.getCornerBlockPos()); - LOGGER.info("Generating section {} with granularity {} at {}", pos, granularity, chunkPosMin); + //LOGGER.info("Generating section {} with granularity {} at {}", pos, granularity, chunkPosMin); task.genFuture = startGenerationEvent(this.generator, chunkPosMin, granularity, dataDetail, task.group::accept); task.genFuture.whenComplete((voidObj, exception) -> { @@ -571,7 +574,7 @@ public class WorldGenerationQueue implements Closeable } else { - LOGGER.info("Section generation at "+pos+" completed"); + //LOGGER.info("Section generation at "+pos+" completed"); task.group.generatorTasks.forEach(worldGenTask -> worldGenTask.future.complete(true)); } boolean worked = this.inProgressGenTasksByLodPos.remove(pos, task); @@ -580,6 +583,66 @@ public class WorldGenerationQueue implements Closeable } + /** + * Removes all {@link WorldGenTask}'s and {@link WorldGenTaskGroup}'s + * that are outside the player's render distance.
+ * This is done to prevent generating chunks where the player isn't.

+ * + * TODO it would be better in the long term to query what chunks should be generated each tick + * instead of keeping a running list of every chunk pos that could ever need generating. + * Said list can get very long and is often troublesome to use. + */ + private void removeOutOfRangeTasks(DhBlockPos2D targetBlockPos) + { + int numberOfTasksRemoved = 0; + + DhChunkPos targetChunkPos = new DhChunkPos(targetBlockPos); + + int chunkRenderDistance = Config.Client.Graphics.Quality.lodChunkRenderDistance.get(); + chunkRenderDistance += 6; // add a buffer where the user can move without clearing any tasks + + DhChunkPos minChunkPos = new DhChunkPos(targetChunkPos.x - chunkRenderDistance, targetChunkPos.z - chunkRenderDistance); + DhChunkPos maxChunkPos = new DhChunkPos(targetChunkPos.x + chunkRenderDistance, targetChunkPos.z + chunkRenderDistance); + + + Iterator taskGroupIter = this.waitingTaskGroupsByLodPos.values().iterator(); + + // go through each TaskGroup + while (taskGroupIter.hasNext()) + { + // go through each WorldGenTask in the TaskGroup + WorldGenTaskGroup taskGroup = taskGroupIter.next(); + Iterator taskIter = taskGroup.generatorTasks.iterator(); + while (taskIter.hasNext()) + { + // remove this task if it has been garbage collected + WorldGenTask task = taskIter.next(); + DhChunkPos centerChunkPos = new DhChunkPos(task.pos.getCenterBlockPos()); // TODO this assumes the world gen tasks are exactly 1 chunk wide, which isn't always the case. But it works well enough for now + + if (!DhChunkPos.isChunkPosBetween(minChunkPos, centerChunkPos, maxChunkPos)) + { + taskIter.remove(); + task.future.complete(false); + + numberOfTasksRemoved++; + } + } + + // remove this group if it is now empty + if (taskGroup.generatorTasks.isEmpty()) + { + taskGroupIter.remove(); + } + } + + + if (numberOfTasksRemoved != 0) + { +// LOGGER.info(numberOfTasksRemoved+" world gen tasks removed."); + } + } + + //==========// // shutdown // diff --git a/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java b/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java index e60b129ce..8a902ea0f 100644 --- a/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java +++ b/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java @@ -39,7 +39,7 @@ public class DhLodPos implements Comparable public DhLodUnit getZ() { return new DhLodUnit(this.detailLevel, this.z); } public int getBlockWidth() { return this.getBlockWidth(this.detailLevel); } - public int getBlockWidth(byte detailLevel) + public int getBlockWidth(byte detailLevel) // TODO this needs some documentation or a better name describing what is happening { LodUtil.assertTrue(detailLevel <= this.detailLevel); return BitShiftUtil.powerOfTwo(this.detailLevel - detailLevel);