This commit is contained in:
s809
2023-09-28 22:54:35 +05:00
11 changed files with 357 additions and 274 deletions
@@ -94,176 +94,6 @@ public interface IFullDataSource
FullDataPointIdMap getMapping();
/**
* @param highestGeneratorDetailLevel the smallest numerical detail level that the un-generated positions should be split into
* @return the list of {@link DhSectionPos} that aren't generated in this data source.
*/
default ArrayList<DhSectionPos> getUngeneratedPosList(byte highestGeneratorDetailLevel, boolean onlyReturnPositionsTheGeneratorCanAccept)
{
ArrayList<DhSectionPos> posArray = this.getUngeneratedPosList(this.getSectionPos(), highestGeneratorDetailLevel);
if (onlyReturnPositionsTheGeneratorCanAccept)
{
LinkedList<DhSectionPos> posList = new LinkedList<>(posArray);
ArrayList<DhSectionPos> cleanedPosArray = new ArrayList<>();
while (posList.size() > 0)
{
DhSectionPos pos = posList.remove();
if (pos.getDetailLevel() > highestGeneratorDetailLevel)
{
pos.forEachChild((childPos) -> { posList.push(childPos); });
}
else
{
cleanedPosArray.add(pos);
}
}
return cleanedPosArray;
}
else
{
return posArray;
}
}
default ArrayList<DhSectionPos> getUngeneratedPosList(DhSectionPos quadrantPos, byte highestGeneratorDetailLevel)
{
ArrayList<DhSectionPos> ungeneratedPosList = new ArrayList<>();
int sourceRelWidth = this.getWidthInDataPoints();
if (quadrantPos.getDetailLevel() < highestGeneratorDetailLevel)
{
throw new IllegalArgumentException("detail level lower than world generator can accept.");
}
else if (quadrantPos.getDetailLevel() == highestGeneratorDetailLevel)
{
// we are at the highest detail level the world generator can accept,
// we either need to generate this whole section, or not at all
// TODO combine duplicate code
byte childDetailLevel = (byte) (quadrantPos.getDetailLevel());
int quadrantDetailLevelDiff = this.getSectionPos().getDetailLevel() - childDetailLevel;
int widthInSecPos = BitShiftUtil.powerOfTwo(quadrantDetailLevelDiff);
int relWidthForSecPos = sourceRelWidth / widthInSecPos;
DhSectionPos minSecPos = this.getSectionPos().convertNewToDetailLevel(childDetailLevel);
DhSectionPos inputPos = quadrantPos;
int minRelX = inputPos.getX() - minSecPos.getX();
int minRelZ = inputPos.getZ() - minSecPos.getZ();
int maxRelX = minRelX + 1;
int maxRelZ = minRelZ + 1;
minRelX = minRelX * relWidthForSecPos;
minRelZ = minRelZ * relWidthForSecPos;
maxRelX = maxRelX * relWidthForSecPos;
maxRelZ = maxRelZ * relWidthForSecPos;
boolean quadrantFullyGenerated = true;
for (int relX = minRelX; relX < maxRelX; relX++)
{
for (int relZ = minRelZ; relZ < maxRelZ; relZ++)
{
SingleColumnFullDataAccessor column = this.tryGet(relX, relZ);
if (column == null || !column.doesColumnExist())
{
// no data for this relative position
quadrantFullyGenerated = false;
break;
}
}
}
if (!quadrantFullyGenerated)
{
// at least 1 data point is missing,
// this whole section must be regenerated
ungeneratedPosList.add(quadrantPos);
}
}
else
{
// TODO comment
// TODO combine duplicate code
byte childDetailLevel = (byte) (quadrantPos.getDetailLevel() - 1);
for (int i = 0; i < 4; i++)
{
int quadrantDetailLevelDiff = this.getSectionPos().getDetailLevel() - childDetailLevel;
int widthInSecPos = BitShiftUtil.powerOfTwo(quadrantDetailLevelDiff);
int relWidthForSecPos = sourceRelWidth / widthInSecPos;
DhSectionPos minSecPos = this.getSectionPos().convertNewToDetailLevel(childDetailLevel);
DhSectionPos inputPos = quadrantPos.getChildByIndex(i);
int minRelX = inputPos.getX() - minSecPos.getX();
int minRelZ = inputPos.getZ() - minSecPos.getZ();
int maxRelX = minRelX + 1;
int maxRelZ = minRelZ + 1;
minRelX = minRelX * relWidthForSecPos;
minRelZ = minRelZ * relWidthForSecPos;
maxRelX = maxRelX * relWidthForSecPos;
maxRelZ = maxRelZ * relWidthForSecPos;
boolean quadrantFullyGenerated = true;
boolean quadrantEmpty = true;
for (int relX = minRelX; relX < maxRelX; relX++)
{
for (int relZ = minRelZ; relZ < maxRelZ; relZ++)
{
SingleColumnFullDataAccessor column = this.tryGet(relX, relZ);
if (column == null || !column.doesColumnExist())
{
// no data for this relative position
quadrantFullyGenerated = false;
}
else
{
// data exists for this pos
quadrantEmpty = false;
}
}
}
if (quadrantFullyGenerated)
{
// no generation necessary
continue;
}
else if (quadrantEmpty)
{
// nothing exists for this sub quadrant, add this sub-quadrant's position
ungeneratedPosList.add(inputPos);
}
else
{
// some data exists in this quadrant, but not all that we need
// recurse down to determine which sub-quadrant positions will need generation
ungeneratedPosList.addAll(this.getUngeneratedPosList(inputPos, highestGeneratorDetailLevel));
}
}
}
return ungeneratedPosList;
}
//=======================//
@@ -58,7 +58,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
protected static ExecutorService fileHandlerThreadPool;
protected static ConfigChangeListener<Integer> configListener;
private final ConcurrentHashMap<DhSectionPos, FullDataMetaFile> loadedMetaFileBySectionPos = new ConcurrentHashMap<>();
protected final ConcurrentHashMap<DhSectionPos, FullDataMetaFile> loadedMetaFileBySectionPos = new ConcurrentHashMap<>();
public Map<DhSectionPos, Integer> getLoadStates(Iterable<DhSectionPos> posList)
{
@@ -67,7 +67,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
public boolean doesFileExist;
//TODO: Atm can't find a better way to store when genQueue is checked.
/** indicates if this file has been checked for missing sections to generate or not */
public boolean genQueueChecked = false;
public AbstractFullDataSourceLoader fullDataSourceLoader;
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFull
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.generation.MissingWorldGenPositionFinder;
import com.seibel.distanthorizons.core.generation.IWorldGenerationQueue;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
@@ -84,26 +85,79 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
this.handlerF3Message.close();
}
//===========//
// overrides //
//===========//
@Override
public CompletableFuture<IFullDataSource> readAsync(DhSectionPos pos)
{
CompletableFuture<IFullDataSource> future = super.readAsync(pos);
return future.thenApply((dataSource) ->
{
// add world gen tasks for missing columns in the data source
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
FullDataMetaFile metaFile = this.loadedMetaFileBySectionPos.get(pos);
if (worldGenQueue != null && metaFile != null)
{
this.queueWorldGenForMissingColumnsInDataSource(worldGenQueue, metaFile, dataSource);
}
return dataSource;
});
}
@Override
public void onRenderDataFileLoaded(DhSectionPos pos)
{
// add world gen tasks for missing columns in the data source
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
FullDataMetaFile metaFile = this.getLoadOrMakeFile(pos, false);
if (worldGenQueue != null && metaFile != null)
{
metaFile.getDataSourceWithoutCachingAsync().thenApply((fullDataSource) ->
{
this.queueWorldGenForMissingColumnsInDataSource(worldGenQueue, metaFile, fullDataSource);
return fullDataSource;
});
}
}
//==================//
// generation queue //
//==================//
/** Assumes there isn't a pre-existing queue. */
public void setGenerationQueue(IWorldGenerationQueue newWorldGenQueue)
/**
* Assigns the queue for handling world gen and does first time setup as well. <br>
* Assumes there isn't a pre-existing queue.
*/
public void setWorldGenerationQueue(IWorldGenerationQueue newWorldGenQueue)
{
boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue);
LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!");
LOGGER.info("Set world gen queue for level {} to start.", this.level);
this.ForEachFile(metaFile -> {
IFullDataSource data = metaFile.getCachedDataSourceNowOrNull();
if (data instanceof CompleteFullDataSource) return;
metaFile.genQueueChecked = false; // unset it so it can be checked again
if (data != null)
LOGGER.info("Set world gen queue for level "+this.level+" to start.");
this.ForEachFile(metaFile ->
{
IFullDataSource dataSource = metaFile.getCachedDataSourceNowOrNull();
if (dataSource == null)
{
metaFile.markNeedsUpdate();
return;
}
metaFile.genQueueChecked = false; // allow the system to check for missing positions again
this.queueWorldGenForMissingColumnsInDataSource(this.worldGenQueueRef.get(), metaFile, dataSource);
if (dataSource instanceof CompleteFullDataSource)
{
return;
}
metaFile.markNeedsUpdate();
});
flushAndSave(); // Trigger an update to the meta files
this.flushAndSave(); // Trigger an update to the meta files
}
public void clearGenerationQueue()
@@ -128,6 +182,8 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
this.worldGenQueueRef.get().cancelGenTasks(removedRequests);
}
//=================//
// event listeners //
//=================//
@@ -146,60 +202,12 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
return newSource;
}
//========//
// events //
//========//
@Nullable
private CompletableFuture<IFullDataSource> tryStartGenTask(FullDataMetaFile file, IIncompleteFullDataSource dataSource) // TODO after generation is finished, save and free any full datasources that aren't in use (IE high detail ones below the top)
{
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
// breaks down the missing positions into the desired detail level that the gen queue could accept
if (worldGenQueue != null && !file.genQueueChecked)
{
DhSectionPos pos = file.pos;
file.genQueueChecked = true;
byte maxSectDataDetailLevel = worldGenQueue.largestDataDetail();
byte targetDataDetailLevel = dataSource.getDataDetailLevel();
if (targetDataDetailLevel > maxSectDataDetailLevel)
{
ArrayList<FullDataMetaFile> existingFiles = new ArrayList<>();
byte sectDetailLevel = (byte) (DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + maxSectDataDetailLevel);
pos.forEachChildAtLevel(sectDetailLevel, childPos -> existingFiles.add(this.getLoadOrMakeFile(childPos, true)));
return this.sampleFromFileArray(dataSource, existingFiles, true).thenApply(this::tryPromoteDataSource)
.exceptionally((ex) ->
{
FullDataMetaFile newMetaFile = this.removeCorruptedFile(pos, file, ex);
return null;
});
}
else
{
this.incompleteDataSources.put(pos, dataSource);
// queue this section to be generated
GenTask genTask = new GenTask(pos, new WeakReference<>(dataSource));
worldGenQueue.submitGenTask(pos, dataSource.getDataDetailLevel(), genTask)
.whenComplete((genTaskResult, ex) ->
{
if (genTaskResult.success)
{
this.onWorldGenTaskComplete(genTaskResult, ex, genTask, pos);
this.fireOnGenPosSuccessListeners(pos);
}
else
{
file.genQueueChecked = false;
}
this.incompleteDataSources.remove(pos);
});
}
// return the empty dataSource (it will be populated later)
return CompletableFuture.completedFuture(dataSource);
}
return null;
}
// Try update the gen queue on this data source. If null, then nothing was done.
@Nullable
private CompletableFuture<IFullDataSource> updateFromExistingDataSourcesAsync(FullDataMetaFile file, IIncompleteFullDataSource data, boolean usePooledDataSources)
@@ -227,6 +235,17 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
});
}
}
@Nullable
private CompletableFuture<IFullDataSource> tryStartGenTask(FullDataMetaFile metaFile, IIncompleteFullDataSource dataSource) // TODO after generation is finished, save and free any full datasources that aren't in use (IE high detail ones below the top)
{
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue != null)
{
this.queueWorldGenForMissingColumnsInDataSource(worldGenQueue, metaFile, dataSource);
return CompletableFuture.completedFuture(dataSource);
}
return null;
}
@Override
public CompletableFuture<IFullDataSource> onDataFileCreatedAsync(FullDataMetaFile file)
@@ -343,6 +362,38 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
//================//
// helper methods //
//================//
private void queueWorldGenForMissingColumnsInDataSource(IWorldGenerationQueue worldGenQueue, FullDataMetaFile metaFile, IFullDataSource dataSource)
{
// Due to a bug in the current system, some Complete data sources aren't actually complete
// and will need additional generation to finish
//if (dataSource instanceof CompleteFullDataSource)
//{
// return;
//}
if (metaFile.genQueueChecked)
{
// world gen has already been checked for this file
return;
}
metaFile.genQueueChecked = true;
byte minGeneratorSectionDetailLevel = (byte) (worldGenQueue.highestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
ArrayList<DhSectionPos> genPosList = MissingWorldGenPositionFinder.getUngeneratedPosList(dataSource, minGeneratorSectionDetailLevel, true);
for (DhSectionPos genPos : genPosList)
{
GenTask genTask = new GenTask(dataSource.getSectionPos(), new WeakReference<>(dataSource));
worldGenQueue.submitGenTask(genPos, dataSource.getSectionPos().getDetailLevel(), genTask);
}
}
//================//
// helper classes //
//================//
@@ -367,11 +418,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
@Override
public boolean isMemoryAddressValid()
{
IFullDataSource ref = this.targetFullDataSourceRef.get();
return ref != null && !((IIncompleteFullDataSource) ref).hasBeenPromoted();
}
public boolean isMemoryAddressValid() { return this.targetFullDataSourceRef.get() != null; }
@Override
@Nullable
@@ -41,6 +41,9 @@ public interface IFullDataSourceProvider extends AutoCloseable
CompletableFuture<IFullDataSource> onDataFileCreatedAsync(FullDataMetaFile file);
default CompletableFuture<DataFileUpdateResult> onDataFileUpdateAsync(IFullDataSource fullDataSource, FullDataMetaFile file, boolean dataChanged) { return CompletableFuture.completedFuture(new DataFileUpdateResult(fullDataSource, dataChanged)); }
/** Can be used to update world gen queues or run any other data checking necessary when initially loading a file */
default void onRenderDataFileLoaded(DhSectionPos pos) { }
File computeDataFilePath(DhSectionPos pos);
ExecutorService getIOExecutor();
@@ -125,6 +125,9 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
LodUtil.assertTrue(this.baseMetaData != null);
this.doesFileExist = this.file.exists();
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderDataFileStatus);
// handles world gen queuing for missing columns
this.fullDataSourceProvider.onRenderDataFileLoaded(this.baseMetaData.pos);
}
@@ -30,7 +30,9 @@ import java.util.concurrent.CompletableFuture;
public interface IWorldGenerationQueue extends Closeable
{
/** the largest numerical detail level */
byte largestDataDetail();
byte lowestDataDetail();
/** the smallest numerical detail level */
byte highestDataDetail();
CompletableFuture<WorldGenResult> submitGenTask(DhSectionPos pos, byte requiredDataDetail, IWorldGenTaskTracker tracker);
void cancelGenTasks(Iterable<DhSectionPos> positions);
@@ -0,0 +1,213 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.LinkedList;
/**
* Handles finding any positions in a given {@link IFullDataSource} that
* aren't generated.
*/
public class MissingWorldGenPositionFinder
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/**
* @param generatorDetailLevel the detail level that the un-generated positions should be split into.
* @param onlyReturnPositionsTheGeneratorCanAccept
* If true this will only return positions with the same detail level as generatorDetailLevel. <br>
* If false this will return the lowest detail level possible for totally empty sections. <br>
* As of 2023-9-28 both have been tested and confirmed working with the Batch world generator, the only difference is that
* the task list will be deceptively small if this value is false.
*
* @return the list of {@link DhSectionPos} that aren't generated in this data source.
*/
public static ArrayList<DhSectionPos> getUngeneratedPosList(IFullDataSource dataSource, byte generatorDetailLevel, boolean onlyReturnPositionsTheGeneratorCanAccept)
{
ArrayList<DhSectionPos> posArray = getUngeneratedPosListForQuadrant(dataSource, dataSource.getSectionPos(), generatorDetailLevel);
if (onlyReturnPositionsTheGeneratorCanAccept)
{
LinkedList<DhSectionPos> posList = new LinkedList<>(posArray);
// subdivide positions until they match the generatorDetailLevel
ArrayList<DhSectionPos> cleanedPosArray = new ArrayList<>();
while (posList.size() > 0)
{
DhSectionPos pos = posList.remove();
if (pos.getDetailLevel() > generatorDetailLevel)
{
pos.forEachChild((childPos) -> posList.push(childPos));
}
else
{
cleanedPosArray.add(pos);
}
}
return cleanedPosArray;
}
else
{
return posArray;
}
}
private static ArrayList<DhSectionPos> getUngeneratedPosListForQuadrant(IFullDataSource dataSource, DhSectionPos quadrantPos, byte generatorDetailLevel)
{
ArrayList<DhSectionPos> ungeneratedPosList = new ArrayList<>();
int sourceRelWidthInDataPoints = dataSource.getWidthInDataPoints();
if (quadrantPos.getDetailLevel() == generatorDetailLevel)
{
// we are at the highest detail level the world generator can accept,
// we either need to generate this whole section, or not at all
ESectionPopulationState populationState = getPopulationStateForPos(dataSource, quadrantPos, sourceRelWidthInDataPoints);
if (populationState != ESectionPopulationState.COMPLETE)
{
// at least 1 data point is missing,
// this whole section must be generated
ungeneratedPosList.add(quadrantPos);
}
}
else if (quadrantPos.getDetailLevel() > generatorDetailLevel)
{
// detail level too low for world generator, check child positions
for (int i = 0; i < 4; i++)
{
DhSectionPos inputPos = quadrantPos.getChildByIndex(i);
ESectionPopulationState populationState = getPopulationStateForPos(dataSource, inputPos, sourceRelWidthInDataPoints);
if (populationState == ESectionPopulationState.COMPLETE)
{
// no generation necessary
continue;
}
else if (populationState == ESectionPopulationState.EMPTY)
{
// nothing exists for this sub quadrant, add this sub-quadrant's position
ungeneratedPosList.add(inputPos);
}
else if (populationState == ESectionPopulationState.PARTIAL)
{
// some data exists in this quadrant, but not all that we need
// recurse down to determine which sub-quadrant positions will need generation
ungeneratedPosList.addAll(getUngeneratedPosListForQuadrant(dataSource, inputPos, generatorDetailLevel));
}
}
}
else
{
throw new IllegalArgumentException("detail level lower than world generator can accept.");
}
return ungeneratedPosList;
}
private static ESectionPopulationState getPopulationStateForPos(IFullDataSource dataSource, DhSectionPos inputPos, int sourceRelWidthInDataPoints)
{
// TODO comment
byte childDetailLevel = inputPos.getDetailLevel();
int quadrantDetailLevelDiff = dataSource.getSectionPos().getDetailLevel() - childDetailLevel;
int widthInSecPos = BitShiftUtil.powerOfTwo(quadrantDetailLevelDiff);
int relWidthForSecPos = sourceRelWidthInDataPoints / widthInSecPos;
DhSectionPos minSecPos = dataSource.getSectionPos().convertNewToDetailLevel(childDetailLevel);
int minRelX = inputPos.getX() - minSecPos.getX();
int maxRelX = minRelX + 1;
int minRelZ = inputPos.getZ() - minSecPos.getZ();
int maxRelZ = minRelZ + 1;
minRelX = minRelX * relWidthForSecPos;
maxRelX = maxRelX * relWidthForSecPos;
minRelZ = minRelZ * relWidthForSecPos;
maxRelZ = maxRelZ * relWidthForSecPos;
boolean quadrantFullyGenerated = true;
boolean quadrantEmpty = true;
for (int relX = minRelX; relX < maxRelX; relX++)
{
for (int relZ = minRelZ; relZ < maxRelZ; relZ++)
{
SingleColumnFullDataAccessor column = dataSource.tryGet(relX, relZ);
if (column == null || !column.doesColumnExist())
{
// no data for this relative position
quadrantFullyGenerated = false;
}
else
{
// data exists for this pos
quadrantEmpty = false;
}
}
}
if (quadrantFullyGenerated)
{
// no generation necessary
return ESectionPopulationState.COMPLETE;
}
else if (quadrantEmpty)
{
// nothing exists for this sub quadrant, add this sub-quadrant's position
return ESectionPopulationState.EMPTY;
}
else
{
// some data exists in this quadrant, but not all that we need
// recurse down to determine which sub-quadrant positions will need generation
return ESectionPopulationState.PARTIAL;
}
}
//================//
// helper classes //
//================//
private enum ESectionPopulationState
{
COMPLETE,
EMPTY,
PARTIAL
}
}
@@ -57,7 +57,6 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
private final IDhApiWorldGenerator generator;
/** contains the positions that need to be generated */
//private final QuadTree<WorldGenTask> waitingTaskQuadTree;
private final ConcurrentHashMap<DhSectionPos, WorldGenTask> waitingTasks = new ConcurrentHashMap<>();
private final ConcurrentHashMap<DhSectionPos, InProgressWorldGenTaskGroup> inProgressGenTasksByLodPos = new ConcurrentHashMap<>();
@@ -67,11 +66,15 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
public final byte minGranularity;
/** largest numerical detail level allowed */
public final byte largestDataDetail;
public final byte lowestDataDetail;
@Override
public byte largestDataDetail() { return this.largestDataDetail; }
/** lowest numerical detail level allowed */
public final byte smallestDataDetail;
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 */
private volatile CompletableFuture<Void> generatorClosingFuture = null;
@@ -108,13 +111,8 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
this.generator = generator;
this.maxGranularity = generator.getMaxGenerationGranularity();
this.minGranularity = generator.getMinGenerationGranularity();
this.largestDataDetail = generator.getLargestDataDetailLevel();
this.smallestDataDetail = generator.getSmallestDataDetailLevel();
//FIXME: Currently resizing view dist doesn't update this, causing some gen task to fail.
int treeWidth = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH * 2; // TODO the *2 is to allow for generation edge cases, and should probably be removed at some point
byte treeMinDetailLevel = LodUtil.CHUNK_DETAIL_LEVEL; // The min level should be at least fill in 1 ChunkSizedFullDataAccessor.
//this.waitingTaskQuadTree = new QuadTree<>(treeWidth, DhBlockPos2D.ZERO /*the quad tree will be re-centered later*/, treeMinDetailLevel);
this.lowestDataDetail = generator.getLargestDataDetailLevel();
this.highestDataDetail = generator.getSmallestDataDetailLevel();
if (this.minGranularity < LodUtil.CHUNK_DETAIL_LEVEL)
@@ -147,30 +145,22 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
// make sure the generator can provide the requested position
if (requiredDataDetail < this.smallestDataDetail)
if (requiredDataDetail < this.highestDataDetail)
{
throw new UnsupportedOperationException("Current generator does not meet requiredDataDetail level");
}
if (requiredDataDetail > this.largestDataDetail)
if (requiredDataDetail > this.lowestDataDetail)
{
requiredDataDetail = this.largestDataDetail;
requiredDataDetail = this.lowestDataDetail;
}
// Assert that the data at least can fill in 1 single ChunkSizedFullDataAccessor
LodUtil.assertTrue(pos.getDetailLevel() > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL);
//if (this.waitingTaskQuadTree.isSectionPosInBounds(requestPos))
{
CompletableFuture<WorldGenResult> future = new CompletableFuture<>();
//this.waitingTaskQuadTree.setValue(requestPos, new WorldGenTask(pos, requiredDataDetail, tracker, future));
waitingTasks.put(pos, new WorldGenTask(pos, requiredDataDetail, tracker, future));
return future;
}
//else
//{
//return CompletableFuture.completedFuture(WorldGenResult.CreateFail());
//}
CompletableFuture<WorldGenResult> future = new CompletableFuture<>();
this.waitingTasks.put(pos, new WorldGenTask(pos, requiredDataDetail, tracker, future));
return future;
}
@Override
@@ -302,7 +292,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
// the newly selected task, we cannot use it,
// as some chunks may have already been written into.
LOGGER.warn("A task already exists for this position, todo: {}", closestTask.pos);
LOGGER.trace("A task already exists for this position, todo: "+closestTask.pos);
}
// a task has been started
@@ -340,7 +330,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
DhSectionPos taskPos = inProgressTaskGroup.group.pos;
byte granularity = (byte) (taskPos.getDetailLevel() - taskDetailLevel);
LodUtil.assertTrue(granularity >= this.minGranularity && granularity <= this.maxGranularity);
LodUtil.assertTrue(taskDetailLevel >= this.smallestDataDetail && taskDetailLevel <= this.largestDataDetail);
LodUtil.assertTrue(taskDetailLevel >= this.highestDataDetail && taskDetailLevel <= this.lowestDataDetail);
DhChunkPos chunkPosMin = new DhChunkPos(taskPos.getSectionBBoxPos().getCornerBlockPos());
@@ -348,9 +338,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
if (this.alreadyGeneratedPosHashSet.containsKey(inProgressTaskGroup.group.pos))
{
// temporary solution to prevent generating the same section multiple times
LOGGER.warn("Duplicate generation section " + taskPos + " with granularity [" + granularity + "] at " + chunkPosMin + ". Skipping...");
//StackTraceElement[] stackTrace = this.alreadyGeneratedPosHashSet.get(inProgressTaskGroup.group.pos);
LOGGER.trace("Duplicate generation section " + taskPos + " with granularity [" + granularity + "] at " + chunkPosMin + ". Skipping...");
// sending a success result is necessary to make sure the render sections are reloaded correctly
inProgressTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(new DhSectionPos(granularity, taskPos.getX(), taskPos.getZ()))));
@@ -45,9 +45,6 @@ public final class WorldGenTask
this.future = future;
}
public boolean StillValid()
{
return taskTracker.isMemoryAddressValid();
}
public boolean StillValid() { return this.taskTracker.isMemoryAddressValid(); }
}
@@ -79,7 +79,7 @@ public class WorldGenModule implements Closeable
newWgs.closeAsync(false);
}
dataFileHandler.addWorldGenCompleteListener(this.onWorldGenCompleteListener);
dataFileHandler.setGenerationQueue(newWgs.worldGenerationQueue);
dataFileHandler.setWorldGenerationQueue(newWgs.worldGenerationQueue);
}
public void stopWorldGen(GeneratedFullDataFileHandler dataFileHandler)