Refactor WorldGenerationQueue
This commit is contained in:
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user