Refactor WorldGenerationQueue

This commit is contained in:
James Seibel
2023-01-22 19:56:24 -06:00
parent e698df0b19
commit 365371c5b9
2 changed files with 345 additions and 241 deletions
@@ -36,24 +36,44 @@ public class WorldGenerationQueue implements Closeable
private final IDhApiWorldGenerator generator;
private final ConcurrentLinkedQueue<WorldGenTask> looseTasks = new ConcurrentLinkedQueue<>();
/**
* This list contains all of the {@link WorldGenTask}'s that haven't been processed yet. <br>
* These tasks may or may not be necessary or valid. <br.
* All valid tasks in this list will eventually be added to
* the {@link WorldGenerationQueue#waitingTaskGroupsByLodPos} list (provided they aren't garbage collected first).
*/
private final ConcurrentLinkedQueue<WorldGenTask> looseWoldGenTasks = new ConcurrentLinkedQueue<>();
// FIXME: Concurrency issue on close!
// FIXME: This is using up a TONS of time to process!
private final ConcurrentSkipListMap<DhLodPos, WorldGenTaskGroup> taskGroups = new ConcurrentSkipListMap<>(
(a, b) -> {
private final ConcurrentSkipListMap<DhLodPos, WorldGenTaskGroup> 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<DhLodPos, InProgressWorldGenTaskGroup> inProgress = new ConcurrentHashMap<>();
private final ConcurrentHashMap<DhLodPos, InProgressWorldGenTaskGroup> 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<Boolean> 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<Boolean> 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<WorldGenTaskGroup> 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 <br>
* 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<WorldGenTaskGroup> groupIter = this.taskGroups.values().iterator();
Iterator<WorldGenTaskGroup> groupIter = this.waitingTaskGroupsByLodPos.values().iterator();
// go through each TaskGroup
while (groupIter.hasNext())
{
// go through each WorldGenTask in the TaskGroup
WorldGenTaskGroup group = groupIter.next();
Iterator<WorldGenTask> taskIter = group.generatorTasks.iterator();
WorldGenTaskGroup taskGroup = groupIter.next();
Iterator<WorldGenTask> 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<WorldGenTaskGroup> 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<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
{
this.taskGroups.values().forEach(g -> g.generatorTasks.forEach(t -> t.future.complete(false)));
this.taskGroups.clear();
ArrayList<CompletableFuture<Void>> 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<CompletableFuture<Void>> array = new ArrayList<>(this.inProgressGenTasksByLodPos.size());
this.inProgressGenTasksByLodPos.values().forEach(runningTaskGroup ->
{
CompletableFuture<Void> genFuture = runningTask.genFuture; // Do this to prevent it getting swapped out
CompletableFuture<Void> 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;
}
@@ -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()));
}
}