From df0a5f5bd0b65f935f514b31819ed2401ffff5ba Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 14 Mar 2023 07:30:42 -0500 Subject: [PATCH] finish the world generatior --- .../GeneratedFullDataFileHandler.java | 21 ++++- .../core/generation/WorldGenerationQueue.java | 81 +++++++++---------- .../core/generation/tasks/WorldGenResult.java | 34 ++++++++ .../core/generation/tasks/WorldGenTask.java | 6 +- .../generation/tasks/WorldGenTaskGroup.java | 3 +- .../com/seibel/lod/core/pos/DhSectionPos.java | 7 ++ .../lod/core/render/LodRenderSection.java | 7 ++ .../lod/core/util/objects/QuadTree.java | 13 +++ 8 files changed, 121 insertions(+), 51 deletions(-) create mode 100644 core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenResult.java diff --git a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java index 78fe7759f..053fc0ef0 100644 --- a/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/file/fullDatafile/GeneratedFullDataFileHandler.java @@ -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 siblingFuture : genTaskResult.childFutures) + { + siblingFuture.whenComplete((siblingGenTaskResult, siblingEx) -> this.onWorldGenTaskComplete(siblingGenTaskResult, siblingEx, genTask, pos)); + } + } genTask.releaseStrongReference(); } 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 e446445da..b17907b58 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 @@ -75,23 +75,16 @@ public class WorldGenerationQueue implements Closeable // task handling // //=================// - public CompletableFuture submitGenTask(DhLodPos pos, byte requiredDataDetail, IWorldGenTaskTracker tracker) + public CompletableFuture 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 future = new CompletableFuture<>(); + CompletableFuture 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> 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 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 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 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 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> 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; } diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenResult.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenResult.java new file mode 100644 index 000000000..f6bb45be7 --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenResult.java @@ -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> childFutures = new LinkedList<>(); + + + public static WorldGenResult CreateSplit(Collection> 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> childFutures) + { + this.success = success; + this.pos = pos; + + if (childFutures != null) + { + this.childFutures.addAll(childFutures); + } + } + + +} diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTask.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTask.java index be2de0637..812aab9b5 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTask.java +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTask.java @@ -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 future; + public final CompletableFuture future; - public WorldGenTask(DhLodPos pos, byte dataDetail, IWorldGenTaskTracker taskTracker, CompletableFuture future) + + public WorldGenTask(DhLodPos pos, byte dataDetail, IWorldGenTaskTracker taskTracker, CompletableFuture future) { this.dataDetailLevel = dataDetail; this.pos = pos; diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTaskGroup.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTaskGroup.java index 4a045aa02..ca5187d63 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTaskGroup.java +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTaskGroup.java @@ -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 { diff --git a/core/src/main/java/com/seibel/lod/core/pos/DhSectionPos.java b/core/src/main/java/com/seibel/lod/core/pos/DhSectionPos.java index ff253dff2..d1dccbaf0 100644 --- a/core/src/main/java/com/seibel/lod/core/pos/DhSectionPos.java +++ b/core/src/main/java/com/seibel/lod/core/pos/DhSectionPos.java @@ -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) */ diff --git a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java index fb1e3cf46..bdf209036 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodRenderSection.java @@ -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) diff --git a/core/src/main/java/com/seibel/lod/core/util/objects/QuadTree.java b/core/src/main/java/com/seibel/lod/core/util/objects/QuadTree.java index e82540ae7..071d6c62b 100644 --- a/core/src/main/java/com/seibel/lod/core/util/objects/QuadTree.java +++ b/core/src/main/java/com/seibel/lod/core/util/objects/QuadTree.java @@ -236,6 +236,19 @@ public class QuadTree } 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();