Merge remote-tracking branch 'upstream-core/main'

# Conflicts:
#	core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiDebuggingConfig.java
#	core/src/main/java/com/seibel/distanthorizons/core/config/Config.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/HighDetailIncompleteFullDataSource.java
#	core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java
#	core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java
#	core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java
#	core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java
#	core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java
#	core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java
This commit is contained in:
Steveplays28
2023-09-22 15:40:31 +02:00
27 changed files with 1042 additions and 480 deletions
@@ -46,6 +46,6 @@ public class DhApiDebuggingConfig implements IDhApiDebuggingConfig
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.lodOnlyMode); }
public IDhApiConfigValue<Boolean> debugWireframeRendering()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.DebugWireframeRendering.enableRendering); }
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Debugging.DebugWireframe.enableRendering); }
}
@@ -1258,12 +1258,57 @@ public class Config
public static ConfigCategory debugWireframe = new ConfigCategory.Builder()
.set(DebugWireframe.class)
.build();
public static class DebugWireframe
{
public static ConfigEntry<Boolean> enableRendering = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If enabled, various wireframes for debugging internal functions will be drawn. \n"
+ "\n"
+ "NOTE: There WILL be performance hit! \n"
+ " Additionally, only stuff that's loaded after you enable this \n"
+ " will render their debug wireframes.")
.build();
public static ConfigEntry<Boolean> showWorldGenQueue = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render queued world gen tasks?")
.build();
public static ConfigEntry<Boolean> showRenderSectionStatus = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render LOD section status?")
.build();
public static ConfigEntry<Boolean> showFullDataFileStatus = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render full data file status?")
.build();
public static ConfigEntry<Boolean> showFullDataFileSampling = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render full data file sampling progress?")
.build();
public static ConfigEntry<Boolean> showRenderDataFileStatus = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment("Render render data file status?")
.build();
}
// can be set to public inorder to show in the config file and UI
public static ConfigCategory exampleConfigScreen = new ConfigCategory.Builder()
.set(ExampleConfigScreen.class)
.build();
/** This class is used to debug the different features of the config GUI */
// FIXME: WARNING: Some of the options in this class dont get show n in the default UI
// This will throw a warning when opened in the default ui to tell you about it not showing
@@ -41,7 +41,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
* Used to map a numerical IDs to a Biome/BlockState pair.
*
* @author Leetom
* @version 2022-10-2
*/
public class FullDataPointIdMap
{
@@ -61,7 +60,7 @@ public class FullDataPointIdMap
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/** should only be used for debugging */
private final DhSectionPos pos;
private DhSectionPos pos;
/** The index should be the same as the Entry's ID */
private final ArrayList<Entry> entryList = new ArrayList<>();
@@ -78,7 +77,7 @@ public class FullDataPointIdMap
//=========//
// methods //
// getters //
//=========//
private Entry getEntry(int id)
@@ -102,6 +101,12 @@ public class FullDataPointIdMap
public IBiomeWrapper getBiomeWrapper(int id) { return this.getEntry(id).biome; }
public IBlockStateWrapper getBlockStateWrapper(int id) { return this.getEntry(id).blockState; }
//=========//
// setters //
//=========//
/**
* If an entry with the given values already exists nothing will
* be added but the existing item's ID will still be returned.
@@ -169,6 +174,20 @@ public class FullDataPointIdMap
return remappedEntryIds;
}
/** Should only be used if this map is going to be reused, otherwise bad things will happen. */
public void clear(DhSectionPos pos)
{
this.pos = pos;
this.entryList.clear();
this.idMap.clear();
}
//=============//
// serializing //
//=============//
/** Serializes all contained entries into the given stream, formatted in UTF */
public void serialize(DhDataOutputStream outputStream) throws IOException
{
@@ -235,6 +254,12 @@ public class FullDataPointIdMap
return newMap;
}
//===========//
// overrides //
//===========//
@Override
public boolean equals(Object other)
{
@@ -36,10 +36,7 @@ public class FullDataArrayAccessor implements IFullDataAccessor
{
protected final FullDataPointIdMap mapping;
/**
* A flattened 2D array (for the X and Z directions) containing an array for the Y direction.
* TODO the flattened array is probably to reduce garbage collection overhead, but is doing it this way worth while? Having a 3D array would be much easier to understand
*/
/** A flattened 2D array (for the X and Z directions) containing an array for the Y direction. */
protected final long[][] dataArrays;
/** measured in data points */
@@ -133,20 +130,24 @@ public class FullDataArrayAccessor implements IFullDataAccessor
/**
* Takes a higher detail {@link FullDataArrayAccessor}'s and converts the data to a lower detail level.
*
* @param fullDataAccessor must be larger than this {@link FullDataArrayAccessor} and its width must a power of two larger (example: this.width = 4, other.width = 8)
* @param incomingFullDataAccessor must be larger than this {@link FullDataArrayAccessor} and its width must a power of two larger (example: this.width = 4, other.width = 8)
*/
public void downsampleFrom(FullDataArrayAccessor fullDataAccessor)
public void downsampleFrom(FullDataArrayAccessor incomingFullDataAccessor)
{
// validate that the incoming data isn't smaller than this accessor
LodUtil.assertTrue(fullDataAccessor.width >= this.width && fullDataAccessor.width % this.width == 0);
LodUtil.assertTrue(incomingFullDataAccessor.width >= this.width && incomingFullDataAccessor.width % this.width == 0);
int dataPointsPerWidthUnit = fullDataAccessor.width / this.width;
int dataPointsPerWidthUnit = incomingFullDataAccessor.width / this.width;
for (int xOffset = 0; xOffset < this.width; xOffset++)
{
for (int zOffset = 0; zOffset < this.width; zOffset++)
{
FullDataArrayAccessor subView = incomingFullDataAccessor.subView(dataPointsPerWidthUnit,
xOffset * dataPointsPerWidthUnit,
zOffset * dataPointsPerWidthUnit);
SingleColumnFullDataAccessor column = this.get(xOffset, zOffset);
column.downsampleFrom(fullDataAccessor.subView(dataPointsPerWidthUnit, xOffset * dataPointsPerWidthUnit, zOffset * dataPointsPerWidthUnit));
column.downsampleFrom(subView);
}
}
}
@@ -137,7 +137,7 @@ public class SingleColumnFullDataAccessor implements IFullDataAccessor
{
if (target.mapping.equals(this.mapping))
{
target.dataArrays[target.dataArrayIndex] = this.dataArrays[this.dataArrayIndex].clone();
System.arraycopy(this.dataArrays[this.dataArrayIndex], 0, target.dataArrays[target.dataArrayIndex], 0, this.dataArrays[this.dataArrayIndex].length);
}
else
{
@@ -23,35 +23,49 @@ import com.google.common.collect.HashMultimap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public abstract class AbstractFullDataSourceLoader
{
public static final HashMultimap<Class<? extends IFullDataSource>, AbstractFullDataSourceLoader> LOADER_REGISTRY = HashMultimap.create();
public static final HashMap<Long, Class<? extends IFullDataSource>> DATATYPE_ID_REGISTRY = new HashMap<>();
private static final int AVAILABLE_PROCESSOR_COUNT = Runtime.getRuntime().availableProcessors();
public static final HashMultimap<Class<? extends IFullDataSource>, AbstractFullDataSourceLoader> loaderRegistry = HashMultimap.create();
public final Class<? extends IFullDataSource> fullDataSourceClass;
public static final HashMap<Long, Class<? extends IFullDataSource>> datatypeIdRegistry = new HashMap<>();
public final long datatypeId;
public final byte[] loaderSupportedVersions;
/** used when pooling data sources */
private final ArrayList<IFullDataSource> cachedSources = new ArrayList<>();
private final ReadWriteLock cacheReadWriteLock = new ReentrantReadWriteLock();
//=============//
// constructor //
//=============//
public AbstractFullDataSourceLoader(Class<? extends IFullDataSource> fullDataSourceClass, long datatypeId, byte[] loaderSupportedVersions)
{
this.datatypeId = datatypeId;
this.loaderSupportedVersions = loaderSupportedVersions;
Arrays.sort(loaderSupportedVersions); // sort to allow fast access
this.fullDataSourceClass = fullDataSourceClass;
if (datatypeIdRegistry.containsKey(datatypeId) && datatypeIdRegistry.get(datatypeId) != fullDataSourceClass)
if (DATATYPE_ID_REGISTRY.containsKey(datatypeId) && DATATYPE_ID_REGISTRY.get(datatypeId) != fullDataSourceClass)
{
throw new IllegalArgumentException("Loader for datatypeId " + datatypeId + " already registered with different class: "
+ datatypeIdRegistry.get(datatypeId) + " != " + fullDataSourceClass);
+ DATATYPE_ID_REGISTRY.get(datatypeId) + " != " + fullDataSourceClass);
}
Set<AbstractFullDataSourceLoader> loaders = loaderRegistry.get(fullDataSourceClass);
Set<AbstractFullDataSourceLoader> loaders = LOADER_REGISTRY.get(fullDataSourceClass);
if (loaders.stream().anyMatch(other ->
{
// see if any loaderSupportsVersion conflicts with this one
@@ -68,31 +82,129 @@ public abstract class AbstractFullDataSourceLoader
throw new IllegalArgumentException("Loader for class " + fullDataSourceClass + " that supports one of the version in "
+ Arrays.toString(loaderSupportedVersions) + " already registered!");
}
datatypeIdRegistry.put(datatypeId, fullDataSourceClass);
loaderRegistry.put(fullDataSourceClass, this);
DATATYPE_ID_REGISTRY.put(datatypeId, fullDataSourceClass);
LOADER_REGISTRY.put(fullDataSourceClass, this);
}
//================//
// loader getters //
//================//
public static AbstractFullDataSourceLoader getLoader(long dataTypeId, byte dataVersion)
{
return LOADER_REGISTRY.get(DATATYPE_ID_REGISTRY.get(dataTypeId)).stream()
.filter(loader -> Arrays.binarySearch(loader.loaderSupportedVersions, dataVersion) >= 0)
.findFirst().orElse(null);
}
public static AbstractFullDataSourceLoader getLoader(Class<? extends IFullDataSource> clazz, byte dataVersion)
{
return LOADER_REGISTRY.get(clazz).stream()
.filter(loader -> Arrays.binarySearch(loader.loaderSupportedVersions, dataVersion) >= 0)
.findFirst().orElse(null);
}
//==================//
// abstract methods //
//==================//
protected abstract IFullDataSource createEmptyDataSource(DhSectionPos pos);
//==============//
// data loading //
//==============//
/**
* Can return null if any of the requirements aren't met.
*
* @throws InterruptedException if the loader thread is interrupted, generally happens when the level is shutting down
*/
public abstract IFullDataSource loadData(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException;
public static AbstractFullDataSourceLoader getLoader(long dataTypeId, byte dataVersion)
public IFullDataSource loadDataSource(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
return loaderRegistry.get(datatypeIdRegistry.get(dataTypeId)).stream()
.filter(l -> Arrays.binarySearch(l.loaderSupportedVersions, dataVersion) >= 0)
.findFirst().orElse(null);
IFullDataSource dataSource = this.createEmptyDataSource(dataFile.pos);
dataSource.populateFromStream(dataFile, inputStream, level);
return dataSource;
}
public static AbstractFullDataSourceLoader getLoader(Class<? extends IFullDataSource> clazz, byte dataVersion)
/** Should be used in conjunction with {@link AbstractFullDataSourceLoader#returnPooledDataSource} to return the pooled sources. */
public IFullDataSource loadTemporaryDataSource(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
return loaderRegistry.get(clazz).stream()
.filter(loader -> Arrays.binarySearch(loader.loaderSupportedVersions, dataVersion) >= 0)
.findFirst().orElse(null);
IFullDataSource dataSource = this.tryGetPooledSource();
if (dataSource != null)
{
dataSource.repopulateFromStream(dataFile, inputStream, level);
}
else
{
dataSource = this.loadDataSource(dataFile, inputStream, level);
}
return dataSource;
}
//=====================//
// data source pooling //
//=====================//
/** @return null if no pooled source exists */
public IFullDataSource tryGetPooledSource()
{
try
{
this.cacheReadWriteLock.readLock().lock();
int index = this.cachedSources.size() - 1;
if (index == -1)
{
return null;
}
else
{
return this.cachedSources.remove(index);
}
}
finally
{
this.cacheReadWriteLock.readLock().unlock();
}
}
/**
* Doesn't have to be called, if a data source isn't returned, nothing will be leaked.
* It just means a new source must be constructed next time {@link AbstractFullDataSourceLoader#tryGetPooledSource} is called.
*/
public void returnPooledDataSource(IFullDataSource dataSource)
{
if (dataSource == null)
{
return;
}
else if (dataSource.getClass() != this.fullDataSourceClass)
{
return;
}
else if (this.cachedSources.size() > 25)
{
return;
}
try
{
this.cacheReadWriteLock.writeLock().lock();
this.cachedSources.add(dataSource);
}
finally
{
this.cacheReadWriteLock.writeLock().unlock();
}
}
}
@@ -30,18 +30,10 @@ import java.io.IOException;
public class CompleteFullDataSourceLoader extends AbstractFullDataSourceLoader
{
public CompleteFullDataSourceLoader()
{
super(CompleteFullDataSource.class, CompleteFullDataSource.TYPE_ID, new byte[]{CompleteFullDataSource.DATA_FORMAT_VERSION});
}
public CompleteFullDataSourceLoader() { super(CompleteFullDataSource.class, CompleteFullDataSource.TYPE_ID, new byte[]{CompleteFullDataSource.DATA_FORMAT_VERSION}); }
@Override
public IFullDataSource loadData(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
CompleteFullDataSource dataSource = CompleteFullDataSource.createEmpty(dataFile.pos);
dataSource.populateFromStream(dataFile, inputStream, level);
return dataSource;
}
protected IFullDataSource createEmptyDataSource(DhSectionPos pos) { return CompleteFullDataSource.createEmpty(pos); }
/** Uses a given stream to create a temporary {@link CompleteFullDataSource}, which is not saved. */
public CompleteFullDataSource loadData(DhSectionPos pos, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
@@ -19,9 +19,11 @@
package com.seibel.distanthorizons.core.dataObjects.fullData.loader;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.HighDetailIncompleteFullDataSource;
@@ -29,17 +31,9 @@ import java.io.IOException;
public class HighDetailIncompleteFullDataSourceLoader extends AbstractFullDataSourceLoader
{
public HighDetailIncompleteFullDataSourceLoader()
{
super(HighDetailIncompleteFullDataSource.class, HighDetailIncompleteFullDataSource.TYPE_ID, new byte[]{HighDetailIncompleteFullDataSource.DATA_FORMAT_VERSION});
}
public HighDetailIncompleteFullDataSourceLoader() { super(HighDetailIncompleteFullDataSource.class, HighDetailIncompleteFullDataSource.TYPE_ID, new byte[]{HighDetailIncompleteFullDataSource.DATA_FORMAT_VERSION}); }
@Override
public IFullDataSource loadData(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
HighDetailIncompleteFullDataSource dataSource = HighDetailIncompleteFullDataSource.createEmpty(dataFile.pos);
dataSource.populateFromStream(dataFile, inputStream, level);
return dataSource;
}
protected IFullDataSource createEmptyDataSource(DhSectionPos pos) { return HighDetailIncompleteFullDataSource.createEmpty(pos); }
}
@@ -19,9 +19,11 @@
package com.seibel.distanthorizons.core.dataObjects.fullData.loader;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.LowDetailIncompleteFullDataSource;
@@ -29,17 +31,9 @@ import java.io.IOException;
public class LowDetailIncompleteFullDataSourceLoader extends AbstractFullDataSourceLoader
{
public LowDetailIncompleteFullDataSourceLoader()
{
super(LowDetailIncompleteFullDataSource.class, LowDetailIncompleteFullDataSource.TYPE_ID, new byte[]{LowDetailIncompleteFullDataSource.DATA_FORMAT_VERSION});
}
public LowDetailIncompleteFullDataSourceLoader() { super(LowDetailIncompleteFullDataSource.class, LowDetailIncompleteFullDataSource.TYPE_ID, new byte[]{LowDetailIncompleteFullDataSource.DATA_FORMAT_VERSION}); }
@Override
public IFullDataSource loadData(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
LowDetailIncompleteFullDataSource dataSource = LowDetailIncompleteFullDataSource.createEmpty(dataFile.pos);
dataSource.populateFromStream(dataFile, inputStream, level);
return dataSource;
}
protected IFullDataSource createEmptyDataSource(DhSectionPos pos) { return LowDetailIncompleteFullDataSource.createEmpty(pos); }
}
@@ -43,6 +43,7 @@ import org.apache.logging.log4j.Logger;
import javax.annotation.CheckForNull;
import java.io.*;
import java.util.Arrays;
import java.util.function.Consumer;
/**
@@ -64,7 +65,7 @@ public class CompleteFullDataSource extends FullDataArrayAccessor implements IFu
/** written to the binary file to mark what {@link IFullDataSource} the binary file corresponds to */
public static final long TYPE_ID = "CompleteFullDataSource".hashCode();
private final DhSectionPos sectionPos;
private DhSectionPos sectionPos;
private boolean isEmpty = true;
public EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.EMPTY;
@@ -96,7 +97,6 @@ public class CompleteFullDataSource extends FullDataArrayAccessor implements IFu
// stream handling //
//=================//
@Override
public void writeSourceSummaryInfo(IDhLevel level, DhDataOutputStream outputStream) throws IOException
{
@@ -107,7 +107,7 @@ public class CompleteFullDataSource extends FullDataArrayAccessor implements IFu
}
@Override
public FullDataSourceSummaryData readSourceSummaryInfo(@CheckForNull FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException
public FullDataSourceSummaryData readSourceSummaryInfo(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException
{
int dataDetail = inputStream.readInt();
if (dataFile != null && dataFile.baseMetaData != null && dataDetail != dataFile.baseMetaData.dataLevel)
@@ -205,12 +205,33 @@ public class CompleteFullDataSource extends FullDataArrayAccessor implements IFu
long[][] dataPointArray = new long[width * width][];
long[][] dataPointArrays;
if (this.width == width) // attempt to use the existing dataArrays if possible
{
dataPointArrays = this.dataArrays;
}
else
{
dataPointArrays = new long[width * width][];
}
for (int x = 0; x < width; x++)
{
for (int z = 0; z < width; z++)
{
dataPointArray[x * width + z] = new long[dataInputStream.readInt()];
int requestedArrayLength = dataInputStream.readInt();
int arrayIndex = x * width + z;
// attempt to use the existing dataArrays if possible
if (dataPointArrays[arrayIndex] == null || dataPointArrays[arrayIndex].length != requestedArrayLength)
{
dataPointArrays[arrayIndex] = new long[requestedArrayLength];
}
else
{
// clear the existing array to prevent any data leakage
Arrays.fill(dataPointArrays[arrayIndex], 0);
}
}
}
@@ -223,20 +244,20 @@ public class CompleteFullDataSource extends FullDataArrayAccessor implements IFu
throw new IOException("invalid data length end guard");
}
for (int xz = 0; xz < dataPointArray.length; xz++) // x and z are combined
for (int xz = 0; xz < dataPointArrays.length; xz++) // x and z are combined
{
if (dataPointArray[xz].length != 0)
if (dataPointArrays[xz].length != 0)
{
for (int y = 0; y < dataPointArray[xz].length; y++)
for (int y = 0; y < dataPointArrays[xz].length; y++)
{
dataPointArray[xz][y] = dataInputStream.readLong();
dataPointArrays[xz][y] = dataInputStream.readLong();
}
}
}
return dataPointArray;
return dataPointArrays;
}
@Override
public void setDataPoints(long[][] dataPoints)
@@ -418,6 +439,14 @@ public class CompleteFullDataSource extends FullDataArrayAccessor implements IFu
@Override
public DhSectionPos getSectionPos() { return this.sectionPos; }
@Override
public void resizeDataStructuresForRepopulation(DhSectionPos pos)
{
// no data structures need to be changed, only the source's position
this.sectionPos = pos;
}
@Override
public byte getDataDetailLevel() { return (byte) (this.sectionPos.getDetailLevel() - SECTION_SIZE_OFFSET); }
@@ -77,12 +77,12 @@ public class HighDetailIncompleteFullDataSource implements IIncompleteFullDataSo
protected final FullDataPointIdMap mapping;
private final DhSectionPos sectionPos;
private final FullDataArrayAccessor[] sparseData;
private final DhLodPos chunkPos;
private DhSectionPos sectionPos;
private FullDataArrayAccessor[] sparseData;
private DhLodPos chunkPos;
public final int sectionCount;
public final int dataPointsPerSection;
public int sectionCount;
public int dataPointsPerSection;
public boolean isEmpty = true;
public EDhApiWorldGenerationStep worldGenStep = EDhApiWorldGenerationStep.EMPTY;
private boolean isPromoted = false;
@@ -362,7 +362,18 @@ public class HighDetailIncompleteFullDataSource implements IIncompleteFullDataSo
{
for (int dataPointColIndex = 0; dataPointColIndex < dataPoints[arrayAccessorIndex].length; dataPointColIndex++)
{
System.arraycopy(dataPoints[arrayAccessorIndex][dataPointColIndex], 0, this.sparseData[arrayAccessorIndex].get(dataPointColIndex).getRaw(), 0, dataPoints[dataPointColIndex].length);
long[] incomingColumn = dataPoints[arrayAccessorIndex][dataPointColIndex];
long[] destinationColumn = this.sparseData[arrayAccessorIndex].get(dataPointColIndex).getRaw();
// use the existing arrays if possible
if (incomingColumn.length == destinationColumn.length)
{
System.arraycopy(incomingColumn, 0, destinationColumn, 0, incomingColumn.length);
}
else
{
this.sparseData[arrayAccessorIndex].get(dataPointColIndex).setNew(incomingColumn);
}
}
}
}
@@ -384,10 +395,10 @@ public class HighDetailIncompleteFullDataSource implements IIncompleteFullDataSo
}
@Override
public void writeIdMappings(DhDataOutputStream outputStream) throws IOException
public void writeIdMappings(DhDataOutputStream dataOutputStream) throws IOException
{
outputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
this.mapping.serialize(outputStream);
dataOutputStream.writeInt(IFullDataSource.DATA_GUARD_BYTE);
this.mapping.serialize(dataOutputStream);
}
@Override
@@ -419,16 +430,37 @@ public class HighDetailIncompleteFullDataSource implements IIncompleteFullDataSo
// getters //
//=========//
@Override
public DhSectionPos getSectionPos() { return this.sectionPos; }
@Override
@Override
public DhSectionPos getSectionPos() { return this.sectionPos; }
@Override
public void resizeDataStructuresForRepopulation(DhSectionPos pos)
{
// update the position
this.sectionPos = pos;
this.sectionCount = BitShiftUtil.powerOfTwo(this.sectionPos.getDetailLevel() - SPARSE_UNIT_DETAIL);
this.dataPointsPerSection = SECTION_SIZE / this.sectionCount;
this.chunkPos = this.sectionPos.getMinCornerLodPos(SPARSE_UNIT_DETAIL);
// update the data container
int dataPointCount = this.sectionCount * this.sectionCount;
if (this.sparseData.length != dataPointCount)
{
this.sparseData = new FullDataArrayAccessor[this.sectionCount * this.sectionCount];
}
}
@Override
public byte getDataDetailLevel() { return (byte) (this.sectionPos.getDetailLevel() - SECTION_SIZE_OFFSET); }
@Override
public long getTypeId() { return TYPE_ID; }
@Override
public byte getBinaryDataFormatVersion() { return DATA_FORMAT_VERSION; }
public byte getBinaryDataFormatVersion() { return DATA_FORMAT_VERSION; }
@Override
public EDhApiWorldGenerationStep getWorldGenStep() { return this.worldGenStep; }
@@ -67,7 +67,7 @@ public class LowDetailIncompleteFullDataSource extends FullDataArrayAccessor imp
public static final long TYPE_ID = "LowDetailIncompleteFullDataSource".hashCode();
private final DhSectionPos sectionPos;
private DhSectionPos sectionPos;
private final BitSet isColumnNotEmpty;
private boolean isEmpty = true;
@@ -294,6 +294,14 @@ public class LowDetailIncompleteFullDataSource extends FullDataArrayAccessor imp
@Override
public DhSectionPos getSectionPos() { return this.sectionPos; }
@Override
public void resizeDataStructuresForRepopulation(DhSectionPos pos)
{
// no data structures need to be changed, only the source's position
this.sectionPos = pos;
}
@Override
public byte getDataDetailLevel() { return (byte) (this.sectionPos.getDetailLevel() - SECTION_SIZE_OFFSET); }
@Override
@@ -441,46 +449,36 @@ public class LowDetailIncompleteFullDataSource extends FullDataArrayAccessor imp
}
}
private void sampleFrom(CompleteFullDataSource completeSource)
private void sampleFrom(CompleteFullDataSource inputSource)
{
DhSectionPos pos = completeSource.getSectionPos();
DhSectionPos inputPos = inputSource.getSectionPos();
this.isEmpty = false;
this.downsampleFrom(completeSource);
if (this.getDataDetailLevel() > this.sectionPos.getDetailLevel()) // TODO what does this mean?
DhLodPos baseOffset = this.sectionPos.getMinCornerLodPos(this.getDataDetailLevel());
DhSectionPos inputOffset = inputPos.convertNewToDetailLevel(this.getDataDetailLevel());
int offsetX = inputOffset.getX() - baseOffset.x;
int offsetZ = inputOffset.getZ() - baseOffset.z;
int numberOfDataPointsToUpdate = WIDTH / this.sectionPos.getWidthCountForLowerDetailedSection(inputSource.getSectionPos().getDetailLevel()); // can be 0 if the input source is significantly smaller than this data source
// should be 1 at minimum, to prevent divide by zero errors (and because trying to get 0 data points doesn't make any sense)
numberOfDataPointsToUpdate = Math.max(1, numberOfDataPointsToUpdate);
int inputFractionWidth = inputSource.width() / numberOfDataPointsToUpdate;
for (int x = 0; x < numberOfDataPointsToUpdate; x++)
{
DhLodPos thisLodPos = this.sectionPos.getMinCornerLodPos(this.getDataDetailLevel());
DhLodPos dataLodPos = pos.getMinCornerLodPos(this.getDataDetailLevel());
int offsetX = dataLodPos.x - thisLodPos.x;
int offsetZ = dataLodPos.z - thisLodPos.z;
int dataWidth = this.sectionPos.getWidthCountForLowerDetailedSection(this.getDataDetailLevel());
for (int xOffset = 0; xOffset < dataWidth; xOffset++)
for (int z = 0; z < numberOfDataPointsToUpdate; z++)
{
for (int zOffset = 0; zOffset < dataWidth; zOffset++)
{
this.isColumnNotEmpty.set((offsetX + xOffset) * WIDTH + offsetZ + zOffset, true);
}
SingleColumnFullDataAccessor thisDataColumn = this.get(offsetX + x, offsetZ + z);
SingleColumnFullDataAccessor inputDataColumn = inputSource.get(inputFractionWidth * x, inputFractionWidth * z);
inputDataColumn.deepCopyTo(thisDataColumn);
int notEmptyIndex = (offsetX + x) * WIDTH + (offsetZ + z);
this.isColumnNotEmpty.set(notEmptyIndex, true);
}
}
else
{
DhLodPos dataPos = pos.getSectionBBoxPos();
int lowerSectionsPerData = this.sectionPos.getWidthCountForLowerDetailedSection(dataPos.detailLevel);
if (dataPos.x % lowerSectionsPerData != 0 || dataPos.z % lowerSectionsPerData != 0)
{
return;
}
DhLodPos basePos = this.sectionPos.getMinCornerLodPos(this.getDataDetailLevel());
dataPos = dataPos.convertToDetailLevel(this.getDataDetailLevel());
int offsetX = dataPos.x - basePos.x;
int offsetZ = dataPos.z - basePos.z;
this.isColumnNotEmpty.set(offsetX * WIDTH + offsetZ, true);
}
}
private void sampleFrom(LowDetailIncompleteFullDataSource spottySource)
@@ -279,4 +279,11 @@ public interface IFullDataSource
*/
void populateFromStream(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException;
/**
* Should only be implemented by {@link IStreamableFullDataSource} to prevent potential stream read/write inconsistencies.
*
* @see IStreamableFullDataSource#repopulateFromStream(FullDataMetaFile, DhDataInputStream, IDhLevel)
*/
void repopulateFromStream(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException;
}
@@ -22,11 +22,10 @@ package com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.FullDataArrayAccessor;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import java.io.IOException;
@@ -40,8 +39,6 @@ import java.io.IOException;
*
* @param <SummaryDataType> defines the object holding this data source's summary data, extends {@link IStreamableFullDataSource.FullDataSourceSummaryData}.
* @param <DataContainerType> defines the object holding the data points, probably long[][] or long[][][].
* @apiNote James would've preferred to have this as an abstract class,
* however that is impossible. See the apiNote in
* {@link IStreamableFullDataSource#populateFromStream(FullDataMetaFile, DhDataInputStream, IDhLevel) populateFromStream}
* for the full reasoning.
*/
@@ -52,14 +49,28 @@ public interface IStreamableFullDataSource<SummaryDataType extends IStreamableFu
// stream handling //
//=================//
/**
* Clears and then overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an existing {@link IStreamableFullDataSource} and can be used in place of a constructor to reuse an existing {@link IStreamableFullDataSource} object.
*
* @see IStreamableFullDataSource#populateFromStream
*/
@Override
default void repopulateFromStream(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
// clear/overwrite the old data
this.resizeDataStructuresForRepopulation(dataFile.pos);
this.getMapping().clear(dataFile.pos);
// set the new data
this.populateFromStream(dataFile, inputStream, level);
}
/**
* Overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an empty {@link IStreamableFullDataSource} and functions similar to a constructor.
*
* @apiNote James would've preferred that {@link IStreamableFullDataSource} was an abstract class,
* so this could've been a constructor.
* However, several inheritors of this interface already extend {@link FullDataArrayAccessor}, making that impossible.
*/
@Override
default void populateFromStream(FullDataMetaFile dataFile, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
{
SummaryDataType summaryData = this.readSourceSummaryInfo(dataFile, inputStream, level);
@@ -79,6 +90,7 @@ public interface IStreamableFullDataSource<SummaryDataType extends IStreamableFu
}
@Override
default void writeToStream(DhDataOutputStream outputStream, IDhLevel level) throws IOException
{
this.writeSourceSummaryInfo(level, outputStream);
@@ -94,6 +106,9 @@ public interface IStreamableFullDataSource<SummaryDataType extends IStreamableFu
/** Note: this should only be used if the data source is being reused. Normally data sources shouldn't change. */
void resizeDataStructuresForRepopulation(DhSectionPos pos);
/**
* Includes information about the source file that doesn't need to be saved in each data point. Like the source's size and y-level.
*/
@@ -45,7 +45,7 @@ import java.util.concurrent.*;
*
* @see ColumnRenderBufferBuilder
*/
public class ColumnRenderBuffer extends AbstractRenderBuffer implements IDebugRenderable
public class ColumnRenderBuffer extends AbstractRenderBuffer
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -71,19 +71,8 @@ public class ColumnRenderBuffer extends AbstractRenderBuffer implements IDebugRe
{
this.pos = pos;
this.debugPos = debugPos;
vbos = new GLVertexBuffer[0];
vbosTransparent = new GLVertexBuffer[0];
DebugRenderer.register(this);
}
public void debugRender(DebugRenderer r)
{
if (closed || vbos == null)
{
return;
}
Color c = Color.green;
//r.renderBox(debugPos, 128, 128, 0.05f, c);
this.vbos = new GLVertexBuffer[0];
this.vbosTransparent = new GLVertexBuffer[0];
}
@@ -69,8 +69,7 @@ public class FullDataToRenderDataTransformer
// public transformer interface //
//==============================//
public static CompletableFuture<ColumnRenderSource> transformFullDataToRenderSourceAsync(IFullDataSource fullDataSource, IDhClientLevel level) { return CompletableFuture.supplyAsync(() -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); }
public static CompletableFuture<ColumnRenderSource> transformFullDataToRenderSourceAsync(CompletableFuture<IFullDataSource> fullDataSourceFuture, IDhClientLevel level) { return fullDataSourceFuture.thenApplyAsync((fullDataSource) -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); }
public static CompletableFuture<ColumnRenderSource> transformFullDataToRenderSourceUsingExecutorAsync(IFullDataSource fullDataSource, IDhClientLevel level) { return CompletableFuture.supplyAsync(() -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); }
private static ColumnRenderSource transformFullDataToRenderSource(IFullDataSource fullDataSource, IDhClientLevel level)
{
if (fullDataSource == null)
@@ -184,9 +183,11 @@ public class FullDataToRenderDataTransformer
{
int baseX = pos.getMinCornerLodPos().getCornerBlockPos().x;
int baseZ = pos.getMinCornerLodPos().getCornerBlockPos().z;
for (int x = 0; x < pos.getWidthCountForLowerDetailedSection(dataDetail); x++)
int width = pos.getWidthCountForLowerDetailedSection(dataDetail);
for (int x = 0; x < width; x++)
{
for (int z = 0; z < pos.getWidthCountForLowerDetailedSection(dataDetail); z++)
for (int z = 0; z < width; z++)
{
throwIfThreadInterrupted();
@@ -32,12 +32,14 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhLodPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.MetaFileScanUtil;
import com.seibel.distanthorizons.core.util.FileUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.MetaFileScanUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import org.apache.logging.log4j.Logger;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -394,28 +396,81 @@ public class FullDataFileHandler implements IFullDataSourceProvider
LowDetailIncompleteFullDataSource.createEmpty(pos);
}
/** populates the given data source using the given array of files */
protected CompletableFuture<IIncompleteFullDataSource> sampleFromFileArray(IIncompleteFullDataSource recipientFullDataSource, ArrayList<FullDataMetaFile> existingFiles)
/**
* Populates the given data source using the given array of files
* @param usePooledDataSources if enabled the data sources necessary for this sampling will not be stored beyond what is necessary for the sampling.
* This helps reduce garbage collector pressure if the data sources will never be used again.
*/
protected CompletableFuture<IIncompleteFullDataSource> sampleFromFileArray(IIncompleteFullDataSource recipientFullDataSource, ArrayList<FullDataMetaFile> existingFiles, boolean usePooledDataSources)
{
// read in the existing data
final ArrayList<CompletableFuture<Void>> loadDataFutures = new ArrayList<>(existingFiles.size());
for (FullDataMetaFile existingFile : existingFiles)
boolean showFullDataFileSampling = Config.Client.Advanced.Debugging.DebugWireframe.showFullDataFileSampling.get();
if (showFullDataFileSampling)
{
loadDataFutures.add(existingFile.getOrLoadCachedDataSourceAsync()
.exceptionally((ex) -> /*Ignore file read errors*/null)
.thenAccept((existingFullDataSource) ->
{
if (existingFullDataSource == null)
{
return;
}
//LOGGER.info("Merging data from {} into {}", data.getSectionPos(), pos);
recipientFullDataSource.sampleFrom(existingFullDataSource);
})
);
DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(
new DebugRenderer.Box(recipientFullDataSource.getSectionPos(), 64f, 72f, 0.03f, Color.MAGENTA),
0.2, 32f));
}
return CompletableFuture.allOf(loadDataFutures.toArray(new CompletableFuture[0])).thenApply(voidObj -> recipientFullDataSource);
// read in the existing data
final ArrayList<CompletableFuture<IFullDataSource>> sampleDataFutures = new ArrayList<>(existingFiles.size());
for (int i = 0; i < existingFiles.size(); i++)
{
FullDataMetaFile existingFile = existingFiles.get(i);
CompletableFuture<IFullDataSource> loadFileFuture = usePooledDataSources ? existingFile.getOrLoadCachedDataSourceAsync() : existingFile.getDataSourceWithoutCachingAsync();
CompletableFuture<IFullDataSource> sampleSourceFuture = loadFileFuture.whenComplete((existingFullDataSource, ex) ->
{
if (existingFullDataSource == null || ex != null)
{
// Ignore file read errors
//LOGGER.warn(recipientFullDataSource.getSectionPos()+" sample from, file read error for file "+existingFile.pos+": "+ex.getMessage(), ex);
return;
}
if (showFullDataFileSampling)
{
DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(
new DebugRenderer.Box(recipientFullDataSource.getSectionPos(), 64f, 72f, 0.03f, Color.MAGENTA.darker()),
0.2, 32f));
}
try
{
recipientFullDataSource.sampleFrom(existingFullDataSource);
}
catch (Exception e)
{
LOGGER.warn("Unable to sample "+existingFullDataSource.getSectionPos()+" into "+recipientFullDataSource.getSectionPos(), e);
//throw e;
}
// pooling temporary data sources massively reduces garbage collector overhead when just sampling (going from ~8 GB/sec to ~90 MB/sec)
if (!usePooledDataSources && !existingFile.cacheLoadingDataSource)
{
existingFile.clearCachedDataSource();
// get the data loader
AbstractFullDataSourceLoader dataSourceLoader;
if (existingFile.fullDataSourceLoader != null)
{
dataSourceLoader = existingFile.fullDataSourceLoader;
}
else
{
// shouldn't normally happen, but sometimes does
dataSourceLoader = AbstractFullDataSourceLoader.getLoader(existingFile.baseMetaData.dataTypeId, existingFile.baseMetaData.binaryDataFormatVersion);
}
dataSourceLoader.returnPooledDataSource(existingFullDataSource);
}
});
sampleDataFutures.add(sampleSourceFuture);
}
return CompletableFuture.allOf(sampleDataFutures.toArray(new CompletableFuture[0]))
.thenApply(voidObj -> recipientFullDataSource);
}
protected void makeFiles(ArrayList<DhSectionPos> posList, ArrayList<FullDataMetaFile> output)
@@ -447,7 +502,7 @@ public class FullDataFileHandler implements IFullDataSourceProvider
else
{
this.makeFiles(missing, existFiles);
return this.sampleFromFileArray(source, existFiles).thenApply(IIncompleteFullDataSource::tryPromotingToCompleteDataSource)
return this.sampleFromFileArray(source, existFiles, true).thenApply(IIncompleteFullDataSource::tryPromotingToCompleteDataSource)
.exceptionally((e) ->
{
FullDataMetaFile newMetaFile = this.removeCorruptedFile(pos, file, e);
@@ -87,6 +87,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
*/
private DataSourceReferenceTracker.FullDataSourceSoftRef cachedFullDataSourceRef = new DataSourceReferenceTracker.FullDataSourceSoftRef(this,null);
private final AtomicReference<CompletableFuture<IFullDataSource>> dataSourceLoadFutureRef = new AtomicReference<>(null);
public volatile Boolean cacheLoadingDataSource = null;
// === Concurrent Write tracking ===
private final AtomicReference<GuardedMultiAppendQueue> writeQueueRef = new AtomicReference<>(new GuardedMultiAppendQueue());
@@ -113,7 +114,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
this.level = level;
LodUtil.assertTrue(this.baseMetaData == null);
this.doesFileExist = false;
DebugRenderer.register(this);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataFileStatus);
}
@@ -141,7 +142,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
}
this.fullDataSourceClass = this.fullDataSourceLoader.fullDataSourceClass;
DebugRenderer.register(this);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataFileStatus);
}
@@ -160,14 +161,36 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
return this.cachedFullDataSourceRef.get();
}
/** @return if any data was cleared */
public boolean clearCachedDataSource()
{
boolean dataExists = this.cachedFullDataSourceRef.get() != null;
if (dataExists)
{
this.cachedFullDataSourceRef.close();
this.cachedFullDataSourceRef.clear();
this.cacheLoadingDataSource = null;
}
return dataExists;
}
public CompletableFuture<IFullDataSource> getOrLoadCachedDataSourceAsync()
public CompletableFuture<IFullDataSource> getDataSourceWithoutCachingAsync() { return this.getOrLoadCachedDataSourceAsync(false); }
public CompletableFuture<IFullDataSource> getOrLoadCachedDataSourceAsync() { return this.getOrLoadCachedDataSourceAsync(true); }
private CompletableFuture<IFullDataSource> getOrLoadCachedDataSourceAsync(boolean cacheLoadingSource)
{
checkAndLogPhantomDataSourceLifeCycles();
CompletableFuture<IFullDataSource> potentialLoadFuture = this.getCachedDataSourceAsync();
if (potentialLoadFuture != null)
{
if (cacheLoadingSource)
{
this.cacheLoadingDataSource = true;
}
// return the in-process future
return potentialLoadFuture;
}
@@ -181,11 +204,13 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
// two threads attempted to start this job at the same time, only use the first future
potentialLoadFuture = this.dataSourceLoadFutureRef.get();
}
this.cacheLoadingDataSource = cacheLoadingSource;
}
CompletableFuture<IFullDataSource> dataSourceLoadFuture = potentialLoadFuture;
final CompletableFuture<IFullDataSource> dataSourceLoadFuture = potentialLoadFuture;
if (!this.doesFileExist)
{
// create a new Meta file and data source
@@ -231,10 +256,21 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
try (FileInputStream fileInputStream = this.getFileInputStream();
DhDataInputStream compressedStream = new DhDataInputStream(fileInputStream))
{
fullDataSource = this.fullDataSourceLoader.loadData(this, compressedStream, this.level);
if (cacheLoadingSource)
{
fullDataSource = this.fullDataSourceLoader.loadDataSource(this, compressedStream, this.level);
}
else
{
fullDataSource = this.fullDataSourceLoader.loadTemporaryDataSource(this, compressedStream, this.level);
}
}
catch (Exception ex)
{
/// TODO temporary fix
dataSourceLoadFuture.completeExceptionally(ex);
this.dataSourceLoadFutureRef.set(null);
// can happen if there is a missing file or the file was incorrectly formatted, or terminated early
throw new CompletionException(ex);
}
@@ -373,11 +409,11 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
public CompletableFuture<Void> flushAndSaveAsync()
{
checkAndLogPhantomDataSourceLifeCycles();
boolean isEmpty = this.writeQueueRef.get().queue.isEmpty() && !needsUpdate;
boolean isEmpty = this.writeQueueRef.get().queue.isEmpty() && !this.needsUpdate;
if (!isEmpty)
{
// This will flush the data to disk.
return this.getOrLoadCachedDataSourceAsync().thenApply((fullDataSource) -> null /* ignore the result, just wait for the load to finish*/ );
return this.getDataSourceWithoutCachingAsync().thenApply((fullDataSource) -> null /* ignore the result, just wait for the load to finish*/ );
}
else
{
@@ -438,16 +474,22 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
return;
}
IFullDataSource cached = this.cachedFullDataSourceRef.get();
if (this.needsUpdate)
{
debugRenderer.renderBox(new DebugRenderer.Box(this.pos, 80f, 96f, 0.05f, Color.red));
}
IFullDataSource cachedDataSource = this.cachedFullDataSourceRef.get();
boolean needsUpdate = !this.writeQueueRef.get().queue.isEmpty() || this.needsUpdate;
// determine the color
Color color = Color.black;
if (cached != null)
if (cachedDataSource != null)
{
if (cached instanceof CompleteFullDataSource)
if (cachedDataSource instanceof CompleteFullDataSource)
{
color = Color.GREEN;
}
@@ -455,7 +497,6 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
{
color = Color.YELLOW;
}
}
else if (this.dataSourceLoadFutureRef.get() != null)
{
@@ -465,9 +506,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
{
color = Color.RED;
}
boolean needsUpdate = !this.writeQueueRef.get().queue.isEmpty() || this.needsUpdate;
if (needsUpdate)
else if (needsUpdate)
{
color = color.darker().darker();
}
@@ -557,16 +596,23 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
new DataObjSoftTracker(this, fullDataSource);
}
if (this.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
boolean showFullDataFileStatus = Config.Client.Advanced.Debugging.DebugWireframe.showFullDataFileStatus.get();
boolean showFullDataFileSampling = Config.Client.Advanced.Debugging.DebugWireframe.showFullDataFileSampling.get();
if (showFullDataFileStatus || showFullDataFileSampling)
{
Color color = dataSourceChanged ? Color.GREEN : Color.GREEN.darker().darker();
DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(
new DebugRenderer.Box(this.pos, 64f, 72f, 0.03f, Color.green.darker()),
0.2, 32f));
new DebugRenderer.Box(this.pos, 64f, 72f, 0.03f, color),
0.2, 32f));
}
// save the updated data source
this.cachedFullDataSourceRef = new DataSourceReferenceTracker.FullDataSourceSoftRef(this, fullDataSource);
if (this.cacheLoadingDataSource)
{
// save the updated data source
this.cachedFullDataSourceRef = new DataSourceReferenceTracker.FullDataSourceSoftRef(this, fullDataSource);
}
// the task is complete
completionFuture.complete(fullDataSource);
@@ -575,13 +621,91 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I
if (this.needsUpdate)
{
// another update was requested while this update was being processed
this.getOrLoadCachedDataSourceAsync();
if (this.cacheLoadingDataSource)
{
this.getOrLoadCachedDataSourceAsync();
}
else
{
this.getDataSourceWithoutCachingAsync();
}
}
});
return completionFuture;
}
/*
private void applyWriteQueueAndSave(IFullDataSource fullDataSourceToUpdate)
{
boolean dataChanged = this.applyWriteQueueToFullDataSource(fullDataSourceToUpdate);
this.needsUpdate = false;
// attempt to promote the data source
if (fullDataSourceToUpdate instanceof IIncompleteFullDataSource)
{
IFullDataSource newSource = ((IIncompleteFullDataSource) fullDataSourceToUpdate).tryPromotingToCompleteDataSource();
dataChanged |= (newSource != fullDataSourceToUpdate);
fullDataSourceToUpdate = newSource;
}
// the provider may need to modify other files based on this data source changing
IFullDataSourceProvider.DataFileUpdateResult dataFileUpdateResult = this.fullDataSourceProvider.onDataFileUpdateAsync(fullDataSourceToUpdate, this, dataChanged);
IFullDataSource fullDataSource = dataFileUpdateResult.fullDataSource;
boolean dataSourceChanged = dataFileUpdateResult.dataSourceChanged;
// only save to file if something was changed
if (dataSourceChanged)
{
this.writeDataSource(fullDataSource);
}
// keep track of non-null data sources
if (fullDataSource != null)
{
new DataObjTracker(fullDataSource);
new DataObjSoftTracker(this, fullDataSource);
}
boolean showFullDataFileStatus = Config.Client.Advanced.Debugging.DebugWireframe.showFullDataFileStatus.get();
boolean showFullDataFileSampling = Config.Client.Advanced.Debugging.DebugWireframe.showFullDataFileSampling.get();
if (showFullDataFileStatus || showFullDataFileSampling)
{
Color color;
if (this.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
{
color = Color.GREEN;
}
else
{
color = Color.GREEN.darker().darker();
}
DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(
new DebugRenderer.Box(this.pos, 64f, 72f, 0.03f, color),
0.2, 32f));
}
// save the updated data source
this.cachedFullDataSourceRef = new DataSourceReferenceTracker.FullDataSourceSoftRef(this, fullDataSource);
if (this.needsUpdate)
{
// another update was requested while this update was being processed
this.getOrLoadCachedDataSourceAsync();
}
}
*/
/** @return true if the queue was not empty and chunk data was applied to this meta file's {@link IFullDataSource}. */
private boolean applyWriteQueueToFullDataSource(IFullDataSource fullDataSource)
{
@@ -139,11 +139,11 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
{
ArrayList<FullDataMetaFile> existingFiles = new ArrayList<>();
byte sectDetailLevel = (byte) (DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + maxSectDataDetailLevel);
pos.forEachChildAtLevel(sectDetailLevel, p -> existingFiles.add(getLoadOrMakeFile(p, true)));
return sampleFromFileArray(dataSource, existingFiles).thenApply(this::tryPromoteDataSource)
.exceptionally((e) ->
pos.forEachChildAtLevel(sectDetailLevel, childPos -> existingFiles.add(this.getLoadOrMakeFile(childPos, true)));
return this.sampleFromFileArray(dataSource, existingFiles, true).thenApply(this::tryPromoteDataSource)
.exceptionally((ex) ->
{
FullDataMetaFile newMetaFile = removeCorruptedFile(pos, file, e);
FullDataMetaFile newMetaFile = this.removeCorruptedFile(pos, file, ex);
return null;
});
}
@@ -175,7 +175,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
// Try update the gen queue on this data source. If null, then nothing was done.
@Nullable
private CompletableFuture<IFullDataSource> updateFromExistingDataSourcesAsync(FullDataMetaFile file, IIncompleteFullDataSource data)
private CompletableFuture<IFullDataSource> updateFromExistingDataSourcesAsync(FullDataMetaFile file, IIncompleteFullDataSource data, boolean usePooledDataSources)
{
DhSectionPos pos = file.pos;
ArrayList<FullDataMetaFile> existingFiles = new ArrayList<>();
@@ -191,7 +191,8 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
{
// There are other data source files to sample from.
this.makeFiles(missingPositions, existingFiles);
return this.sampleFromFileArray(data, existingFiles).thenApply(this::tryPromoteDataSource)
return this.sampleFromFileArray(data, existingFiles, usePooledDataSources)
.thenApply(this::tryPromoteDataSource)
.exceptionally((e) ->
{
this.removeCorruptedFile(pos, file, e);
@@ -205,7 +206,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
{
DhSectionPos pos = file.pos;
IIncompleteFullDataSource data = this.makeEmptyDataSource(pos);
CompletableFuture<IFullDataSource> future = this.updateFromExistingDataSourcesAsync(file, data);
CompletableFuture<IFullDataSource> future = this.updateFromExistingDataSourcesAsync(file, data, true);
// Cant start gen task, so return the data
return future == null ? CompletableFuture.completedFuture(data) : future;
}
@@ -228,7 +229,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
IWorldGenerationQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue != null)
{
CompletableFuture<IFullDataSource> future = this.updateFromExistingDataSourcesAsync(file, (IIncompleteFullDataSource) fullDataSource);
CompletableFuture<IFullDataSource> future = this.updateFromExistingDataSourcesAsync(file, (IIncompleteFullDataSource) fullDataSource, false);
if (future != null)
{
final boolean finalDataChanged = dataChanged;
@@ -66,7 +66,7 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
* When clearing, don't set to null, instead create a SoftReference containing null.
* This makes null checks simpler.
*/
private DataSourceReferenceTracker.RenderDataSourceSoftRef cachedRenderDataSource = new DataSourceReferenceTracker.RenderDataSourceSoftRef(this, null);
private DataSourceReferenceTracker.RenderDataSourceSoftRef cachedRenderDataSourceRef = new DataSourceReferenceTracker.RenderDataSourceSoftRef(this, null);
private final AtomicReference<CompletableFuture<ColumnRenderSource>> renderSourceLoadFutureRef = new AtomicReference<>(null);
private final IDhClientLevel clientLevel;
@@ -108,7 +108,7 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
this.clientLevel = clientLevel;
LodUtil.assertTrue(this.baseMetaData == null);
this.doesFileExist = this.file.exists();
DebugRenderer.register(this);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderDataFileStatus);
}
@@ -124,7 +124,7 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
this.clientLevel = clientLevel;
LodUtil.assertTrue(this.baseMetaData != null);
this.doesFileExist = this.file.exists();
DebugRenderer.register(this);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderDataFileStatus);
}
@@ -139,7 +139,7 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
LodUtil.assertTrue(this.pos.overlapsExactly(chunkSectionPos), "Chunk pos " + chunkSectionPos + " doesn't overlap with section " + this.pos);
// update the render source if one exists
CompletableFuture<ColumnRenderSource> renderSourceLoadFuture = this.getCachedDataSourceAsync(false);
CompletableFuture<ColumnRenderSource> renderSourceLoadFuture = this.getCachedDataSourceAsync(true);
if (renderSourceLoadFuture == null)
{
return;
@@ -148,17 +148,22 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
renderSourceLoadFuture.thenAccept((renderSource) ->
{
boolean dataUpdated = renderSource.updateWithChunkData(chunkDataView, clientLevel);
boolean dataUpdated = renderSource.updateWithChunkData(chunkDataView, this.clientLevel);
// add a debug renderer
float offset = new Random(System.nanoTime() ^ Thread.currentThread().getId()).nextFloat() * 16f;
Color debugColor = dataUpdated ? Color.blue : Color.red;
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(chunkDataView.getSectionPos(), 32f, 64f + offset, 0.07f, debugColor),
2.0, 16f
)
);
// add a debug particle
boolean showRenderDataFileStatus = Config.Client.Advanced.Debugging.DebugWireframe.showRenderDataFileStatus.get();
if (showRenderDataFileStatus)
{
float offset = new Random(System.nanoTime() ^ Thread.currentThread().getId()).nextFloat() * 16f;
Color debugColor = dataUpdated ? Color.blue : Color.red;
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(chunkDataView.getSectionPos(), 32f, 64f + offset, 0.07f, debugColor),
2.0, 16f
)
);
}
});
}
@@ -208,7 +213,7 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
this.updateRenderCacheAsync(newColumnRenderSource).whenComplete((voidObj, ex) ->
{
this.cachedRenderDataSource = new DataSourceReferenceTracker.RenderDataSourceSoftRef(this, newColumnRenderSource);
this.cachedRenderDataSourceRef = new DataSourceReferenceTracker.RenderDataSourceSoftRef(this, newColumnRenderSource);
this.renderSourceLoadFutureRef.set(null);
getSourceFuture.complete(newColumnRenderSource);
@@ -256,7 +261,7 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
this.renderSourceLoadFutureRef.set(null);
this.cachedRenderDataSource = new DataSourceReferenceTracker.RenderDataSourceSoftRef(this, renderSource);
this.cachedRenderDataSourceRef = new DataSourceReferenceTracker.RenderDataSourceSoftRef(this, renderSource);
getSourceFuture.complete(renderSource);
});
}
@@ -336,26 +341,36 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
// convert the full data source into a render source
CompletableFuture<ColumnRenderSource> transformFuture = FullDataToRenderDataTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, this.clientLevel)
.handle((newRenderSource, ex) ->
CompletableFuture<ColumnRenderSource> transformFuture = fullDataSourceFuture
.handle((fullDataSource, ex) ->
{
if (ex == null)
{
ColumnRenderSource newRenderSource = null;
try
{
newRenderSource = FullDataToRenderDataTransformer.transformFullDataToRenderSourceUsingExecutorAsync(fullDataSource, this.clientLevel).join();
}
catch (Exception e)
{
LOGGER.error("Unable to transform full data to render data for file: "+this.file, e);
}
try
{
if (newRenderSource != null)
{
renderSource.updateFromRenderSource(newRenderSource);
// update the meta data
this.baseMetaData.dataVersion.set(renderDataVersionRef.value);
this.baseMetaData.dataLevel = renderSource.getDataDetail();
this.baseMetaData.dataTypeId = RENDER_SOURCE_TYPE_ID;
this.baseMetaData.binaryDataFormatVersion = renderSource.getRenderDataFormatVersion();
// save to file
this.save(renderSource);
}
// update the meta data
this.baseMetaData.dataVersion.set(renderDataVersionRef.value);
this.baseMetaData.dataLevel = renderSource.getDataDetail();
this.baseMetaData.dataTypeId = RENDER_SOURCE_TYPE_ID;
this.baseMetaData.binaryDataFormatVersion = renderSource.getRenderDataFormatVersion();
// save to file
this.save(renderSource);
}
catch (Throwable e)
{
@@ -438,16 +453,15 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
@Override
public void debugRender(DebugRenderer debugRenderer)
{
if (!Config.Client.Advanced.Debugging.DebugWireframeRendering.renderDataMetaFile.get()) return;
Color color = Color.black;
ColumnRenderSource cached = this.cachedRenderDataSource.get();
if (cached != null)
if (this.cachedRenderDataSourceRef.get() != null)
{
color = Color.GREEN;
return;
//color = Color.GREEN;
}
else if (this.renderSourceLoadFutureRef.get() != null)
// determine the color
Color color = Color.black;
if (this.renderSourceLoadFutureRef.get() != null)
{
color = Color.BLUE;
}
@@ -478,7 +492,7 @@ public class RenderDataMetaFile extends AbstractMetaDataContainerFile implements
// attempt to get the cached render source
ColumnRenderSource cachedRenderDataSource = this.cachedRenderDataSource.get();
ColumnRenderSource cachedRenderDataSource = this.cachedRenderDataSourceRef.get();
if (cachedRenderDataSource == null)
{
// no cached data exists and no one is trying to load it
@@ -125,7 +125,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
{
throw new IllegalArgumentException(IDhApiWorldGenerator.class.getSimpleName() + ": max granularity smaller than min granularity!");
}
DebugRenderer.register(this);
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue);
LOGGER.info("Created world gen queue");
}
@@ -673,7 +673,7 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
LOGGER.warn("Failed to close generation queue: ", e);
}
LOGGER.info("Finished closing " + WorldGenerationQueue.class.getSimpleName());
DebugRenderer.unregister(this);
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue);
}
@@ -720,18 +720,10 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender
//=======//
@Override
public void debugRender(DebugRenderer r)
public void debugRender(DebugRenderer renderer)
{
if (!Config.Client.Advanced.Debugging.DebugWireframeRendering.worldGenerationQueue.get()) return;
//if (true) return;
waitingTasks.keySet().forEach((pos) -> {
//DhLodPos pos = t.pos;
r.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.blue));
});
this.inProgressGenTasksByLodPos.forEach((pos, t) -> {
r.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.red));
});
this.waitingTasks.keySet().forEach((pos) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.blue)); });
this.inProgressGenTasksByLodPos.forEach((pos, t) -> { renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, Color.red)); });
}
}
@@ -204,14 +204,14 @@ public class DhClientServerLevel extends DhLevel implements IDhClientLevel, IDhS
@Override
public void onWorldGenTaskComplete(DhSectionPos pos)
{
//if (pos.sectionDetailLevel == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(pos, 128f, 156f, 0.09f, Color.red.darker()),
0.2, 32f
)
);
clientside.reloadPos(pos);
this.clientside.reloadPos(pos);
}
@Override
@@ -95,28 +95,7 @@ public class LodRenderSection implements IDebugRenderable
this.pos = pos;
this.parentQuadTree = parentQuadTree;
DebugRenderer.register(this);
}
public void debugRender(DebugRenderer debugRenderer)
{
if (!Config.Client.Advanced.Debugging.DebugWireframeRendering.lodRenderSection.get()) return;
Color color = Color.red;
if (this.renderSourceProvider == null) color = Color.black;
if (this.renderSourceLoadFuture != null) color = Color.yellow;
if (renderSource != null)
{
color = Color.blue;
if (buildRenderBufferFuture != null) color = Color.magenta;
if (canRenderNow()) color = Color.cyan;
if (canRenderNow() && isRenderingEnabled) color = Color.green;
}
debugRenderer.renderBox(new DebugRenderer.Box(this.pos, 400, 8f, Objects.hashCode(this), 0.1f, color));
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
}
@@ -163,7 +142,8 @@ public class LodRenderSection implements IDebugRenderable
public void reload(ILodRenderSourceProvider renderDataProvider)
{
// debug rendering
if (this.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
boolean showRenderSectionStatus = Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get();
if (showRenderSectionStatus && this.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
@@ -250,18 +230,13 @@ public class LodRenderSection implements IDebugRenderable
);
}
//================//
// Render Methods //
//================//
private void cancelBuildBuffer()
{
if (this.buildRenderBufferFuture != null)
{
//LOGGER.info("Cancelling build of render buffer for {}", sectionPos);
this.buildRenderBufferFuture.cancel(true);
this.buildRenderBufferFuture = null;
}
}
public void markBufferDirty() { this.lastSwapLocalVersion = -1; }
//=================//
// buffer building //
//=================//
private LodRenderSection[] getNeighbors()
{
@@ -304,6 +279,34 @@ public class LodRenderSection implements IDebugRenderable
public boolean canSwapBuffer() { return this.buildRenderBufferFuture != null && this.buildRenderBufferFuture.isDone(); }
private void cancelBuildBuffer()
{
if (this.buildRenderBufferFuture != null)
{
//LOGGER.info("Cancelling build of render buffer for {}", sectionPos);
this.buildRenderBufferFuture.cancel(true);
this.buildRenderBufferFuture = null;
}
}
public synchronized void disposeRenderData() // synchronized is a band-aid solution to prevent a rare bug where the future isn't canceled in the right order
{
this.disposeRenderBuffer();
this.renderSource = null;
if (this.renderSourceLoadFuture != null)
{
this.renderSourceLoadFuture.cancel(true);
this.renderSourceLoadFuture = null;
}
}
public void disposeRenderBuffer()
{
this.cancelBuildBuffer();
this.disposeActiveBuffer = true;
}
/**
* Try and swap in new render buffer for this section. Note that before this call, there should be no other
@@ -327,7 +330,8 @@ public class LodRenderSection implements IDebugRenderable
if (this.canBuildBuffer())
{
// debug
if (this.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
boolean showRenderSectionStatus = Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get();
if (showRenderSectionStatus && this.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
@@ -426,30 +430,43 @@ public class LodRenderSection implements IDebugRenderable
public void dispose()
{
this.disposeRenderData();
DebugRenderer.unregister(this);
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
if (this.disposeActiveBuffer && this.activeRenderBufferRef.get() != null)
{
this.activeRenderBufferRef.get().close();
}
}
public synchronized void disposeRenderData() // synchronized is a band-aid solution to prevent a rare bug where the future isn't canceled in the right order
@Override
public void debugRender(DebugRenderer debugRenderer)
{
this.disposeRenderBuffer();
this.renderSource = null;
if (this.renderSourceLoadFuture != null)
Color color = Color.red;
if (this.renderSourceProvider == null)
{
this.renderSourceLoadFuture.cancel(true);
this.renderSourceLoadFuture = null;
color = Color.black;
}
else if (this.renderSourceLoadFuture != null)
{
color = Color.yellow;
}
else if (this.renderSource != null)
{
color = Color.blue;
if (this.buildRenderBufferFuture != null)
{
color = Color.magenta;
}
else if (this.canRenderNow())
{
color = Color.cyan;
}
else if (this.canRenderNow() && this.isRenderingEnabled)
{
color = Color.green;
}
}
debugRenderer.renderBox(new DebugRenderer.Box(this.pos, 400, 8f, Objects.hashCode(this), 0.1f, color));
}
public void disposeRenderBuffer()
{
this.cancelBuildBuffer();
this.disposeActiveBuffer = true;
}
public void markBufferDirty() { this.lastSwapLocalVersion = -1; }
}
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.api.enums.config.EGpuUploadMethod;
import com.seibel.distanthorizons.api.enums.config.ELoggerMode;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
@@ -39,6 +40,7 @@ import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import org.apache.logging.log4j.LogManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL32;
import java.awt.*;
@@ -52,7 +54,6 @@ import java.util.concurrent.PriorityBlockingQueue;
public class DebugRenderer
{
public static DebugRenderer INSTANCE = new DebugRenderer();
public DebugRenderer() { }
public static final ConfigBasedLogger logger = new ConfigBasedLogger(
LogManager.getLogger(TestRenderer.class), () -> ELoggerMode.LOG_ALL_TO_CHAT);
@@ -60,6 +61,24 @@ public class DebugRenderer
LogManager.getLogger(TestRenderer.class), () -> ELoggerMode.LOG_ALL_TO_CHAT, 1);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
// rendering setup
private ShaderProgram basicShader;
private GLVertexBuffer boxBuffer;
private GLElementBuffer boxOutlineBuffer;
private VertexAttribute va;
private boolean init = false;
// used when rendering
private Mat4f transformThiFrame;
private Vec3f camPosFloatThisFrame;
private final RendererLists rendererLists = new RendererLists();
private final PriorityBlockingQueue<BoxParticle> particles = new PriorityBlockingQueue<>();
// A box from 0,0,0 to 1,1,1
private static final float[] box_vertices = {
// Pos x y z
@@ -90,6 +109,145 @@ public class DebugRenderer
3, 7,
};
//=============//
// constructor //
//=============//
public DebugRenderer() { }
//==============//
// registration //
//==============//
public static void makeParticle(BoxParticle particle)
{
if (INSTANCE != null && Config.Client.Advanced.Debugging.DebugWireframe.enableRendering.get())
{
INSTANCE.particles.add(particle);
}
}
public static void register(IDebugRenderable renderable, ConfigEntry<Boolean> config) { if (INSTANCE != null) { INSTANCE.addRenderer(renderable, config); } }
public void addRenderer(IDebugRenderable renderable, ConfigEntry<Boolean> config) { this.rendererLists.addRenderable(renderable, config); }
public static void unregister(IDebugRenderable renderable, ConfigEntry<Boolean> config) { if (INSTANCE != null) { INSTANCE.removeRenderer(renderable, config); } }
private void removeRenderer(IDebugRenderable renderable, ConfigEntry<Boolean> config) { this.rendererLists.removeRenderable(renderable, config); }
public static void clearRenderables() { INSTANCE.rendererLists.clearRenderables(); }
//===========//
// rendering //
//===========//
public void init()
{
if (this.init)
{
return;
}
this.init = true;
this.va = VertexAttribute.create();
this.va.bind();
// Pos
this.va.setVertexAttribute(0, 0, VertexAttribute.VertexPointer.addVec3Pointer(false));
this.va.completeAndCheck(Float.BYTES * 3);
this.basicShader = new ShaderProgram("shaders/debug/vert.vert", "shaders/debug/frag.frag",
"fragColor", new String[]{"vPosition"});
this.createBuffer();
}
private void createBuffer()
{
ByteBuffer buffer = ByteBuffer.allocateDirect(box_vertices.length * Float.BYTES);
buffer.order(ByteOrder.nativeOrder());
buffer.asFloatBuffer().put(box_vertices);
buffer.rewind();
this.boxBuffer = new GLVertexBuffer(false);
this.boxBuffer.bind();
this.boxBuffer.uploadBuffer(buffer, 8, EGpuUploadMethod.DATA, box_vertices.length * Float.BYTES);
buffer = ByteBuffer.allocateDirect(box_outline_indices.length * Integer.BYTES);
buffer.order(ByteOrder.nativeOrder());
buffer.asIntBuffer().put(box_outline_indices);
buffer.rewind();
this.boxOutlineBuffer = new GLElementBuffer(false);
this.boxOutlineBuffer.bind();
this.boxOutlineBuffer.uploadBuffer(buffer, EGpuUploadMethod.DATA, box_outline_indices.length * Integer.BYTES, GL32.GL_STATIC_DRAW);
}
public void render(Mat4f transform)
{
this.transformThiFrame = transform;
Vec3d camPos = MC_RENDER.getCameraExactPosition();
camPosFloatThisFrame = new Vec3f((float) camPos.x, (float) camPos.y, (float) camPos.z);
GLState glState = new GLState();
this.init();
GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer());
GL32.glViewport(0, 0, MC_RENDER.getTargetFrameBufferViewportWidth(), MC_RENDER.getTargetFrameBufferViewportHeight());
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
//GL32.glLineWidth(2);
GL32.glEnable(GL32.GL_DEPTH_TEST);
GL32.glDisable(GL32.GL_STENCIL_TEST);
GL32.glDisable(GL32.GL_BLEND);
GL32.glDisable(GL32.GL_SCISSOR_TEST);
this.basicShader.bind();
this.va.bind();
this.va.bindBufferToAllBindingPoint(this.boxBuffer.getId());
this.boxOutlineBuffer.bind();
this.rendererLists.render(this);
BoxParticle head = null;
while ((head = this.particles.poll()) != null && head.isDead(System.nanoTime()))
{
}
if (head != null)
{
this.particles.add(head);
}
for (BoxParticle particle : this.particles)
{
this.renderBox(particle.getBox());
}
glState.restore();
}
public void renderBox(Box box)
{
Mat4f boxTransform = Mat4f.createTranslateMatrix(box.a.x - this.camPosFloatThisFrame.x, box.a.y - this.camPosFloatThisFrame.y, box.a.z - this.camPosFloatThisFrame.z);
boxTransform.multiply(Mat4f.createScaleMatrix(box.b.x - box.a.x, box.b.y - box.a.y, box.b.z - box.a.z));
Mat4f t = this.transformThiFrame.copy();
t.multiply(boxTransform);
this.basicShader.setUniform(this.basicShader.getUniformLocation("transform"), t);
this.basicShader.setUniform(this.basicShader.getUniformLocation("uColor"), box.color);
GL32.glDrawElements(GL32.GL_LINES, box_outline_indices.length, GL32.GL_UNSIGNED_INT, 0);
}
//================//
// helper classes //
//================//
public static final class Box
{
public Vec3f a;
@@ -149,14 +307,6 @@ public class DebugRenderer
}
ShaderProgram basicShader;
GLVertexBuffer boxBuffer;
GLElementBuffer boxOutlineBuffer;
VertexAttribute va;
boolean init = false;
private final LinkedList<WeakReference<IDebugRenderable>> renderers = new LinkedList<>();
public static final class BoxParticle implements Comparable<BoxParticle>
{
public Box box;
@@ -172,35 +322,27 @@ public class DebugRenderer
this.yChange = yChange;
}
public BoxParticle(Box box, long ns, float yChange)
{
this(box, System.nanoTime(), ns, yChange);
}
public BoxParticle(Box box, long nanoSecondDuratoin, float yChange) { this(box, System.nanoTime(), nanoSecondDuratoin, yChange); }
public BoxParticle(Box box, double secondDuration, float yChange) { this(box, System.nanoTime(), (long) (secondDuration * 1000000000), yChange); }
public BoxParticle(Box box, double s, float yChange)
{
this(box, System.nanoTime(), (long) (s * 1000000000), yChange);
}
@Override
public int compareTo(@NotNull DebugRenderer.BoxParticle o)
public int compareTo(@NotNull BoxParticle particle)
{
return Long.compare(startTime + duration, o.startTime + o.duration);
return Long.compare(this.startTime + this.duration, particle.startTime + particle.duration);
}
Box getBox()
public Box getBox()
{
long now = System.nanoTime();
float percent = (now - startTime) / (float) duration;
float percent = (now - this.startTime) / (float) this.duration;
percent = (float) Math.pow(percent, 4);
float yDiff = yChange * percent;
return new Box(new Vec3f(box.a.x, box.a.y + yDiff, box.a.z), new Vec3f(box.b.x, box.b.y + yDiff, box.b.z), box.color);
float yDiff = this.yChange * percent;
return new Box(new Vec3f(this.box.a.x, this.box.a.y + yDiff, this.box.a.z), new Vec3f(this.box.b.x, this.box.b.y + yDiff, this.box.b.z), this.box.color);
}
boolean isDead(long time)
{
return time - startTime > duration;
}
public boolean isDead(long time) { return (time - this.startTime) > this.duration; }
}
@@ -209,17 +351,16 @@ public class DebugRenderer
public Box box;
public BoxParticle particaleOnClose;
public BoxWithLife(Box box, long ns, float yChange, Color deathColor)
{
this.box = box;
this.particaleOnClose = new BoxParticle(new Box(box.a, box.b, deathColor), -1, ns, yChange);
DebugRenderer.register(this);
register(this, null);
}
public BoxWithLife(Box box, long ns, float yChange)
{
this(box, ns, yChange, box.color);
}
public BoxWithLife(Box box, long ns, float yChange) { this(box, ns, yChange, box.color); }
public BoxWithLife(Box box, double s, float yChange, Color deathColor)
{
@@ -227,46 +368,74 @@ public class DebugRenderer
this.particaleOnClose = new BoxParticle(new Box(box.a, box.b, deathColor), s, yChange);
}
public BoxWithLife(Box box, double s, float yChange)
{
this(box, s, yChange, box.color);
}
public BoxWithLife(Box box, double s, float yChange) { this(box, s, yChange, box.color); }
@Override
public void debugRender(DebugRenderer r)
{
r.renderBox(box);
}
public void debugRender(DebugRenderer renderer) { renderer.renderBox(this.box); }
@Override
public void close()
{
makeParticle(new BoxParticle(particaleOnClose.getBox(), System.nanoTime(), particaleOnClose.duration, particaleOnClose.yChange));
DebugRenderer.unregister(this);
makeParticle(new BoxParticle(this.particaleOnClose.getBox(), System.nanoTime(), this.particaleOnClose.duration, this.particaleOnClose.yChange));
unregister(this, null);
}
}
private final PriorityBlockingQueue<BoxParticle> particles = new PriorityBlockingQueue<>();
public static void unregister(IDebugRenderable r)
{
if (INSTANCE == null) return;
INSTANCE.removeRenderer(r);
}
public static void makeParticle(BoxParticle particle)
private static class RendererLists
{
if (INSTANCE == null) return;
if (!Config.Client.Advanced.Debugging.DebugWireframeRendering.enableRendering.get()) return;
INSTANCE.particles.add(particle);
}
private void removeRenderer(IDebugRenderable r)
{
synchronized (this.renderers)
public final LinkedList<WeakReference<IDebugRenderable>> generalRenderableList = new LinkedList<>();
private final HashMap<ConfigEntry<Boolean>, LinkedList<WeakReference<IDebugRenderable>>> renderableListByConfig = new HashMap<>();
// registration //
public void addRenderable(IDebugRenderable renderable, @Nullable ConfigEntry<Boolean> config)
{
Iterator<WeakReference<IDebugRenderable>> iterator = this.renderers.iterator();
synchronized (this)
{
if (config != null)
{
if (!this.renderableListByConfig.containsKey(config))
{
this.renderableListByConfig.put(config, new LinkedList<>());
}
LinkedList<WeakReference<IDebugRenderable>> renderableList = this.renderableListByConfig.get(config);
renderableList.add(new WeakReference<>(renderable));
}
else
{
this.generalRenderableList.add(new WeakReference<>(renderable));
}
}
}
public void removeRenderable(IDebugRenderable renderable, @Nullable ConfigEntry<Boolean> config)
{
synchronized (this)
{
if (config != null)
{
if (this.renderableListByConfig.containsKey(config))
{
LinkedList<WeakReference<IDebugRenderable>> renderableList = this.renderableListByConfig.get(config);
this.removeRenderableFromInternalList(renderableList, renderable);
}
}
else
{
this.removeRenderableFromInternalList(this.generalRenderableList, renderable);
}
}
}
private void removeRenderableFromInternalList(LinkedList<WeakReference<IDebugRenderable>> rendererList, IDebugRenderable renderable)
{
Iterator<WeakReference<IDebugRenderable>> iterator = rendererList.iterator();
while (iterator.hasNext())
{
WeakReference<IDebugRenderable> renderableRef = iterator.next();
@@ -275,131 +444,63 @@ public class DebugRenderer
iterator.remove();
continue;
}
if (renderableRef.get() == r)
if (renderableRef.get() == renderable)
{
iterator.remove();
return;
}
}
}
}
public static void clearRenderables() { INSTANCE.renderers.clear(); }
public void init()
{
if (init) return;
init = true;
va = VertexAttribute.create();
va.bind();
// Pos\
va.setVertexAttribute(0, 0, VertexAttribute.VertexPointer.addVec3Pointer(false));
va.completeAndCheck(Float.BYTES * 3);
basicShader = new ShaderProgram("shaders/debug/vert.vert", "shaders/debug/frag.frag",
"fragColor", new String[]{"vPosition"});
createBuffer();
}
private void createBuffer()
{
ByteBuffer buffer = ByteBuffer.allocateDirect(box_vertices.length * Float.BYTES);
buffer.order(ByteOrder.nativeOrder());
buffer.asFloatBuffer().put(box_vertices);
buffer.rewind();
boxBuffer = new GLVertexBuffer(false);
boxBuffer.bind();
boxBuffer.uploadBuffer(buffer, 8, EGpuUploadMethod.DATA, box_vertices.length * Float.BYTES);
buffer = ByteBuffer.allocateDirect(box_outline_indices.length * Integer.BYTES);
buffer.order(ByteOrder.nativeOrder());
buffer.asIntBuffer().put(box_outline_indices);
buffer.rewind();
boxOutlineBuffer = new GLElementBuffer(false);
boxOutlineBuffer.bind();
boxOutlineBuffer.uploadBuffer(buffer, EGpuUploadMethod.DATA, box_outline_indices.length * Integer.BYTES, GL32.GL_STATIC_DRAW);
}
public void addRenderer(IDebugRenderable r)
{
if (!Config.Client.Advanced.Debugging.DebugWireframeRendering.enableRendering.get()) return;
synchronized (renderers)
public void clearRenderables()
{
renderers.add(new WeakReference<>(r));
}
}
public static void register(IDebugRenderable r)
{
if (INSTANCE == null) return;
INSTANCE.addRenderer(r);
}
private Mat4f transform_this_frame;
private Vec3f camf;
public void renderBox(Box box)
{
Mat4f boxTransform = Mat4f.createTranslateMatrix(box.a.x - camf.x, box.a.y - camf.y, box.a.z - camf.z);
boxTransform.multiply(Mat4f.createScaleMatrix(box.b.x - box.a.x, box.b.y - box.a.y, box.b.z - box.a.z));
Mat4f t = transform_this_frame.copy();
t.multiply(boxTransform);
basicShader.setUniform(basicShader.getUniformLocation("transform"), t);
basicShader.setUniform(basicShader.getUniformLocation("uColor"), box.color);
GL32.glDrawElements(GL32.GL_LINES, box_outline_indices.length, GL32.GL_UNSIGNED_INT, 0);
}
public void render(Mat4f transform)
{
transform_this_frame = transform;
Vec3d cam = MC_RENDER.getCameraExactPosition();
camf = new Vec3f((float) cam.x, (float) cam.y, (float) cam.z);
GLState state = new GLState();
init();
GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer());
GL32.glViewport(0, 0, MC_RENDER.getTargetFrameBufferViewportWidth(), MC_RENDER.getTargetFrameBufferViewportHeight());
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
//GL32.glLineWidth(2);
GL32.glEnable(GL32.GL_DEPTH_TEST);
GL32.glDisable(GL32.GL_STENCIL_TEST);
GL32.glDisable(GL32.GL_BLEND);
GL32.glDisable(GL32.GL_SCISSOR_TEST);
basicShader.bind();
va.bind();
va.bindBufferToAllBindingPoint(boxBuffer.getId());
boxOutlineBuffer.bind();
synchronized (renderers)
{
Iterator<WeakReference<IDebugRenderable>> it = renderers.iterator();
while (it.hasNext())
for (ConfigEntry<Boolean> config : this.renderableListByConfig.keySet())
{
WeakReference<IDebugRenderable> ref = it.next();
IDebugRenderable r = ref.get();
if (r == null)
LinkedList<WeakReference<IDebugRenderable>> renderableList = this.renderableListByConfig.get(config);
if (config.get() && renderableList != null)
{
it.remove();
continue;
renderableList.clear();
}
r.debugRender(this);
}
}
BoxParticle head = null;
while ((head = particles.poll()) != null && head.isDead(System.nanoTime()))
{
}
if (head != null)
{
particles.add(head);
}
particles.forEach(b -> renderBox(b.getBox()));
state.restore();
// rendering //
public void render(DebugRenderer debugRenderer)
{
this.renderList(debugRenderer, this.generalRenderableList);
for (ConfigEntry<Boolean> config : this.renderableListByConfig.keySet())
{
LinkedList<WeakReference<IDebugRenderable>> renderableList = this.renderableListByConfig.get(config);
if (config.get() && renderableList != null && renderableList.size() != 0)
{
this.renderList(debugRenderer, renderableList);
}
}
}
private void renderList(DebugRenderer debugRenderer, LinkedList<WeakReference<IDebugRenderable>> rendererList)
{
synchronized (this)
{
Iterator<WeakReference<IDebugRenderable>> iterator = rendererList.iterator();
while (iterator.hasNext())
{
WeakReference<IDebugRenderable> ref = iterator.next();
IDebugRenderable renderable = ref.get();
if (renderable == null)
{
iterator.remove();
continue;
}
renderable.debugRender(debugRenderer);
}
}
}
}
}
@@ -198,6 +198,8 @@ public class LodRenderer
return;
}
// get MC's shader program and save MC's render state so we can restore it later
LagSpikeCatcher drawSaveGLState = new LagSpikeCatcher();
GLState minecraftGlState = new GLState();
@@ -348,7 +350,7 @@ public class LodRenderer
this.shaderProgram.unbind();
if (Config.Client.Advanced.Debugging.DebugWireframeRendering.enableRendering.get())
if (Config.Client.Advanced.Debugging.DebugWireframe.enableRendering.get())
{
profiler.popPush("Debug wireframes");
// Note: this can be very slow if a lot of boxes are being rendered
@@ -460,4 +462,4 @@ public class LodRenderer
});
}
}
}
@@ -49,17 +49,24 @@ public class FileUtil
}
if (file.renameTo(corruptedFile))
if (file.exists())
{
LOGGER.error("Renamed corrupted file to [" + corruptedFileName + "].");
if (file.renameTo(corruptedFile))
{
LOGGER.error("Renamed corrupted file to [" + corruptedFileName + "].");
}
else
{
LOGGER.error("Failed to rename corrupted file to [" + corruptedFileName + "]. Attempting to delete file...");
if (!file.delete())
{
LOGGER.error("Unable to delete corrupted file [" + corruptedFileName + "].");
}
}
}
else
{
LOGGER.error("Failed to rename corrupted file to [" + corruptedFileName + "]. Attempting to delete file...");
if (!file.delete())
{
LOGGER.error("Unable to delete corrupted file [" + corruptedFileName + "].");
}
LOGGER.error("Corrupted file [" + file + "] doesn't exist.");
}
return corruptedFile;
@@ -409,10 +409,6 @@
"Only Render LODs",
"distanthorizons.config.client.advanced.debugging.lodOnlyMode.@tooltip":
"If enabled this will disable (most) vanilla Minecraft rendering. \n\nNOTE: Do not report any issues when this mode is on! \nThis setting is only for fun and debugging. \nMod compatibility is not guaranteed.",
"distanthorizons.config.client.advanced.debugging.debugWireframeRendering":
"Enable Debug Wireframe Rendering",
"distanthorizons.config.client.advanced.debugging.debugWireframeRendering.@tooltip":
"If enabled, various wireframes for debugging internal functions will be drawn.",
"distanthorizons.config.client.advanced.debugging.enableWhiteWorld":
"Enable white world",
"distanthorizons.config.client.advanced.debugging.enableWhiteWorld.@tooltip":
@@ -522,7 +518,26 @@
"Linkable test",
"distanthorizons.config.client.advanced.debugging.exampleConfigScreen.linkableTest.@tooltip":
"The value of this should be the same as in Category Test",
"distanthorizons.config.client.advanced.debugging.debugWireframe":
"Debug Wireframe",
"distanthorizons.config.client.advanced.debugging.debugWireframe.enableRendering":
"Enable Debug Wireframe Rendering",
"distanthorizons.config.client.advanced.debugging.debugWireframe.enableRendering.@tooltip":
"If enabled, various wireframes for debugging internal functions will be drawn.",
"distanthorizons.config.client.advanced.debugging.debugWireframe.showWorldGenQueue":
"Show World Gen Queue",
"distanthorizons.config.client.advanced.debugging.debugWireframe.showRenderSectionStatus":
"Show Render Section Status",
"distanthorizons.config.client.advanced.debugging.debugWireframe.showFullDataFileStatus":
"Show Full Data file Status",
"distanthorizons.config.client.advanced.debugging.debugWireframe.showFullDataFileSampling":
"Show Full Data file Sampling",
"distanthorizons.config.client.advanced.debugging.debugWireframe.showRenderDataFileStatus":
"Show Render Data file Status",
"distanthorizons.config.client.resetSettingsConfirmation":
"Reset All Settings?",