Separate inner classes and refactor world generator code
This commit is contained in:
@@ -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<GenerationQueue> queue = new AtomicReference<>(null);
|
||||
AtomicReference<WorldGenerationQueue> queue = 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 GenerationQueue.GenTaskTracker {
|
||||
class GenTask extends AbstractWorldGenTaskTracker
|
||||
{
|
||||
final DhSectionPos pos;
|
||||
WeakReference<ILodDataSource> 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) {
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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<Void> 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<Void> generateChunks(DhChunkPos chunkPosMin,
|
||||
byte granularity, byte targetDataDetail,
|
||||
Consumer<IChunkWrapper> resultConsumer);
|
||||
|
||||
@Override
|
||||
default CompletableFuture<Void> 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<Void> generate(DhChunkPos chunkPosMin,
|
||||
byte granularity, byte targetDataDetail,
|
||||
Consumer<ChunkSizedData> resultConsumer)
|
||||
{
|
||||
return this.generateChunks(chunkPosMin, granularity, targetDataDetail, (chunk) ->
|
||||
return this.generateChunks(chunkPosMin, granularity, targetDataDetail, (chunk) ->
|
||||
{
|
||||
resultConsumer.accept(LodDataBuilder.createChunkData(chunk));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Void> generate(DhChunkPos chunkPosMin, byte granularity, byte targetDataDetail, Consumer<ChunkSizedData> resultConsumer);
|
||||
|
||||
/** Returns whether the generator is unable to accept new generation requests. */
|
||||
boolean isBusy();
|
||||
|
||||
}
|
||||
+79
-163
@@ -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: <a href="https://stackoverflow.com/questions/3706219/algorithm-for-iterating-over-an-outward-spiral-on-a-discrete-2d-grid-from-the-or">...</a>
|
||||
* 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<ChunkSizedData> getConsumer();
|
||||
}
|
||||
|
||||
final IGenerator generator;
|
||||
|
||||
static final class GenTask
|
||||
{
|
||||
final DhLodPos pos;
|
||||
final byte dataDetail;
|
||||
final GenTaskTracker taskTracker;
|
||||
final CompletableFuture<Boolean> future;
|
||||
|
||||
GenTask(DhLodPos pos, byte dataDetail, GenTaskTracker taskTracker, CompletableFuture<Boolean> future)
|
||||
{
|
||||
this.dataDetail = dataDetail;
|
||||
this.pos = pos;
|
||||
this.taskTracker = taskTracker;
|
||||
this.future = future;
|
||||
}
|
||||
}
|
||||
|
||||
static final class TaskGroup
|
||||
{
|
||||
final DhLodPos pos;
|
||||
byte dataDetail;
|
||||
final LinkedList<GenTask> 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<GenTask> iter = this.members.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
GenTask task = iter.next();
|
||||
Consumer<ChunkSizedData> 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<Void> genFuture = null;
|
||||
|
||||
InProgressTask(TaskGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
}
|
||||
|
||||
static class SplitTask extends GenTaskTracker
|
||||
{
|
||||
final GenTaskTracker parentTracker;
|
||||
final CompletableFuture<Boolean> parentFuture;
|
||||
boolean cachedValid = true;
|
||||
|
||||
SplitTask(GenTaskTracker parentTracker, CompletableFuture<Boolean> 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<ChunkSizedData> getConsumer() { return this.parentTracker.getConsumer(); }
|
||||
|
||||
}
|
||||
|
||||
private final ConcurrentLinkedQueue<GenTask> looseTasks = new ConcurrentLinkedQueue<>();
|
||||
private final ConcurrentLinkedQueue<WorldGenTask> looseTasks = new ConcurrentLinkedQueue<>();
|
||||
// FIXME: Concurrency issue on close!
|
||||
// FIXME: This is using up a TONS of time to process!
|
||||
private final ConcurrentSkipListMap<DhLodPos, TaskGroup> taskGroups = new ConcurrentSkipListMap<>(
|
||||
@@ -168,21 +44,26 @@ public class GenerationQueue implements Closeable
|
||||
}
|
||||
); // Accessed by poller only
|
||||
|
||||
private final ConcurrentHashMap<DhLodPos, InProgressTask> inProgress = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<DhLodPos, InProgressWorldGenTask> 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<Void> 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<Boolean> submitGenTask(DhLodPos pos, byte requiredDataDetail, GenTaskTracker tracker)
|
||||
|
||||
|
||||
|
||||
public CompletableFuture<Boolean> 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<Boolean>[] subFutures = new CompletableFuture[subPosCount * subPosCount];
|
||||
ArrayList<GenTask> subTasks = new ArrayList<>(subPosCount * subPosCount);
|
||||
SplitTask splitTask = new SplitTask(tracker, new CompletableFuture<>());
|
||||
ArrayList<WorldGenTask> 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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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<GenTask> taskIter = group.members.iterator();
|
||||
Iterator<WorldGenTask> 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<Void> 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<CompletableFuture<Void>> 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: <a href="https://stackoverflow.com/questions/3706219/algorithm-for-iterating-over-an-outward-spiral-on-a-discrete-2d-grid-from-the-or">...</a>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -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<ChunkSizedData> getConsumer();
|
||||
|
||||
}
|
||||
@@ -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<Void> genFuture = null;
|
||||
|
||||
|
||||
public InProgressWorldGenTask(TaskGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Boolean> parentFuture;
|
||||
public boolean cachedValid = true;
|
||||
|
||||
|
||||
|
||||
public SplitTaskTracker(AbstractWorldGenTaskTracker parentTracker, CompletableFuture<Boolean> 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<ChunkSizedData> getConsumer() { return this.parentTracker.getConsumer(); }
|
||||
|
||||
}
|
||||
@@ -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<WorldGenTask> generatorTasks = new LinkedList<>();
|
||||
|
||||
|
||||
|
||||
public TaskGroup(DhLodPos pos, byte dataDetail)
|
||||
{
|
||||
this.pos = pos;
|
||||
this.dataDetail = dataDetail;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void accept(ChunkSizedData data)
|
||||
{
|
||||
Iterator<WorldGenTask> tasks = this.generatorTasks.iterator();
|
||||
while (tasks.hasNext())
|
||||
{
|
||||
WorldGenTask task = tasks.next();
|
||||
Consumer<ChunkSizedData> consumer = task.taskTracker.getConsumer();
|
||||
if (consumer == null)
|
||||
{
|
||||
tasks.remove();
|
||||
task.future.complete(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
consumer.accept(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Boolean> future;
|
||||
|
||||
|
||||
public WorldGenTask(DhLodPos pos, byte dataDetail, AbstractWorldGenTaskTracker taskTracker, CompletableFuture<Boolean> future)
|
||||
{
|
||||
this.dataDetailLevel = dataDetail;
|
||||
this.pos = pos;
|
||||
this.taskTracker = taskTracker;
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Void> 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;
|
||||
|
||||
+2
-1
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user