diff --git a/core/src/main/java/com/seibel/lod/core/file/datafile/GeneratedDataFileHandler.java b/core/src/main/java/com/seibel/lod/core/file/datafile/GeneratedDataFileHandler.java index 96766b48a..1ca37b5f6 100644 --- a/core/src/main/java/com/seibel/lod/core/file/datafile/GeneratedDataFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/file/datafile/GeneratedDataFileHandler.java @@ -5,7 +5,8 @@ import com.seibel.lod.core.datatype.ILodDataSource; import com.seibel.lod.core.datatype.full.ChunkSizedData; import com.seibel.lod.core.datatype.full.SparseDataSource; import com.seibel.lod.core.datatype.full.SpottyDataSource; -import com.seibel.lod.core.generation.GenerationQueue; +import com.seibel.lod.core.generation.tasks.AbstractWorldGenTaskTracker; +import com.seibel.lod.core.generation.WorldGenerationQueue; import com.seibel.lod.core.level.IDhServerLevel; import com.seibel.lod.core.pos.DhSectionPos; import com.seibel.lod.core.logging.DhLoggerBuilder; @@ -22,11 +23,12 @@ import java.util.function.Consumer; public class GeneratedDataFileHandler extends DataFileHandler { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - AtomicReference queue = new AtomicReference<>(null); + AtomicReference queue = new AtomicReference<>(null); // TODO: Should I include a lib that impl weak concurrent hash map? final Map genQueue = Collections.synchronizedMap(new WeakHashMap<>()); - class GenTask extends GenerationQueue.GenTaskTracker { + class GenTask extends AbstractWorldGenTaskTracker + { final DhSectionPos pos; WeakReference targetData; ILodDataSource loadedTargetData = null; @@ -60,7 +62,7 @@ public class GeneratedDataFileHandler extends DataFileHandler { super(level, saveRootDir); } - public void setGenerationQueue(GenerationQueue newQueue) { + public void setGenerationQueue(WorldGenerationQueue newQueue) { boolean worked = queue.compareAndSet(null, newQueue); LodUtil.assertTrue(worked, "previous queue is still here!"); synchronized (genQueue) { @@ -85,8 +87,8 @@ public class GeneratedDataFileHandler extends DataFileHandler { } } - public GenerationQueue popGenerationQueue() { - GenerationQueue cas = queue.getAndSet(null); + public WorldGenerationQueue popGenerationQueue() { + WorldGenerationQueue cas = queue.getAndSet(null); LodUtil.assertTrue(cas != null, "there are no previous live generation queue!"); return cas; } @@ -102,7 +104,7 @@ public class GeneratedDataFileHandler extends DataFileHandler { // None exist. IIncompleteDataSource dataSource = pos.sectionDetail <= SparseDataSource.MAX_SECTION_DETAIL ? SparseDataSource.createEmpty(pos) : SpottyDataSource.createEmpty(pos); - GenerationQueue getQueue = queue.get(); + WorldGenerationQueue getQueue = queue.get(); GenTask task = new GenTask(pos, new WeakReference<>(dataSource)); genQueue.put(dataSource, task); if (getQueue != null) { diff --git a/core/src/main/java/com/seibel/lod/core/generation/BatchGenerator.java b/core/src/main/java/com/seibel/lod/core/generation/BatchGenerator.java index 157f0ab4b..7b17eab9f 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/BatchGenerator.java +++ b/core/src/main/java/com/seibel/lod/core/generation/BatchGenerator.java @@ -26,6 +26,7 @@ import com.seibel.lod.core.dependencyInjection.SingletonInjector; import com.seibel.lod.core.logging.DhLoggerBuilder; import com.seibel.lod.core.pos.DhChunkPos; import com.seibel.lod.core.util.BitShiftUtil; +import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory; import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; @@ -38,7 +39,7 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; /** - * + * @author Leetom * @version 2022-11-25 */ public class BatchGenerator implements IChunkGenerator @@ -103,7 +104,7 @@ public class BatchGenerator implements IChunkGenerator int chunkXMin = chunkPosMin.x; int chunkZMin = chunkPosMin.z; - int genChunkSize = BitShiftUtil.powerOfTwo(granularity - 4); // minus 4 for chunk size as its equal to dividing by 16 + int genChunkSize = BitShiftUtil.powerOfTwo(granularity - 4); // minus 4 for chunk size is equal to dividing by 16 double runTimeRatio = Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get() > 1 ? 1.0 : Config.Client.Advanced.Threading.numberOfWorldGenerationThreads.get(); @@ -111,19 +112,19 @@ public class BatchGenerator implements IChunkGenerator } @Override - public byte getMinDataDetail() { return 0; } + public byte getMinDataDetailLevel() { return LodUtil.BLOCK_DETAIL_LEVEL; } @Override - public byte getMaxDataDetail() { return 0; } + public byte getMaxDataDetailLevel() { return LodUtil.BLOCK_DETAIL_LEVEL; } @Override - public int getPriority() { return 0; } + public int getPriority() { return LodUtil.BLOCK_DETAIL_LEVEL; } @Override - public byte getMinGenerationGranularity() { return 4; } + public byte getMinGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL; } @Override - public byte getMaxGenerationGranularity() { return 6; } + public byte getMaxGenerationGranularity() { return LodUtil.CHUNK_DETAIL_LEVEL + 2; } @Override public void close() { this.stop(true); } diff --git a/core/src/main/java/com/seibel/lod/core/generation/IChunkGenerator.java b/core/src/main/java/com/seibel/lod/core/generation/IChunkGenerator.java index 5c9fb525d..358e32725 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/IChunkGenerator.java +++ b/core/src/main/java/com/seibel/lod/core/generation/IChunkGenerator.java @@ -5,27 +5,74 @@ import com.seibel.lod.core.datatype.transform.LodDataBuilder; import com.seibel.lod.core.pos.DhChunkPos; import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; +import java.io.Closeable; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; /** + * @author Leetom * @version 2022-11-25 */ -public interface IChunkGenerator extends IGenerator +public interface IChunkGenerator extends Closeable { - CompletableFuture generateChunks(DhChunkPos chunkPosMin, - byte granularity, byte targetDataDetail, + //============// + // parameters // + //============// + + /** + * What is the detail/resolution of the data? (This will offset the generation granularity) + * (minimum detail is 0, maximum detail is 255) (though that high isn't really... realistic) + * (0 = 1x1 block per data, 1 = 2x2 block per data, 2 = 4x4 block per data... etc.) + * TODO: System currently only supports 1x1 block per data. + */ + byte getMinDataDetailLevel(); + byte getMaxDataDetailLevel(); + + int getPriority(); + + /** + * What is the min batch size of a single generation call? + * (minimum return value is 4 since that's the MC chunk size) + * (4 -> 16x16 data per call, 5 -> 32x32 data per call, 6 -> 64x64 data per call... etc.) + */ + byte getMinGenerationGranularity(); + + /** + * What is the max batch size of a single generation call? + * The system will try to group tasks to the max batch size if possible + * (minimum return value is 4 since that's the MC chunk size) + * (4 -> 16x16 data per call, 5 -> 32x32 data per call, 6 -> 64x64 data per call... etc.) + */ + byte getMaxGenerationGranularity(); + + /** Returns whether the generator is unable to accept new generation requests. */ + boolean isBusy(); + + + + //=================// + // world generator // + //=================// + + CompletableFuture generateChunks(DhChunkPos chunkPosMin, + byte granularity, byte targetDataDetail, Consumer resultConsumer); - @Override - default CompletableFuture generate(DhChunkPos chunkPosMin, - byte granularity, byte targetDataDetail, + + /** + * Start a generation event + * (Note that the chunkPos is always aligned to the granularity) + * (For example, if the granularity is 4, data detail is 0, the chunkPos will be aligned to 16x16 blocks) + */ + default CompletableFuture generate(DhChunkPos chunkPosMin, + byte granularity, byte targetDataDetail, Consumer resultConsumer) { - return this.generateChunks(chunkPosMin, granularity, targetDataDetail, (chunk) -> + return this.generateChunks(chunkPosMin, granularity, targetDataDetail, (chunk) -> { resultConsumer.accept(LodDataBuilder.createChunkData(chunk)); }); } + } diff --git a/core/src/main/java/com/seibel/lod/core/generation/IGenerator.java b/core/src/main/java/com/seibel/lod/core/generation/IGenerator.java deleted file mode 100644 index b72506b25..000000000 --- a/core/src/main/java/com/seibel/lod/core/generation/IGenerator.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.seibel.lod.core.generation; - -import com.seibel.lod.core.datatype.full.ChunkSizedData; -import com.seibel.lod.core.pos.DhChunkPos; - -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -/** - * @version 2022-11-25 - */ -public interface IGenerator extends AutoCloseable -{ - /** - * What is the detail/resolution of the data? (This will offset the generation granularity) - * (minimum detail is 0, maximum detail is 255) (though that high isn't really... realistic) - * (0 = 1x1 block per data, 1 = 2x2 block per data, 2 = 4x4 block per data... etc.) - * TODO: System currently only supports 1x1 block per data. - */ - byte getMinDataDetail(); - byte getMaxDataDetail(); - int getPriority(); - - /** - * What is the min batch size of a single generation? - * (minimum return value is 4 since that's the MC chunk size) - * (4 -> 16x16 data per call, 5 -> 32x32 data per call, 6 -> 64x64 data per call... etc.) - */ - byte getMinGenerationGranularity(); - - /** - * What is the max batch size of a single generation? The system will try to group tasks to the max batch size if possible - * (minimum return value is 4 since that's the MC chunk size) - * (4 -> 16x16 data per call, 5 -> 32x32 data per call, 6 -> 64x64 data per call... etc.) - */ - byte getMaxGenerationGranularity(); - - /** - * Start a generation event - * (Note that the chunkPos is always aligned to the granularity) - * (For example, if the granularity is 4, data detail is 0, the chunkPos will be aligned to 16x16 blocks) - */ - CompletableFuture generate(DhChunkPos chunkPosMin, byte granularity, byte targetDataDetail, Consumer resultConsumer); - - /** Returns whether the generator is unable to accept new generation requests. */ - boolean isBusy(); - -} diff --git a/core/src/main/java/com/seibel/lod/core/generation/GenerationQueue.java b/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java similarity index 77% rename from core/src/main/java/com/seibel/lod/core/generation/GenerationQueue.java rename to core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java index c48d7f479..e2c2931c9 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/GenerationQueue.java +++ b/core/src/main/java/com/seibel/lod/core/generation/WorldGenerationQueue.java @@ -1,6 +1,6 @@ package com.seibel.lod.core.generation; -import com.seibel.lod.core.datatype.full.ChunkSizedData; +import com.seibel.lod.core.generation.tasks.*; import com.seibel.lod.core.pos.DhBlockPos2D; import com.seibel.lod.core.pos.DhLodPos; import com.seibel.lod.core.pos.Pos2D; @@ -13,145 +13,21 @@ import org.apache.logging.log4j.Logger; import java.io.Closeable; import java.util.*; import java.util.concurrent.*; -import java.util.function.Consumer; /** + * @author Leetom * @version 2022-11-25 */ -public class GenerationQueue implements Closeable +public class WorldGenerationQueue implements Closeable { public static final int SHUTDOWN_TIMEOUT_SEC = 10; public static final int MAX_TASKS_PROCESSED_PER_TICK = 10000; private static final Logger LOGGER = DhLoggerBuilder.getLogger(); + private final IChunkGenerator generator; - /** - * Source: ... - * Description: Left-upper semi-diagonal (0-4-16-36-64) contains squared layer number (4 * layer^2). - * External if-statement defines layer and finds (pre-)result for position in corresponding row or - * column of left-upper semi-plane, and internal if-statement corrects result for mirror position. - */ - private static int gridSpiralIndexing(int X, int Y) - { - int index = 0; - if (X * X >= Y * Y) - { - index = 4 * X * X - X - Y; - if (X < Y) - index = index - 2 * (X - Y); - } - else - { - index = 4 * Y * Y - X - Y; - if (X < Y) - index = index + 2 * (X - Y); - } - - return index; - } - - public static abstract class GenTaskTracker - { - public abstract boolean isValid(); - - public abstract Consumer getConsumer(); - } - - final IGenerator generator; - - static final class GenTask - { - final DhLodPos pos; - final byte dataDetail; - final GenTaskTracker taskTracker; - final CompletableFuture future; - - GenTask(DhLodPos pos, byte dataDetail, GenTaskTracker taskTracker, CompletableFuture future) - { - this.dataDetail = dataDetail; - this.pos = pos; - this.taskTracker = taskTracker; - this.future = future; - } - } - - static final class TaskGroup - { - final DhLodPos pos; - byte dataDetail; - final LinkedList members = new LinkedList<>(); // Accessed by gen poller thread only - - TaskGroup(DhLodPos pos, byte dataDetail) - { - this.pos = pos; - this.dataDetail = dataDetail; - } - - void accept(ChunkSizedData data) - { - Iterator iter = this.members.iterator(); - while (iter.hasNext()) - { - GenTask task = iter.next(); - Consumer consumer = task.taskTracker.getConsumer(); - if (consumer == null) - { - iter.remove(); - task.future.complete(false); - } - else - { - consumer.accept(data); - } - } - } - } - - static final class InProgressTask - { - final TaskGroup group; - CompletableFuture genFuture = null; - - InProgressTask(TaskGroup group) - { - this.group = group; - } - } - - static class SplitTask extends GenTaskTracker - { - final GenTaskTracker parentTracker; - final CompletableFuture parentFuture; - boolean cachedValid = true; - - SplitTask(GenTaskTracker parentTracker, CompletableFuture parentFuture) - { - this.parentTracker = parentTracker; - this.parentFuture = parentFuture; - } - - boolean recheckState() - { - if (!this.cachedValid) - return false; - - this.cachedValid = this.parentTracker.isValid(); - if (!this.cachedValid) - this.parentFuture.complete(false); - - return this.cachedValid; - } - - @Override - public boolean isValid() { return this.cachedValid; } - - @Override - public Consumer getConsumer() { return this.parentTracker.getConsumer(); } - - } - - private final ConcurrentLinkedQueue looseTasks = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue looseTasks = new ConcurrentLinkedQueue<>(); // FIXME: Concurrency issue on close! // FIXME: This is using up a TONS of time to process! private final ConcurrentSkipListMap taskGroups = new ConcurrentSkipListMap<>( @@ -168,21 +44,26 @@ public class GenerationQueue implements Closeable } ); // Accessed by poller only - private final ConcurrentHashMap inProgress = new ConcurrentHashMap<>(); + private final ConcurrentHashMap inProgress = new ConcurrentHashMap<>(); + // granularity is the detail level for batching world generator requests together private final byte maxGranularity; private final byte minGranularity; + private final byte maxDataDetail; private final byte minDataDetail; private volatile CompletableFuture closer = null; - public GenerationQueue(IGenerator generator) + + + + public WorldGenerationQueue(IChunkGenerator generator) { this.generator = generator; this.maxGranularity = generator.getMaxGenerationGranularity(); this.minGranularity = generator.getMinGenerationGranularity(); - this.maxDataDetail = generator.getMaxDataDetail(); - this.minDataDetail = generator.getMinDataDetail(); + this.maxDataDetail = generator.getMaxDataDetailLevel(); + this.minDataDetail = generator.getMinDataDetailLevel(); if (this.minGranularity < 4) throw new IllegalArgumentException("DH-IGenerator: min granularity must be at least 4!"); @@ -190,7 +71,10 @@ public class GenerationQueue implements Closeable throw new IllegalArgumentException("DH-IGenerator: max granularity smaller than min granularity!"); } - public CompletableFuture submitGenTask(DhLodPos pos, byte requiredDataDetail, GenTaskTracker tracker) + + + + public CompletableFuture submitGenTask(DhLodPos pos, byte requiredDataDetail, AbstractWorldGenTaskTracker tracker) { if (this.closer != null) return CompletableFuture.completedFuture(false); @@ -212,8 +96,8 @@ public class GenerationQueue implements Closeable int subPosCount = pos.getBlockWidth(subDetail); DhLodPos cornerSubPos = pos.getCorner(subDetail); CompletableFuture[] subFutures = new CompletableFuture[subPosCount * subPosCount]; - ArrayList subTasks = new ArrayList<>(subPosCount * subPosCount); - SplitTask splitTask = new SplitTask(tracker, new CompletableFuture<>()); + ArrayList subTasks = new ArrayList<>(subPosCount * subPosCount); + SplitTaskTracker splitTaskTracker = new SplitTaskTracker(tracker, new CompletableFuture<>()); { int i = 0; for (int ox = 0; ox < subPosCount; ox++) @@ -222,31 +106,31 @@ public class GenerationQueue implements Closeable { CompletableFuture subFuture = new CompletableFuture<>(); subFutures[i++] = subFuture; - subTasks.add(new GenTask(cornerSubPos.addOffset(ox, oz), requiredDataDetail, splitTask, subFuture)); + subTasks.add(new WorldGenTask(cornerSubPos.addOffset(ox, oz), requiredDataDetail, splitTaskTracker, subFuture)); } } } CompletableFuture.allOf(subFutures).whenComplete((v, ex) -> { if (ex != null) - splitTask.parentFuture.completeExceptionally(ex); - if (!splitTask.recheckState()) + splitTaskTracker.parentFuture.completeExceptionally(ex); + if (!splitTaskTracker.recheckState()) return; // Auto join future for (CompletableFuture subFuture : subFutures) { boolean successful = subFuture.join(); if (!successful) { - splitTask.parentFuture.complete(false); + splitTaskTracker.parentFuture.complete(false); return; } } - splitTask.parentFuture.complete(true); + splitTaskTracker.parentFuture.complete(true); }); this.looseTasks.addAll(subTasks); if (this.closer != null) return CompletableFuture.completedFuture(false); else - return splitTask.parentFuture; + return splitTaskTracker.parentFuture; } else if (granularity < this.minGranularity) { @@ -254,7 +138,7 @@ public class GenerationQueue implements Closeable byte parentDetail = (byte) (this.minGranularity + requiredDataDetail); DhLodPos parentPos = pos.convertUpwardsTo(parentDetail); CompletableFuture future = new CompletableFuture<>(); - this.looseTasks.add(new GenTask(parentPos, requiredDataDetail, tracker, future)); + this.looseTasks.add(new WorldGenTask(parentPos, requiredDataDetail, tracker, future)); if (this.closer != null) return CompletableFuture.completedFuture(false); else @@ -263,7 +147,7 @@ public class GenerationQueue implements Closeable else { CompletableFuture future = new CompletableFuture<>(); - this.looseTasks.add(new GenTask(pos, requiredDataDetail, tracker, future)); + this.looseTasks.add(new WorldGenTask(pos, requiredDataDetail, tracker, future)); if (this.closer != null) return CompletableFuture.completedFuture(false); else @@ -293,7 +177,7 @@ public class GenerationQueue implements Closeable // 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.members.addAll(group.members); + target.generatorTasks.addAll(group.generatorTasks); } } @@ -337,7 +221,7 @@ public class GenerationQueue implements Closeable { // We can just append us into the existing list. for (TaskGroup g : groups) - newGroup.members.addAll(g.members); + newGroup.generatorTasks.addAll(g.generatorTasks); } else { @@ -346,7 +230,7 @@ public class GenerationQueue implements Closeable boolean worked = this.taskGroups.remove(parentPos, newGroup); // Pop it off for later proper merge check LodUtil.assertTrue(worked); for (TaskGroup g : groups) - newGroup.members.addAll(g.members); + newGroup.generatorTasks.addAll(g.generatorTasks); this.addAndCombineGroup(newGroup); // Recursive check the new group } } @@ -355,7 +239,7 @@ public class GenerationQueue implements Closeable // There should not be any higher granularity to check, as otherwise we would have merged ages ago newGroup = new TaskGroup(parentPos, target.dataDetail); for (TaskGroup g : groups) - newGroup.members.addAll(g.members); + 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 @@ -372,9 +256,9 @@ public class GenerationQueue implements Closeable int taskProcessed = 0; while (!this.looseTasks.isEmpty() && taskProcessed < MAX_TASKS_PROCESSED_PER_TICK) { - GenTask task = this.looseTasks.poll(); + WorldGenTask task = this.looseTasks.poll(); taskProcessed++; - byte taskDataDetail = task.dataDetail; + byte taskDataDetail = task.dataDetailLevel; byte taskGranularity = (byte) (task.pos.detailLevel - taskDataDetail); LodUtil.assertTrue(taskGranularity >= 4 && taskGranularity >= this.minGranularity && taskGranularity <= this.maxGranularity); @@ -385,7 +269,7 @@ public class GenerationQueue implements Closeable if (group.dataDetail <= taskDataDetail) { // We can just append us into the existing list. - group.members.add(task); + group.generatorTasks.add(task); } else { @@ -393,7 +277,7 @@ public class GenerationQueue implements Closeable group.dataDetail = taskDataDetail; boolean worked = this.taskGroups.remove(task.pos, group); // Pop it off for later proper merge check LodUtil.assertTrue(worked); - group.members.add(task); + group.generatorTasks.add(task); this.addAndCombineGroup(group); } } @@ -409,7 +293,7 @@ public class GenerationQueue implements Closeable if (group != null && group.dataDetail == taskDataDetail) { // We can just append to the higher granularity group one - group.members.add(task); + group.generatorTasks.add(task); didAnything = true; break; } @@ -417,7 +301,7 @@ public class GenerationQueue implements Closeable if (!didAnything) { group = new TaskGroup(task.pos, taskDataDetail); - group.members.add(task); + group.generatorTasks.add(task); this.addAndCombineGroup(group); } } @@ -437,10 +321,10 @@ public class GenerationQueue implements Closeable while (groupIter.hasNext()) { TaskGroup group = groupIter.next(); - Iterator taskIter = group.members.iterator(); + Iterator taskIter = group.generatorTasks.iterator(); while (taskIter.hasNext()) { - GenTask task = taskIter.next(); + WorldGenTask task = taskIter.next(); if (!task.taskTracker.isValid()) { taskIter.remove(); @@ -448,7 +332,7 @@ public class GenerationQueue implements Closeable } } - if (group.members.isEmpty()) + if (group.generatorTasks.isEmpty()) groupIter.remove(); } } @@ -485,8 +369,8 @@ public class GenerationQueue implements Closeable if (best != null) { - InProgressTask startedTask = new InProgressTask(best); - InProgressTask casTask = this.inProgress.putIfAbsent(best.pos, startedTask); + InProgressWorldGenTask startedTask = new InProgressWorldGenTask(best); + InProgressWorldGenTask 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) @@ -516,7 +400,7 @@ public class GenerationQueue implements Closeable this.pollAndStartNext(targetPos); } - private void startTaskGroup(InProgressTask task) + private void startTaskGroup(InProgressWorldGenTask task) { byte dataDetail = task.group.dataDetail; DhLodPos pos = task.group.pos; @@ -533,12 +417,12 @@ public class GenerationQueue implements Closeable { if (!UncheckedInterruptedException.isThrowableInterruption(ex)) LOGGER.error("Error generating data for section {}", pos, ex); - task.group.members.forEach(m -> m.future.complete(false)); + task.group.generatorTasks.forEach(m -> m.future.complete(false)); } else { LOGGER.info("Section generation at {} complated", pos); - task.group.members.forEach(m -> m.future.complete(true)); + task.group.generatorTasks.forEach(m -> m.future.complete(true)); } boolean worked = inProgress.remove(pos, task); LodUtil.assertTrue(worked); @@ -547,7 +431,7 @@ public class GenerationQueue implements Closeable public CompletableFuture startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning) { - this.taskGroups.values().forEach(g -> g.members.forEach(t -> t.future.complete(false))); + this.taskGroups.values().forEach(g -> g.generatorTasks.forEach(t -> t.future.complete(false))); this.taskGroups.clear(); ArrayList> array = new ArrayList<>(inProgress.size()); this.inProgress.values().forEach(runningTask -> @@ -584,4 +468,36 @@ public class GenerationQueue implements Closeable LOGGER.error("Failed to close generation queue: ", e); } } + + + + //================// + // helper methods // + //================// + + /** + * Source: ... + * Description: Left-upper semi-diagonal (0-4-16-36-64) contains squared layer number (4 * layer^2). + * External if-statement defines layer and finds (pre-)result for position in corresponding row or + * column of left-upper semi-plane, and internal if-statement corrects result for mirror position. + */ + private static int gridSpiralIndexing(int X, int Y) + { + int index = 0; + if (X * X >= Y * Y) + { + index = 4 * X * X - X - Y; + if (X < Y) + index = index - 2 * (X - Y); + } + else + { + index = 4 * Y * Y - X - Y; + if (X < Y) + index = index + 2 * (X - Y); + } + + return index; + } + } diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/AbstractWorldGenTaskTracker.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/AbstractWorldGenTaskTracker.java new file mode 100644 index 000000000..7261df891 --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/AbstractWorldGenTaskTracker.java @@ -0,0 +1,17 @@ +package com.seibel.lod.core.generation.tasks; + +import com.seibel.lod.core.datatype.full.ChunkSizedData; + +import java.util.function.Consumer; + +/** + * @author Leetom + * @version 2022-11-25 + */ +public abstract class AbstractWorldGenTaskTracker +{ + public abstract boolean isValid(); + + public abstract Consumer getConsumer(); + +} diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/InProgressWorldGenTask.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/InProgressWorldGenTask.java new file mode 100644 index 000000000..e4f997a1c --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/InProgressWorldGenTask.java @@ -0,0 +1,20 @@ +package com.seibel.lod.core.generation.tasks; + +import java.util.concurrent.CompletableFuture; + +/** + * @author Leetom + * @version 2022-11-25 + */ +public final class InProgressWorldGenTask +{ + public final TaskGroup group; + public CompletableFuture genFuture = null; + + + public InProgressWorldGenTask(TaskGroup group) + { + this.group = group; + } + +} diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/SplitTaskTracker.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/SplitTaskTracker.java new file mode 100644 index 000000000..6739019b7 --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/SplitTaskTracker.java @@ -0,0 +1,46 @@ +package com.seibel.lod.core.generation.tasks; + +import com.seibel.lod.core.datatype.full.ChunkSizedData; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +/** + * @author Leetom + * @version 2022-11-25 + */ +public class SplitTaskTracker extends AbstractWorldGenTaskTracker +{ + public final AbstractWorldGenTaskTracker parentTracker; + public final CompletableFuture parentFuture; + public boolean cachedValid = true; + + + + public SplitTaskTracker(AbstractWorldGenTaskTracker parentTracker, CompletableFuture parentFuture) + { + this.parentTracker = parentTracker; + this.parentFuture = parentFuture; + } + + + + public boolean recheckState() + { + if (!this.cachedValid) + return false; + + this.cachedValid = this.parentTracker.isValid(); + if (!this.cachedValid) + this.parentFuture.complete(false); + + return this.cachedValid; + } + + @Override + public boolean isValid() { return this.cachedValid; } + + @Override + public Consumer getConsumer() { return this.parentTracker.getConsumer(); } + +} diff --git a/core/src/main/java/com/seibel/lod/core/generation/tasks/TaskGroup.java b/core/src/main/java/com/seibel/lod/core/generation/tasks/TaskGroup.java new file mode 100644 index 000000000..7282f5493 --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/TaskGroup.java @@ -0,0 +1,50 @@ +package com.seibel.lod.core.generation.tasks; + +import com.seibel.lod.core.datatype.full.ChunkSizedData; +import com.seibel.lod.core.pos.DhLodPos; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.function.Consumer; + +/** + * @author Leetom + * @version 2022-11-25 + */ +public final class TaskGroup +{ + public final DhLodPos pos; + public byte dataDetail; + /** Only accessed by the generator polling thread */ + public final LinkedList generatorTasks = new LinkedList<>(); + + + + public TaskGroup(DhLodPos pos, byte dataDetail) + { + this.pos = pos; + this.dataDetail = dataDetail; + } + + + + public void accept(ChunkSizedData data) + { + Iterator tasks = this.generatorTasks.iterator(); + while (tasks.hasNext()) + { + WorldGenTask task = tasks.next(); + Consumer consumer = task.taskTracker.getConsumer(); + if (consumer == null) + { + tasks.remove(); + task.future.complete(false); + } + else + { + consumer.accept(data); + } + } + } + +} 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 new file mode 100644 index 000000000..d0b710755 --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/generation/tasks/WorldGenTask.java @@ -0,0 +1,27 @@ +package com.seibel.lod.core.generation.tasks; + +import com.seibel.lod.core.pos.DhLodPos; + +import java.util.concurrent.CompletableFuture; + +/** + * @author Leetom + * @version 2022-11-25 + */ +public final class WorldGenTask +{ + public final DhLodPos pos; + public final byte dataDetailLevel; + public final AbstractWorldGenTaskTracker taskTracker; + public final CompletableFuture future; + + + public WorldGenTask(DhLodPos pos, byte dataDetail, AbstractWorldGenTaskTracker taskTracker, CompletableFuture future) + { + this.dataDetailLevel = dataDetail; + this.pos = pos; + this.taskTracker = taskTracker; + this.future = future; + } + +} 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 18053d784..979b65d4d 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 @@ -5,7 +5,7 @@ import com.seibel.lod.core.datatype.full.ChunkSizedData; import com.seibel.lod.core.datatype.full.FullDataSource; import com.seibel.lod.core.datatype.transform.ChunkToLodBuilder; import com.seibel.lod.core.file.datafile.IDataSourceProvider; -import com.seibel.lod.core.generation.GenerationQueue; +import com.seibel.lod.core.generation.WorldGenerationQueue; import com.seibel.lod.core.pos.DhLodPos; import com.seibel.lod.core.pos.DhSectionPos; import com.seibel.lod.core.render.LodQuadTree; @@ -317,7 +317,7 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel if (wgs != null) { wgs.batchGenerator.update(); - wgs.generationQueue.pollAndStartClosest(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); + wgs.worldGenerationQueue.pollAndStartClosest(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); } } @@ -365,25 +365,25 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel class WorldGenState { final BatchGenerator batchGenerator; - final GenerationQueue generationQueue; + final WorldGenerationQueue worldGenerationQueue; WorldGenState() { this.batchGenerator = new BatchGenerator(DhClientServerLevel.this); - this.generationQueue = new GenerationQueue(this.batchGenerator); - dataFileHandler.setGenerationQueue(this.generationQueue); + this.worldGenerationQueue = new WorldGenerationQueue(this.batchGenerator); + dataFileHandler.setGenerationQueue(this.worldGenerationQueue); } CompletableFuture close(boolean doInterrupt) { dataFileHandler.popGenerationQueue(); - return this.generationQueue.startClosing(true, doInterrupt) - .exceptionally(ex -> + return this.worldGenerationQueue.startClosing(true, doInterrupt) + .exceptionally(ex -> { LOGGER.error("Error closing generation queue", ex); return null; }).thenRun(this.batchGenerator::close) - .exceptionally(ex -> + .exceptionally(ex -> { LOGGER.error("Error closing world gen", ex); return null; diff --git a/core/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractBatchGenerationEnvionmentWrapper.java b/core/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractBatchGenerationEnvionmentWrapper.java index 7af14ea6e..24a9e5d3f 100644 --- a/core/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractBatchGenerationEnvionmentWrapper.java +++ b/core/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractBatchGenerationEnvionmentWrapper.java @@ -25,7 +25,8 @@ import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; -public abstract class AbstractBatchGenerationEnvionmentWrapper { +public abstract class AbstractBatchGenerationEnvionmentWrapper +{ public enum Steps { Empty, StructureStart, StructureReference, Biomes, Noise, Surface, Carvers, LiquidCarvers, Features, Light, }