Fix world gen for extreme render distances

This commit is contained in:
James Seibel
2024-03-10 20:44:12 -05:00
parent 20394068b2
commit 28de6f93af
12 changed files with 355 additions and 129 deletions
@@ -24,7 +24,9 @@ import com.seibel.distanthorizons.core.file.ISourceProvider;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.repo.FullDataRepo;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
/**
@@ -36,16 +38,40 @@ public interface IFullDataSourceProvider extends ISourceProvider<NewFullDataSour
CompletableFuture<NewFullDataSource> getAsync(DhSectionPos pos);
NewFullDataSource get(DhSectionPos pos);
/**
* If this provider has the ability to create (world gen) or get (networking)
* missing data sources this method will queue the given position
* for generation or retrieval.
*/
void queuePositionForGenerationOrRetrievalIfNecessary(DhSectionPos pos);
CompletableFuture<Void> updateDataSourceAsync(NewFullDataSource chunkData);
/** @return -1 if this provider never has unsaved data sources */
default int getUnsavedDataSourceCount() { return -1; }
// retrieval (world gen) //
/**
* If true this {@link IFullDataSourceProvider} can generate or retrieve
* {@link NewFullDataSource}'s that aren't currently in the database.
*/
default boolean canRetrieveMissingDataSources() { return false; }
/** @return null if it was unable to generate any positions, an empty array if all positions were generated */
@Nullable
default ArrayList<DhSectionPos> getPositionsToRetrieve(DhSectionPos pos) { return null; }
/**
* Returns how many positions could potentially be generated for this position assuming the position is empty.
* Used when estimating the total number of retrieval requests.
*/
default int getMaxPossibleRetrievalPositionCountForPos(DhSectionPos pos) { return -1; }
/** @return true if the position was queued, false if not */
default boolean queuePositionForRetrieval(DhSectionPos genPos) { return false; }
/**
* @return false if the provider isn't accepting new requests,
* this can be due to having a full queue or some other
* limiting factor.
*/
default boolean canQueueRetrieval() { return false; }
/** Can be used to display how many total retrieval requests might be available. */
default void setTotalRetrievalPositionCount(int newCount) { }
}
@@ -127,7 +127,7 @@ public class NewFullDataFileHandler
@Override
protected NewFullDataSource createNewDataSourceFromExistingDtos(DhSectionPos pos)
{
// TODO maybe just set children update flags to true?
// TODO maybe just set children update flags to true?
return NewFullDataSource.createEmpty(pos);
}
@@ -31,13 +31,15 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.repo.NewFullDataSourceRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -46,18 +48,14 @@ public class NewGeneratedFullDataFileHandler extends NewFullDataFileHandler impl
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final AtomicReference<IWorldGenerationQueue> worldGenQueueRef = new AtomicReference<>(null);
public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20;
private final AtomicReference<IWorldGenerationQueue> worldGenQueueRef = new AtomicReference<>(null);
private final ArrayList<IOnWorldGenCompleteListener> onWorldGenTaskCompleteListeners = new ArrayList<>();
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 5_000);
// TODO name better
// this is just the list of section pos that have had their world generation
// calculated and queued this session.
@Deprecated
private final Set<DhSectionPos> genHandledPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
//=============//
@@ -68,29 +66,6 @@ public class NewGeneratedFullDataFileHandler extends NewFullDataFileHandler impl
//===========//
// overrides //
//===========//
@Override
public NewFullDataSource get(DhSectionPos pos) { return this.get(pos, true); }
public NewFullDataSource get(DhSectionPos pos, boolean runWorldGenCheck)
{
NewFullDataSource dataSource = super.get(pos);
if (runWorldGenCheck)
{
this.tryQueueSection(pos);
}
return dataSource;
}
@Override
public void queuePositionForGenerationOrRetrievalIfNecessary(DhSectionPos pos) { this.tryQueueSection(pos); }
//==================//
// generation queue //
//==================//
@@ -106,11 +81,7 @@ public class NewGeneratedFullDataFileHandler extends NewFullDataFileHandler impl
LOGGER.info("Set world gen queue for level ["+this.level+"].");
}
public void clearGenerationQueue()
{
this.worldGenQueueRef.set(null);
this.genHandledPosSet.clear();
}
public void clearGenerationQueue() { this.worldGenQueueRef.set(null); }
/** Can be used to remove positions that are outside the player's render distance. */
public void removeGenRequestIf(Function<DhSectionPos, Boolean> removeIf)
@@ -122,14 +93,6 @@ public class NewGeneratedFullDataFileHandler extends NewFullDataFileHandler impl
{
worldGenQueue.removeGenRequestIf(removeIf);
}
this.genHandledPosSet.forEach((pos) ->
{
if (removeIf.apply(pos))
{
this.genHandledPosSet.remove(pos);
}
});
}
@Override
@@ -191,61 +154,92 @@ public class NewGeneratedFullDataFileHandler extends NewFullDataFileHandler impl
//================//
// helper methods //
//================//
//===================================//
// world gen (data source retrieval) //
//===================================//
/** does nothing if this section or one of it's parents has already been queued */
public void tryQueueSection(DhSectionPos pos)
@Override
public boolean canRetrieveMissingDataSources() { return true; }
@Override
public void setTotalRetrievalPositionCount(int newCount)
{
IWorldGenerationQueue tempWorldGenQueue = this.worldGenQueueRef.get();
if (tempWorldGenQueue == null)
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue != null)
{
return;
worldGenQueue.setEstimatedTotalTaskCount(newCount);
}
if (this.genHandledPosSet.contains(pos))
}
@Override
public boolean canQueueRetrieval()
{
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null)
{
return;
// we can't queue anything if the world generator isn't set up yet
return false;
}
AtomicBoolean positionAlreadyHandled = new AtomicBoolean(false);
pos.forEachPosUpToDetailLevel(NewFullDataFileHandler.TOP_SECTION_DETAIL_LEVEL, (parentPos) ->
ThreadPoolExecutor updateExecutor = ThreadPoolUtil.getUpdatePropagatorExecutor();
if (updateExecutor == null || updateExecutor.getQueue().size() >= MAX_UPDATE_TASK_COUNT)
{
if (!positionAlreadyHandled.get())
{
if (this.genHandledPosSet.contains(parentPos))
{
positionAlreadyHandled.set(true);
}
}
});
if (positionAlreadyHandled.get())
{
return;
// don't queue additional world gen requests if the updater is behind
return false;
}
this.genHandledPosSet.add(pos);
// don't queue additional world gen requests beyond the max allotted
int maxQueueCount = MAX_WORLD_GEN_REQUESTS_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfWorldGenerationThreads.get();
return worldGenQueue.getWaitingTaskCount() < maxQueueCount;
}
@Override
public boolean queuePositionForRetrieval(DhSectionPos genPos)
{
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null)
{
return false;
}
// get the un-generated pos list
byte minGeneratorSectionDetailLevel = (byte) (tempWorldGenQueue.highestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
GenTask genTask = new GenTask(genPos);
CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitGenTask(genPos, (byte) (genPos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTask);
worldGenFuture.whenComplete((genTaskResult, ex) -> this.onWorldGenTaskComplete(genTaskResult, ex));
return true;
}
@Override
public ArrayList<DhSectionPos> getPositionsToRetrieve(DhSectionPos pos)
{
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null)
{
return null;
}
// TODO based on the column generation step, only check children that are un-generated
ArrayList<DhSectionPos> generationList = new ArrayList<>();
byte minGeneratorSectionDetailLevel = (byte) (worldGenQueue.highestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
pos.forEachChildAtDetailLevel(minGeneratorSectionDetailLevel, (genPos) ->
{
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null)
if (!this.repo.existsWithKey(genPos))
{
return;
// nothing exists for this position, it needs generation
generationList.add(genPos);
}
if (this.repo.existsWithKey(genPos))
else
{
// TODO only pull in the generation steps
NewFullDataSource potentialDataSource = this.get(genPos, false);
byte[] columnGenerationSteps = ((NewFullDataSourceRepo)this.repo).getColumnGenerationStepForPos(genPos);
if (columnGenerationSteps == null)
{
// shouldn't happen, but just in case
return;
}
EDhApiWorldGenerationStep currentMinWorldGenStep = EDhApiWorldGenerationStep.LIGHT;
checkWorldGenLoop:
@@ -254,7 +248,7 @@ public class NewGeneratedFullDataFileHandler extends NewFullDataFileHandler impl
for (int z = 0; z < NewFullDataSource.WIDTH; z++)
{
int index = NewFullDataSource.relativePosToIndex(x, z);
byte genStepValue = potentialDataSource.columnGenerationSteps[index];
byte genStepValue = columnGenerationSteps[index];
if (genStepValue < currentMinWorldGenStep.value)
{
@@ -275,19 +269,38 @@ public class NewGeneratedFullDataFileHandler extends NewFullDataFileHandler impl
if (currentMinWorldGenStep != EDhApiWorldGenerationStep.EMPTY)
{
// no world gen needed
// no world gen needed for this position
return;
}
generationList.add(genPos);
}
// queue each new gen task
GenTask genTask = new GenTask(genPos);
CompletableFuture<WorldGenResult> worldGenFuture = tempWorldGenQueue.submitGenTask(genPos, (byte) (genPos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTask);
worldGenFuture.whenComplete((genTaskResult, ex) -> this.onWorldGenTaskComplete(genTaskResult, ex));
});
return generationList;
}
@Override
public int getMaxPossibleRetrievalPositionCountForPos(DhSectionPos pos)
{
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null)
{
return -1;
}
int minGeneratorSectionDetailLevel = worldGenQueue.highestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
int detailLevelDiff = pos.getDetailLevel() - minGeneratorSectionDetailLevel;
return BitShiftUtil.powerOfTwo(detailLevelDiff);
}
//=======//
// debug //
//=======//
@Override
public void debugRender(DebugRenderer renderer)
{
@@ -47,6 +47,7 @@ public class RenderSourceFileHandler extends AbstractLegacyDataSourceHandler<Col
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private final F3Screen.NestedMessage threadPoolMsg;
private int totalRetrievalPositionCount = 0;
public final IFullDataSourceProvider fullDataSourceProvider;
@@ -73,10 +74,6 @@ public class RenderSourceFileHandler extends AbstractLegacyDataSourceHandler<Col
@Override
public ColumnRenderSource get(DhSectionPos pos)
{
// call the full data provider to make sure the full data is up to date
// and any necessary world generation has been queued/completed
this.fullDataSourceProvider.queuePositionForGenerationOrRetrievalIfNecessary(pos);
return super.get(pos);
}
@@ -119,6 +116,19 @@ public class RenderSourceFileHandler extends AbstractLegacyDataSourceHandler<Col
//=====================//
// extension overrides //
//=====================//
@Override
public CompletableFuture<Void> updateDataSourceAsync(NewFullDataSource inputDataSource)
{
// TODO once the legacy data provider has been replaced this can be removed
this.updateDataSourceAtPos(inputDataSource.getSectionPos(), inputDataSource);
return CompletableFuture.completedFuture(null);
}
//=========//
// F3 menu //
@@ -148,12 +158,6 @@ public class RenderSourceFileHandler extends AbstractLegacyDataSourceHandler<Col
return lines.toArray(new String[0]);
}
@Override
public CompletableFuture<Void> updateDataSourceAsync(NewFullDataSource inputDataSource)
{
this.updateDataSourceAtPos(inputDataSource.getSectionPos(), inputDataSource);
return CompletableFuture.completedFuture(null);
}
//=====================//
@@ -45,6 +45,10 @@ public interface IWorldGenerationQueue extends Closeable
int getWaitingTaskCount();
int getInProgressTaskCount();
/** used for rendering to the F3 menu */
int getEstimatedTotalTaskCount();
void setEstimatedTotalTaskCount(int newEstimate);
CompletableFuture<Void> startClosing(boolean cancelCurrentGeneration, boolean alsoInterruptRunning);
void close();
@@ -67,13 +67,8 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
/** largest numerical detail level allowed */
public final byte lowestDataDetail;
@Override
public byte lowestDataDetail() { return this.lowestDataDetail; }
/** smallest numerical detail level allowed */
public final byte highestDataDetail;
@Override
public byte highestDataDetail() { return this.highestDataDetail; }
/** If not null this generator is in the process of shutting down */
@@ -96,6 +91,9 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
private final HashMap<DhSectionPos, StackTraceElement[]> alreadyGeneratedPosHashSet = new HashMap<>(MAX_ALREADY_GENERATED_COUNT);
private final Queue<DhSectionPos> alreadyGeneratedPosQueue = new LinkedList<>();
/** just used for rendering to the F3 menu */
private int estimatedTotalTaskCount = 0;
//==============//
@@ -498,13 +496,24 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
//=========//
// getters //
//=========//
//===================//
// getters / setters //
//===================//
public int getWaitingTaskCount() { return this.waitingTasks.size(); }
public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); }
@Override
public byte lowestDataDetail() { return this.lowestDataDetail; }
@Override
public byte highestDataDetail() { return this.highestDataDetail; }
@Override
public int getEstimatedTotalTaskCount() { return this.estimatedTotalTaskCount; }
@Override
public void setEstimatedTotalTaskCount(int newEstimate) { this.estimatedTotalTaskCount = newEstimate; }
//==========//
// shutdown //
@@ -316,7 +316,7 @@ public class ClientLevelModule implements Closeable, AbstractNewDataSourceHandle
this.quadtree = new LodQuadTree(dhClientLevel, Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH * 2,
// initial position is (0,0) just in case the player hasn't loaded in yet, the tree will be moved once the level starts ticking
0, 0,
this.renderSourceFileHandler);
this.renderSourceFileHandler.fullDataSourceProvider, this.renderSourceFileHandler);
RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree);
this.renderer = new LodRenderer(renderBufferHandler);
@@ -53,8 +53,9 @@ public class WorldGenModule implements Closeable
{
int waitingCount = worldGenState.worldGenerationQueue.getWaitingTaskCount();
int inProgressCount = worldGenState.worldGenerationQueue.getInProgressTaskCount();
int totalCountEstimate = worldGenState.worldGenerationQueue.getEstimatedTotalTaskCount();
return "World Gen Tasks: "+waitingCount+", (in progress: "+inProgressCount+")";
return "World Gen Tasks: "+waitingCount+"/"+totalCountEstimate+", (in progress: "+inProgressCount+")";
}
else
{
@@ -224,6 +224,12 @@ public class DhSectionPos
return (centerBlockPos * BitShiftUtil.powerOfTwo(this.detailLevel)) + positionOffset;
}
public int getManhattanBlockDistance(DhBlockPos2D blockPos)
{
return Math.abs(this.getCenterBlockPosX() - blockPos.x)
+ Math.abs(this.getCenterBlockPosZ() - blockPos.z);
}
//==================//
@@ -23,20 +23,25 @@ import com.seibel.distanthorizons.api.enums.config.EHorizontalQuality;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.renderfile.IRenderSourceProvider;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -48,8 +53,13 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
public static final byte TREE_LOWEST_DETAIL_LEVEL = ColumnRenderSource.SECTION_SIZE_OFFSET;
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */
private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleThreadPool("QuadTree Full Data Retrieval Queue Populator");
private static final int WORLD_GEN_QUEUE_UPDATE_DELAY_IN_MS = 1_000;
public final int blockRenderDistanceDiameter;
private final IFullDataSourceProvider fullDataSourceProvider;
private final IRenderSourceProvider renderSourceProvider;
/**
@@ -60,6 +70,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
private final ConfigChangeListener<EHorizontalQuality> horizontalScaleChangeListener;
private final ReentrantLock treeReadWriteLock = new ReentrantLock();
private final AtomicBoolean fullDataRetrievalQueueRunning = new AtomicBoolean(false);
/** the smallest numerical detail level number that can be rendered */
private byte maxRenderDetailLevel;
@@ -80,12 +91,14 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
public LodQuadTree(
IDhClientLevel level, int viewDiameterInBlocks,
int initialPlayerBlockX, int initialPlayerBlockZ,
IRenderSourceProvider provider)
IFullDataSourceProvider fullDataSourceProvider,
IRenderSourceProvider renderSourceProvider)
{
super(viewDiameterInBlocks, new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), TREE_LOWEST_DETAIL_LEVEL);
this.level = level;
this.renderSourceProvider = provider;
this.fullDataSourceProvider = fullDataSourceProvider;
this.renderSourceProvider = renderSourceProvider;
this.blockRenderDistanceDiameter = viewDiameterInBlocks;
this.horizontalScaleChangeListener = new ConfigChangeListener<>(Config.Client.Advanced.Graphics.Quality.horizontalQuality, (newHorizontalScale) -> this.onHorizontalQualityChange());
@@ -163,6 +176,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
// walk through each root node
ArrayList<LodRenderSection> nodesNeedingRetrieval = new ArrayList<>();
Iterator<DhSectionPos> rootPosIterator = this.rootNodePosIterator();
while (rootPosIterator.hasNext())
{
@@ -174,14 +188,23 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
}
QuadNode<LodRenderSection> rootNode = this.getNode(rootPos);
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, rootNode, rootNode.sectionPos, false);
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, rootNode, rootNode.sectionPos, false, nodesNeedingRetrieval);
}
// full data retrieval (world gen)
if (!this.fullDataRetrievalQueueRunning.get())
{
this.fullDataRetrievalQueueRunning.set(true);
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() -> this.queueFullDataRetrievalTasks(playerPos, nodesNeedingRetrieval));
}
}
/** @return whether the current position is able to render (note: not if it IS rendering, just if it is ABLE to.) */
private boolean recursivelyUpdateRenderSectionNode(
DhBlockPos2D playerPos,
QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> quadNode, DhSectionPos sectionPos,
boolean parentRenderSectionIsEnabled)
boolean parentRenderSectionIsEnabled,
ArrayList<LodRenderSection> nodesNeedingRetrieval)
{
//===============================//
// node and render section setup //
@@ -237,7 +260,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
DhSectionPos childPos = childPosIterator.next();
QuadNode<LodRenderSection> childNode = rootNode.getNode(childPos);
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, canThisPosRender || parentRenderSectionIsEnabled);
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, canThisPosRender || parentRenderSectionIsEnabled, nodesNeedingRetrieval);
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
}
@@ -259,7 +282,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
DhSectionPos childPos = childPosIterator.next();
QuadNode<LodRenderSection> childNode = rootNode.getNode(childPos);
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, parentRenderSectionIsEnabled);
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, childPos, parentRenderSectionIsEnabled, nodesNeedingRetrieval);
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
}
if (!allChildrenSectionsAreLoaded)
@@ -276,6 +299,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
// TODO this should only equal the expected detail level, the (expectedDetailLevel-1) is a temporary fix to prevent corners from being cut out
else if (sectionPos.getDetailLevel() == expectedDetailLevel || sectionPos.getDetailLevel() == expectedDetailLevel - 1)
{
// this is the detail level we want to render //
/* Can be uncommented to easily debug a single render section. */
/* Don't forget the disableRendering() at the bottom though. */
//if (sectionPos.getDetailLevel() == 10
@@ -285,9 +311,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
// sectionPos.getZ() == -4
// ))
{
// this is the detail level we want to render //
// prepare this section for rendering
renderSection.loadRenderSource(this.renderSourceProvider, this.level); // TODO this should fire for the lowest detail level first, wait for it to finish then fire the next highest to prevent waiting forever for 2 million chunk section to finish sampling everything
// TODO this should fire for the lowest detail level first to improve loading speed
renderSection.loadRenderSource(this.renderSourceProvider, this.level);
// wait for the parent to disable before enabling this section, so we don't overdraw/overlap render sections
if (!parentRenderSectionIsEnabled && renderSection.canRenderNow())
@@ -308,6 +334,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
});
}
}
if (!renderSection.isFullyGenerated())
{
nodesNeedingRetrieval.add(renderSection);
}
}
//else
//{
@@ -400,7 +431,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
*/
public void clearRenderDataCache()
{
if (this.treeReadWriteLock.tryLock())
if (this.treeReadWriteLock.tryLock()) // TODO make async, can lock render thread
{
try
{
@@ -451,14 +482,70 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements AutoClose
//=================================//
// full data retrieval (world gen) //
//=================================//
private void queueFullDataRetrievalTasks(DhBlockPos2D playerPos, ArrayList<LodRenderSection> nodesNeedingRetrieval)
{
try
{
// add a slight delay since we don't need to check the world gen queue every tick
Thread.sleep(WORLD_GEN_QUEUE_UPDATE_DELAY_IN_MS);
// sort the nodes from nearest to farthest
nodesNeedingRetrieval.sort((a, b) ->
{
int aDist = a.pos.getManhattanBlockDistance(playerPos);
int bDist = b.pos.getManhattanBlockDistance(playerPos);
return Integer.compare(aDist, bDist);
});
// add retrieval tasks to the queue
for (int i = 0; i < nodesNeedingRetrieval.size(); i++)
{
LodRenderSection renderSection = nodesNeedingRetrieval.get(i);
if (!this.fullDataSourceProvider.canQueueRetrieval())
{
break;
}
renderSection.tryQueuingMissingLodRetrieval(this.fullDataSourceProvider);
}
// calculate an estimate for the max number of tasks for the queue
int totalWorldGenCount = 0;
for (int i = 0; i < nodesNeedingRetrieval.size(); i++)
{
LodRenderSection renderSection = nodesNeedingRetrieval.get(i);
if (!renderSection.missingPositionsCalculated())
{
// may be higher than the actual amount
totalWorldGenCount += this.fullDataSourceProvider.getMaxPossibleRetrievalPositionCountForPos(renderSection.pos);
}
else
{
totalWorldGenCount += renderSection.ungeneratedPositionCount();
}
}
this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenCount);
}
catch (Exception e)
{
LOGGER.error("Unexpected error: "+e.getMessage(), e);
}
finally
{
this.fullDataRetrievalQueueRunning.set(false);
}
}
//==================//
// config listeners //
//==================//
private void onHorizontalQualityChange()
{
this.clearRenderDataCache();
}
private void onHorizontalQualityChange() { this.clearRenderDataCache(); }
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBufferBuilder;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider;
import com.seibel.distanthorizons.core.file.renderfile.IRenderSourceProvider;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -36,6 +37,7 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.util.ArrayList;
import java.util.Objects;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
@@ -84,6 +86,10 @@ public class LodRenderSection implements IDebugRenderable
private final QuadTree<LodRenderSection> parentQuadTree;
private boolean missingPositionsCalculated = false;
/** should be an empty array if no positions need to be generated */
private ArrayList<DhSectionPos> missingGenerationPos = null;
//=============//
@@ -407,6 +413,56 @@ public class LodRenderSection implements IDebugRenderable
//=================================//
// full data retrieval (world gen) //
//=================================//
public boolean isFullyGenerated() { return this.missingPositionsCalculated && this.missingGenerationPos.size() == 0; }
public boolean missingPositionsCalculated() { return this.missingPositionsCalculated; }
public int ungeneratedPositionCount() { return (this.missingGenerationPos != null) ? this.missingGenerationPos.size() : 0; }
public void tryQueuingMissingLodRetrieval(IFullDataSourceProvider fullDataSourceProvider)
{
if (fullDataSourceProvider.canRetrieveMissingDataSources() && fullDataSourceProvider.canQueueRetrieval())
{
// calculate the missing positions if not already done
if (!this.missingPositionsCalculated)
{
this.missingGenerationPos = fullDataSourceProvider.getPositionsToRetrieve(this.pos);
if (this.missingGenerationPos != null)
{
this.missingPositionsCalculated = true;
}
}
// if the missing positions were found, queue them
if (this.missingGenerationPos != null)
{
// queue from last to first to prevent shifting the array unnecessarily
for (int i = this.missingGenerationPos.size() - 1; i >= 0; i--)
{
if (!fullDataSourceProvider.canQueueRetrieval())
{
// the data source provider isn't accepting any more jobs
break;
}
DhSectionPos pos = this.missingGenerationPos.remove(i);
boolean positionQueued = fullDataSourceProvider.queuePositionForRetrieval(pos);
if (!positionQueued)
{
// shouldn't normally happen, but just in case
this.missingGenerationPos.add(pos);
break;
}
}
}
}
}
//==============//
// base methods //
//==============//
@@ -209,6 +209,26 @@ public class NewFullDataSourceRepo extends AbstractDhRepo<DhSectionPos, NewFullD
return list;
}
/** @return null if nothing exists for this position */
public byte[] getColumnGenerationStepForPos(DhSectionPos pos)
{
int detailLevel = pos.getDetailLevel() - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
Map<String, Object> resultMap = this.queryDictionaryFirst(
"select ColumnGenerationStep " +
"from "+this.getTableName()+" " +
"WHERE DetailLevel = "+detailLevel+" AND PosX = "+pos.getX()+" AND PosZ = "+pos.getZ());
if (resultMap != null)
{
return (byte[]) resultMap.get("ColumnGenerationStep");
}
else
{
return null;
}
}
}