diff --git a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiDataCompressionMode.java b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiDataCompressionMode.java
index ce277088c..4e093a234 100644
--- a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiDataCompressionMode.java
+++ b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EDhApiDataCompressionMode.java
@@ -56,14 +56,15 @@ public enum EDhApiDataCompressionMode
*/
LZ4(1),
- /**
+ /*
* Decent speed and good compression.
*
* Read Speed: 11.78 MS / DTO
* Write Speed: 16.76 MS / DTO
* Compression ratio: 0.2199
*/
- Z_STD(2),
+ //@Deprecated
+ //Z_STD(2),
/**
* Extremely slow, but very good compression.
@@ -82,7 +83,8 @@ public enum EDhApiDataCompressionMode
EDhApiDataCompressionMode(int value) { this.value = (byte) value; }
- public static EDhApiDataCompressionMode getFromValue(byte value)
+ /** @throws IllegalArgumentException if the value doesn't map to a value */
+ public static EDhApiDataCompressionMode getFromValue(byte value) throws IllegalArgumentException
{
EDhApiDataCompressionMode[] enumList = EDhApiDataCompressionMode.values();
for (int i = 0; i < enumList.length; i++)
diff --git a/core/build.gradle b/core/build.gradle
index 405fca7a4..7e3e9ac7a 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -40,37 +40,8 @@ dependencies { // All of these dependencies are in Vanilla Minecraft, but we nee
runtimeOnly "org.lwjgl:lwjgl-stb::$lwjglNatives"
runtimeOnly "org.lwjgl:lwjgl-tinyfd::$lwjglNatives"
-
- // fast util
- shade("it.unimi.dsi:fastutil:${rootProject.fastutil_version}")
-
- // Compression
- // needs to be here and in core to prevent runtime/compiler errors
- shade("org.lz4:lz4-java:${rootProject.lz4_version}") // LZ4
- shade("com.github.luben:zstd-jni:${rootProject.zstd_version}") // Zstd
- shade("org.tukaani:xz:${rootProject.xz_version}") // LZMA
-
- // Sqlite Database
- shade("org.xerial:sqlite-jdbc:${rootProject.sqlite_jdbc_version}")
-
- // Netty
- implementation("io.netty:netty-buffer:${rootProject.netty_version}")
- implementation("io.netty:netty-codec:${rootProject.netty_version}")
- implementation("io.netty:netty-transport:${rootProject.netty_version}")
- implementation("io.netty:netty-handler:${rootProject.netty_version}")
-
- // NightConfig (includes Toml & Json)
- // needs to be here and in core to prevent runtime/compiler errors
- shade("com.electronwill.night-config:toml:${rootProject.nightconfig_version}")
- shade("com.electronwill.night-config:json:${rootProject.nightconfig_version}")
-
-
- // needed for the standalone jar
- shade("org.apache.logging.log4j:log4j-core:2.23.1")
- shade("org.apache.logging.log4j:log4j-api:2.23.1")
-
- // SVG (not needed atm)
- //shade("com.formdev:svgSalamander:${rootProject.svgSalamander_version}")
+ // FIXME for some reason this line doesn't actually shade in the library
+// shade "it.unimi.dsi:fastutil:${rootProject.fastutil_version}" // Add our own fastutil version
// Some other dependencies
@@ -78,47 +49,15 @@ dependencies { // All of these dependencies are in Vanilla Minecraft, but we nee
implementation("com.google.code.findbugs:jsr305:3.0.2")
implementation("com.google.common:google-collect:0.5")
implementation("com.google.guava:guava:31.1-jre")
-
+
}
artifacts {
- shade shadowJar
shadowedArtifact shadowJar // Setup the configuration shadowedArtifact to be the shadowJar
}
shadowJar {
- configurations = [project.configurations.shade]
-
def librariesLocation = "distanthorizons.libraries"
-
-
- relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
-
- // LWJGL
- // Only ever shadow the dependencies we use otherwise some stuff would break when running on an external client
- relocate "org.lwjgl.system.jawt", "${librariesLocation}.lwjgl.system.jawt"
-
- // Compression
- relocate "net.jpountz", "${librariesLocation}.jpountz"
- relocate "com.github.luben", "${librariesLocation}.github.luben"
- relocate "org.tukaani", "${librariesLocation}.tukaani"
-
- // Sqlite Database
- //At the moment, there is a bug in this library which doesnt allow it to be relocated
-// relocate "org.sqlite", "${librariesLocation}.sqlite"
-
- // JOML
- if (project.hasProperty("embed_joml") && embed_joml == "true")
- relocate "org.joml", "${librariesLocation}.joml"
-
- // NightConfig (includes Toml & Json)
- relocate "com.electronwill.nightconfig", "${librariesLocation}.electronwill.nightconfig"
-
- // Netty
- relocate "io.netty", "${librariesLocation}.netty"
-
- relocate "org.apache.logging", "${librariesLocation}.apache.logging"
-
-
+// relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
mergeServiceFiles()
}
\ No newline at end of file
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/Initializer.java b/core/src/main/java/com/seibel/distanthorizons/core/Initializer.java
index b84c721ef..76c2f0515 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/Initializer.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/Initializer.java
@@ -28,10 +28,10 @@ import com.seibel.distanthorizons.core.api.external.methods.config.DhApiConfig;
import com.seibel.distanthorizons.core.api.external.methods.data.DhApiTerrainDataRepo;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
-import io.netty.buffer.ByteBuf;
import net.jpountz.lz4.LZ4FrameOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.tukaani.xz.XZOutputStream;
import java.awt.*;
@@ -47,13 +47,17 @@ public class Initializer
{
// if any library isn't present in the jar its class
// will throw an error (not an exception)
- Class> compressor = LZ4FrameOutputStream.class;
- Class> networking = ByteBuf.class;
- Class> toml = com.electronwill.nightconfig.core.Config.class;
+ Class> fastCompressor = LZ4FrameOutputStream.class;
+ Class> smallCompressor = XZOutputStream.class;
+ //Class> networking = ByteBuf.class;
+ Class> config = com.electronwill.nightconfig.core.Config.class;
+ Class> oldFastUtil = it.unimi.dsi.fastutil.longs.LongArrayList.class; // available in 8.2.1
+ //Class> newFastUtil = it.unimi.dsi.fastutil.ints.IntUnaryOperator.class; // available in 8.5.13
}
catch (Throwable e)
{
LOGGER.fatal("Critical programmer error: One or more libraries aren't present. Error: [" + e.getMessage() + "].");
+ // throwing here should crash the game, notifying the developer that something is wrong
throw e;
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
index cd7efb2b6..a10ce8750 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java
@@ -56,6 +56,8 @@ import org.lwjgl.glfw.GLFW;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
@@ -78,8 +80,7 @@ public class ClientApi
private boolean configOverrideReminderPrinted = false;
- private boolean migrationMessageShown = false;
- private boolean showMigrationMessageNextFrame = false;
+ private final Queue chatMessageQueueForNextFrame = new LinkedBlockingQueue<>();
public boolean rendererDisabledBecauseOfExceptions = false;
@@ -362,23 +363,22 @@ public class ClientApi
this.configOverrideReminderPrinted = true;
// remind the user that this is a development build
- MC.sendChatMessage("Distant Horizons nightly experimental build version [" + ModInfo.VERSION+"].");
- MC.sendChatMessage("You are running an unsupported version of Distant Horizons!");
+ MC.sendChatMessage("Distant Horizons nightly/unstable build, version: [" + ModInfo.VERSION+"].");
+ MC.sendChatMessage("Issues may occur with this version.");
MC.sendChatMessage("Here be dragons!");
MC.sendChatMessage("");
}
- // data migration
- if (this.showMigrationMessageNextFrame
- && !this.migrationMessageShown
- && Config.Client.Advanced.LodBuilding.showMigrationChatWarning.get())
+ // generic messages
+ while (!this.chatMessageQueueForNextFrame.isEmpty())
{
- this.showMigrationMessageNextFrame = false;
- this.migrationMessageShown = true;
-
- MC.sendChatMessage("Old Distant Horizons data is being migrated.");
- MC.sendChatMessage("During migration LODs may load slowly and DH world gen is disabled.");
- MC.sendChatMessage("");
+ String message = this.chatMessageQueueForNextFrame.poll();
+ if (message == null)
+ {
+ // done to prevent potential null pointers
+ message = "";
+ }
+ MC.sendChatMessage(message);
}
IProfilerWrapper profiler = MC.getProfiler();
@@ -535,7 +535,10 @@ public class ClientApi
}
}
- // TODO there's probably a better way of handling chat messages
- public void showMigrationMessageOnNextFrame() { this.showMigrationMessageNextFrame = true; }
+ /**
+ * Queues the given message to appear in chat the next valid frame.
+ * Useful for queueing up messages that may be triggered before the user has loaded into the world.
+ */
+ public void showChatMessageNextFrame(String chatMessage) { this.chatMessageQueueForNextFrame.add(chatMessage); }
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java
index 969a83ed7..7ba59dbe7 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java
@@ -114,6 +114,8 @@ public class SharedApi
{
DebugRenderer.clearRenderables();
MC_RENDER.clearTargetFrameBuffer();
+ // needs to be closed on world shutdown to clear out un-processed chunks
+ UPDATING_CHUNK_POS_SET.clear();
}
// recommend that the garbage collector cleans up any objects from the old world and thread pools
@@ -369,4 +371,4 @@ public class SharedApi
}
-}
+}
\ No newline at end of file
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 42dcbdb99..f87bc2fef 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
@@ -780,12 +780,6 @@ public class Config
+ "Estimated average DTO read speed: 1.85 ms\n"
+ "Estimated average DTO write speed: 9.46 ms\n"
+ "\n"
- + EDhApiDataCompressionMode.Z_STD + " \n"
- + "A good middle ground between speed and compression.\n"
- + "Expected Compression Ratio: 0.21\n"
- + "Estimated average DTO read speed: 11.78 ms\n"
- + "Estimated average DTO write speed: 16.77 ms\n"
- + "\n"
+ EDhApiDataCompressionMode.LZMA2 + " \n"
+ "Slow but very good compression.\n"
+ "Expected Compression Ratio: 0.14\n"
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 ce362a4e6..61a29f3e5 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
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.dataObjects.fullData;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -259,9 +260,14 @@ public class FullDataPointIdMap
}
/** Creates a new IdBiomeBlockStateMap from the given UTF formatted stream */
- public static FullDataPointIdMap deserialize(DhDataInputStream inputStream, DhSectionPos pos, ILevelWrapper levelWrapper) throws IOException, InterruptedException
+ public static FullDataPointIdMap deserialize(DhDataInputStream inputStream, DhSectionPos pos, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{
int entityCount = inputStream.readInt();
+ if (entityCount < 0)
+ {
+ throw new DataCorruptedException("FullDataPointIdMap deserialize entry count should have a number greater than or equal to 0, returned value ["+entityCount+"].");
+ }
+
// only used when debugging
HashMap dataPointEntryBySerialization = new HashMap<>();
@@ -269,6 +275,13 @@ public class FullDataPointIdMap
FullDataPointIdMap newMap = new FullDataPointIdMap(pos);
for (int i = 0; i < entityCount; i++)
{
+ // necessary to prevent issues with deserializing objects after the level has been closed
+ if (Thread.interrupted())
+ {
+ throw new InterruptedException(FullDataPointIdMap.class.getSimpleName() + " task interrupted.");
+ }
+
+
String entryString = inputStream.readUTF();
Entry newEntry = Entry.deserialize(entryString, levelWrapper);
newMap.entryList.add(newEntry);
@@ -457,18 +470,12 @@ public class FullDataPointIdMap
public String serialize() { return this.biome.getSerialString() + BLOCK_STATE_SEPARATOR_STRING + this.blockState.getSerialString(); }
- public static Entry deserialize(String str, ILevelWrapper levelWrapper) throws IOException, InterruptedException
+ public static Entry deserialize(String str, ILevelWrapper levelWrapper) throws IOException, DataCorruptedException
{
String[] stringArray = str.split(BLOCK_STATE_SEPARATOR_STRING);
if (stringArray.length != 2)
{
- throw new IOException("Failed to deserialize BiomeBlockStateEntry");
- }
-
- // necessary to prevent issues with deserializing objects after the level has been closed
- if (Thread.interrupted())
- {
- throw new InterruptedException(FullDataPointIdMap.class.getSimpleName() + " task interrupted.");
+ throw new DataCorruptedException("Failed to deserialize BiomeBlockStateEntry");
}
IBiomeWrapper biome = WRAPPER_FACTORY.deserializeBiomeWrapper(stringArray[0], levelWrapper);
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV1.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV1.java
index 37cdf0452..4f782837b 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV1.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV1.java
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
@@ -152,7 +153,7 @@ public class FullDataSourceV1 implements IDataSource
* Clears and then overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an existing {@link FullDataSourceV1} and can be used in place of a constructor to reuse an existing {@link FullDataSourceV1} object.
*/
- public void repopulateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
+ public void repopulateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException, DataCorruptedException
{
// clear/overwrite the old data
this.resizeDataStructuresForRepopulation(dto.pos);
@@ -166,7 +167,7 @@ public class FullDataSourceV1 implements IDataSource
* Overwrites any data in this object with the data from the given file and stream.
* This is expected to be used with an empty {@link FullDataSourceV1} and functions similar to a constructor.
*/
- public void populateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException
+ public void populateFromStream(FullDataSourceV1DTO dto, DhDataInputStream inputStream, IDhLevel level) throws IOException, InterruptedException, DataCorruptedException
{
FullDataSourceSummaryData summaryData = this.readSourceSummaryInfo(dto, inputStream, level);
this.setSourceSummaryData(summaryData);
@@ -361,7 +362,7 @@ public class FullDataSourceV1 implements IDataSource
outputStream.writeInt(DATA_GUARD_BYTE);
this.mapping.serialize(outputStream);
}
- public FullDataPointIdMap readIdMappings(DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException
+ public FullDataPointIdMap readIdMappings(DhDataInputStream inputStream, ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{
int guardByte = inputStream.readInt();
if (guardByte != DATA_GUARD_BYTE)
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java
index afa03d0eb..632d0722a 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java
@@ -32,6 +32,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -618,7 +619,16 @@ public class FullDataSourceV2 implements IDataSource
{
if (height != 0)
{
- newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
+ try
+ {
+ long datapoint = FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight);
+ newColumnList.add(datapoint);
+ }
+ catch (DataCorruptedException e)
+ {
+ // shouldn't happen, (especially if validation is disabled) but just in case
+ LOGGER.warn("Skipping corrupt datapoint for pos "+inputDataSource.pos+" at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
+ }
}
lastId = id;
@@ -632,7 +642,15 @@ public class FullDataSourceV2 implements IDataSource
// add the last slice if present
if (height != 0)
{
- newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
+ try
+ {
+ newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
+ }
+ catch (DataCorruptedException e)
+ {
+ // shouldn't happen, (especially if validation is disabled) but just in case
+ LOGGER.warn("Skipping corrupt datapoint for pos "+inputDataSource.pos+" at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
+ }
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java
index 2aabb2e71..1ec8b9a44 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java
@@ -239,8 +239,14 @@ public class FullDataToRenderDataTransformer
{
if (colorBelowWithAvoidedBlocks)
{
- //mare sure to not trnasfer alpha if for some reason grass is transparent
- colorToApplyToNextBlock = ColorUtil.setAlpha(level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block),255);
+ int tempColor = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block);
+ if (ColorUtil.getAlpha(tempColor) == 0)
+ {
+ //make sure to not transfer the color when alpha is 0
+ continue;
+ }
+ //mare sure to not trnasfer alpha if for some reason grass is semi transparent
+ colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255);
skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight;
}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java
index 9437ec217..bc6da58f6 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java
@@ -35,6 +35,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
@@ -133,98 +134,106 @@ public class LodDataBuilder
EDhApiWorldCompressionMode worldCompressionMode = Config.Client.Advanced.LodBuilding.worldCompression.get();
boolean ignoreHiddenBlocks = (worldCompressionMode != EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
- int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
- for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
+ try
{
- for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
+ int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
+ for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{
- LongArrayList longs = new LongArrayList(chunkWrapper.getHeight() / 4);
- int lastY = chunkWrapper.getMaxBuildHeight();
- IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
- IBlockStateWrapper blockState = AIR;
- int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
-
-
- byte blockLight;
- byte skyLight;
- if (lastY < chunkWrapper.getMaxBuildHeight())
+ for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
{
- // FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting
- blockLight = (byte) chunkWrapper.getBlockLight(relBlockX, lastY + 1, relBlockZ);
- skyLight = (byte) chunkWrapper.getSkyLight(relBlockX, lastY + 1, relBlockZ);
- }
- else
- {
- //we are at the height limit. There are no torches here, and sky is not obscured.
- blockLight = 0;
- skyLight = 15;
- }
-
-
- // determine the starting Y Pos
- int y = chunkWrapper.getLightBlockingHeightMapValue(relBlockX,relBlockZ);
- // go up until we reach open air or the world limit
- IBlockStateWrapper topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
- while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
- {
- try
- {
- // This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light.
- // Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks.
- y++;
- topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
- }
- catch (Exception e)
- {
- if (!getTopErrorLogged)
- {
- LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
- getTopErrorLogged = true;
- }
-
- y--;
- break;
- }
- }
-
-
- for (; y >= minBuildHeight; y--)
- {
- IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
- IBlockStateWrapper newBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
- byte newBlockLight = (byte) chunkWrapper.getBlockLight(relBlockX, y + 1, relBlockZ);
- byte newSkyLight = (byte) chunkWrapper.getSkyLight(relBlockX, y + 1, relBlockZ);
+ LongArrayList longs = new LongArrayList(chunkWrapper.getHeight() / 4);
+ int lastY = chunkWrapper.getMaxBuildHeight();
+ IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
+ IBlockStateWrapper blockState = AIR;
+ int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
- // save the biome/block change
- if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
+
+ byte blockLight;
+ byte skyLight;
+ if (lastY < chunkWrapper.getMaxBuildHeight())
{
- // if we ignore hidden blocks, don't save this biome/block change
- // wait until the block is visible and then save the new datapoint
- if (!ignoreHiddenBlocks
- // if the last block is air, this block will always be visible
- || blockState.isAir()
- // check if this block is visible from any direction
- || blockVisible(chunkWrapper, relBlockX, y, relBlockZ))
+ // FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting
+ blockLight = (byte) chunkWrapper.getBlockLight(relBlockX, lastY + 1, relBlockZ);
+ skyLight = (byte) chunkWrapper.getSkyLight(relBlockX, lastY + 1, relBlockZ);
+ }
+ else
+ {
+ //we are at the height limit. There are no torches here, and sky is not obscured.
+ blockLight = 0;
+ skyLight = 15;
+ }
+
+
+ // determine the starting Y Pos
+ int y = chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ);
+ // go up until we reach open air or the world limit
+ IBlockStateWrapper topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
+ while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
+ {
+ try
{
- longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
- biome = newBiome;
- blockState = newBlockState;
- mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
- blockLight = newBlockLight;
- skyLight = newSkyLight;
- lastY = y;
+ // This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light.
+ // Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks.
+ y++;
+ topBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
+ }
+ catch (Exception e)
+ {
+ if (!getTopErrorLogged)
+ {
+ LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
+ getTopErrorLogged = true;
+ }
+
+ y--;
+ break;
}
}
+
+
+ for (; y >= minBuildHeight; y--)
+ {
+ IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
+ IBlockStateWrapper newBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ);
+ byte newBlockLight = (byte) chunkWrapper.getBlockLight(relBlockX, y + 1, relBlockZ);
+ byte newSkyLight = (byte) chunkWrapper.getSkyLight(relBlockX, y + 1, relBlockZ);
+
+ // save the biome/block change
+ if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
+ {
+ // if we ignore hidden blocks, don't save this biome/block change
+ // wait until the block is visible and then save the new datapoint
+ if (!ignoreHiddenBlocks
+ // if the last block is air, this block will always be visible
+ || blockState.isAir()
+ // check if this block is visible from any direction
+ || blockVisible(chunkWrapper, relBlockX, y, relBlockZ))
+ {
+ longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
+ biome = newBiome;
+ blockState = newBlockState;
+ mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
+ blockLight = newBlockLight;
+ skyLight = newSkyLight;
+ lastY = y;
+ }
+ }
+ }
+ longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
+
+ dataSource.setSingleColumn(longs,
+ relBlockX + chunkOffsetX,
+ relBlockZ + chunkOffsetZ,
+ EDhApiWorldGenerationStep.LIGHT,
+ worldCompressionMode);
}
- longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
-
- dataSource.setSingleColumn(longs,
- relBlockX + chunkOffsetX,
- relBlockZ + chunkOffsetZ,
- EDhApiWorldGenerationStep.LIGHT,
- worldCompressionMode);
}
}
+ catch (DataCorruptedException e)
+ {
+ LOGGER.error("Unable to convert chunk at pos ["+chunkWrapper.getChunkPos()+"] to an LOD. Error: "+e.getMessage(), e);
+ return null;
+ }
LodUtil.assertTrue(!dataSource.isEmpty);
return dataSource;
@@ -292,7 +301,7 @@ public class LodDataBuilder
/** @throws ClassCastException if an API user returns the wrong object type(s) */
- public static FullDataSourceV2 createFromApiChunkData(DhApiChunk dataPoints) throws ClassCastException
+ public static FullDataSourceV2 createFromApiChunkData(DhApiChunk dataPoints) throws ClassCastException, DataCorruptedException
{
FullDataSourceV2 accessor = FullDataSourceV2.createEmpty(new DhSectionPos(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ)));
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractDataSourceHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractDataSourceHandler.java
index 9d3e00b86..96ed0902c 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractDataSourceHandler.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/AbstractDataSourceHandler.java
@@ -8,6 +8,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
import com.seibel.distanthorizons.core.util.LodUtil;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.PositionalLockProvider;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import org.apache.logging.log4j.Logger;
@@ -16,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
+import java.io.StreamCorruptedException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@@ -31,6 +33,8 @@ public abstract class AbstractDataSourceHandler
implements AutoCloseable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
+ private static final Set CORRUPT_DATA_ERRORS_LOGGED = Collections.newSetFromMap(new ConcurrentHashMap<>());
+
/**
* The highest numerical detail level possible.
@@ -95,7 +99,7 @@ public abstract class AbstractDataSourceHandler
/** When this is called the parent folders should be created */
protected abstract TRepo createRepo();
- protected abstract TDataSource createDataSourceFromDto(TDTO dto) throws InterruptedException, IOException;
+ protected abstract TDataSource createDataSourceFromDto(TDTO dto) throws InterruptedException, IOException, DataCorruptedException;
protected abstract TDTO createDtoFromDataSource(TDataSource dataSource);
protected abstract TDataSource makeEmptyDataSource(DhSectionPos pos);
@@ -145,8 +149,23 @@ public abstract class AbstractDataSourceHandler
TDTO dto = this.repo.getByKey(pos);
if (dto != null)
{
- // load from database
- dataSource = this.createDataSourceFromDto(dto);
+ try
+ {
+ // load from database
+ dataSource = this.createDataSourceFromDto(dto);
+ }
+ catch (DataCorruptedException e)
+ {
+ // Only log each message type once.
+ // This is done to prevent logging "No compression mode with the value [2]" 10,000 times
+ // if the user is migrating from a nightly build and used ZStd.
+ if (CORRUPT_DATA_ERRORS_LOGGED.add(e.getMessage()))
+ {
+ LOGGER.warn("Corrupted data found at pos " + pos + ". Data at position will be deleted so it can be re-generated to prevent issues. Future errors with this same message won't be logged. Error: " + e.getMessage(), e);
+ }
+
+ this.repo.deleteWithKey(pos);
+ }
}
else
{
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV1.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV1.java
index fde42a6a7..1ee291fcc 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV1.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV1.java
@@ -7,6 +7,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV1DTO;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV1Repo;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@@ -72,7 +73,7 @@ public class FullDataSourceProviderV1
}
}
- protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException
+ protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException
{
FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos);
dataSource.populateFromStream(dto, dto.getInputStream(), this.level);
@@ -128,6 +129,13 @@ public class FullDataSourceProviderV1
}
}
catch (InterruptedException ignore) { }
+ catch (DataCorruptedException e)
+ {
+ // stack trace not included since a lot of corrupt data would cause the log to get quite messy,
+ // and it should be fairly easy to see what the problem was from the message
+ LOGGER.warn("Corrupted data found at pos "+pos+". Data at position will be deleted so it can be re-generated and to prevent future issues. Error: "+e.getMessage());
+ this.repo.deleteWithKey(pos);
+ }
catch (IOException e)
{
LOGGER.warn("File read Error for pos ["+pos+"], error: "+e.getMessage(), e);
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java
index 6a3f99422..9ee70f7c4 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataSourceProviderV2.java
@@ -35,6 +35,7 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.core.util.ThreadUtil;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Logger;
@@ -90,6 +91,8 @@ public class FullDataSourceProviderV2
protected final AtomicBoolean migrationThreadRunning = new AtomicBoolean(true);
protected final FullDataSourceProviderV1 legacyFileHandler;
+ protected boolean migrationStartMessageQueued = false;
+
protected long legacyDeletionCount = -1;
protected long migrationCount = -1;
@@ -168,7 +171,7 @@ public class FullDataSourceProviderV2
}
@Override
- protected FullDataSourceV2 createDataSourceFromDto(FullDataSourceV2DTO dto) throws InterruptedException, IOException
+ protected FullDataSourceV2 createDataSourceFromDto(FullDataSourceV2DTO dto) throws InterruptedException, IOException, DataCorruptedException
{ return dto.createPooledDataSource(this.level.getLevelWrapper()); }
@Override
@@ -323,7 +326,7 @@ public class FullDataSourceProviderV2
}
catch (Exception e)
{
- LOGGER.error("issue in update for parent pos: " + parentUpdatePos);
+ LOGGER.error("issue in update for parent pos: " + parentUpdatePos+ " Error: "+e.getMessage(), e);
}
finally
{
@@ -394,7 +397,7 @@ public class FullDataSourceProviderV2
{
// this should only be shown once per session but should be shown during
// either when the deletion or migration phases start
- ClientApi.INSTANCE.showMigrationMessageOnNextFrame();
+ this.showMigrationStartMessage();
LOGGER.info("deleting [" + dimensionName + "] - [" + totalDeleteCount + "] unused data sources...");
@@ -447,7 +450,7 @@ public class FullDataSourceProviderV2
ArrayList legacyDataSourceList = this.legacyFileHandler.getDataSourcesToMigrate(MIGRATION_BATCH_COUNT);
if (!legacyDataSourceList.isEmpty())
{
- ClientApi.INSTANCE.showMigrationMessageOnNextFrame();
+ this.showMigrationStartMessage();
// keep going until every data source has been migrated
@@ -520,11 +523,13 @@ public class FullDataSourceProviderV2
if (this.migrationThreadRunning.get())
{
LOGGER.info("migration complete for: [" + dimensionName + "]-[" + this.saveDir + "].");
+ this.showMigrationEndMessage(true);
this.migrationCount = 0;
}
else
{
LOGGER.info("migration stopped for: [" + dimensionName + "]-[" + this.saveDir + "].");
+ this.showMigrationEndMessage(false);
}
}
else
@@ -539,6 +544,41 @@ public class FullDataSourceProviderV2
public long getTotalMigrationCount() { return this.migrationCount; }
+ private void showMigrationStartMessage()
+ {
+ if (this.migrationStartMessageQueued)
+ {
+ return;
+ }
+ this.migrationStartMessageQueued = true;
+
+ String dimName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
+ ClientApi.INSTANCE.showChatMessageNextFrame(
+ "Old Distant Horizons data is being migrated for ["+dimName+"]. \n" +
+ "While migrating LODs may load slowly \n" +
+ "and DH world gen will be disabled. \n" +
+ "You can see migration progress in the F3 menu."
+ );
+ }
+
+ private void showMigrationEndMessage(boolean success)
+ {
+ String dimName = this.level.getLevelWrapper().getDimensionType().getDimensionName();
+
+ if (success)
+ {
+ ClientApi.INSTANCE.showChatMessageNextFrame("Distant Horizons data migration for ["+dimName+"] completed.");
+ }
+ else
+ {
+ ClientApi.INSTANCE.showChatMessageNextFrame(
+ "Distant Horizons data migration for ["+dimName+"] stopped. \n" +
+ "Some data may not have been migrated."
+ );
+ }
+ }
+
+
//=======================//
// retrieval (world gen) //
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java
index ca8947d97..2c390d4d1 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java
@@ -208,14 +208,18 @@ public class SubDimensionLevelMatcher implements AutoCloseable
for (File testLevelFolder : this.potentialLevelFolders)
{
LOGGER.info("Testing level folder: [" + LodUtil.shortenString(testLevelFolder.getName(), 8) + "]");
+
+ FullDataSourceV2 testFullDataSource = null;
try
{
// get the data source to compare against
- IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false, null);
- FullDataSourceV2 testFullDataSource = tempLevel.getFullDataProvider().getAsync(new DhSectionPos(this.playerData.playerBlockPos)).join();
- if (testFullDataSource == null)
+ try (IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false, null))
{
- continue;
+ testFullDataSource = tempLevel.getFullDataProvider().getAsync(new DhSectionPos(this.playerData.playerBlockPos)).join();
+ if (testFullDataSource == null)
+ {
+ continue;
+ }
}
@@ -334,6 +338,13 @@ public class SubDimensionLevelMatcher implements AutoCloseable
// for now we are just assuming it is an unrelated file
LOGGER.warn("Error checking level: "+e.getMessage(), e);
}
+ finally
+ {
+ if (testFullDataSource != null)
+ {
+ try { testFullDataSource.close(); } catch (Exception ignore) {}
+ }
+ }
}
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 04c485bf2..da1595893 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
@@ -36,6 +36,7 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException;
import com.seibel.distanthorizons.core.util.ThreadUtil;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
@@ -469,6 +470,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints);
chunkDataConsumer.accept(dataSource);
}
+ catch (DataCorruptedException e)
+ {
+ LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
+ Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
+ }
catch (ClassCastException e)
{
LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e);
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/logging/ConfigBasedLogger.java b/core/src/main/java/com/seibel/distanthorizons/core/logging/ConfigBasedLogger.java
index 19fb61dfe..1177e4ad2 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/logging/ConfigBasedLogger.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/logging/ConfigBasedLogger.java
@@ -111,29 +111,10 @@ public class ConfigBasedLogger
}
}
- public void error(String str, Object... param)
- {
- log(Level.ERROR, str, param);
- }
-
- public void warn(String str, Object... param)
- {
- log(Level.WARN, str, param);
- }
-
- public void info(String str, Object... param)
- {
- log(Level.INFO, str, param);
- }
-
- public void debug(String str, Object... param)
- {
- log(Level.DEBUG, str, param);
- }
-
- public void trace(String str, Object... param)
- {
- log(Level.TRACE, str, param);
- }
+ public void error(String str, Object... param) { this.log(Level.ERROR, str, param); }
+ public void warn(String str, Object... param) { this.log(Level.WARN, str, param); }
+ public void info(String str, Object... param) { this.log(Level.INFO, str, param); }
+ public void debug(String str, Object... param) { this.log(Level.DEBUG, str, param); }
+ public void trace(String str, Object... param) { this.log(Level.TRACE, str, param); }
}
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 584e275f1..157caf28c 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
@@ -57,6 +57,9 @@ import org.apache.logging.log4j.LogManager;
import org.lwjgl.opengl.GL32;
import java.awt.*;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@@ -402,17 +405,10 @@ public class LodRenderer
this.renderTransparentBuffers(profiler, renderEventParam, renderEventParam.partialTicks);
}
-
- if (this.usingMcFrameBuffer)
- {
- // If MC's framebuffer is being used the depth needs to be cleared to prevent rendering on top of MC.
- // This should only happen when Optifine shaders are being used.
- GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
- }
-
drawLagSpikeCatcher.end("LodDraw");
+
//=================//
// debug rendering //
//=================//
@@ -425,12 +421,21 @@ public class LodRenderer
combinedMatrix.multiply(renderEventParam.dhModelViewMatrix);
// Note: this can be very slow if a lot of boxes are being rendered
- DebugRenderer.INSTANCE.render(combinedMatrix);
+ DebugRenderer.INSTANCE.render(combinedMatrix);
profiler.popPush("LOD cleanup");
}
+ if (this.usingMcFrameBuffer)
+ {
+ // If MC's framebuffer is being used the depth needs to be cleared to prevent rendering on top of MC.
+ // This should only happen when Optifine shaders are being used.
+ GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
+ }
+
+
+
//=============================//
// Apply to the MC FrameBuffer //
//=============================//
@@ -591,8 +596,6 @@ public class LodRenderer
{
if (this.usingMcFrameBuffer && framebufferOverride == null)
{
- // recreating the GL State at this point is necessary in order to get the correct depth texture
- minecraftGlState.saveState();
if (ENABLE_DUMP_GL_STATE)
{
tickLogger.debug("Re-saving GL state due to Optifine presence: " + minecraftGlState);
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/FullDataSourceV2DTO.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/FullDataSourceV2DTO.java
index cf7345d10..ed87622c8 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/FullDataSourceV2DTO.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/dto/FullDataSourceV2DTO.java
@@ -26,6 +26,8 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.network.protocol.INetworkObject;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
+import com.seibel.distanthorizons.core.util.FullDataPointUtil;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -33,15 +35,16 @@ import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
+import java.io.*;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
/** handles storing {@link FullDataSourceV2}'s in the database. */
public class FullDataSourceV2DTO implements IBaseDTO, INetworkObject
{
+ public static final boolean VALIDATE_INPUT_DATAPOINTS = true;
+
+
public DhSectionPos pos;
public int levelMinY;
@@ -59,7 +62,7 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
public byte[] compressedMappingByteArray;
public byte dataFormatVersion;
- public EDhApiDataCompressionMode compressionModeEnum;
+ public byte compressionModeValue;
public boolean applyToParent;
@@ -81,7 +84,7 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
return new FullDataSourceV2DTO(
dataSource.getPos(),
- checkedDataPointArray.checksum, compressedWorldGenStepByteArray, compressedWorldCompressionModeByteArray, FullDataSourceV2.DATA_FORMAT_VERSION, compressionModeEnum, checkedDataPointArray.byteArray,
+ checkedDataPointArray.checksum, compressedWorldGenStepByteArray, compressedWorldCompressionModeByteArray, FullDataSourceV2.DATA_FORMAT_VERSION, compressionModeEnum.value, checkedDataPointArray.byteArray,
dataSource.lastModifiedUnixDateTime, dataSource.createdUnixDateTime,
mappingByteArray, dataSource.applyToParent,
dataSource.levelMinY
@@ -93,7 +96,7 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
public FullDataSourceV2DTO(
DhSectionPos pos,
- int dataChecksum, byte[] compressedColumnGenStepByteArray, byte[] compressedWorldCompressionModeByteArray, byte dataFormatVersion, EDhApiDataCompressionMode compressionModeEnum, byte[] compressedDataByteArray,
+ int dataChecksum, byte[] compressedColumnGenStepByteArray, byte[] compressedWorldCompressionModeByteArray, byte dataFormatVersion, byte compressionModeValue, byte[] compressedDataByteArray,
long lastModifiedUnixDateTime, long createdUnixDateTime,
byte[] compressedMappingByteArray, boolean applyToParent,
int levelMinY)
@@ -104,7 +107,7 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
this.compressedWorldCompressionModeByteArray = compressedWorldCompressionModeByteArray;
this.dataFormatVersion = dataFormatVersion;
- this.compressionModeEnum = compressionModeEnum;
+ this.compressionModeValue = compressionModeValue;
this.compressedDataByteArray = compressedDataByteArray;
this.compressedMappingByteArray = compressedMappingByteArray;
@@ -123,32 +126,46 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
// data source population //
//========================//
- public FullDataSourceV2 createPooledDataSource(@NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException
+ public FullDataSourceV2 createPooledDataSource(@NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{
FullDataSourceV2 dataSource = FullDataSourceV2.DATA_SOURCE_POOL.getPooledSource(this.pos, false);
return this.populateDataSource(dataSource, levelWrapper);
}
- public FullDataSourceV2 populateDataSource(FullDataSourceV2 dataSource, @NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException
+ public FullDataSourceV2 populateDataSource(FullDataSourceV2 dataSource, @NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{ return this.internalPopulateDataSource(dataSource, levelWrapper, false); }
/**
* May be missing one or more data fields.
* Designed to be used without access to Minecraft or any supporting objects.
*/
- public FullDataSourceV2 createUnitTestDataSource() throws IOException, InterruptedException
+ public FullDataSourceV2 createUnitTestDataSource() throws IOException, InterruptedException, DataCorruptedException
{ return this.internalPopulateDataSource(FullDataSourceV2.createEmpty(this.pos), null, true); }
- private FullDataSourceV2 internalPopulateDataSource(FullDataSourceV2 dataSource, ILevelWrapper levelWrapper, boolean unitTest) throws IOException, InterruptedException
+ private FullDataSourceV2 internalPopulateDataSource(FullDataSourceV2 dataSource, ILevelWrapper levelWrapper, boolean unitTest) throws IOException, InterruptedException, DataCorruptedException
{
if (FullDataSourceV2.DATA_FORMAT_VERSION != this.dataFormatVersion)
{
throw new IllegalStateException("There should only be one data format [" + FullDataSourceV2.DATA_FORMAT_VERSION + "].");
}
- dataSource.columnGenerationSteps = readBlobToGenerationSteps(this.compressedColumnGenStepByteArray, this.compressionModeEnum);
- dataSource.columnWorldCompressionMode = readBlobToGenerationSteps(this.compressedWorldCompressionModeByteArray, this.compressionModeEnum);
- dataSource.dataPoints = readBlobToDataSourceDataArray(this.compressedDataByteArray, this.compressionModeEnum);
+
+ EDhApiDataCompressionMode compressionModeEnum;
+ try
+ {
+ compressionModeEnum = this.getCompressionMode();
+ }
+ catch (IllegalArgumentException e)
+ {
+ // may happen if ZStd was used (which was added and removed during the nightly builds)
+ // or if the compressor value is changed to an invalid option
+ throw new DataCorruptedException(e);
+ }
+
+
+ dataSource.columnGenerationSteps = readBlobToGenerationSteps(this.compressedColumnGenStepByteArray, compressionModeEnum);
+ dataSource.columnWorldCompressionMode = readBlobToGenerationSteps(this.compressedWorldCompressionModeByteArray, compressionModeEnum);
+ dataSource.dataPoints = readBlobToDataSourceDataArray(this.compressedDataByteArray, compressionModeEnum);
dataSource.mapping.clear(dataSource.getPos());
// should only be null when used in a unit test
@@ -159,7 +176,7 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
throw new NullPointerException("No level wrapper present, unable to deserialize data map. This should only be used for unit tests.");
}
- dataSource.mapping.mergeAndReturnRemappedEntityIds(readBlobToDataMapping(this.compressedMappingByteArray, dataSource.getPos(), levelWrapper, this.compressionModeEnum));
+ dataSource.mapping.mergeAndReturnRemappedEntityIds(readBlobToDataMapping(this.compressedMappingByteArray, dataSource.getPos(), levelWrapper, compressionModeEnum));
}
dataSource.lastModifiedUnixDateTime = this.lastModifiedUnixDateTime;
@@ -217,7 +234,7 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
return new CheckedByteArray(checksum, byteArrayOutputStream.toByteArray());
}
- private static LongArrayList[] readBlobToDataSourceDataArray(byte[] compressedDataByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
+ private static LongArrayList[] readBlobToDataSourceDataArray(byte[] compressedDataByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedDataByteArray);
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum);
@@ -230,12 +247,21 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
{
// read the column length
short dataColumnLength = compressedIn.readShort(); // separate variables are used for debugging and in case validation wants to be added later
+ if (dataColumnLength < 0)
+ {
+ throw new DataCorruptedException("Read DataSource Blob data at index ["+xz+"], column length ["+dataColumnLength+"] should be greater than zero.");
+ }
+
LongArrayList dataColumn = new LongArrayList(new long[dataColumnLength]);
// read column data (will be skipped if no data was present)
for (int y = 0; y < dataColumnLength; y++)
{
long dataPoint = compressedIn.readLong();
+ if (VALIDATE_INPUT_DATAPOINTS)
+ {
+ FullDataPointUtil.validateDatapoint(dataPoint);
+ }
dataColumn.set(y, dataPoint);
}
@@ -259,15 +285,22 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
return byteArrayOutputStream.toByteArray();
}
- private static byte[] readBlobToGenerationSteps(byte[] dataByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException
+ private static byte[] readBlobToGenerationSteps(byte[] dataByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(dataByteArray);
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum);
- byte[] columnGenStepByteArray = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
- compressedIn.readFully(columnGenStepByteArray);
-
- return columnGenStepByteArray;
+ try
+ {
+ byte[] columnGenStepByteArray = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
+ compressedIn.readFully(columnGenStepByteArray);
+
+ return columnGenStepByteArray;
+ }
+ catch (EOFException e)
+ {
+ throw new DataCorruptedException(e);
+ }
}
@@ -307,7 +340,7 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
return byteArrayOutputStream.toByteArray();
}
- private static FullDataPointIdMap readBlobToDataMapping(byte[] compressedMappingByteArray, DhSectionPos pos, @NotNull ILevelWrapper levelWrapper, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException
+ private static FullDataPointIdMap readBlobToDataMapping(byte[] compressedMappingByteArray, DhSectionPos pos, @NotNull ILevelWrapper levelWrapper, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedMappingByteArray);
DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum);
@@ -334,7 +367,7 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
out.writeBytes(this.compressedMappingByteArray);
out.writeByte(this.dataFormatVersion);
- out.writeByte(this.compressionModeEnum.ordinal());
+ out.writeByte(this.compressionModeValue);
out.writeBoolean(this.applyToParent);
@@ -360,7 +393,7 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
in.readBytes(this.compressedMappingByteArray);
this.dataFormatVersion = in.readByte();
- this.compressionModeEnum = EDhApiDataCompressionMode.values()[in.readByte()];
+ this.compressionModeValue = in.readByte();
this.applyToParent = in.readBoolean();
@@ -379,6 +412,13 @@ public class FullDataSourceV2DTO implements IBaseDTO, INetworkObje
+ //================//
+ // helper methods //
+ //================//
+
+ public EDhApiDataCompressionMode getCompressionMode() throws IllegalArgumentException { return EDhApiDataCompressionMode.getFromValue(this.compressionModeValue); }
+
+
//================//
// helper classes //
//================//
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV2Repo.java b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV2Repo.java
index e2caa3618..153fcb6d6 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV2Repo.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/sql/repo/FullDataSourceV2Repo.java
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import org.apache.logging.log4j.Logger;
@@ -91,8 +92,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo.
- */
-
-package com.seibel.distanthorizons.core.util;
-
-import it.unimi.dsi.fastutil.booleans.BooleanObjectImmutablePair;
-
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.atomic.AtomicReferenceArray;
-import java.util.function.Predicate;
-
-/**
- * While java 8 does have built in atomic operations, there doesn't seem to be any Compare And Exchange operation...
- * So here we implement our own.
- */
-public class AtomicsUtil
-{
-
- public static T conditionalAndExchange(AtomicReference atomic, Predicate requirement, T newValue)
- {
- while (true)
- {
- T oldValue = atomic.get();
- if (!requirement.test(oldValue)) return oldValue;
- if (atomic.weakCompareAndSet(oldValue, newValue)) return oldValue;
- }
- }
-
- public static BooleanObjectImmutablePair conditionalAndExchangeWeak(AtomicReference atomic, Predicate requirement, T newValue)
- {
- T oldValue = atomic.get();
- if (requirement.test(oldValue) && atomic.weakCompareAndSet(oldValue, newValue))
- {
- return new BooleanObjectImmutablePair<>(true, oldValue);
- }
- else
- {
- return new BooleanObjectImmutablePair<>(false, oldValue);
- }
- }
-
- /**
- * If the {@link AtomicReference}'s current value matches the expected value, the newValue will be swapped in and the expected value returned.
- * If the {@link AtomicReference}'s current value DOESN'T match the expected value, the {@link AtomicReference}'s current value will be returned without modification.
- */
- public static T compareAndExchange(AtomicReference atomic, T expected, T newValue)
- {
- while (true)
- {
- T oldValue = atomic.get();
- if (oldValue != expected)
- {
- return oldValue;
- }
- else if (atomic.weakCompareAndSet(expected, newValue))
- {
- return expected;
- }
- }
- }
-
- public static BooleanObjectImmutablePair compareAndExchangeWeak(AtomicReference atomic, T expected, T newValue)
- {
- T oldValue = atomic.get();
- if (oldValue == expected && atomic.weakCompareAndSet(expected, newValue))
- {
- return new BooleanObjectImmutablePair<>(true, expected);
- }
- else
- {
- return new BooleanObjectImmutablePair<>(false, oldValue);
- }
- }
-
- // Additionally, we implement some helper methods for frequently used atomic operations. //
-
- // Compare with expected value and set new value if equal. Then return whatever value the atomic now contains.
- public static T compareAndSetThenGet(AtomicReference atomic, T expected, T newValue)
- {
- while (true)
- {
- T oldValue = atomic.get();
- if (oldValue != expected) return oldValue;
- if (atomic.weakCompareAndSet(expected, newValue)) return newValue;
- }
- }
-
-
-
- // Below is the array version of the above. //
-
- public static T compareAndExchange(AtomicReferenceArray array, int index, T expected, T newValue)
- {
- while (true)
- {
- T oldValue = array.get(index);
- if (oldValue != expected) return oldValue;
- if (array.weakCompareAndSet(index, expected, newValue)) return expected;
- }
- }
-
- public static BooleanObjectImmutablePair compareAndExchangeWeak(AtomicReferenceArray array, int index, T expected, T newValue)
- {
- T oldValue = array.get(index);
- if (oldValue == expected && array.weakCompareAndSet(index, expected, newValue))
- {
- return new BooleanObjectImmutablePair<>(true, expected);
- }
- else
- {
- return new BooleanObjectImmutablePair<>(false, oldValue);
- }
- }
-
- public static T compareAndSetThenGet(AtomicReferenceArray array, int index, T expected, T newValue)
- {
- while (true)
- {
- T oldValue = array.get(index);
- if (oldValue != expected) return oldValue;
- if (array.weakCompareAndSet(index, expected, newValue)) return newValue;
- }
- }
-
-}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/FullDataPointUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/FullDataPointUtil.java
index b63c6f0e7..2b30f66c2 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/util/FullDataPointUtil.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/util/FullDataPointUtil.java
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.util;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV1;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.jetbrains.annotations.Contract;
@@ -72,23 +73,11 @@ public class FullDataPointUtil
* creates a new datapoint with the given values
* @param relMinY relative to the minimum level Y value
*/
- public static long encode(int id, int height, int relMinY, byte blockLight, byte skyLight)
+ public static long encode(int id, int height, int relMinY, byte blockLight, byte skyLight) throws DataCorruptedException
{
if (RUN_VALIDATION)
{
- // assertions are inside if-blocks to prevent unnecessary string concatenations
- if (relMinY < 0 || relMinY >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
- {
- LodUtil.assertNotReach("Trying to create datapoint with y[" + relMinY + "] out of range!");
- }
- if (height <= 0 || height >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
- {
- LodUtil.assertNotReach("Trying to create datapoint with height[" + height + "] out of range!");
- }
- if (relMinY + height > RenderDataPointUtil.MAX_WORLD_Y_SIZE)
- {
- LodUtil.assertNotReach("Trying to create datapoint with y+depth[" + (relMinY + height) + "] out of range!");
- }
+ validateData(id, height, relMinY, blockLight, skyLight);
}
@@ -116,6 +105,44 @@ public class FullDataPointUtil
return data;
}
+ public static void validateDatapoint(long datapoint) throws DataCorruptedException { validateData(getId(datapoint), getHeight(datapoint), getBottomY(datapoint), (byte)getBlockLight(datapoint), (byte)getSkyLight(datapoint)); }
+ /**
+ * Throws {@link DataCorruptedException} if any of the given values are outside
+ * their expected range.
+ */
+ public static void validateData(int id, int height, int relMinY, byte blockLight, byte skyLight) throws DataCorruptedException
+ {
+ // ID
+ if (id < 0)
+ {
+ throw new DataCorruptedException("Full datapoint ID [" + relMinY + "] must be greater than zero.");
+ }
+
+ // height
+ if (relMinY < 0 || relMinY >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
+ {
+ throw new DataCorruptedException("Full datapoint relative min y [" + relMinY + "] must be in the range [0 - "+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"] (inclusive).");
+ }
+ if (height <= 0 || height >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
+ {
+ throw new DataCorruptedException("Full datapoint height [" + height + "] must be in the range [1 - "+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"] (inclusive).");
+ }
+ if (relMinY + height > RenderDataPointUtil.MAX_WORLD_Y_SIZE)
+ {
+ throw new DataCorruptedException("Full datapoint y+depth [" + (relMinY + height) + "] is higher than the maximum world Y height ["+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"].");
+ }
+
+ // lighting
+ if (blockLight < LodUtil.MIN_MC_LIGHT || blockLight > LodUtil.MAX_MC_LIGHT)
+ {
+ throw new DataCorruptedException("Full datapoint block light [" + blockLight + "] must be in the range ["+LodUtil.MIN_MC_LIGHT+" - "+LodUtil.MAX_MC_LIGHT+"] (inclusive).");
+ }
+ if (skyLight < LodUtil.MIN_MC_LIGHT || skyLight > LodUtil.MAX_MC_LIGHT)
+ {
+ throw new DataCorruptedException("Full datapoint sky light [" + skyLight + "] must be in the range ["+LodUtil.MIN_MC_LIGHT+" - "+LodUtil.MAX_MC_LIGHT+"] (inclusive).");
+ }
+ }
+
/** Returns the BlockState/Biome pair ID used to identify this LOD's color */
public static int getId(long data) { return (int) (data & ID_MASK); }
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/DataCorruptedException.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/DataCorruptedException.java
new file mode 100644
index 000000000..3195bb320
--- /dev/null
+++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/DataCorruptedException.java
@@ -0,0 +1,24 @@
+package com.seibel.distanthorizons.core.util.objects;
+
+/**
+ * Thrown when a DH handled resource or datasource isn't in the
+ * correct format.
+ *
+ * IE: a blocklight with the value -4 when it should be between 0 and 15.
+ */
+public class DataCorruptedException extends Exception
+{
+ /** replaces this exception's stack trace with the incoming one */
+ public DataCorruptedException(Exception e)
+ {
+ super(e.getMessage());
+ this.setStackTrace(e.getStackTrace());
+ this.addSuppressed(e);
+ }
+
+ public DataCorruptedException(String message)
+ {
+ super(message);
+ }
+
+}
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/DhDataInputStream.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/DhDataInputStream.java
index 1c0fb0114..3fc8b2728 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/DhDataInputStream.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/DhDataInputStream.java
@@ -19,9 +19,8 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams;
-import com.github.luben.zstd.RecyclingBufferPool;
-import com.github.luben.zstd.ZstdInputStream;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
+import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import net.jpountz.lz4.LZ4FrameInputStream;
import org.tukaani.xz.XZInputStream;
@@ -51,8 +50,6 @@ public class DhDataInputStream extends DataInputStream
return stream;
case LZ4:
return new LZ4FrameInputStream(stream);
- case Z_STD:
- return new ZstdInputStream(stream, RecyclingBufferPool.INSTANCE);
case LZMA2:
// Note: all LZMA/XZ compressors can be decompressed using this same InputStream
return new XZInputStream(stream);
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/DhDataOutputStream.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/DhDataOutputStream.java
index 3337cb26f..54e50b4d5 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/DhDataOutputStream.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/DhDataOutputStream.java
@@ -19,14 +19,10 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams;
-import com.github.luben.zstd.RecyclingBufferPool;
-import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
-import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FrameOutputStream;
import net.jpountz.xxhash.XXHashFactory;
-import org.apache.logging.log4j.Logger;
import org.tukaani.xz.*;
import java.io.*;
@@ -59,8 +55,6 @@ public class DhDataOutputStream extends DataOutputStream
LZ4Factory.nativeInstance().fastCompressor(),
XXHashFactory.nativeInstance().hash32(),
LZ4FrameOutputStream.FLG.Bits.BLOCK_INDEPENDENCE);
- case Z_STD:
- return new ZstdOutputStream(stream, RecyclingBufferPool.INSTANCE);
case LZMA2:
// using an array cache significantly reduces GC pressure
ResettableArrayCache arrayCache = LZMA_RESETTABLE_ARRAY_CACHE_GETTER.get();
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/LzmaArrayCache.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/LzmaArrayCache.java
index e932e9936..5c982130c 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/LzmaArrayCache.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/dataStreams/LzmaArrayCache.java
@@ -2,12 +2,12 @@ package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
-import it.unimi.dsi.fastutil.ints.IntUnaryOperator;
import org.apache.logging.log4j.Logger;
import org.tukaani.xz.ArrayCache;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.function.IntUnaryOperator;
import java.util.concurrent.atomic.AtomicInteger;
/**
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/ILightMapWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/ILightMapWrapper.java
index d16cbfa44..9d601653b 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/ILightMapWrapper.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/misc/ILightMapWrapper.java
@@ -28,7 +28,7 @@ import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindab
public interface ILightMapWrapper extends IBindable
{
- // Returns the binded texture position
+ /** Returns the bound texture position */
void bind();
void unbind();
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/IClientLevelWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/IClientLevelWrapper.java
index 12e24e99d..7cdd14bbf 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/IClientLevelWrapper.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/world/IClientLevelWrapper.java
@@ -28,6 +28,7 @@ import org.jetbrains.annotations.Nullable;
*/
public interface IClientLevelWrapper extends ILevelWrapper
{
+
@Nullable
IServerLevelWrapper tryGetServerSideWrapper();
@@ -36,4 +37,8 @@ public interface IClientLevelWrapper extends ILevelWrapper
/** @return -1 if there was a problem getting the color */
int getDirtBlockColor();
+ /** Will return null if there was an issue finding the biome. */
+ @Nullable
+ IBiomeWrapper getPlainsBiomeWrapper();
+
}
\ No newline at end of file
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 8a4d06c3a..7c2238261 100644
--- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json
+++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json
@@ -321,7 +321,7 @@
"distanthorizons.config.client.advanced.worldGenerator.distantGeneratorMode":
"Distance Generator Mode",
"distanthorizons.config.client.advanced.worldGenerator.distantGeneratorMode.@tooltip":
- "How complicated the generation should be when generating LODs outside the vanilla render distance.\n\n§6§6Fastest:§r Biome only\n§6Best Quality:§r Features (suggested)",
+ "How complicated the generation should be when generating LODs outside the vanilla render distance.",
"distanthorizons.config.client.advanced.worldGenerator.worldGenLightingEngine":
"Lighting Engine",
"distanthorizons.config.client.advanced.worldGenerator.worldGenLightingEngine.@tooltip":
@@ -797,13 +797,11 @@
"Full",
"distanthorizons.config.enum.EDhApiDataCompressionMode.UNCOMPRESSED":
- "1. Uncompressed",
+ "Uncompressed",
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZ4":
- "2. LZ4",
- "distanthorizons.config.enum.EDhApiDataCompressionMode.Z_STD":
- "3. Zstd",
+ "Fast/Big - LZ4",
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZMA2":
- "4. LZMA2",
+ "Slow/Small - LZMA2",
"distanthorizons.config.enum.EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS":
"1. Merge Same Blocks",
diff --git a/core/src/test/java/tests/CompressionTest.java b/core/src/test/java/tests/CompressionTest.java
index 68319522c..b3d3de3a8 100644
--- a/core/src/test/java/tests/CompressionTest.java
+++ b/core/src/test/java/tests/CompressionTest.java
@@ -213,11 +213,11 @@ public class CompressionTest
}
//@Test
- public void Zstd() // middle of the road
- {
- String compressorName = "Zstd";
- this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD);
- }
+ //public void Zstd() // middle of the road
+ //{
+ // String compressorName = "Zstd";
+ // this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD);
+ //}
//@Test
public void LZMA2() // very slow, very good compression though
@@ -293,7 +293,7 @@ public class CompressionTest
// uncompressed input //
FullDataSourceV2DTO uncompressedDto = uncompressedRepo.getByKey(pos);
- Assert.assertEquals(uncompressedDto.compressionModeEnum, EDhApiDataCompressionMode.UNCOMPRESSED);
+ Assert.assertEquals(uncompressedDto.compressionModeValue, EDhApiDataCompressionMode.UNCOMPRESSED.value);
FullDataSourceV2 uncompressedDataSource = uncompressedDto.createUnitTestDataSource();
long uncompressedDtoSize = uncompressedRepo.getDataSizeInBytes(pos);