diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiDebuggingConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiDebuggingConfig.java index 5fc74c69f..e34b964b9 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiDebuggingConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiDebuggingConfig.java @@ -46,6 +46,6 @@ public class DhApiDebuggingConfig implements IDhApiDebuggingConfig { return new DhApiConfigValue(Config.Client.Advanced.Debugging.lodOnlyMode); } public IDhApiConfigValue debugWireframeRendering() - { return new DhApiConfigValue(Config.Client.Advanced.Debugging.DebugWireframeRendering.enableRendering); } + { return new DhApiConfigValue(Config.Client.Advanced.Debugging.DebugWireframe.enableRendering); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index 1d4d01d22..1da4d4813 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -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 enableRendering = new ConfigEntry.Builder() + .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 showWorldGenQueue = new ConfigEntry.Builder() + .set(false) + .comment("Render queued world gen tasks?") + .build(); + + public static ConfigEntry showRenderSectionStatus = new ConfigEntry.Builder() + .set(false) + .comment("Render LOD section status?") + .build(); + + public static ConfigEntry showFullDataFileStatus = new ConfigEntry.Builder() + .set(false) + .comment("Render full data file status?") + .build(); + + public static ConfigEntry showFullDataFileSampling = new ConfigEntry.Builder() + .set(false) + .comment("Render full data file sampling progress?") + .build(); + + public static ConfigEntry showRenderDataFileStatus = new ConfigEntry.Builder() + .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 diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java index bb7f69b3a..bf6e15a99 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/FullDataPointIdMap.java @@ -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 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) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/FullDataArrayAccessor.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/FullDataArrayAccessor.java index f2bfa5ad1..1829d59f4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/FullDataArrayAccessor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/FullDataArrayAccessor.java @@ -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); } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/SingleColumnFullDataAccessor.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/SingleColumnFullDataAccessor.java index 6a418034c..236959cdb 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/SingleColumnFullDataAccessor.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/accessor/SingleColumnFullDataAccessor.java @@ -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 { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/AbstractFullDataSourceLoader.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/AbstractFullDataSourceLoader.java index 32eb41f19..d7246e80f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/AbstractFullDataSourceLoader.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/AbstractFullDataSourceLoader.java @@ -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, AbstractFullDataSourceLoader> LOADER_REGISTRY = HashMultimap.create(); + public static final HashMap> DATATYPE_ID_REGISTRY = new HashMap<>(); + + private static final int AVAILABLE_PROCESSOR_COUNT = Runtime.getRuntime().availableProcessors(); + - public static final HashMultimap, AbstractFullDataSourceLoader> loaderRegistry = HashMultimap.create(); public final Class fullDataSourceClass; - public static final HashMap> datatypeIdRegistry = new HashMap<>(); public final long datatypeId; public final byte[] loaderSupportedVersions; + /** used when pooling data sources */ + private final ArrayList cachedSources = new ArrayList<>(); + private final ReadWriteLock cacheReadWriteLock = new ReentrantReadWriteLock(); + + //=============// + // constructor // + //=============// + public AbstractFullDataSourceLoader(Class 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 loaders = loaderRegistry.get(fullDataSourceClass); + Set 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 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 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(); + } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/CompleteFullDataSourceLoader.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/CompleteFullDataSourceLoader.java index 7be126d92..36d975894 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/CompleteFullDataSourceLoader.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/CompleteFullDataSourceLoader.java @@ -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 diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/HighDetailIncompleteFullDataSourceLoader.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/HighDetailIncompleteFullDataSourceLoader.java index cb1ce495e..ea5dda512 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/HighDetailIncompleteFullDataSourceLoader.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/HighDetailIncompleteFullDataSourceLoader.java @@ -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); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/LowDetailIncompleteFullDataSourceLoader.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/LowDetailIncompleteFullDataSourceLoader.java index aeec53396..f5f642d39 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/LowDetailIncompleteFullDataSourceLoader.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/loader/LowDetailIncompleteFullDataSourceLoader.java @@ -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); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java index edebb5f2d..7cb73cc2a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/CompleteFullDataSource.java @@ -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); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/HighDetailIncompleteFullDataSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/HighDetailIncompleteFullDataSource.java index ab3388136..b4035e3b6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/HighDetailIncompleteFullDataSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/HighDetailIncompleteFullDataSource.java @@ -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; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/LowDetailIncompleteFullDataSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/LowDetailIncompleteFullDataSource.java index 9e9fa81d3..537797155 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/LowDetailIncompleteFullDataSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/LowDetailIncompleteFullDataSource.java @@ -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) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IFullDataSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IFullDataSource.java index f0599441e..b7e0770e8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IFullDataSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IFullDataSource.java @@ -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; + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IStreamableFullDataSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IStreamableFullDataSource.java index dde6b657b..9a12568e7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IStreamableFullDataSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IStreamableFullDataSource.java @@ -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 defines the object holding this data source's summary data, extends {@link IStreamableFullDataSource.FullDataSourceSummaryData}. * @param 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 transformFullDataToRenderSourceAsync(IFullDataSource fullDataSource, IDhClientLevel level) { return CompletableFuture.supplyAsync(() -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); } - public static CompletableFuture transformFullDataToRenderSourceAsync(CompletableFuture fullDataSourceFuture, IDhClientLevel level) { return fullDataSourceFuture.thenApplyAsync((fullDataSource) -> transformFullDataToRenderSource(fullDataSource, level), transformerThreadPool); } + public static CompletableFuture 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(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java index 34ca6d695..b3b8c57f2 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataFileHandler.java @@ -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 sampleFromFileArray(IIncompleteFullDataSource recipientFullDataSource, ArrayList 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 sampleFromFileArray(IIncompleteFullDataSource recipientFullDataSource, ArrayList existingFiles, boolean usePooledDataSources) { - // read in the existing data - final ArrayList> 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> sampleDataFutures = new ArrayList<>(existingFiles.size()); + for (int i = 0; i < existingFiles.size(); i++) + { + FullDataMetaFile existingFile = existingFiles.get(i); + + + CompletableFuture loadFileFuture = usePooledDataSources ? existingFile.getOrLoadCachedDataSourceAsync() : existingFile.getDataSourceWithoutCachingAsync(); + + CompletableFuture 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 posList, ArrayList 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); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java index bfec48dbe..9f91b756c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java @@ -87,6 +87,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I */ private DataSourceReferenceTracker.FullDataSourceSoftRef cachedFullDataSourceRef = new DataSourceReferenceTracker.FullDataSourceSoftRef(this,null); private final AtomicReference> dataSourceLoadFutureRef = new AtomicReference<>(null); + public volatile Boolean cacheLoadingDataSource = null; // === Concurrent Write tracking === private final AtomicReference 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 getOrLoadCachedDataSourceAsync() + + + public CompletableFuture getDataSourceWithoutCachingAsync() { return this.getOrLoadCachedDataSourceAsync(false); } + public CompletableFuture getOrLoadCachedDataSourceAsync() { return this.getOrLoadCachedDataSourceAsync(true); } + private CompletableFuture getOrLoadCachedDataSourceAsync(boolean cacheLoadingSource) { checkAndLogPhantomDataSourceLifeCycles(); CompletableFuture 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 dataSourceLoadFuture = potentialLoadFuture; + final CompletableFuture 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 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) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java index e15369033..f4e5072e4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java @@ -139,11 +139,11 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler { ArrayList 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 updateFromExistingDataSourcesAsync(FullDataMetaFile file, IIncompleteFullDataSource data) + private CompletableFuture updateFromExistingDataSourcesAsync(FullDataMetaFile file, IIncompleteFullDataSource data, boolean usePooledDataSources) { DhSectionPos pos = file.pos; ArrayList 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 future = this.updateFromExistingDataSourcesAsync(file, data); + CompletableFuture 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 future = this.updateFromExistingDataSourcesAsync(file, (IIncompleteFullDataSource) fullDataSource); + CompletableFuture future = this.updateFromExistingDataSourcesAsync(file, (IIncompleteFullDataSource) fullDataSource, false); if (future != null) { final boolean finalDataChanged = dataChanged; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java index a9a07910f..92a2dd6d9 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderDataMetaFile.java @@ -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> 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 renderSourceLoadFuture = this.getCachedDataSourceAsync(false); + CompletableFuture 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 transformFuture = FullDataToRenderDataTransformer.transformFullDataToRenderSourceAsync(fullDataSourceFuture, this.clientLevel) - .handle((newRenderSource, ex) -> + CompletableFuture 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 diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index d53aa5188..11d3da087 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -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)); }); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java index 3156f0666..2043dd006 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientServerLevel.java @@ -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 diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java index ccf86b080..0c44cd489 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java @@ -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; } - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java index afc2419c4..8b8396237 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/DebugRenderer.java @@ -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 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 config) { if (INSTANCE != null) { INSTANCE.addRenderer(renderable, config); } } + public void addRenderer(IDebugRenderable renderable, ConfigEntry config) { this.rendererLists.addRenderable(renderable, config); } + + public static void unregister(IDebugRenderable renderable, ConfigEntry config) { if (INSTANCE != null) { INSTANCE.removeRenderer(renderable, config); } } + private void removeRenderer(IDebugRenderable renderable, ConfigEntry 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> renderers = new LinkedList<>(); - public static final class BoxParticle implements Comparable { 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 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> generalRenderableList = new LinkedList<>(); + + private final HashMap, LinkedList>> renderableListByConfig = new HashMap<>(); + + + + // registration // + + public void addRenderable(IDebugRenderable renderable, @Nullable ConfigEntry config) { - Iterator> iterator = this.renderers.iterator(); + synchronized (this) + { + if (config != null) + { + if (!this.renderableListByConfig.containsKey(config)) + { + this.renderableListByConfig.put(config, new LinkedList<>()); + } + + LinkedList> renderableList = this.renderableListByConfig.get(config); + renderableList.add(new WeakReference<>(renderable)); + } + else + { + this.generalRenderableList.add(new WeakReference<>(renderable)); + } + } + } + + public void removeRenderable(IDebugRenderable renderable, @Nullable ConfigEntry config) + { + synchronized (this) + { + if (config != null) + { + if (this.renderableListByConfig.containsKey(config)) + { + LinkedList> renderableList = this.renderableListByConfig.get(config); + this.removeRenderableFromInternalList(renderableList, renderable); + } + } + else + { + this.removeRenderableFromInternalList(this.generalRenderableList, renderable); + } + } + } + private void removeRenderableFromInternalList(LinkedList> rendererList, IDebugRenderable renderable) + { + Iterator> iterator = rendererList.iterator(); while (iterator.hasNext()) { WeakReference 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> it = renderers.iterator(); - while (it.hasNext()) + for (ConfigEntry config : this.renderableListByConfig.keySet()) { - WeakReference ref = it.next(); - IDebugRenderable r = ref.get(); - if (r == null) + LinkedList> 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 config : this.renderableListByConfig.keySet()) + { + LinkedList> renderableList = this.renderableListByConfig.get(config); + if (config.get() && renderableList != null && renderableList.size() != 0) + { + this.renderList(debugRenderer, renderableList); + } + } + } + private void renderList(DebugRenderer debugRenderer, LinkedList> rendererList) + { + synchronized (this) + { + Iterator> iterator = rendererList.iterator(); + while (iterator.hasNext()) + { + WeakReference ref = iterator.next(); + IDebugRenderable renderable = ref.get(); + if (renderable == null) + { + iterator.remove(); + continue; + } + + renderable.debugRender(debugRenderer); + } + } + } } - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java index dffa6c179..addfe6711 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java @@ -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 }); } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/FileUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/FileUtil.java index d311e1432..26ab91db4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/FileUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/FileUtil.java @@ -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; diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index 8d8f428a7..a3c50f8b5 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -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?",