refactor Core world gen queuing

This commit is contained in:
James Seibel
2023-01-15 16:57:42 -06:00
parent 0b6b14177e
commit 2b930f3fd7
6 changed files with 293 additions and 163 deletions
@@ -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<T> {
public class AppliedConfigState<T>
{
final ConfigEntry<T> entry;
T activeValue;
public AppliedConfigState(ConfigEntry<T> entryToWatch) {
public AppliedConfigState(ConfigEntry<T> 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; }
}
@@ -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<WorldGenerationQueue> queue = new AtomicReference<>(null);
private AtomicReference<WorldGenerationQueue> worldGenQueueRef = new AtomicReference<>(null);
// TODO: Should I include a lib that impl weak concurrent hash map?
final Map<ILodDataSource, GenTask> genQueue = Collections.synchronizedMap(new WeakHashMap<>());
class GenTask extends AbstractWorldGenTaskTracker
private final Map<ILodDataSource, GenTask> 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<ILodDataSource> targetData;
ILodDataSource loadedTargetData = null;
GenTask(DhSectionPos pos, WeakReference<ILodDataSource> targetData) {
this.pos = pos;
this.targetData = targetData;
}
@Override
public boolean isValid() {
return targetData.get() != null;
}
@Override
public Consumer<ChunkSizedData> 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<ILodDataSource, GenTask> 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<ILodDataSource, GenTask> 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<ILodDataSource> onCreateDataFile(DataMetaFile file) {
public CompletableFuture<ILodDataSource> onCreateDataFile(DataMetaFile file)
{
DhSectionPos pos = file.pos;
ArrayList<DataMetaFile> existFiles = new ArrayList<>();
ArrayList<DataMetaFile> existingFiles = new ArrayList<>();
ArrayList<DhSectionPos> 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<CompletableFuture<Void>> futures = new ArrayList<>(existFiles.size());
final ArrayList<CompletableFuture<Void>> 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<ILodDataSource> targetData;
private ILodDataSource loadedTargetData = null;
GenTask(DhSectionPos pos, WeakReference<ILodDataSource> targetData)
{
this.pos = pos;
this.targetData = targetData;
}
@Override
public boolean isValid() { return this.targetData.get() != null; }
@Override
public Consumer<ChunkSizedData> 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; }
}
}
@@ -61,7 +61,9 @@ public class WorldGenerationQueue implements Closeable
private final byte maxDataDetail;
private final byte minDataDetail;
private volatile CompletableFuture<Void> closer = null;
/** If not null this generator is in the process of shutting down */
private volatile CompletableFuture<Void> 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<Boolean> 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<Boolean>[] subFutures = new CompletableFuture[subPosCount * subPosCount];
ArrayList<WorldGenTask> subTasks = new ArrayList<>(subPosCount * subPosCount);
CompletableFuture<Boolean>[] subFutures = new CompletableFuture[subPosWidthCount * subPosWidthCount];
ArrayList<WorldGenTask> 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<Boolean> subFuture = new CompletableFuture<>();
subFutures[i++] = subFuture;
subTasks.add(new WorldGenTask(cornerSubPos.addOffset(ox, oz), requiredDataDetail, splitTaskTracker, subFuture));
}
CompletableFuture<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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<TaskGroup> groupIter = this.taskGroups.values().iterator();
// go through each TaskGroup
while (groupIter.hasNext())
{
// go through each WorldGenTask in the TaskGroup
TaskGroup group = groupIter.next();
Iterator<WorldGenTask> 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<CompletableFuture<Void>> array = new ArrayList<>(inProgress.size());
ArrayList<CompletableFuture<Void>> array = new ArrayList<>(this.inProgress.size());
this.inProgress.values().forEach(runningTask ->
{
CompletableFuture<Void> 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)
{
@@ -10,6 +10,10 @@ import java.util.function.Consumer;
*/
public abstract class AbstractWorldGenTaskTracker
{
/**
* Returns true if the task hasn't been garbage collected. <br>
* TODO rename to fit the above description better
*/
public abstract boolean isValid();
public abstract Consumer<ChunkSizedData> getConsumer();
@@ -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<Boolean> 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<ChunkSizedData> getConsumer() { return this.parentTracker.getConsumer(); }
@@ -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<Boolean> generatorEnabled;
public F3Screen.NestedMessage f3Msg;
public final AtomicReference<RenderState> renderState = new AtomicReference<>();
public final AtomicReference<WorldGenState> worldGenState = new AtomicReference<>();
private final AtomicReference<RenderState> renderState = new AtomicReference<>();
private final AtomicReference<WorldGenState> 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<Void> 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<Void> close(boolean doInterrupt)
{
dataFileHandler.popGenerationQueue();
DhClientServerLevel.this.dataFileHandler.popGenerationQueue();
return this.worldGenerationQueue.startClosing(true, doInterrupt)
.exceptionally(ex ->
{