From 2b930f3fd7a3aafe4d4a224334bd9c0910d58503 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 15 Jan 2023 16:57:42 -0600 Subject: [PATCH] refactor Core world gen queuing --- .../lod/core/config/AppliedConfigState.java | 34 ++- .../datafile/GeneratedDataFileHandler.java | 245 +++++++++++------- .../core/generation/WorldGenerationQueue.java | 111 +++++--- .../tasks/AbstractWorldGenTaskTracker.java | 4 + .../generation/tasks/SplitTaskTracker.java | 25 +- .../lod/core/level/DhClientServerLevel.java | 37 ++- 6 files changed, 293 insertions(+), 163 deletions(-) diff --git a/core/src/main/java/com/seibel/lod/core/config/AppliedConfigState.java b/core/src/main/java/com/seibel/lod/core/config/AppliedConfigState.java index 9f1a9caf8..f6322ac50 100644 --- a/core/src/main/java/com/seibel/lod/core/config/AppliedConfigState.java +++ b/core/src/main/java/com/seibel/lod/core/config/AppliedConfigState.java @@ -3,25 +3,33 @@ package com.seibel.lod.core.config; import com.seibel.lod.core.config.types.ConfigEntry; // TODO: Make this intergrate with the config system -public class AppliedConfigState { +public class AppliedConfigState +{ final ConfigEntry entry; T activeValue; - - public AppliedConfigState(ConfigEntry entryToWatch) { + + + + public AppliedConfigState(ConfigEntry entryToWatch) + { this.entry = entryToWatch; - activeValue = entryToWatch.get(); + this.activeValue = entryToWatch.get(); } - - public boolean pollNewValue() { - T newValue = entry.get(); - if (newValue.equals(activeValue)) { + + + + /** Returns true if the value was changed */ + public boolean pollNewValue() + { + T newValue = this.entry.get(); + if (newValue.equals(this.activeValue)) + { return false; } - activeValue = newValue; + this.activeValue = newValue; return true; } - - public T get() { - return activeValue; - } + + public T get() { return this.activeValue; } + } 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 1ca37b5f6..6498492df 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 @@ -20,125 +20,103 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -public class GeneratedDataFileHandler extends DataFileHandler { +public class GeneratedDataFileHandler extends DataFileHandler +{ private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - - AtomicReference queue = new AtomicReference<>(null); + + private AtomicReference worldGenQueueRef = 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 AbstractWorldGenTaskTracker + private final Map worldGenTaskQueue = Collections.synchronizedMap(new WeakHashMap<>()); + + + + public GeneratedDataFileHandler(IDhServerLevel level, File saveRootDir) { super(level, saveRootDir); } + + + + /** + * Assumes there isn't a pre-existing queue. + */ + public void setGenerationQueue(WorldGenerationQueue newQueue) { - final DhSectionPos pos; - WeakReference targetData; - ILodDataSource loadedTargetData = null; - GenTask(DhSectionPos pos, WeakReference targetData) { - this.pos = pos; - this.targetData = targetData; - } - @Override - public boolean isValid() { - return targetData.get() != null; - } - @Override - public Consumer getConsumer() { - if (loadedTargetData == null) { - loadedTargetData = targetData.get(); - if (loadedTargetData == null) return null; - } - return (chunk) -> { - if (chunk.getBBoxLodPos().overlaps(loadedTargetData.getSectionPos().getSectionBBoxPos())) - write(loadedTargetData.getSectionPos(), chunk); - }; - } - - void releaseStrongReference() { - loadedTargetData = null; - } - } - - - public GeneratedDataFileHandler(IDhServerLevel level, File saveRootDir) { - super(level, saveRootDir); - } - - public void setGenerationQueue(WorldGenerationQueue newQueue) { - boolean worked = queue.compareAndSet(null, newQueue); - LodUtil.assertTrue(worked, "previous queue is still here!"); - synchronized (genQueue) { - for (Map.Entry entry : genQueue.entrySet()) { - ILodDataSource source = entry.getKey(); - DhSectionPos pos = source.getSectionPos(); - GenTask task = entry.getValue(); - queue.get().submitGenTask(pos.getSectionBBoxPos(), source.getDataDetail(), task) - .whenComplete( - (b, ex) -> { - if (ex != null) LOGGER.error("Uncaught Gen Task Exception at {}:", pos, ex); - ILodDataSource data = task.targetData.get(); - if (ex == null && b) { - files.get(task.pos).metaData.dataVersion.incrementAndGet(); - genQueue.remove(data, task); - return; - } - task.releaseStrongReference(); - } - ); + // this is outside the synchronized block to allow for the assertTrue to catch if the method is incorrectly called twice + boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newQueue); + LodUtil.assertTrue(oldQueueExists, "previous queue is still here!"); + + + synchronized (this.worldGenTaskQueue) + { + for (Map.Entry genTaskEntry : this.worldGenTaskQueue.entrySet()) + { + ILodDataSource source = genTaskEntry.getKey(); + + DhSectionPos taskPos = source.getSectionPos(); + GenTask task = genTaskEntry.getValue(); + + this.worldGenQueueRef.get().submitGenTask(taskPos.getSectionBBoxPos(), source.getDataDetail(), task) + .whenComplete( (genTaskCompleted, ex) -> this.onWorldGenTaskComplete(genTaskCompleted, ex, task, taskPos) ); } } } - - public WorldGenerationQueue popGenerationQueue() { - WorldGenerationQueue cas = queue.getAndSet(null); - LodUtil.assertTrue(cas != null, "there are no previous live generation queue!"); + + + public WorldGenerationQueue popGenerationQueue() + { + WorldGenerationQueue cas = this.worldGenQueueRef.getAndSet(null); + LodUtil.assertTrue(cas != null, "there is no previous live generation queue!"); return cas; } - + @Override - public CompletableFuture onCreateDataFile(DataMetaFile file) { + public CompletableFuture onCreateDataFile(DataMetaFile file) + { DhSectionPos pos = file.pos; - ArrayList existFiles = new ArrayList<>(); + ArrayList existingFiles = new ArrayList<>(); ArrayList missing = new ArrayList<>(); - selfSearch(pos, pos, existFiles, missing); - LodUtil.assertTrue(!missing.isEmpty() || !existFiles.isEmpty()); - if (missing.size() == 1 && existFiles.isEmpty() && missing.get(0).equals(pos)) { + this.selfSearch(pos, pos, existingFiles, missing); + LodUtil.assertTrue(!missing.isEmpty() || !existingFiles.isEmpty()); + if (missing.size() == 1 && existingFiles.isEmpty() && missing.get(0).equals(pos)) + { // None exist. IIncompleteDataSource dataSource = pos.sectionDetail <= SparseDataSource.MAX_SECTION_DETAIL ? - SparseDataSource.createEmpty(pos) : SpottyDataSource.createEmpty(pos); - WorldGenerationQueue getQueue = queue.get(); + SparseDataSource.createEmpty(pos) : + SpottyDataSource.createEmpty(pos); + + WorldGenerationQueue queue = this.worldGenQueueRef.get(); GenTask task = new GenTask(pos, new WeakReference<>(dataSource)); - genQueue.put(dataSource, task); - if (getQueue != null) { - getQueue.submitGenTask(dataSource.getSectionPos().getSectionBBoxPos(), + this.worldGenTaskQueue.put(dataSource, task); + if (queue != null) + { + queue.submitGenTask(dataSource.getSectionPos().getSectionBBoxPos(), dataSource.getDataDetail(), task) - .whenComplete( - (b, ex) -> { - if (ex != null) LOGGER.error("Uncaught Gen Task Exception at {}:", pos, ex); - ILodDataSource data = task.targetData.get(); - if (ex == null && b) { - files.get(task.pos).metaData.dataVersion.incrementAndGet(); - genQueue.remove(data, task); - return; - } - task.releaseStrongReference(); - } - ); + .whenComplete( (genTaskCompleted, ex) -> this.onWorldGenTaskComplete(genTaskCompleted, ex, task, pos) ); } return CompletableFuture.completedFuture(dataSource); - } else { - for (DhSectionPos missingPos : missing) { - DataMetaFile newfile = atomicGetOrMakeFile(missingPos); - if (newfile != null) existFiles.add(newfile); + } + else + { + for (DhSectionPos missingPos : missing) + { + DataMetaFile newFile = this.atomicGetOrMakeFile(missingPos); + if (newFile != null) + { + existingFiles.add(newFile); + } } - final ArrayList> futures = new ArrayList<>(existFiles.size()); + + final ArrayList> futures = new ArrayList<>(existingFiles.size()); final IIncompleteDataSource dataSource = pos.sectionDetail <= SparseDataSource.MAX_SECTION_DETAIL ? SparseDataSource.createEmpty(pos) : SpottyDataSource.createEmpty(pos); - LOGGER.debug("Creating {} from sampling {} files: {}", pos, existFiles.size(), existFiles); + LOGGER.debug("Creating {} from sampling {} files: {}", pos, existingFiles.size(), existingFiles); - for (DataMetaFile f : existFiles) { - futures.add(f.loadOrGetCached() + for (DataMetaFile existingFile : existingFiles) + { + futures.add(existingFile.loadOrGetCached() .exceptionally((ex) -> null) - .thenAccept((data) -> { - if (data != null) { + .thenAccept((data) -> + { + if (data != null) + { LOGGER.info("Merging data from {} into {}", data.getSectionPos(), pos); dataSource.sampleFrom(data); } @@ -149,4 +127,77 @@ public class GeneratedDataFileHandler extends DataFileHandler { .thenApply((v) -> dataSource.trySelfPromote()); } } + + + + private void onWorldGenTaskComplete(Boolean genTaskCompleted, Throwable exception, GenTask task, DhSectionPos pos) + { + if (exception != null) + { + LOGGER.error("Uncaught Gen Task Exception at {}:", pos, exception); + } + + ILodDataSource taskSource = task.targetData.get(); + if (exception == null && genTaskCompleted) + { + this.files.get(task.pos).metaData.dataVersion.incrementAndGet(); + + // remove the completed task + this.worldGenTaskQueue.remove(taskSource, task); + return; + } + task.releaseStrongReference(); + } + + + + //==============// + // helper class // + //==============// + + class GenTask extends AbstractWorldGenTaskTracker + { + private final DhSectionPos pos; + private final WeakReference targetData; + private ILodDataSource loadedTargetData = null; + + + + GenTask(DhSectionPos pos, WeakReference targetData) + { + this.pos = pos; + this.targetData = targetData; + } + + + + @Override + public boolean isValid() { return this.targetData.get() != null; } + + @Override + public Consumer getConsumer() + { + if (this.loadedTargetData == null) + { + this.loadedTargetData = this.targetData.get(); + if (this.loadedTargetData == null) + { + return null; + } + } + + return (chunk) -> + { + if (chunk.getBBoxLodPos().overlaps(this.loadedTargetData.getSectionPos().getSectionBBoxPos())) + { + GeneratedDataFileHandler.this.write(this.loadedTargetData.getSectionPos(), chunk); + } + }; + } + + void releaseStrongReference() { this.loadedTargetData = null; } + + } + + } 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 73a6316a9..78603c166 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 @@ -61,7 +61,9 @@ public class WorldGenerationQueue implements Closeable private final byte maxDataDetail; private final byte minDataDetail; - private volatile CompletableFuture closer = null; + + /** If not null this generator is in the process of shutting down */ + private volatile CompletableFuture generatorClosingFuture = null; @@ -74,10 +76,15 @@ public class WorldGenerationQueue implements Closeable this.maxDataDetail = generator.getMaxDataDetailLevel(); this.minDataDetail = generator.getMinDataDetailLevel(); - if (this.minGranularity < 4) - throw new IllegalArgumentException("DH-IGenerator: min granularity must be at least 4!"); + + if (this.minGranularity < LodUtil.CHUNK_DETAIL_LEVEL) + { + throw new IllegalArgumentException(IDhApiWorldGenerator.class.getSimpleName() + ": min granularity must be at least 4 (Chunk sized)!"); + } if (this.maxGranularity < this.minGranularity) - throw new IllegalArgumentException("DH-IGenerator: max granularity smaller than min granularity!"); + { + throw new IllegalArgumentException(IDhApiWorldGenerator.class.getSimpleName() + ": max granularity smaller than min granularity!"); + } } @@ -89,45 +96,64 @@ public class WorldGenerationQueue implements Closeable public CompletableFuture submitGenTask(DhLodPos pos, byte requiredDataDetail, AbstractWorldGenTaskTracker tracker) { - if (this.closer != null) + // if the generator is shutting down, don't add new tasks + if (this.generatorClosingFuture != null) + { return CompletableFuture.completedFuture(false); + } if (requiredDataDetail < this.minDataDetail) { throw new UnsupportedOperationException("Current generator does not meet requiredDataDetail level"); } if (requiredDataDetail > this.maxDataDetail) + { requiredDataDetail = this.maxDataDetail; + } + LodUtil.assertTrue(pos.detailLevel > requiredDataDetail + 4); byte granularity = (byte) (pos.detailLevel - requiredDataDetail); + + + if (granularity > this.maxGranularity) { - // Too big of a chunk. We need to split it up + // The generation section is too big, split it up + byte subDetail = (byte) (this.maxGranularity + requiredDataDetail); - int subPosCount = pos.getBlockWidth(subDetail); + int subPosWidthCount = pos.getBlockWidth(subDetail); + DhLodPos cornerSubPos = pos.getCorner(subDetail); - CompletableFuture[] subFutures = new CompletableFuture[subPosCount * subPosCount]; - ArrayList subTasks = new ArrayList<>(subPosCount * subPosCount); + CompletableFuture[] subFutures = new CompletableFuture[subPosWidthCount * subPosWidthCount]; + ArrayList subTasks = new ArrayList<>(subPosWidthCount * subPosWidthCount); SplitTaskTracker splitTaskTracker = new SplitTaskTracker(tracker, new CompletableFuture<>()); + + // create the new sub-futures + int subFutureIndex = 0; + for (int xOffset = 0; xOffset < subPosWidthCount; xOffset++) { - int i = 0; - for (int ox = 0; ox < subPosCount; ox++) + for (int zOffset = 0; zOffset < subPosWidthCount; zOffset++) { - for (int oz = 0; oz < subPosCount; oz++) - { - CompletableFuture subFuture = new CompletableFuture<>(); - subFutures[i++] = subFuture; - subTasks.add(new WorldGenTask(cornerSubPos.addOffset(ox, oz), requiredDataDetail, splitTaskTracker, subFuture)); - } + CompletableFuture subFuture = new CompletableFuture<>(); + subFutures[subFutureIndex++] = subFuture; + subTasks.add(new WorldGenTask(cornerSubPos.addOffset(xOffset, zOffset), requiredDataDetail, splitTaskTracker, subFuture)); } } - CompletableFuture.allOf(subFutures).whenComplete((v, ex) -> { + + CompletableFuture.allOf(subFutures).whenComplete((v, ex) -> + { if (ex != null) + { splitTaskTracker.parentFuture.completeExceptionally(ex); - if (!splitTaskTracker.recheckState()) + } + + if (!splitTaskTracker.recalculateIsValid()) + { return; // Auto join future + } + for (CompletableFuture subFuture : subFutures) { boolean successful = subFuture.join(); @@ -139,11 +165,9 @@ public class WorldGenerationQueue implements Closeable } splitTaskTracker.parentFuture.complete(true); }); + this.looseTasks.addAll(subTasks); - if (this.closer != null) - return CompletableFuture.completedFuture(false); - else - return splitTaskTracker.parentFuture; + return splitTaskTracker.parentFuture; } else if (granularity < this.minGranularity) { @@ -152,19 +176,17 @@ public class WorldGenerationQueue implements Closeable DhLodPos parentPos = pos.convertToDetailLevel(parentDetail); CompletableFuture future = new CompletableFuture<>(); this.looseTasks.add(new WorldGenTask(parentPos, requiredDataDetail, tracker, future)); - if (this.closer != null) - return CompletableFuture.completedFuture(false); - else - return future; + + return future; } else { + // the requested granularity is within the min and max granularity provided by the world generator, + // no additional task changes are necessary + CompletableFuture future = new CompletableFuture<>(); this.looseTasks.add(new WorldGenTask(pos, requiredDataDetail, tracker, future)); - if (this.closer != null) - return CompletableFuture.completedFuture(false); - else - return future; + return future; } } @@ -273,7 +295,7 @@ public class WorldGenerationQueue implements Closeable taskProcessed++; byte taskDataDetail = task.dataDetailLevel; byte taskGranularity = (byte) (task.pos.detailLevel - taskDataDetail); - LodUtil.assertTrue(taskGranularity >= 4 && taskGranularity >= this.minGranularity && taskGranularity <= this.maxGranularity); + LodUtil.assertTrue(taskGranularity >= LodUtil.CHUNK_DETAIL_LEVEL && taskGranularity >= this.minGranularity && taskGranularity <= this.maxGranularity); // Check existing one TaskGroup group = this.taskGroups.get(task.pos); @@ -311,6 +333,7 @@ public class WorldGenerationQueue implements Closeable break; } } + if (!didAnything) { group = new TaskGroup(task.pos, taskDataDetail); @@ -331,8 +354,11 @@ public class WorldGenerationQueue implements Closeable { // Remove all invalid genTasks and groups Iterator groupIter = this.taskGroups.values().iterator(); + + // go through each TaskGroup while (groupIter.hasNext()) { + // go through each WorldGenTask in the TaskGroup TaskGroup group = groupIter.next(); Iterator taskIter = group.generatorTasks.iterator(); while (taskIter.hasNext()) @@ -405,9 +431,15 @@ public class WorldGenerationQueue implements Closeable 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; + } + this.removeOutdatedGroups(); this.processLooseTasks(); this.pollAndStartNext(targetPos); @@ -451,7 +483,7 @@ public class WorldGenerationQueue implements Closeable { this.taskGroups.values().forEach(g -> g.generatorTasks.forEach(t -> t.future.complete(false))); this.taskGroups.clear(); - ArrayList> array = new ArrayList<>(inProgress.size()); + ArrayList> array = new ArrayList<>(this.inProgress.size()); this.inProgress.values().forEach(runningTask -> { CompletableFuture genFuture = runningTask.genFuture; // Do this to prevent it getting swapped out @@ -465,21 +497,24 @@ public class WorldGenerationQueue implements Closeable return null; })); }); - this.closer = CompletableFuture.allOf(array.toArray(CompletableFuture[]::new)); //FIXME: Closer threading issues with pollAndStartClosest + 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(); - return this.closer; + return this.generatorClosingFuture; } @Override public void close() { - if (this.closer == null) + if (this.generatorClosingFuture == null) + { this.startClosing(true, true); - LodUtil.assertTrue(this.closer != null); + } + LodUtil.assertTrue(this.generatorClosingFuture != null); + try { - this.closer.orTimeout(SHUTDOWN_TIMEOUT_SEC, TimeUnit.SECONDS).join(); + this.generatorClosingFuture.orTimeout(SHUTDOWN_TIMEOUT_SEC, TimeUnit.SECONDS).join(); } catch (Throwable e) { 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 index 7261df891..4e5e53dfb 100644 --- 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 @@ -10,6 +10,10 @@ import java.util.function.Consumer; */ public abstract class AbstractWorldGenTaskTracker { + /** + * Returns true if the task hasn't been garbage collected.
+ * TODO rename to fit the above description better + */ public abstract boolean isValid(); public abstract Consumer getConsumer(); 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 index 6739019b7..f6ef2bc23 100644 --- 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 @@ -4,8 +4,12 @@ import com.seibel.lod.core.datatype.full.ChunkSizedData; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import com.seibel.lod.core.generation.WorldGenerationQueue; /** + * Used to synchronize {@link WorldGenerationQueue} {@link WorldGenTask}'s + * if the {@link WorldGenTask} needs to be split up. + * * @author Leetom * @version 2022-11-25 */ @@ -13,7 +17,9 @@ public class SplitTaskTracker extends AbstractWorldGenTaskTracker { public final AbstractWorldGenTaskTracker parentTracker; public final CompletableFuture parentFuture; - public boolean cachedValid = true; + + /** cached value to allow for quicker checking */ + public boolean isValid = true; @@ -25,20 +31,25 @@ public class SplitTaskTracker extends AbstractWorldGenTaskTracker - public boolean recheckState() + /** Recalculates and returns the new {@link SplitTaskTracker#isValid} value */ + public boolean recalculateIsValid() { - if (!this.cachedValid) + if (!this.isValid) + { return false; + } - this.cachedValid = this.parentTracker.isValid(); - if (!this.cachedValid) + this.isValid = this.parentTracker.isValid(); + if (!this.isValid) + { this.parentFuture.complete(false); + } - return this.cachedValid; + return this.isValid; } @Override - public boolean isValid() { return this.cachedValid; } + public boolean isValid() { return this.isValid; } @Override public Consumer getConsumer() { return this.parentTracker.getConsumer(); } 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 0b76ad2bc..ca155c0af 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 @@ -35,6 +35,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper; import org.apache.logging.log4j.Logger; +import org.lwjgl.system.CallbackI; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; @@ -44,14 +45,16 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + public final LocalSaveStructure save; public final GeneratedDataFileHandler dataFileHandler; public final ChunkToLodBuilder chunkToLodBuilder; public final IServerLevelWrapper serverLevel; private final AppliedConfigState generatorEnabled; public F3Screen.NestedMessage f3Msg; - public final AtomicReference renderState = new AtomicReference<>(); - public final AtomicReference worldGenState = new AtomicReference<>(); + + private final AtomicReference renderState = new AtomicReference<>(); + private final AtomicReference worldGenState = new AtomicReference<>(); @@ -269,7 +272,7 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel if (rs == null) return; } - rs.close().join(); //TODO: Make it async. + rs.close().join(); //TODO: Make this async. } WorldGenState wgs = this.worldGenState.get(); @@ -292,11 +295,14 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel public void doWorldGen() { WorldGenState wgs = this.worldGenState.get(); + + // if the world generator config changes, add/remove the world generator if (this.generatorEnabled.pollNewValue()) { boolean shouldDoWorldGen = this.generatorEnabled.get() && this.renderState.get() != null; if (shouldDoWorldGen && wgs == null) { + // create the new world generator WorldGenState newWgs = new WorldGenState(this); if (!this.worldGenState.compareAndSet(null, newWgs)) { @@ -306,18 +312,23 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel } else if (!shouldDoWorldGen && wgs != null) { + // shut down the world generator while (!this.worldGenState.compareAndSet(wgs, null)) { wgs = this.worldGenState.get(); if (wgs == null) + { return; + } } wgs.close(true).join(); //TODO: Make it async. } } + if (wgs != null) { + // queue new world generation requests wgs.chunkGenerator.preGeneratorTaskStart(); wgs.worldGenerationQueue.pollAndStartClosest(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos())); } @@ -344,17 +355,23 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel final RenderBufferHandler renderBufferHandler; //TODO: Should this be owned by renderer? final LodRenderer renderer; + + RenderState(IClientLevelWrapper clientLevel) { + DhClientServerLevel thisParent = DhClientServerLevel.this; + this.clientLevel = clientLevel; - this.renderFileHandler = new RenderFileHandler(dataFileHandler, DhClientServerLevel.this, save.getRenderCacheFolder(serverLevel)); + this.renderFileHandler = new RenderFileHandler(thisParent.dataFileHandler, thisParent, thisParent.save.getRenderCacheFolder(thisParent.serverLevel)); this.tree = new LodQuadTree(DhClientServerLevel.this, Config.Client.Graphics.Quality.lodChunkRenderDistance.get() * 16, MC_CLIENT.getPlayerBlockPos().x, MC_CLIENT.getPlayerBlockPos().z, this.renderFileHandler); - this.renderBufferHandler = new RenderBufferHandler(tree); - FileScanUtil.scanFile(save, serverLevel, null, this.renderFileHandler); + this.renderBufferHandler = new RenderBufferHandler(this.tree); + FileScanUtil.scanFile(thisParent.save, thisParent.serverLevel, null, this.renderFileHandler); this.renderer = new LodRenderer(this.renderBufferHandler); } + + CompletableFuture close() { this.renderer.close(); @@ -369,6 +386,8 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel public final IDhApiWorldGenerator chunkGenerator; public final WorldGenerationQueue worldGenerationQueue; + + WorldGenState(IDhLevel level) { IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper()); @@ -383,12 +402,14 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel this.chunkGenerator = worldGenerator; this.worldGenerationQueue = new WorldGenerationQueue(this.chunkGenerator); - dataFileHandler.setGenerationQueue(this.worldGenerationQueue); + DhClientServerLevel.this.dataFileHandler.setGenerationQueue(this.worldGenerationQueue); } + + CompletableFuture close(boolean doInterrupt) { - dataFileHandler.popGenerationQueue(); + DhClientServerLevel.this.dataFileHandler.popGenerationQueue(); return this.worldGenerationQueue.startClosing(true, doInterrupt) .exceptionally(ex -> {