finish the world generatior

This commit is contained in:
James Seibel
2023-03-14 07:30:42 -05:00
parent 44e280e8b7
commit df0a5f5bd0
8 changed files with 121 additions and 51 deletions
@@ -7,6 +7,7 @@ import com.seibel.lod.core.dataObjects.fullData.sources.SparseFullDataSource;
import com.seibel.lod.core.dataObjects.fullData.sources.SingleChunkFullDataSource;
import com.seibel.lod.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.lod.core.generation.WorldGenerationQueue;
import com.seibel.lod.core.generation.tasks.WorldGenResult;
import com.seibel.lod.core.level.IDhServerLevel;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.logging.DhLoggerBuilder;
@@ -105,7 +106,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
// queue this section to be generated
GenTask genTask = new GenTask(pos, new WeakReference<>(incompleteFullDataSource));
worldGenQueue.submitGenTask(incompleteFullDataSource.getSectionPos().getSectionBBoxPos(), incompleteFullDataSource.getDataDetail(), genTask)
.whenComplete((genTaskCompleted, ex) -> this.onWorldGenTaskComplete(genTaskCompleted, ex, genTask, pos));
.whenComplete((genTaskResult, ex) -> this.onWorldGenTaskComplete(genTaskResult, ex, genTask, pos));
}
// return the empty dataSource (it will be populated later)
@@ -149,18 +150,20 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
}
}
private void onWorldGenTaskComplete(Boolean genTaskCompleted, Throwable exception, GenTask genTask, DhSectionPos pos)
private void onWorldGenTaskComplete(WorldGenResult genTaskResult, Throwable exception, GenTask genTask, DhSectionPos pos)
{
if (exception != null)
{
// don't log the shutdown exceptions
// don't log shutdown exceptions
if (!(exception instanceof CancellationException || exception.getCause() instanceof CancellationException))
{
LOGGER.error("Uncaught Gen Task Exception at " + pos + ":", exception);
}
}
else if (genTaskCompleted)
else if (genTaskResult.success)
{
// generation completed, update the files and listener(s)
this.files.get(genTask.pos).flushAndSave();
// fire the event listeners
@@ -172,6 +175,16 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
// this.files.get(genTask.pos).metaData.dataVersion.incrementAndGet();
return;
}
else
{
// generation didn't complete
// if the generation task was split up into smaller positions, wait for them to complete
for (CompletableFuture<WorldGenResult> siblingFuture : genTaskResult.childFutures)
{
siblingFuture.whenComplete((siblingGenTaskResult, siblingEx) -> this.onWorldGenTaskComplete(siblingGenTaskResult, siblingEx, genTask, pos));
}
}
genTask.releaseStrongReference();
}
@@ -75,23 +75,16 @@ public class WorldGenerationQueue implements Closeable
// task handling //
//=================//
public CompletableFuture<Boolean> submitGenTask(DhLodPos pos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
public CompletableFuture<WorldGenResult> submitGenTask(DhLodPos pos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
{
// TODO implement multiple detail level generation
if (pos.detailLevel != 6)
// if (!(pos.detailLevel >= this.minGranularity && pos.detailLevel <= this.maxGranularity))
{
return CompletableFuture.completedFuture(false);
}
// the generator is shutting down, don't add new tasks
if (this.generatorClosingFuture != null)
{
return CompletableFuture.completedFuture(false);
return CompletableFuture.completedFuture(WorldGenResult.CreateFail());
}
// TODO what does these checks and the assert below mean?
// make sure the generator can provide the requested position
if (requiredDataDetail < this.minDataDetail)
{
throw new UnsupportedOperationException("Current generator does not meet requiredDataDetail level");
@@ -101,11 +94,12 @@ public class WorldGenerationQueue implements Closeable
requiredDataDetail = this.maxDataDetail;
}
// TODO what does this assert mean?
LodUtil.assertTrue(pos.detailLevel > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL/*TODO is chunkDetailLevel the correct replacement? otherwise the magic number was 4*/);
CompletableFuture<Boolean> future = new CompletableFuture<>();
CompletableFuture<WorldGenResult> future = new CompletableFuture<>();
this.waitingTaskQuadTree.set(new DhSectionPos(pos.detailLevel, pos.x, pos.z), new WorldGenTask(pos, requiredDataDetail, tracker, future));
return future;
}
@@ -174,13 +168,16 @@ public class WorldGenerationQueue implements Closeable
if (genTask != null && !genTask.taskTracker.isMemoryAddressValid())
{
taskIterator.remove();
genTask.future.complete(false);
genTask.future.complete(WorldGenResult.CreateFail());
}
}
}
}
/** @param targetPos the position to center the generation around */
/**
* @param targetPos the position to center the generation around
* @return false if no tasks were found to generate
*/
private boolean startNextWorldGenTask(DhBlockPos2D targetPos)
{
WorldGenTask closestTask = null;
@@ -259,35 +256,46 @@ public class WorldGenerationQueue implements Closeable
}
else
{
// detail level is (probably) too high, split up the task
LodUtil.assertTrue(closestTask == removedWorldGenTask); // should be the same memory address, removedWorldGenTask shouldn't be null // TODO why shouldn't it be null?
// detail level is too high (if the detail level was too low, the generator would've ignored the request),
// split up the task
// make sure that we have a task to split up
LodUtil.assertTrue(closestTask == removedWorldGenTask);
// split up the task and add each one to the tree
LinkedList<CompletableFuture<WorldGenResult>> childFutures = new LinkedList<>();
DhSectionPos sectionPos = new DhSectionPos(closestTask.pos.detailLevel, closestTask.pos.x, closestTask.pos.z);
sectionPos.forEachChild((childDhSectionPos) ->
{
WorldGenTask newGenTask = new WorldGenTask(new DhLodPos(childDhSectionPos.sectionDetailLevel, childDhSectionPos.sectionX, childDhSectionPos.sectionZ), childDhSectionPos.sectionDetailLevel, removedWorldGenTask.taskTracker, removedWorldGenTask.future /*TODO probably need to do something about the futures here*/);
CompletableFuture<WorldGenResult> newFuture = new CompletableFuture<>();
childFutures.add(newFuture);
WorldGenTask newGenTask = new WorldGenTask(new DhLodPos(childDhSectionPos.sectionDetailLevel, childDhSectionPos.sectionX, childDhSectionPos.sectionZ), childDhSectionPos.sectionDetailLevel, removedWorldGenTask.taskTracker, newFuture);
this.waitingTaskQuadTree.set(childDhSectionPos.sectionDetailLevel, childDhSectionPos.sectionX, childDhSectionPos.sectionZ, newGenTask);
});
// send the child futures to the future recipient, to notify them of the new tasks
removedWorldGenTask.future.complete(WorldGenResult.CreateSplit(childFutures));
// return true so we attempt to generate again
return true;
}
}
private void startWorldGenTaskGroup(InProgressWorldGenTaskGroup task)
private void startWorldGenTaskGroup(InProgressWorldGenTaskGroup inProgressTaskGroup)
{
byte taskDetailLevel = task.group.dataDetail;
DhLodPos taskPos = task.group.pos;
byte taskDetailLevel = inProgressTaskGroup.group.dataDetail;
DhLodPos taskPos = inProgressTaskGroup.group.pos;
byte granularity = (byte) (taskPos.detailLevel - taskDetailLevel);
LodUtil.assertTrue(granularity >= this.minGranularity && granularity <= this.maxGranularity);
LodUtil.assertTrue(taskDetailLevel >= this.minDataDetail && taskDetailLevel <= this.maxDataDetail);
DhChunkPos chunkPosMin = new DhChunkPos(taskPos.getCornerBlockPos());
LOGGER.info("Generating section "+taskPos+" with granularity "+granularity+" at "+chunkPosMin);
// LOGGER.info("Generating section "+taskPos+" with granularity "+granularity+" at "+chunkPosMin);
task.genFuture = startGenerationEvent(this.generator, chunkPosMin, granularity, taskDetailLevel, task.group::onGenerationComplete);
task.genFuture.whenComplete((voidObj, exception) ->
inProgressTaskGroup.genFuture = startGenerationEvent(this.generator, chunkPosMin, granularity, taskDetailLevel, inProgressTaskGroup.group::onGenerationComplete);
inProgressTaskGroup.genFuture.whenComplete((voidObj, exception) ->
{
if (exception != null)
{
@@ -297,14 +305,14 @@ public class WorldGenerationQueue implements Closeable
LOGGER.error("Error generating data for section "+taskPos, exception);
}
task.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(false));
inProgressTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateFail()));
}
else
{
//LOGGER.info("Section generation at "+pos+" completed");
task.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(true));
inProgressTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(new DhSectionPos(granularity, taskPos))));
}
boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, task);
boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, inProgressTaskGroup);
LodUtil.assertTrue(worked);
});
}
@@ -317,25 +325,9 @@ public class WorldGenerationQueue implements Closeable
public CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
{
// remove any incomplete generation tasks
for (byte detailLevel = QuadTree.TREE_LOWEST_DETAIL_LEVEL; detailLevel < this.waitingTaskQuadTree.treeMaxDetailLevel; detailLevel++)
{
// TODO remove
// Iterator<WorldGenTask> ringListIterator = this.waitingTaskQuadTree.getRingList(detailLevel).iterator();
// while (ringListIterator.hasNext())
// {
// WorldGenTask worldGenTask = ringListIterator.next();
// if (worldGenTask != null)
// {
// try
// {
// worldGenTask.future.cancel(true);
// }
// catch (CancellationException ignored)
// { /* don't log shutdown exceptions */ }
// }
// }
// TODO shouldn't I clear the list? not just cancel each item?
MovableGridRingList<WorldGenTask> ringList = this.waitingTaskQuadTree.getRingList(detailLevel);
ringList.clear((worldGenTask) ->
{
@@ -352,6 +344,7 @@ public class WorldGenerationQueue implements Closeable
}
// stop and remove any in progress tasks
ArrayList<CompletableFuture<Void>> inProgressTasksCancelingFutures = new ArrayList<>(this.inProgressGenTasksByLodPos.size());
this.inProgressGenTasksByLodPos.values().forEach(runningTaskGroup ->
{
@@ -377,7 +370,7 @@ public class WorldGenerationQueue implements Closeable
return null;
}));
});
this.generatorClosingFuture = CompletableFuture.allOf(inProgressTasksCancelingFutures.toArray(new CompletableFuture[0])); //FIXME: Closer threading issues with runCurrentGenTasksUntilBusy
this.generatorClosingFuture = CompletableFuture.allOf(inProgressTasksCancelingFutures.toArray(new CompletableFuture[0]));
return this.generatorClosingFuture;
}
@@ -0,0 +1,34 @@
package com.seibel.lod.core.generation.tasks;
import com.seibel.lod.core.pos.DhSectionPos;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.CompletableFuture;
public class WorldGenResult
{
/** true if terrain was generated */
public final boolean success;
/** the position that was generated, will be null if nothing was generated */
public final DhSectionPos pos;
/** 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<CompletableFuture<WorldGenResult>> childFutures = new LinkedList<>();
public static WorldGenResult CreateSplit(Collection<CompletableFuture<WorldGenResult>> siblingFutures) { return new WorldGenResult(false, null, siblingFutures); }
public static WorldGenResult CreateFail() { return new WorldGenResult(false, null, null); }
public static WorldGenResult CreateSuccess(DhSectionPos pos) { return new WorldGenResult(true, pos, null); }
private WorldGenResult(boolean success, DhSectionPos pos, Collection<CompletableFuture<WorldGenResult>> childFutures)
{
this.success = success;
this.pos = pos;
if (childFutures != null)
{
this.childFutures.addAll(childFutures);
}
}
}
@@ -2,6 +2,7 @@ package com.seibel.lod.core.generation.tasks;
import com.seibel.lod.core.pos.DhLodPos;
import java.util.LinkedList;
import java.util.concurrent.CompletableFuture;
/**
@@ -13,10 +14,11 @@ public final class WorldGenTask
public final DhLodPos pos;
public final byte dataDetailLevel;
public final IWorldGenTaskTracker taskTracker;
public final CompletableFuture<Boolean> future;
public final CompletableFuture<WorldGenResult> future;
public WorldGenTask(DhLodPos pos, byte dataDetail, IWorldGenTaskTracker taskTracker, CompletableFuture<Boolean> future)
public WorldGenTask(DhLodPos pos, byte dataDetail, IWorldGenTaskTracker taskTracker, CompletableFuture<WorldGenResult> future)
{
this.dataDetailLevel = dataDetail;
this.pos = pos;
@@ -2,6 +2,7 @@ package com.seibel.lod.core.generation.tasks;
import com.seibel.lod.core.dataObjects.fullData.sources.ChunkSizedFullDataSource;
import com.seibel.lod.core.pos.DhLodPos;
import com.seibel.lod.core.pos.DhSectionPos;
import java.util.Iterator;
import java.util.LinkedList;
@@ -38,7 +39,7 @@ public final class WorldGenTaskGroup
if (onGenTaskCompleteConsumer == null)
{
tasks.remove();
task.future.complete(false);
task.future.complete(WorldGenResult.CreateFail());
}
else
{
@@ -66,6 +66,13 @@ public class DhSectionPos
this.sectionZ = lodPos.z;
}
public DhSectionPos(byte detailLevel, DhLodPos dhLodPos)
{
this.sectionDetailLevel = detailLevel;
this.sectionX = dhLodPos.x;
this.sectionZ = dhLodPos.z;
}
/** Returns the center for the highest detail level (0) */
@@ -80,6 +80,13 @@ public class LodRenderSection
public void reload(ILodRenderSourceProvider renderDataProvider)
{
// don't accidentally enable rendering for a disabled section
if (!this.isRenderEnabled)
{
return;
}
this.renderSourceProvider = renderDataProvider;
if (this.loadFuture != null)
@@ -236,6 +236,19 @@ public class QuadTree<T>
}
public boolean isDetailLevelEmpty(byte detailLevel) { return this.getRingList(detailLevel).isEmpty(); }
/** returns the number of items in this QuadTree */
public int size()
{
int size = 0;
for (byte detailLevel = QuadTree.TREE_LOWEST_DETAIL_LEVEL; detailLevel < this.treeMaxDetailLevel; detailLevel++)
{
size += getRingList(detailLevel).size();
}
return size;
}
public String getDebugString()
{
StringBuilder sb = new StringBuilder();