Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core
This commit is contained in:
-170
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=======================//
|
||||
|
||||
+1
-1
@@ -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)
|
||||
{
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+112
-65
@@ -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
|
||||
|
||||
+3
@@ -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();
|
||||
|
||||
|
||||
+3
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -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);
|
||||
|
||||
+213
@@ -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
|
||||
}
|
||||
}
|
||||
+19
-31
@@ -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()))));
|
||||
|
||||
+1
-4
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user