refactor Core world gen queuing
This commit is contained in:
@@ -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; }
|
||||
|
||||
}
|
||||
|
||||
+148
-97
@@ -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)
|
||||
{
|
||||
|
||||
+4
@@ -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 ->
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user