From 365371c5b95a202ead03410896c704c67ebe66a0 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 22 Jan 2023 19:56:24 -0600 Subject: [PATCH] Refactor WorldGenerationQueue --- .../core/generation/WorldGenerationQueue.java | 584 +++++++++++------- .../lod/core/level/DhClientServerLevel.java | 2 +- 2 files changed, 345 insertions(+), 241 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 842794b97..f8c4e6672 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 @@ -36,24 +36,44 @@ public class WorldGenerationQueue implements Closeable private final IDhApiWorldGenerator generator; - private final ConcurrentLinkedQueue looseTasks = new ConcurrentLinkedQueue<>(); + /** + * This list contains all of the {@link WorldGenTask}'s that haven't been processed yet.
+ * These tasks may or may not be necessary or valid. looseWoldGenTasks = new ConcurrentLinkedQueue<>(); // FIXME: Concurrency issue on close! // FIXME: This is using up a TONS of time to process! - private final ConcurrentSkipListMap taskGroups = new ConcurrentSkipListMap<>( - (a, b) -> { + private final ConcurrentSkipListMap waitingTaskGroupsByLodPos = new ConcurrentSkipListMap<>( + (a, b) -> + { + // 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; - if (a.x != b.x) + } + else if (a.x != b.x) + { return a.x - b.x; + } + else + { return a.z - b.z; + } } ); // Accessed by poller only - private final ConcurrentHashMap inProgress = new ConcurrentHashMap<>(); + private final ConcurrentHashMap inProgressGenTasksByLodPos = new ConcurrentHashMap<>(); // granularity is the detail level for batching world generator requests together private final byte maxGranularity; @@ -166,7 +186,7 @@ public class WorldGenerationQueue implements Closeable splitTaskTracker.parentFuture.complete(true); }); - this.looseTasks.addAll(subTasks); + this.looseWoldGenTasks.addAll(subTasks); return splitTaskTracker.parentFuture; } else if (granularity < this.minGranularity) @@ -175,7 +195,7 @@ public class WorldGenerationQueue implements Closeable byte parentDetail = (byte) (this.minGranularity + requiredDataDetail); DhLodPos parentPos = pos.convertToDetailLevel(parentDetail); CompletableFuture future = new CompletableFuture<>(); - this.looseTasks.add(new WorldGenTask(parentPos, requiredDataDetail, tracker, future)); + this.looseWoldGenTasks.add(new WorldGenTask(parentPos, requiredDataDetail, tracker, future)); return future; } @@ -185,188 +205,45 @@ public class WorldGenerationQueue implements Closeable // no additional task changes are necessary CompletableFuture future = new CompletableFuture<>(); - this.looseTasks.add(new WorldGenTask(pos, requiredDataDetail, tracker, future)); + this.looseWoldGenTasks.add(new WorldGenTask(pos, requiredDataDetail, tracker, future)); return future; } } - private void addAndCombineGroup(WorldGenTaskGroup target) + public void runCurrentGenTasksUntilBusy(DhBlockPos2D targetPos) { - byte granularity = (byte) (target.pos.detailLevel - target.dataDetail); - LodUtil.assertTrue(granularity <= this.maxGranularity && granularity >= this.minGranularity); - LodUtil.assertTrue(!this.taskGroups.containsKey(target.pos)); - - // Check and merge all those who has exactly the same dataDetail, and overlaps the position, but have lower granularity than us - if (granularity > this.minGranularity) + if (this.generator == null) { - // TODO: Optimize this check - Iterator groupIter = this.taskGroups.values().iterator(); - while (groupIter.hasNext()) - { - WorldGenTaskGroup group = groupIter.next(); - if (group.dataDetail != target.dataDetail) - continue; - if (!group.pos.overlaps(target.pos)) - continue; - - // We should have already ALWAYS selected the higher granularity. - LodUtil.assertTrue(group.pos.detailLevel < target.pos.detailLevel); - groupIter.remove(); // Remove and consume all from that lower granularity request - target.generatorTasks.addAll(group.generatorTasks); - } + throw new IllegalStateException("generator is null"); } - // Now, Check if we are the missing piece in the 4 quadrants, and if so, combine the four into a new higher granularity group - if (granularity < this.maxGranularity) - { // Obviously, only do so if we aren't at the maxGranularity already - // Check for merging and upping the granularity - DhLodPos corePos = target.pos; - DhLodPos parentPos = corePos.convertToDetailLevel((byte) (corePos.detailLevel + 1)); - int targetChildId = target.pos.getChildIndexOfParent(); - boolean allPassed = true; - for (int i = 0; i < 4; i++) - { - if (i == targetChildId) - continue; - WorldGenTaskGroup group = this.taskGroups.get(parentPos.getChildPosByIndex(i)); - if (group == null || group.dataDetail != target.dataDetail) - { - allPassed = false; - break; - } - } - if (allPassed) - { - LodUtil.assertTrue(!this.taskGroups.containsKey(parentPos) || this.taskGroups.get(parentPos).dataDetail != target.dataDetail); - WorldGenTaskGroup[] groups = new WorldGenTaskGroup[4]; - for (int i = 0; i < 4; i++) - { - if (i == targetChildId) - groups[i] = target; - else - groups[i] = this.taskGroups.remove(parentPos.getChildPosByIndex(i)); - LodUtil.assertTrue(groups[i] != null && groups[i].dataDetail == target.dataDetail); - } - - WorldGenTaskGroup newGroup = this.taskGroups.get(parentPos); - if (newGroup != null) - { - LodUtil.assertTrue(newGroup.dataDetail != target.dataDetail); // if it is equal, we should have been merged ages ago - if (newGroup.dataDetail < target.dataDetail) - { - // We can just append us into the existing list. - for (WorldGenTaskGroup g : groups) - newGroup.generatorTasks.addAll(g.generatorTasks); - } - else - { - // We need to upgrade the requested dataDetail of the group. - newGroup.dataDetail = target.dataDetail; - boolean worked = this.taskGroups.remove(parentPos, newGroup); // Pop it off for later proper merge check - LodUtil.assertTrue(worked); - for (WorldGenTaskGroup g : groups) - newGroup.generatorTasks.addAll(g.generatorTasks); - this.addAndCombineGroup(newGroup); // Recursive check the new group - } - } - else - { - // There should not be any higher granularity to check, as otherwise we would have merged ages ago - newGroup = new WorldGenTaskGroup(parentPos, target.dataDetail); - for (WorldGenTaskGroup g : groups) - newGroup.generatorTasks.addAll(g.generatorTasks); - this.addAndCombineGroup(newGroup); // Recursive check the new group - } - return; // We have merged. So no need to add the target group - } - } - // Finally, we should be safe to add the target group into the list - WorldGenTaskGroup v = this.taskGroups.put(target.pos, target); - LodUtil.assertTrue(v == null); // should never be replacing other things + // 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()) + { + this.removeOutdatedTaskGroups(); + this.processLooseTasks(); + this.startNextWorldGenTask(targetPos); + } } - private void processLooseTasks() + /** + * Removes all invalid {@link WorldGenTask}'s and {@link WorldGenTaskGroup}'s
+ * This generally happens if a worldGenTask has been garbage collected. + */ + private void removeOutdatedTaskGroups() { - int taskProcessed = 0; - - WorldGenTask task = this.looseTasks.poll(); // using poll prevents concurrency issues where the list is cleared after asking if it was empty - while (task != null && taskProcessed < MAX_TASKS_PROCESSED_PER_TICK) - { - taskProcessed++; - byte taskDataDetail = task.dataDetailLevel; - byte taskGranularity = (byte) (task.pos.detailLevel - taskDataDetail); - LodUtil.assertTrue(taskGranularity >= LodUtil.CHUNK_DETAIL_LEVEL && taskGranularity >= this.minGranularity && taskGranularity <= this.maxGranularity); - - // Check existing one - WorldGenTaskGroup group = this.taskGroups.get(task.pos); - if (group != null) - { - if (group.dataDetail <= taskDataDetail) - { - // We can just append us into the existing list. - group.generatorTasks.add(task); - } - else - { - // We need to upgrade the requested dataDetail of the group. - group.dataDetail = taskDataDetail; - boolean worked = this.taskGroups.remove(task.pos, group); // Pop it off for later proper merge check - LodUtil.assertTrue(worked); - group.generatorTasks.add(task); - this.addAndCombineGroup(group); - } - } - else - { - - // Check higher granularity one - byte granularity = taskGranularity; - boolean didAnything = false; - while (++granularity <= this.maxGranularity) - { - group = this.taskGroups.get(task.pos.convertToDetailLevel((byte) (taskDataDetail + granularity))); - if (group != null && group.dataDetail == taskDataDetail) - { - // We can just append to the higher granularity group one - group.generatorTasks.add(task); - didAnything = true; - break; - } - } - - if (!didAnything) - { - group = new WorldGenTaskGroup(task.pos, taskDataDetail); - group.generatorTasks.add(task); - this.addAndCombineGroup(group); - } - } - - // get the next task to process (will be null if the list is empty) - task = this.looseTasks.poll(); - } - - if (taskProcessed != 0) - { - LOGGER.info("Processed " + taskProcessed + " loose tasks"); - } - - } - - private void removeOutdatedGroups() - { - // Remove all invalid genTasks and groups - Iterator groupIter = this.taskGroups.values().iterator(); + Iterator groupIter = this.waitingTaskGroupsByLodPos.values().iterator(); // go through each TaskGroup while (groupIter.hasNext()) { // go through each WorldGenTask in the TaskGroup - WorldGenTaskGroup group = groupIter.next(); - Iterator taskIter = group.generatorTasks.iterator(); + WorldGenTaskGroup taskGroup = groupIter.next(); + Iterator taskIter = taskGroup.generatorTasks.iterator(); while (taskIter.hasNext()) { + // remove this task if it has been garbage collected WorldGenTask task = taskIter.next(); if (!task.taskTracker.isMemoryAddressValid()) { @@ -375,86 +252,301 @@ public class WorldGenerationQueue implements Closeable } } - if (group.generatorTasks.isEmpty()) + // remove this group if it is now empty + if (taskGroup.generatorTasks.isEmpty()) + { groupIter.remove(); + } } } - private void pollAndStartNext(DhBlockPos2D targetPos) + /** + * This processes the currently available loose tasks and prepares them + * so, they can actually be used for world generation. + */ + private void processLooseTasks() { - // Select the one with the highest data detail level and closest to the target pos - WorldGenTaskGroup best = null; - long cachedDist = Long.MAX_VALUE; - int lastChebDist = Integer.MIN_VALUE; + int taskProcessed = 0; + + WorldGenTask task = this.looseWoldGenTasks.poll(); // using poll prevents concurrency issues where the list is cleared after asking if it was empty + while (task != null && taskProcessed < MAX_TASKS_PROCESSED_PER_TICK) + { + taskProcessed++; + byte taskDataDetail = task.dataDetailLevel; + byte taskGranularity = (byte) (task.pos.detailLevel - taskDataDetail); + LodUtil.assertTrue(taskGranularity >= LodUtil.CHUNK_DETAIL_LEVEL && taskGranularity >= this.minGranularity && taskGranularity <= this.maxGranularity); + + // Check if a task already exists for this position + WorldGenTaskGroup existingWorldGenGroup = this.waitingTaskGroupsByLodPos.get(task.pos); + if (existingWorldGenGroup != null) + { + // a task already exists for this exact position + + if (existingWorldGenGroup.dataDetail <= taskDataDetail) + { + // the existing group has an equal or lower detail level, + // we can just append the new task to its list. + existingWorldGenGroup.generatorTasks.add(task); + } + else + { + // the existing group has a higher detail level than this one, + // we need to increase the existing group's detail level. + existingWorldGenGroup.dataDetail = taskDataDetail; + + // remove the existing task, so it can be re-added after the necessary modifications + boolean taskRemoved = this.waitingTaskGroupsByLodPos.remove(task.pos, existingWorldGenGroup); + LodUtil.assertTrue(taskRemoved); + + // re-add the task group + existingWorldGenGroup.generatorTasks.add(task); + this.addAndCombineTaskGroup(existingWorldGenGroup); + } + } + else + { + // no task group exists for this position + + // Check if there is one with a higher detail level + byte granularity = taskGranularity; + boolean addedToHigherDetailGroup = false; + while (++granularity <= this.maxGranularity) + { + existingWorldGenGroup = this.waitingTaskGroupsByLodPos.get(task.pos.convertToDetailLevel((byte) (taskDataDetail + granularity))); + if (existingWorldGenGroup != null && existingWorldGenGroup.dataDetail == taskDataDetail) + { + // We can just append to the higher detail level group + existingWorldGenGroup.generatorTasks.add(task); + addedToHigherDetailGroup = true; + break; + } + } + + if (!addedToHigherDetailGroup) + { + // no higher detail group exists that we can append to, + // create a new task group + existingWorldGenGroup = new WorldGenTaskGroup(task.pos, taskDataDetail); + existingWorldGenGroup.generatorTasks.add(task); + this.addAndCombineTaskGroup(existingWorldGenGroup); + } + } + + + // get the next task to process (will be null if the list is empty) + task = this.looseWoldGenTasks.poll(); + } + + if (taskProcessed != 0) + { + LOGGER.info("Processed " + taskProcessed + " loose tasks"); + } + } + /** adds the new TaskGroup either as a new group or combines it into an existing task group */ + private void addAndCombineTaskGroup(WorldGenTaskGroup newTaskGroup) + { + byte newGranularity = (byte) (newTaskGroup.pos.detailLevel - newTaskGroup.dataDetail); + LodUtil.assertTrue(newGranularity <= this.maxGranularity && newGranularity >= this.minGranularity); + LodUtil.assertTrue(!this.waitingTaskGroupsByLodPos.containsKey(newTaskGroup.pos)); + + // Check and merge all those who have exactly the same dataDetail, and overlap the position; but have lower granularity than us + if (newGranularity > this.minGranularity) + { + // TODO: Optimize this check + Iterator groupIter = this.waitingTaskGroupsByLodPos.values().iterator(); + while (groupIter.hasNext()) + { + WorldGenTaskGroup group = groupIter.next(); + if (group.dataDetail != newTaskGroup.dataDetail + || !group.pos.overlaps(newTaskGroup.pos)) + { + continue; + } + + // We should have already ALWAYS selected the higher granularity. + LodUtil.assertTrue(group.pos.detailLevel < newTaskGroup.pos.detailLevel); + groupIter.remove(); // Remove and consume all from that lower granularity request + newTaskGroup.generatorTasks.addAll(group.generatorTasks); + } + } + + // Now, Check if we are the missing piece in the 4 quadrants, and if so, combine the four into a new higher granularity group + if (newGranularity < this.maxGranularity) + { + // Obviously, only do so if we aren't at the maxGranularity already + // Check for merging and upping the granularity + DhLodPos corePos = newTaskGroup.pos; + DhLodPos parentPos = corePos.convertToDetailLevel((byte) (corePos.detailLevel + 1)); + int targetChildId = newTaskGroup.pos.getChildIndexOfParent(); + + boolean allPassed = true; + for (int i = 0; i < 4; i++) + { + if (i == targetChildId) + continue; + WorldGenTaskGroup group = this.waitingTaskGroupsByLodPos.get(parentPos.getChildPosByIndex(i)); + if (group == null || group.dataDetail != newTaskGroup.dataDetail) + { + allPassed = false; + break; + } + } + + if (allPassed) + { + LodUtil.assertTrue(!this.waitingTaskGroupsByLodPos.containsKey(parentPos) || this.waitingTaskGroupsByLodPos.get(parentPos).dataDetail != newTaskGroup.dataDetail); + WorldGenTaskGroup[] groups = new WorldGenTaskGroup[4]; + for (int i = 0; i < 4; i++) + { + if (i == targetChildId) + { + groups[i] = newTaskGroup; + } + else + { + groups[i] = this.waitingTaskGroupsByLodPos.remove(parentPos.getChildPosByIndex(i)); + } + LodUtil.assertTrue(groups[i] != null && groups[i].dataDetail == newTaskGroup.dataDetail); + } + + WorldGenTaskGroup newGroup = this.waitingTaskGroupsByLodPos.get(parentPos); + if (newGroup != null) + { + LodUtil.assertTrue(newGroup.dataDetail != newTaskGroup.dataDetail); // if it is equal, we should have been merged ages ago + if (newGroup.dataDetail < newTaskGroup.dataDetail) + { + // We can just append us into the existing list. + for (WorldGenTaskGroup g : groups) + { + newGroup.generatorTasks.addAll(g.generatorTasks); + } + } + else + { + // We need to upgrade the requested dataDetail of the group. + newGroup.dataDetail = newTaskGroup.dataDetail; + boolean worked = this.waitingTaskGroupsByLodPos.remove(parentPos, newGroup); // Pop it off for later proper merge check + LodUtil.assertTrue(worked); + for (WorldGenTaskGroup g : groups) + { + newGroup.generatorTasks.addAll(g.generatorTasks); + } + this.addAndCombineTaskGroup(newGroup); // Recursive check the new group + } + } + else + { + // There should not be any higher granularity to check, as otherwise we would have merged ages ago + newGroup = new WorldGenTaskGroup(parentPos, newTaskGroup.dataDetail); + for (WorldGenTaskGroup g : groups) + { + newGroup.generatorTasks.addAll(g.generatorTasks); + } + this.addAndCombineTaskGroup(newGroup); // Recursive check the new group + } + + // We have merged. So no need to add the target group + return; + } + } + + // Finally, we should be safe to add the target group into the list + WorldGenTaskGroup existingTaskGroup = this.waitingTaskGroupsByLodPos.put(newTaskGroup.pos, newTaskGroup); + LodUtil.assertTrue(existingTaskGroup == null); // should never be replacing other things + } + + private void startNextWorldGenTask(DhBlockPos2D targetPos) + { + WorldGenTaskGroup closestTaskGroup = null; + long closestGenGroupDist = Long.MAX_VALUE; + int lastChebshevDistToOrigin = Integer.MIN_VALUE; boolean continueNextRound = true; byte currentDetailChecking = -1; - for (WorldGenTaskGroup group : this.taskGroups.values()) + + // Select the TaskGroup closest to the target pos with the highest detail level + for (WorldGenTaskGroup worldGenGroup : this.waitingTaskGroupsByLodPos.values()) { + // the list should be sorted detailLevel first, + // so we should break before getting to a different detail level in the list if (currentDetailChecking == -1) - currentDetailChecking = group.dataDetail; - LodUtil.assertTrue(currentDetailChecking == group.dataDetail); - int chebDistToOrigin = group.pos.getCenterBlockPos().toPos2D().chebyshevDist(Pos2D.ZERO); - if (chebDistToOrigin > lastChebDist) { - if (!continueNextRound) - break; // We have found the best one - continueNextRound = false; - lastChebDist = chebDistToOrigin; + currentDetailChecking = worldGenGroup.dataDetail; } - long dist = group.pos.getCenterBlockPos().distSquared(targetPos); - if (best != null && dist >= cachedDist) + LodUtil.assertTrue(currentDetailChecking == worldGenGroup.dataDetail); + + + // look for the closest position in each given layer around the world origin + // TODO why are we looking around the world's origin? + int chebDistToOrigin = worldGenGroup.pos.getCenterBlockPos().toPos2D().chebyshevDist(Pos2D.ZERO); + if (chebDistToOrigin > lastChebshevDistToOrigin) + { + // this worldGenGroup is 1 layer farther from the world origin + + if (!continueNextRound) + { + // We have found the best worldGenGroup, stop looking + break; + } + else + { + continueNextRound = false; + lastChebshevDistToOrigin = chebDistToOrigin; + } + } + + + // is this worldGenGroup closer to the targetPos than the previous closest? + long dist = worldGenGroup.pos.getCenterBlockPos().distSquared(targetPos); + if (closestTaskGroup != null && dist >= closestGenGroupDist) + { + // this worldGenGroup is farther away continue; - cachedDist = dist; - best = group; - continueNextRound = true; + } + else + { + // this worldGenGroup is closer than the previous closest + closestGenGroupDist = dist; + closestTaskGroup = worldGenGroup; + + continueNextRound = true; + } } - if (best != null) + + // if a new worldGenGroup was found, try starting it + if (closestTaskGroup != null) { - InProgressWorldGenTaskGroup startedTask = new InProgressWorldGenTaskGroup(best); - InProgressWorldGenTaskGroup casTask = this.inProgress.putIfAbsent(best.pos, startedTask); - boolean worked = this.taskGroups.remove(best.pos, best); // Remove the selected task from the group - LodUtil.assertTrue(worked); - if (casTask != null) + InProgressWorldGenTaskGroup newInProgressTask = new InProgressWorldGenTaskGroup(closestTaskGroup); + InProgressWorldGenTaskGroup previousInProgressTask = this.inProgressGenTasksByLodPos.putIfAbsent(closestTaskGroup.pos, newInProgressTask); + + // Remove the selected task from the waiting list + boolean taskRemoved = this.waitingTaskGroupsByLodPos.remove(closestTaskGroup.pos, closestTaskGroup); + LodUtil.assertTrue(taskRemoved); + + if (previousInProgressTask != null) { - // Note: Due to concurrency reasons, even if the currently running task is compatible with selected task, - // we cannot use it, as some chunks may have already been written into. - this.pollAndStartNext(targetPos); // Poll next one. - WorldGenTaskGroup exchange = this.taskGroups.put(best.pos, best); // put back the task. + // There is already a worldGenTask running for this position + + // 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. + + // recursively look for a different worldGenTask to start + this.startNextWorldGenTask(targetPos); + + // TODO why are we putting the task back? since a compatible task is already running, why would we re-add it to the list + WorldGenTaskGroup exchange = this.waitingTaskGroupsByLodPos.put(closestTaskGroup.pos, closestTaskGroup); // put back the task. LodUtil.assertTrue(exchange == null); } else { - this.startTaskGroup(startedTask); + // No worldGenTask is running for this position, start one + this.startWorldGenTaskGroup(newInProgressTask); } } - } - - public void pollAndStartClosest(DhBlockPos2D targetPos) - { - if (this.generator == null) - { - throw new IllegalStateException("generator is null"); - } - if (this.generator.isBusy()) - { - // don't accept new requests if busy - return; - } - - - // generate terrain until the generator is asked to stop (if the while loop wasn't done the world generator wouldn't have enough tasks`) - while (!this.generator.isBusy()) - { - this.removeOutdatedGroups(); - this.processLooseTasks(); - this.pollAndStartNext(targetPos); - } - } - - private void startTaskGroup(InProgressWorldGenTaskGroup task) + private void startWorldGenTaskGroup(InProgressWorldGenTaskGroup task) { byte dataDetail = task.group.dataDetail; DhLodPos pos = task.group.pos; @@ -465,19 +557,22 @@ public class WorldGenerationQueue implements Closeable DhChunkPos chunkPosMin = new DhChunkPos(pos.getCornerBlockPos()); LOGGER.info("Generating section {} with granularity {} at {}", pos, granularity, chunkPosMin); task.genFuture = startGenerationEvent(this.generator, chunkPosMin, granularity, dataDetail, task.group::accept); - task.genFuture.whenComplete((v, ex) -> { + task.genFuture.whenComplete((voidObj, ex) -> + { if (ex != null) { if (!UncheckedInterruptedException.isThrowableInterruption(ex)) - LOGGER.error("Error generating data for section {}", pos, ex); + { + LOGGER.error("Error generating data for section [{}]", pos, ex); + } task.group.generatorTasks.forEach(m -> m.future.complete(false)); } else { - LOGGER.info("Section generation at {} complated", pos); + LOGGER.info("Section generation at [{}] completed", pos); task.group.generatorTasks.forEach(m -> m.future.complete(true)); } - boolean worked = inProgress.remove(pos, task); + boolean worked = this.inProgressGenTasksByLodPos.remove(pos, task); LodUtil.assertTrue(worked); }); } @@ -490,25 +585,34 @@ public class WorldGenerationQueue implements Closeable public CompletableFuture startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning) { - this.taskGroups.values().forEach(g -> g.generatorTasks.forEach(t -> t.future.complete(false))); - this.taskGroups.clear(); - ArrayList> array = new ArrayList<>(this.inProgress.size()); - this.inProgress.values().forEach(runningTask -> + this.waitingTaskGroupsByLodPos.values().forEach(g -> g.generatorTasks.forEach(t -> t.future.complete(false))); + this.waitingTaskGroupsByLodPos.clear(); + ArrayList> array = new ArrayList<>(this.inProgressGenTasksByLodPos.size()); + this.inProgressGenTasksByLodPos.values().forEach(runningTaskGroup -> { - CompletableFuture genFuture = runningTask.genFuture; // Do this to prevent it getting swapped out + CompletableFuture genFuture = runningTaskGroup.genFuture; // Do this to prevent it getting swapped out + if (cancelCurrentGeneration) + { genFuture.cancel(alsoInterruptRunning); - array.add(genFuture.handle((v, ex) -> { + } + + array.add(genFuture.handle((voidObj, ex) -> + { if (ex instanceof CompletionException) + { ex = ex.getCause(); + } if (!UncheckedInterruptedException.isThrowableInterruption(ex)) - LOGGER.error("Error when terminating data generation for section {}", runningTask.group.pos, ex); + { + LOGGER.error("Error when terminating data generation for section {}", runningTaskGroup.group.pos, ex); + } return null; })); }); - this.generatorClosingFuture = CompletableFuture.allOf(array.toArray(CompletableFuture[]::new)); //FIXME: Closer threading issues with pollAndStartClosest - this.looseTasks.forEach(t -> t.future.complete(false)); - this.looseTasks.clear(); + this.generatorClosingFuture = CompletableFuture.allOf(array.toArray(CompletableFuture[]::new)); //FIXME: Closer threading issues with runCurrentGenTasksUntilBusy + this.looseWoldGenTasks.forEach(t -> t.future.complete(false)); + this.looseWoldGenTasks.clear(); return this.generatorClosingFuture; } diff --git a/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java index f2ec0e729..8edc3e715 100644 --- a/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java @@ -329,7 +329,7 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel { // queue new world generation requests wgs.chunkGenerator.preGeneratorTaskStart(); - wgs.worldGenerationQueue.pollAndStartClosest(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); + wgs.worldGenerationQueue.runCurrentGenTasksUntilBusy(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); } }