Compare commits

...

50 Commits

Author SHA1 Message Date
James Seibel 26d4220967 Add logging/messaging for corrupted DB files 2025-12-09 07:12:33 -06:00
James Seibel 5ca754d2ac Fix world gen progress config resetting on reboot 2025-12-06 09:18:34 -06:00
James Seibel f13744e858 Add thread pool priority setting
Setting this to 1 higher than C2ME can reduce issues with Chunky overwhelming DH.
2025-12-05 07:35:16 -06:00
James Seibel 64ac218003 Improve empty LOD debugging slightly 2025-12-05 07:28:57 -06:00
James Seibel 385bd326cf minor world gen related refactoring 2025-12-04 07:39:09 -06:00
James Seibel 6ea864ef6b TEST 2025-11-29 09:59:33 -06:00
James Seibel 4e96728c25 maybe fix concurrency error during world gen shutdown 2025-11-28 16:29:47 -06:00
James Seibel 1c44ef7f0c minor reformatting 2025-11-28 16:23:36 -06:00
James Seibel 227d0d09ba fix getDataPointAtBlockPos() relative Y 2025-11-28 15:53:47 -06:00
James Seibel d7ba3fa724 fix LOD only mode when transparency is disabled 2025-11-28 15:53:38 -06:00
James Seibel 7e46adf469 add the ability to ignore update chunk pos 2025-11-28 10:48:42 -06:00
James Seibel f43e2fa441 don't render thick snow layers 2025-11-28 09:39:03 -06:00
James Seibel f9819d3d46 fix vanilla fading for MC versions before 1.21.5 2025-11-28 08:42:20 -06:00
James Seibel 19b23bea5f add slow world gen warning config 2025-11-27 09:59:16 -06:00
James Seibel d1c0f7ebb4 Update .editorconfig 2025-11-26 13:55:33 -06:00
James Seibel 5a4ddafbbb Z_std_stream localization 2025-11-26 13:52:17 -06:00
James Seibel 7c40d96f2e DhApiTerrainDataPoint to string 2025-11-26 13:52:07 -06:00
James Seibel b535be16c0 auto merge API world gen data
done to reduce memory use with broken API world generators
2025-11-26 13:51:58 -06:00
James Seibel 22f5608f9a hide the compressor config option 2025-11-24 14:31:42 -06:00
James Seibel a498422843 stream cleanup 3 2025-11-24 14:30:17 -06:00
James Seibel bfd6efb4a4 handle ZStd streams 2025-11-24 14:28:06 -06:00
James Seibel c8c9df3a34 data stream cleanup 2025-11-24 14:15:23 -06:00
James Seibel 3349e5b898 clean up DhDataInputStream 2025-11-24 13:51:48 -06:00
James Seibel ed7511ff6a proof-of-concept block Zstd compression 2025-11-24 12:40:49 -06:00
James Seibel 8516e8f9ab re-enable varint unit tests 2025-11-24 12:38:34 -06:00
James Seibel 47a4d1535f minor variable refactoring 2025-11-22 11:01:53 -06:00
James Seibel 33a55dc7cd Delete EventTimer.java 2025-11-22 09:30:00 -06:00
James Seibel 1b4f9e8942 minor throw/this cleanup 2025-11-22 09:24:31 -06:00
James Seibel 2537c4a259 Rename IBatchGeneratorEvnWrapper 2025-11-22 08:16:30 -06:00
James Seibel b74b6e8068 minor RollingAverage refactor 2025-11-22 08:16:11 -06:00
James Seibel 25979d6a76 Move some exception logic into ExceptionUtil 2025-11-21 06:59:03 -06:00
James Seibel 3f287388d5 re-add biome blending to API config options 2025-11-18 07:42:43 -06:00
James Seibel 72d2ba6aae comment out phantom buffer cleanup log 2025-11-18 07:32:58 -06:00
James Seibel 611ed4e24a add mod note in memory low message 2025-11-18 07:32:48 -06:00
James Seibel eac7a38e73 hopefully reduce the chance of downsampling holes 2025-11-18 07:32:18 -06:00
James Seibel afd7da7763 Optimize full data update processing 2025-11-18 07:16:50 -06:00
James Seibel ff7abb6a18 Fix rendering when Iris isn't installed 2025-11-16 16:11:40 -06:00
James Seibel ca3f5da5de Add unit test for data source merging speed 2025-11-16 15:30:16 -06:00
James Seibel 69012ab7e6 rename and cleanup data source update methods 2025-11-16 15:29:13 -06:00
James Seibel e5e502b4f8 Remove unused/broken FullData LevelMinY 2025-11-15 19:09:16 -06:00
James Seibel 42dc0903de Fix shaders when far clip fading is active 2025-11-15 18:20:47 -06:00
James Seibel 4b20637e47 Fix WorldGen after restarting generation 2025-11-15 12:07:53 -06:00
James Seibel 3257ae8480 replace server tick/world gen tick with a timer 2025-11-15 09:47:15 -06:00
James Seibel a6ddc561a0 up protocol version 12 -> 13 2025-11-15 09:42:38 -06:00
James Seibel 7c82c9eb7b add adj data to DTO en/decoding 2025-11-15 09:42:20 -06:00
James Seibel 3c62e18502 Fix gitlab getter Long/Int cast 2025-11-15 07:55:56 -06:00
James Seibel eea5198fb6 Merge branch 'adjData' 2025-11-14 07:46:37 -06:00
James Seibel 6bfcf36687 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2025-11-13 07:19:19 -06:00
s809 91dffa3c3e Prevent auto-pause while pregen is running 2025-11-11 23:48:13 +05:00
James Seibel e0c143881f Fix compression mode javadoc 2025-11-01 08:34:02 -04:00
90 changed files with 1725 additions and 1228 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ insert_final_newline = false
max_line_length = 1000
tab_width = 4
trim_trailing_whitespace = false
ij_continuation_indent_size = 8
ij_continuation_indent_size = 4
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true
@@ -63,7 +63,17 @@ public enum EDhApiDataCompressionMode
* Write Speed: 15.13 MS / DTO <br>
* Compression ratio: 0.2606 <br>
*/
Z_STD(2),
Z_STD(4),
/**
* Similar to {@link EDhApiDataCompressionMode#Z_STD}
* except slower.
* <br>
* This option is only provided for legacy support when processing old databases.
*/
@Deprecated
@DisallowSelectingViaConfigGui
Z_STD_STREAM(2),
/**
@@ -104,7 +104,7 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
* 2 = blending of 5x5 <br>
* ... <br>
*/
// IDhApiConfigValue<Integer> getBiomeBlending();
IDhApiConfigValue<Integer> getBiomeBlending();
@@ -41,6 +41,7 @@ public interface IDhApiEventInjector extends IDependencyInjector<IDhApiEvent>
* @throws IllegalArgumentException if the implementation object doesn't implement the interface
*/
// Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
// TODO why are we removing the class instead of an instance?
boolean unbind(Class<? extends IDhApiEvent> dependencyInterface, Class<? extends IDhApiEvent> dependencyClassToRemove) throws IllegalArgumentException;
@@ -54,27 +54,12 @@ public class DhApiChunk
// constructors //
//==============//
/**
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
* They should have been in bottom -> top order.
*
* @see DhApiChunk#create(int, int, int, int)
*/
@Deprecated
public DhApiChunk(int chunkPosX, int chunkPosZ, int topYBlockPos, int bottomYBlockPos)
{ this(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
/**
* @since API 3.0.0
*/
/** @since API 3.0.0 */
public static DhApiChunk create(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
{ return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos, false); }
{ return new DhApiChunk(chunkPosX, chunkPosZ, bottomYBlockPos, topYBlockPos); }
/**
* Only visible to internal DH methods
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
*/
private DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos, boolean ignoredParameter)
/** Only visible to internal DH methods */
private DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
{
this.chunkPosX = chunkPosX;
this.chunkPosZ = chunkPosZ;
@@ -29,7 +29,7 @@ import java.util.ArrayList;
* Holds a single datapoint of terrain data.
*
* @author James Seibel
* @version 2024-7-20
* @version 2025-11-15
* @since API 1.0.0
*/
public class DhApiTerrainDataPoint
@@ -47,6 +47,10 @@ public class DhApiTerrainDataPoint
public final int blockLightLevel;
public final int skyLightLevel;
/**
* An unsigned block position of the bottom vertex for this LOD relative to the level's minimum height.
* Should be greater than or equal to 0.
*/
public final int bottomYBlockPos;
public final int topYBlockPos;
@@ -59,28 +63,7 @@ public class DhApiTerrainDataPoint
// constructors //
//==============//
/**
* Deprecated due to the topYBlockPos and bottomYBlockPos variables being put in the wrong order.
* They should have been in bottom -> top order.
*
* @see DhApiTerrainDataPoint#create(byte, int, int, int, int, IDhApiBlockStateWrapper, IDhApiBiomeWrapper)
*/
@Deprecated
public DhApiTerrainDataPoint(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int topYBlockPos, int bottomYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
{
this(detailLevel, blockLightLevel, skyLightLevel,
bottomYBlockPos, topYBlockPos,
blockStateWrapper, biomeWrapper,
false);
}
/**
* @since API 3.0.0
*/
/** @since API 3.0.0 */
public static DhApiTerrainDataPoint create(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
@@ -91,20 +74,15 @@ public class DhApiTerrainDataPoint
return new DhApiTerrainDataPoint(
detailLevel, blockLightLevel, skyLightLevel,
bottomYBlockPos, topYBlockPos,
blockStateWrapper, biomeWrapper,
false);
blockStateWrapper, biomeWrapper);
}
/**
* Only visible to internal DH methods
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
*/
/** Only visible to internal DH methods */
private DhApiTerrainDataPoint(
byte detailLevel,
int blockLightLevel, int skyLightLevel,
int bottomYBlockPos, int topYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper,
boolean ignoredParameter
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper
)
{
this.detailLevel = detailLevel;
@@ -118,4 +96,24 @@ public class DhApiTerrainDataPoint
this.biomeWrapper = biomeWrapper;
}
//================//
// base overrides //
//================//
@Override
public String toString()
{
return "[Block:" + this.blockStateWrapper.getSerialString() +
",Biome:" + this.biomeWrapper.getName() +
",TopY:" + this.topYBlockPos +
",BottomY:" + this.bottomYBlockPos +
",BlockLight:" + this.blockLightLevel +
",SkyLight:" + this.skyLightLevel +
"]";
}
}
@@ -31,7 +31,7 @@ public final class ModInfo
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
public static final int PROTOCOL_VERSION = 12;
public static final int PROTOCOL_VERSION = 13;
public static final String WRAPPER_PACKET_PATH = "message";
/** The internal mod name */
@@ -97,10 +97,9 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks); }
// TODO re-implement
// @Override
// public IDhApiConfigValue<Integer> getBiomeBlending()
// { return new DhApiConfigValue<Integer, Integer>(Quality.lodBiomeBlending); }
@Override
public IDhApiConfigValue<Integer> getBiomeBlending()
{ return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Quality.lodBiomeBlending); }
@@ -277,7 +277,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
if (!getSpecificYCoordinate)
{
// if we aren't look for a specific datapoint, add each datapoint to the return array
returnArray[i] = DhApiTerrainDataPointUtil.createApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint);
returnArray[i] = DhApiTerrainDataPointUtil.createApiDatapoint(levelWrapper.getMinHeight(), mapping, requestedDetailLevel, dataPoint);
}
else
{
@@ -294,7 +294,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
if (bottomY <= requestedY && requestedY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY
{
// this datapoint contains the requested block position, return it
DhApiTerrainDataPoint apiTerrainData = DhApiTerrainDataPointUtil.createApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint);
DhApiTerrainDataPoint apiTerrainData = DhApiTerrainDataPointUtil.createApiDatapoint(levelWrapper.getMinHeight(), mapping, requestedDetailLevel, dataPoint);
return DhApiResult.createSuccess(new DhApiTerrainDataPoint[]{apiTerrainData});
}
}
@@ -345,7 +345,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
@Nullable
IDhApiTerrainDataCache dataCache)
{
return this.raycastLodData(levelWrapper, new Vec3d(rayOriginX, rayOriginY, rayOriginZ), new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ), maxRayBlockLength, dataCache);
return this.raycastLodData(levelWrapper,
new Vec3d(rayOriginX, rayOriginY, rayOriginZ),
new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ),
maxRayBlockLength, dataCache);
}
/**
@@ -363,8 +366,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{
rayDirection.normalize();
int minBlockHeight = levelWrapper.getMinHeight();
int maxBlockHeight = levelWrapper.getMaxHeight();
int minLevelBlockHeight = levelWrapper.getMinHeight();
int maxLevelBlockHeight = levelWrapper.getMaxHeight();
@@ -380,7 +383,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
DhApiRaycastResult closetFoundDataPoint = null;
while (blockPos.y >= minBlockHeight && blockPos.y < maxBlockHeight
while (blockPos.y >= minLevelBlockHeight
&& blockPos.y < maxLevelBlockHeight
&& currentLength <= maxRayBlockLength)
{
// get the LOD columns around this position
@@ -403,7 +407,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{
// does this LOD contain the given Y position?
Vec3i dataPointPos = new Vec3i(columnPos.x, dataPoint.bottomYBlockPos, columnPos.z);
if (exactPos.y >= dataPoint.bottomYBlockPos && exactPos.y <= dataPoint.topYBlockPos)
if (exactPos.y >= dataPoint.bottomYBlockPos
&& exactPos.y <= dataPoint.topYBlockPos)
{
if (closetFoundDataPoint == null)
{
@@ -572,12 +577,16 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
}
// draw raycast position
if (rayCast.success && rayCast.payload != null)
if (rayCast.success
&& rayCast.payload != null)
{
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(
DhSectionPos.encode((byte) 0, rayCast.payload.pos.x, rayCast.payload.pos.z), rayCast.payload.dataPoint.bottomYBlockPos, rayCast.payload.dataPoint.topYBlockPos, -0.1f, Color.RED),
DhSectionPos.encode((byte) 0, rayCast.payload.pos.x, rayCast.payload.pos.z),
rayCast.payload.dataPoint.bottomYBlockPos,
rayCast.payload.dataPoint.topYBlockPos,
-0.1f, Color.RED),
1.0, 0f
)
);
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.core.api.external.methods.data.DhApiTerrainDataRepo;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -338,12 +339,6 @@ public class ClientApi
if (clientWorld != null)
{
clientWorld.clientTick();
// Ignore local world gen, as it's managed by server ticking
if (!(clientWorld instanceof DhClientServerWorld))
{
SharedApi.worldGenTick(clientWorld::worldGenTick);
}
}
}
catch (Exception e)
@@ -570,8 +565,13 @@ public class ClientApi
{
// only fade when DH is rendering
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
// only fade when requested
&& Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() == EDhApiMcRenderingFadeMode.DOUBLE_PASS
&&
(
// only fade when requested
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() == EDhApiMcRenderingFadeMode.DOUBLE_PASS
// or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get()
)
// don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{
@@ -53,30 +53,6 @@ public class ServerApi
//=============//
// tick events //
//=============//
public void serverTickEvent()
{
try
{
IDhServerWorld serverWorld = SharedApi.tryGetDhServerWorld();
if (serverWorld != null)
{
serverWorld.serverTick();
SharedApi.worldGenTick(serverWorld::worldGenTick);
}
}
catch (Exception e)
{
// try catch is necessary to prevent crashing the internal server when an exception is thrown
LOGGER.error("ServerTickEvent error: " + e.getMessage(), e);
}
}
//===============//
// server events //
//===============//
@@ -141,16 +141,6 @@ public class SharedApi
}
}
public static void worldGenTick(Runnable worldGenRunnable)
{
lastWorldGenTickDelta--;
if (lastWorldGenTickDelta <= 0)
{
worldGenRunnable.run();
lastWorldGenTickDelta = 20;
}
}
@Nullable
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; }
@@ -8,6 +8,10 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class ChunkUpdateQueueManager
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -15,6 +19,7 @@ public class ChunkUpdateQueueManager
public final ChunkPosQueue updateQueue;
public final ChunkPosQueue preUpdateQueue;
public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
public int maxSize = 500;
@@ -38,12 +43,18 @@ public class ChunkUpdateQueueManager
// list/set methods //
//==================//
public boolean contains(DhChunkPos pos) { return this.updateQueue.contains(pos) || this.preUpdateQueue.contains(pos); }
public boolean contains(DhChunkPos pos)
{
return this.updateQueue.contains(pos)
|| this.ignoredChunkPosSet.contains(pos)
|| this.preUpdateQueue.contains(pos);
}
public void clear()
{
this.updateQueue.clear();
this.preUpdateQueue.clear();
this.ignoredChunkPosSet.clear();
}
public int getQueuedCount() { return this.updateQueue.getQueuedCount() + this.preUpdateQueue.getQueuedCount(); }
public boolean isEmpty()
@@ -131,6 +142,15 @@ public class ChunkUpdateQueueManager
//=========//
// ignores //
//=========//
public void addPosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.add(chunkPos); }
public void removePosToIgnore(DhChunkPos chunkPos) { this.ignoredChunkPosSet.remove(chunkPos); }
//==================//
// position methods //
//==================//
@@ -103,10 +103,7 @@ public class Config
public static ConfigUiLinkedEntry quickEnableWorldGenerator = new ConfigUiLinkedEntry(Common.WorldGenerator.enableDistantGeneration);
public static ConfigEntry<Boolean> quickShowWorldGenProgress = new ConfigEntry.Builder<Boolean>()
.set(false) // TODO should be set by the underlying world gen progress button, not a static default
.setAppearance(EConfigEntryAppearance.ONLY_IN_GUI)
.build();
public static ConfigUiLinkedEntry quickShowWorldGenProgress = new ConfigUiLinkedEntry(Common.WorldGenerator.showGenerationProgress);
public static ConfigUiLinkedEntry quickLodCloudRendering = new ConfigUiLinkedEntry(Advanced.Graphics.GenericRendering.enableCloudRendering);
@@ -1391,36 +1388,8 @@ public class Config
public static ConfigEntry<EDhApiDataCompressionMode> dataCompression = new ConfigEntry.Builder<EDhApiDataCompressionMode>()
.set(EDhApiDataCompressionMode.Z_STD)
.comment(""
+ "What algorithm should be used to compress new LOD data? \n"
+ "This setting will only affect new or updated LOD data, \n"
+ "any data already generated when this setting is changed will be\n"
+ "unaffected until it needs to be re-written to the database.\n"
+ "\n"
+ EDhApiDataCompressionMode.UNCOMPRESSED + " \n"
+ "Should only be used for testing, is worse in every way vs ["+EDhApiDataCompressionMode.LZ4+"].\n"
+ "Expected Compression Ratio: 1.0\n"
+ "Estimated average DTO read speed: 6.09 milliseconds\n"
+ "Estimated average DTO write speed: 6.01 milliseconds\n"
+ "\n"
+ EDhApiDataCompressionMode.LZ4 + " \n"
+ "A good option if you're CPU limited and have plenty of hard drive space.\n"
+ "Expected Compression Ratio: 0.4513\n"
+ "Estimated average DTO read speed: 3.25 ms\n"
+ "Estimated average DTO write speed: 5.99 ms\n"
+ "\n"
+ EDhApiDataCompressionMode.Z_STD + " \n"
+ "A good option if you're CPU limited and have plenty of hard drive space.\n"
+ "Expected Compression Ratio: 0.2606\n"
+ "Estimated average DTO read speed: 9.31 ms\n"
+ "Estimated average DTO write speed: 15.13 ms\n"
+ "\n"
+ EDhApiDataCompressionMode.LZMA2 + " \n"
+ "Slow but very good compression.\n"
+ "Expected Compression Ratio: 0.2\n"
+ "Estimated average DTO read speed: 13.29 ms\n"
+ "Estimated average DTO write speed: 70.95 ms\n"
+ "")
// only visible via the API since there is no reason to use any compressor except ZStandard as of 2025-11-24
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.build();
public static ConfigEntry<EDhApiWorldCompressionMode> worldCompression = new ConfigEntry.Builder<EDhApiWorldCompressionMode>()
@@ -1443,49 +1412,6 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> recalculateChunkHeightmaps = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "True: Recalculate chunk height maps before chunks can be used by DH.\n"
+ " This can fix problems with worlds created by World Painter or \n"
+ " other external tools where the heightmap format may be incorrect. \n"
+ "False: Assume any height maps handled by Minecraft are correct. \n"
+ "\n"
+ "Fastest: False\n"
+ "Most Compatible: True\n"
+ "")
.build();
public static ConfigEntry<Boolean> pullLightingForPregeneratedChunks = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If true LOD generation for pre-existing chunks will attempt to pull the lighting data \n"
+ "saved in Minecraft's Region files. \n"
+ "If false DH will pull in chunks without lighting and re-light them. \n"
+ " \n"
+ "Setting this to true will result in faster LOD generation \n"
+ "for already generated worlds, but is broken by most lighting mods. \n"
+ " \n"
+ "Set this to false if LODs are black. \n"
+ "")
.build();
public static ConfigEntry<Boolean> assumePreExistingChunksAreFinished = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "When DH pulls in pre-existing chunks it will attempt to \n"
+ "run any missing world generation steps; for example: \n"
+ "if a chunk has the status SURFACE, DH will skip BIOMES \n"
+ "and SURFACE, but will run FEATURES. \n"
+ " \n"
+ "However if for some reason the chunks are malformed \n"
+ "or there's some other issue that causes the status \n"
+ "to be incorrect that can either cause world gen \n"
+ "lock-ups and/or crashes. \n"
+ "If either of those happen try setting this to True. \n"
+ "")
.build();
public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build();
@@ -1527,6 +1453,7 @@ public class Config
+ "How many threads should be used by Distant Horizons? \n"
+ "")
.build();
public static final ConfigEntry<Double> threadRunTimeRatio = new ConfigEntry.Builder<Double>()
.setChatCommandName("threading.threadRunTimeRatio")
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getDefaultRunTimeRatio(), 1.0)
@@ -1540,6 +1467,19 @@ public class Config
"")
.build();
public static final ConfigEntry<Integer> threadPriority = new ConfigEntry.Builder<Integer>()
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // only in file since this requires a MC reboot to change
.setMinDefaultMax(Thread.MIN_PRIORITY, // 1
Thread.NORM_PRIORITY, // 5 (1 higher than C2ME's default priority of 4 which can help reduce issues with Chunky)
Thread.MAX_PRIORITY) // 10
.comment(""
+ "What Java thread priority should DH's primary thread pools run with? \n"
+ "\n"
+ "You probably don't need to change this unless you are also \n"
+ "running C2ME and are seeing thread starvation in either C2ME or DH. \n"
+ "")
.build();
}
@@ -1576,14 +1516,6 @@ public class Config
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerLevel> logWorldGenPerformanceToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.logWorldGenPerformance")
.set(EDhApiLoggerLevel.INFO)
.comment(""
+ "If enabled, the mod will log performance about the world generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerLevel> logWorldGenChunkLoadEventToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.logWorldGenLoadEvent")
.set(EDhApiLoggerLevel.INFO)
@@ -1669,6 +1601,14 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> showSlowWorldGenSettingWarnings = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If enabled, a chat message will be displayed when DH has too many chunks \n"
+ "queued for updating. \n"
+ "")
.build();
public static ConfigEntry<Boolean> showModCompatibilityWarningsOnStartup = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
@@ -1892,7 +1832,6 @@ public class Config
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickShowWorldGenProgressConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
}
catch (Exception e)
{
@@ -1,66 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.config.eventHandlers.presets;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorProgressDisplayLocation;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
public class QuickShowWorldGenProgressConfigEventHandler
{
public static QuickShowWorldGenProgressConfigEventHandler INSTANCE = new QuickShowWorldGenProgressConfigEventHandler();
private final ConfigChangeListener<Boolean> quickChangeListener;
private final ConfigChangeListener<EDhApiDistantGeneratorProgressDisplayLocation> fullChangeListener;
/** private since we only ever need one handler at a time */
private QuickShowWorldGenProgressConfigEventHandler()
{
this.quickChangeListener = new ConfigChangeListener<>(Config.Client.quickShowWorldGenProgress,
(val) ->
{
boolean quickShowProgress = Config.Client.quickShowWorldGenProgress.get();
Config.Common.WorldGenerator.showGenerationProgress.set(
quickShowProgress
? EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY
: EDhApiDistantGeneratorProgressDisplayLocation.DISABLED);
});
this.fullChangeListener = new ConfigChangeListener<>(Config.Common.WorldGenerator.showGenerationProgress,
(val) ->
{
boolean showProgress = Config.Common.WorldGenerator.showGenerationProgress.get() != EDhApiDistantGeneratorProgressDisplayLocation.DISABLED;
Config.Client.quickShowWorldGenProgress.setWithoutFiringEvents(showProgress);
});
}
/**
* Set the UI only config based on what is set in the file. <br>
* This should only be called once.
*/
public void setUiOnlyConfigValues()
{
boolean showProgress = Config.Common.WorldGenerator.showGenerationProgress.get() != EDhApiDistantGeneratorProgressDisplayLocation.DISABLED;
Config.Client.quickShowWorldGenProgress.set(showProgress);
}
}
@@ -268,7 +268,7 @@ public class FullDataPointIdMap
// 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 InterruptedException("[" + FullDataPointIdMap.class.getSimpleName() + "] deserializing interrupted.");
}
@@ -44,6 +44,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -92,8 +93,6 @@ public class FullDataSourceV2
public long lastModifiedUnixDateTime;
public long createdUnixDateTime;
public int levelMinY;
/**
* stores how far each column has been generated should start with {@link EDhApiWorldGenerationStep#EMPTY}
*
@@ -299,9 +298,9 @@ public class FullDataSourceV2
* returns {@link FullDataPointUtil#EMPTY_DATA_POINT} if the given {@link DhBlockPos}
* is outside this data source's boundaries.
*/
public long getDataPointAtBlockPos(DhBlockPos blockPos)
public long getDataPointAtBlockPos(int blockPosX, int blockPosY, int blockPosZ, int levelMinY)
{
DhLodPos requestedPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPos.getX(), blockPos.getZ());
DhLodPos requestedPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ);
// stop if the requested blockPos is outside this datasource
{
@@ -331,6 +330,7 @@ public class FullDataSourceV2
// search for a datapoint that contains the given block y position
int relBlockPosY = blockPosY - levelMinY;
long dataPoint;
for (int i = 0; i < dataColumn.size(); i++)
{
@@ -345,14 +345,13 @@ public class FullDataSourceV2
int requestedY = blockPos.getY();
int bottomY = FullDataPointUtil.getBottomY(dataPoint) + this.levelMinY;
int bottomY = FullDataPointUtil.getBottomY(dataPoint);
int height = FullDataPointUtil.getHeight(dataPoint);
int topY = bottomY + height;
// does this datapoint contain the requested Y position?
if (bottomY <= requestedY
&& requestedY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY
if (bottomY <= relBlockPosY
&& relBlockPosY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY
{
return dataPoint;
}
@@ -367,7 +366,7 @@ public class FullDataSourceV2
// updating //
//==========//
public boolean updateFromChunk(@NotNull FullDataSourceV2 inputDataSource)
public boolean updateFromDataSource(@NotNull FullDataSourceV2 inputDataSource)
{
// don't try updating if the input is empty
if (inputDataSource.mapping.isEmpty())
@@ -470,7 +469,7 @@ public class FullDataSourceV2
return dataChanged;
}
public boolean updateFromSameDetailLevel(FullDataSourceV2 inputDataSource, int[] remappedIds)
private boolean updateFromSameDetailLevel(FullDataSourceV2 inputDataSource, int[] remappedIds)
{
// both data sources should have the same detail level
if (DhSectionPos.getDetailLevel(inputDataSource.pos) != DhSectionPos.getDetailLevel(this.pos))
@@ -486,96 +485,103 @@ public class FullDataSourceV2
for (int z = 0; z < WIDTH; z++)
{
int index = relativePosToIndex(x, z);
LongArrayList inputDataArray = inputDataSource.dataPoints[index];
if (inputDataArray != null)
if (inputDataArray == null)
{
byte thisGenState = this.columnGenerationSteps.getByte(index);
byte inputGenState = inputDataSource.columnGenerationSteps.getByte(index);
continue;
}
// determine if this column should be updated
boolean genStateAllowsUpdating = false;
// if the input is downsampled, we only want to replace empty or downsampled values
if (inputGenState == EDhApiWorldGenerationStep.DOWN_SAMPLED.value
&&
(
thisGenState == EDhApiWorldGenerationStep.EMPTY.value
|| thisGenState == EDhApiWorldGenerationStep.DOWN_SAMPLED.value
))
byte thisGenState = this.columnGenerationSteps.getByte(index);
byte inputGenState = inputDataSource.columnGenerationSteps.getByte(index);
// determine if this column should be updated
boolean genStateAllowsUpdating = false;
// if the input is downsampled, we only want to replace empty or downsampled values
if (inputGenState == EDhApiWorldGenerationStep.DOWN_SAMPLED.value
&&
(
thisGenState == EDhApiWorldGenerationStep.EMPTY.value
|| thisGenState == EDhApiWorldGenerationStep.DOWN_SAMPLED.value
))
{
genStateAllowsUpdating = true;
}
// if the input is any other non-empty value,
// replace anything that is less-complete
else if (inputGenState != EDhApiWorldGenerationStep.EMPTY.value
&& thisGenState <= inputGenState)
{
// don't apply less-complete generation data
genStateAllowsUpdating = true;
}
if (!genStateAllowsUpdating)
{
continue;
}
// check if the data changed
if (this.dataPoints[index] == null)
{
// no data was present previously
this.dataPoints[index] = new LongArrayList(inputDataArray);
dataChanged = true;
}
else if (this.dataPoints[index].size() != inputDataArray.size())
{
// data is present, but the size is different
dataChanged = true;
}
int oldDataHash = 0;
if (!dataChanged)
{
// some old data existed with the same length,
// we'll have to compare the caches
oldDataHash = this.dataPoints[index].hashCode();
}
// copy over the new data
this.dataPoints[index].clear();
this.dataPoints[index].addAll(inputDataArray);
this.remapDataColumn(index, remappedIds);
if (RUN_DATA_ORDER_VALIDATION)
{
throwIfDataColumnInWrongOrder(inputDataSource.pos, this.dataPoints[index]);
}
if (!dataChanged)
{
// check if the identical length data column hashes are the same
// hashes need to be compared after the ID's have been remapped otherwise the ID's won't match even if the data is the same
if (oldDataHash != this.dataPoints[index].hashCode())
{
genStateAllowsUpdating = true;
}
// if the input is any other non-empty value,
// replace anything that is less-complete
else if (inputGenState != EDhApiWorldGenerationStep.EMPTY.value
&& thisGenState <= inputGenState)
{
// don't apply less-complete generation data
genStateAllowsUpdating = true;
}
if (genStateAllowsUpdating)
{
// check if the data changed
if (this.dataPoints[index] == null)
{
// no data was present previously
this.dataPoints[index] = new LongArrayList(inputDataArray);
dataChanged = true;
}
else if (this.dataPoints[index].size() != inputDataArray.size())
{
// data is present, but the size is different
dataChanged = true;
}
int oldDataHash = 0;
if (!dataChanged)
{
// some old data existed with the same length,
// we'll have to compare the caches
oldDataHash = this.dataPoints[index].hashCode();
}
// copy over the new data
this.dataPoints[index].clear();
this.dataPoints[index].addAll(inputDataArray);
this.remapDataColumn(index, remappedIds);
if (RUN_DATA_ORDER_VALIDATION)
{
throwIfDataColumnInWrongOrder(inputDataSource.pos, this.dataPoints[index]);
}
if (!dataChanged)
{
// check if the identical length data column hashes are the same
// hashes need to be compared after the ID's have been remapped otherwise the ID's won't match even if the data is the same
if (oldDataHash != this.dataPoints[index].hashCode())
{
// the hashes are different, something was changed
dataChanged = true;
}
}
this.columnGenerationSteps.set(index, inputGenState);
// always overwrite the compression mode since we're replacing this column
this.columnWorldCompressionMode.set(index, inputDataSource.columnWorldCompressionMode.getByte(index));
this.isEmpty = false;
// the hashes are different, something was changed
dataChanged = true;
}
}
this.columnGenerationSteps.set(index, inputGenState);
// always overwrite the compression mode since we're replacing this column
this.columnWorldCompressionMode.set(index, inputDataSource.columnWorldCompressionMode.getByte(index));
this.isEmpty = false;
}
}
return dataChanged;
}
public boolean updateFromOneBelowDetailLevel(FullDataSourceV2 inputDataSource, int[] remappedIds)
private boolean updateFromOneBelowDetailLevel(FullDataSourceV2 inputDataSource, int[] remappedIds)
{
if (DhSectionPos.getDetailLevel(inputDataSource.pos) + 1 != DhSectionPos.getDetailLevel(this.pos))
{
@@ -711,176 +717,221 @@ public class FullDataSourceV2
{
LongArrayList newColumnList = new LongArrayList();
// special numbers:
// -2 = the column's height hasn't been determined yet
// -1 = we've reached the end of the column
int[] currentDatapointIndex = new int[] { -2, -2, -2, -2 };
//=========================//
// get the 4 input columns //
//=========================//
LongArrayList[] inputColumns = new LongArrayList[4];
int colIndex = 0;
for (int inputX = x; inputX < x + 2; inputX++)
{
for (int inputZ = z; inputZ < z + 2; inputZ++, colIndex++)
{
inputColumns[colIndex] = inputDataSource.dataPoints[relativePosToIndex(inputX, inputZ)];
if (inputColumns[colIndex] != null
&& RUN_DATA_ORDER_VALIDATION)
{
throwIfDataColumnInWrongOrder(inputDataSource.pos, inputColumns[colIndex]);
}
}
}
//========================================//
// find all y levels where changes happen //
//========================================//
IntArrayList yTransitions = new IntArrayList();
for (int i = 0; i < 4; i++)
{
if (inputColumns[i] == null
|| inputColumns[i].isEmpty())
{
continue;
}
for (int j = 0; j < inputColumns[i].size(); j++)
{
long datapoint = inputColumns[i].getLong(j);
int minY = FullDataPointUtil.getBottomY(datapoint);
int maxY = minY + FullDataPointUtil.getHeight(datapoint);
if (!yTransitions.contains(minY))
{
yTransitions.add(minY);
}
if (!yTransitions.contains(maxY))
{
yTransitions.add(maxY);
}
}
}
// can happen if the columns are empty
if (yTransitions.isEmpty())
{
return newColumnList;
}
// sort the transitions from bottom to top // TODO
yTransitions.sort(null);
// create index trackers for each column,
// starting with the top-most datapoint
int[] currentIndices = new int[4];
for (int i = 0; i < 4; i++)
{
if (inputColumns[i] != null
&& !inputColumns[i].isEmpty())
{
currentIndices[i] = inputColumns[i].size() - 1;
}
else
{
currentIndices[i] = -1;
}
}
//=======================//
// process each Y change //
//=======================//
int lastId = 0;
byte lastBlockLight = 0;
byte lastSkyLight = 0;
int height = 0;
int minY = 0;
int currentMinY = yTransitions.getInt(0);
int accumulatedHeight = 0;
// these arrays will be reused quite often, so re-using them helps reduce some GC pressure
long[] datapointsForYSlice = new long[4];
int[] mergeIds = new int[4];
int[] mergeBlockLights = new int[4];
int[] mergeSkyLights = new int[4];
for (int blockY = 0; blockY < RenderDataPointUtil.MAX_WORLD_Y_SIZE; blockY++, height++)
for (int yIndex = 0; yIndex < yTransitions.size() - 1; yIndex++)
{
// if each column has reached the end of their data, nothing more needs to be done
if (currentDatapointIndex[0] == -1
&& currentDatapointIndex[1] == -1
&& currentDatapointIndex[2] == -1
&& currentDatapointIndex[3] == -1
)
{
break;
}
// scary double loop but,
// this will only ever loop 4 times,
// once for each of the 4 input columns
Arrays.fill(datapointsForYSlice, 0L);
int colIndex = 0;
for (int inputX = x; inputX < x + 2; inputX++)
{
for (int inputZ = z; inputZ < z + 2; inputZ++, colIndex++)
{
// TODO throw an assertion if the column isn't in top-down order or just fix it...
LongArrayList inputDataArray = inputDataSource.dataPoints[relativePosToIndex(inputX, inputZ)];
if (inputDataArray == null || inputDataArray.size() == 0)
{
currentDatapointIndex[colIndex] = -1;
continue;
}
// determine the last index (the lowest data point) for each column
if (currentDatapointIndex[colIndex] == -2)
{
currentDatapointIndex[colIndex] = inputDataArray.size() - 1;
if (RUN_DATA_ORDER_VALIDATION)
{
throwIfDataColumnInWrongOrder(inputDataSource.pos, inputDataArray);
}
}
int dataPointIndex = currentDatapointIndex[colIndex];
if (dataPointIndex == -1)
{
// went over the end
continue;
}
long datapoint = inputDataArray.getLong(dataPointIndex);
int datapointMinY = FullDataPointUtil.getBottomY(datapoint);
int numbOfBlocksTall = FullDataPointUtil.getHeight(datapoint);
int datapointMaxY = (datapointMinY + numbOfBlocksTall);
// check if y position is inside this datapoint
if (blockY < datapointMinY)
{
// this y-slice is below this datapoint, nothing can be added
continue;
}
else if (blockY >= datapointMaxY)
{
// this y-slice is above the current datapoint,
// try the next data point
int newDatapointIndex = currentDatapointIndex[colIndex] - 1;
if (newDatapointIndex < 0)
{
// went to far, no additional data present
newDatapointIndex = -1;
}
currentDatapointIndex[colIndex] = newDatapointIndex;
// try again with the next data point
inputZ--;
colIndex--;
continue;
}
datapointsForYSlice[colIndex] = datapoint;
}
}
int sliceMinY = yTransitions.getInt(yIndex);
int sliceMaxY = yTransitions.getInt(yIndex + 1);
int sliceHeight = sliceMaxY - sliceMinY;
// Sample at the midpoint of this slice
int sampleY = sliceMinY + (sliceHeight / 2);
// Get data from each column at this Y level
Arrays.fill(mergeIds, 0);
Arrays.fill(mergeBlockLights, 0);
Arrays.fill(mergeSkyLights, 0);
for (int i = 0; i < 4; i++)
{
mergeIds[i] = FullDataPointUtil.getId(datapointsForYSlice[i]);
mergeBlockLights[i] = FullDataPointUtil.getBlockLight(datapointsForYSlice[i]);
mergeSkyLights[i] = FullDataPointUtil.getSkyLight(datapointsForYSlice[i]);
// skip columns that are empty or where we have already reached the bottom
if (currentIndices[i] == -1)
{
continue;
}
LongArrayList column = inputColumns[i];
if (column == null)
{
continue;
}
// move the index down if we've passed the current datapoint
while (currentIndices[i] >= 0)
{
long datapoint = column.getLong(currentIndices[i]);
int inputMinY = FullDataPointUtil.getBottomY(datapoint);
int inputMaxY = inputMinY + FullDataPointUtil.getHeight(datapoint);
if (sampleY >= inputMaxY)
{
// Sample point is above this datapoint, move to next (lower) one
currentIndices[i]--;
}
else if (sampleY >= inputMinY
&& sampleY < inputMaxY)
{
// Sample point is within this datapoint
mergeIds[i] = FullDataPointUtil.getId(datapoint);
mergeBlockLights[i] = FullDataPointUtil.getBlockLight(datapoint);
mergeSkyLights[i] = FullDataPointUtil.getSkyLight(datapoint);
break;
}
else
{
// Sample point is below this datapoint
break;
}
}
}
// determine the most common values for this slice
int id = determineMostValueInColumnSlice(mergeIds, inputDataSource.mapping);
// Determine merged values for this slice
int id = determineMostCommonValueInColumnSlice(mergeIds, inputDataSource.mapping);
byte blockLight = (byte) determineAverageValueInColumnSlice(mergeBlockLights);
byte skyLight = (byte) determineAverageValueInColumnSlice(mergeSkyLights);
// if this slice is different then the last one, create a new one
if (id != lastId
// block and sky light might not be necessary
|| blockLight != lastBlockLight
|| skyLight != lastSkyLight)
// Check if we need to start a new datapoint
if (accumulatedHeight == 0)
{
if (height != 0)
{
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 ["+DhSectionPos.toString(inputDataSource.pos)+"] at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
}
}
// first datapoint
lastId = id;
lastBlockLight = blockLight;
lastSkyLight = skyLight;
height = 0;
minY = blockY;
currentMinY = sliceMinY;
accumulatedHeight = sliceHeight;
}
else if (id != lastId
|| blockLight != lastBlockLight
|| skyLight != lastSkyLight)
{
// the data changed, create a new datapoint
try
{
long datapoint = FullDataPointUtil.encode(lastId, accumulatedHeight, currentMinY, lastBlockLight, lastSkyLight);
newColumnList.add(datapoint);
}
catch (DataCorruptedException e)
{
LOGGER.warn("Skipping corrupt datapoint for pos ["+DhSectionPos.toString(inputDataSource.pos)+"] at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+accumulatedHeight+"], minY["+currentMinY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
}
// start the next datapoint
lastId = id;
lastBlockLight = blockLight;
lastSkyLight = skyLight;
currentMinY = sliceMinY;
accumulatedHeight = sliceHeight;
}
else
{
// this datapoint is the same as the last one,
// just extend it's height
accumulatedHeight += sliceHeight;
}
}
// add the last slice if present
if (height != 0)
// add the final datapoint if needed
if (accumulatedHeight > 0)
{
try
{
newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight));
newColumnList.add(FullDataPointUtil.encode(lastId, accumulatedHeight, currentMinY, lastBlockLight, lastSkyLight));
}
catch (DataCorruptedException e)
{
// shouldn't happen, (especially if validation is disabled) but just in case
LOGGER.warn("Skipping corrupt datapoint for pos ["+DhSectionPos.toString(inputDataSource.pos)+"] at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
LOGGER.warn("Skipping corrupt datapoint for pos ["+DhSectionPos.toString(inputDataSource.pos)+"] at relative position ["+x+","+z+"] with data: ID["+lastId+"], Height["+accumulatedHeight+"], minY["+currentMinY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
}
}
// flip the array if necessary
// TODO why is this sometimes necessary? What did I (James) screw up that causes the mergedInputDataArray
// to sometimes be in a different order? Is it potentially related to what detail level is coming in?
// confirm the array is in the correct order
ensureDataColumnOrder(newColumnList);
return newColumnList;
@@ -898,23 +949,8 @@ public class FullDataSourceV2
dataColumn.set(i, FullDataPointUtil.remap(remappedIds, dataColumn.getLong(i)));
}
}
private static boolean areDataColumnsDifferent(long[] oldDataArray, long[] newDataArray)
{
if (oldDataArray == null || oldDataArray.length != newDataArray.length)
{
// new data was added/removed
return true;
}
else
{
// check if the new column data is different
int oldArrayHash = Arrays.hashCode(oldDataArray);
int newArrayHash = Arrays.hashCode(newDataArray);
return (newArrayHash != oldArrayHash);
}
}
/** @param mapping can be included to ignore air ID's, otherwise all 4 values are treated equally */
private static int determineMostValueInColumnSlice(int[] sliceArray, @Nullable FullDataPointIdMap mapping)
private static int determineMostCommonValueInColumnSlice(int[] sliceArray, @Nullable FullDataPointIdMap mapping)
{
if (RUN_UPDATE_DEV_VALIDATION)
{
@@ -958,7 +994,7 @@ public class FullDataSourceV2
}
}
// return the most common occurance
// return the most common occurrence
int maxCount = Math.max(count0, Math.max(count1, Math.max(count2, count3)));
if (maxCount == count0)
// if the max count is 1 then we'll just go with the first column
@@ -1271,13 +1307,13 @@ public class FullDataSourceV2
{
try
{
LodDataBuilder.correctDataColumnOrder(columnDataPoints);
LodDataBuilder.putListInTopDownOrder(columnDataPoints);
if (this.runApiChunkValidation)
{
LodDataBuilder.validateOrThrowApiDataColumn(columnDataPoints);
}
LongArrayList packedDataPoints = LodDataBuilder.convertApiDataPointListToPackedLongArray(columnDataPoints, this, 0);
LongArrayList packedDataPoints = LodDataBuilder.convertApiDataPointListToPackedLongArray(columnDataPoints, this, 0, true);
// TODO there should be an "unknown" compression and generation step, or be defined via the datapoints
this.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.SURFACE, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
@@ -1300,7 +1336,7 @@ public class FullDataSourceV2
{
long datapoint = dataColumn.getLong(i);
DhApiTerrainDataPoint apiDataPoint = DhApiTerrainDataPointUtil.createApiDatapoint(this.levelMinY, this.mapping, DhSectionPos.getDetailLevel(this.pos), datapoint);
DhApiTerrainDataPoint apiDataPoint = DhApiTerrainDataPointUtil.createApiDatapoint(0, this.mapping, DhSectionPos.getDetailLevel(this.pos), datapoint);
apiList.add(apiDataPoint);
}
@@ -157,8 +157,8 @@ public final class BufferQuad
return false;
// make sure these quads share the same perpendicular axis
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y) ||
(mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown && this.x != quad.x))
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y)
|| (mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown && this.x != quad.x))
{
return false;
}
@@ -24,7 +24,6 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
@@ -46,7 +45,6 @@ public class ColumnBox
//=========//
// builder //
//=========//
@@ -249,10 +247,10 @@ public class ColumnBox
}
private static void makeAdjVerticalQuad(
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout,
@NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight)
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout,
@NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight)
{
// pooled arrays
LongArrayList segments = phantomArrayCheckout.getLongArray(0, 0);
@@ -311,9 +309,10 @@ public class ColumnBox
long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA;
boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint);
boolean adjTransparent = !adjOverVoid
&& RenderDataPointUtil.getAlpha(adjPoint) < 255
&& transparencyEnabled;
boolean adjTransparent =
!adjOverVoid
&& RenderDataPointUtil.getAlpha(adjPoint) < 255
&& transparencyEnabled;
byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint);
byte lightToApply;
@@ -323,14 +322,14 @@ public class ColumnBox
// Adjacent is opaque
boolean adjacentCoversThis =
!adjacentIsSameDetailLevel
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
&&
(
(x == 0 && direction == EDhDirection.WEST)
|| (z == 0 && direction == EDhDirection.NORTH)
|| (x == 256 && direction == EDhDirection.EAST)
|| (z == 256 && direction == EDhDirection.SOUTH)
);
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
&&
(
(x == 0 && direction == EDhDirection.WEST)
|| (z == 0 && direction == EDhDirection.NORTH)
|| (x == 256 && direction == EDhDirection.EAST)
|| (z == 256 && direction == EDhDirection.SOUTH)
);
lightToApply = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED;
}
@@ -363,10 +362,10 @@ public class ColumnBox
{
long segment = segments.getLong(i);
tryAddVerticalFaceWithSkyLightToBuilder(
builder, direction,
x, z, horizontalWidth,
color, irisBlockMaterialId, blockLight,
YSegmentUtil.getSkyLight(segment), inputTransparent, YSegmentUtil.getEndY(segment), YSegmentUtil.getStartY(segment)
builder, direction,
x, z, horizontalWidth,
color, irisBlockMaterialId, blockLight,
YSegmentUtil.getSkyLight(segment), inputTransparent, YSegmentUtil.getEndY(segment), YSegmentUtil.getStartY(segment)
);
}
}
@@ -112,7 +112,8 @@ public class LodBufferContainer implements AutoCloseable
try
{
// skip this event if requested
if (Thread.interrupted() || this.uploadFuture.isCancelled())
if (Thread.interrupted()
|| this.uploadFuture.isCancelled())
{
throw new InterruptedException();
}
@@ -148,8 +148,18 @@ public class LodQuadBuilder
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!");
}
ArrayList<BufferQuad> quadList;
if (this.doTransparency && ColorUtil.getAlpha(color) < 255)
{
quadList = this.transparentQuads[dir.ordinal()];
}
else
{
quadList = this.opaqueQuads[dir.ordinal()];
}
BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir);
ArrayList<BufferQuad> quadList = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ? this.transparentQuads[dir.ordinal()] : this.opaqueQuads[dir.ordinal()];
if (!quadList.isEmpty()
&& (
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
@@ -31,11 +31,9 @@ import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable;
import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.util.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
@@ -54,11 +52,11 @@ public class FullDataToRenderDataTransformer
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final LongOpenHashSet brokenPos = new LongOpenHashSet();
private static final LongOpenHashSet BROKEN_POS_SET = new LongOpenHashSet();
private static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Data Transformer");
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Data Transformer");
private static HashSet<IBlockStateWrapper> snowLayerBlockStates = null;
@@ -198,6 +196,16 @@ public class FullDataToRenderDataTransformer
HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper);
HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper);
// build snow block cache if needed
if (snowLayerBlockStates == null)
{
snowLayerBlockStates = new HashSet<>();
// ignore snow layers 1-3, everything above should be considered a full block
snowLayerBlockStates.add(WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault("minecraft:snow_STATE_{layers:1}", levelWrapper));
snowLayerBlockStates.add(WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault("minecraft:snow_STATE_{layers:2}", levelWrapper));
snowLayerBlockStates.add(WRAPPER_FACTORY.deserializeBlockStateWrapperOrGetDefault("minecraft:snow_STATE_{layers:3}", levelWrapper));
}
int caveCullingMaxY = Config.Client.Advanced.Graphics.Culling.caveCullingHeight.get() - levelWrapper.getMinHeight();
boolean caveCullingEnabled =
Config.Client.Advanced.Graphics.Culling.enableCaveCulling.get()
@@ -252,9 +260,9 @@ public class FullDataToRenderDataTransformer
}
catch (IndexOutOfBoundsException e)
{
if (!brokenPos.contains(fullDataMapping.getPos()))
if (!BROKEN_POS_SET.contains(fullDataMapping.getPos()))
{
brokenPos.add(fullDataMapping.getPos());
BROKEN_POS_SET.add(fullDataMapping.getPos());
String levelId = levelWrapper.getDhIdentifier();
LOGGER.warn("Unable to get data point with id ["+id+"] " +
"(Max possible ID: ["+fullDataMapping.getMaxValidId()+"]) " +
@@ -324,10 +332,26 @@ public class FullDataToRenderDataTransformer
// non-solid block check //
//=======================//
if (ignoreNonCollidingBlocks
&& !block.isSolid()
&& !block.isLiquid()
&& block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
boolean ignoreNonSolidBlock =
ignoreNonCollidingBlocks
&& !block.isSolid()
&& !block.isLiquid()
&& block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE;
// merge snow into the block below it
if (snowLayerBlockStates.contains(block))
{
// sometimes a snow datapoint will be multiple blocks tall,
// in that case we just want to drop the top by 1
blockHeight -= 1;
if (blockHeight == 0)
{
// this snow block was entirely removed, just color the block below it
ignoreNonSolidBlock = true;
}
}
if (ignoreNonSolidBlock)
{
if (colorBelowWithAvoidedBlocks)
{
@@ -74,7 +74,7 @@ public class LodDataBuilder
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
dataSource.isEmpty = false;
dataSource.isEmpty = false; // this will be set to "true" if any blocks are found
// chunk updates always propagate up
dataSource.applyToParent = true;
@@ -244,6 +244,15 @@ public class LodDataBuilder
blockLight = newBlockLight;
skyLight = newSkyLight;
lastY = y;
// mark the data source as non-empty if we find any non-air blocks
if (dataSource.isEmpty
&& newBlockState != null
&& !newBlockState.isAir())
{
dataSource.isEmpty = false;
}
}
}
@@ -261,7 +270,6 @@ public class LodDataBuilder
return null;
}
LodUtil.assertTrue(!dataSource.isEmpty);
return dataSource;
}
@@ -289,13 +297,29 @@ public class LodDataBuilder
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{
List<DhApiTerrainDataPoint> columnDataPoints = apiChunk.getDataPoints(relBlockX, relBlockZ);
LodDataBuilder.correctDataColumnOrder(columnDataPoints);
// mark the data source as non-empty if we find any non-air blocks
if (dataSource.isEmpty)
{
for (int i = 0; i < columnDataPoints.size(); i++)
{
DhApiTerrainDataPoint dataPoint = columnDataPoints.get(i);
if (dataPoint.blockStateWrapper != null
&& !dataPoint.blockStateWrapper.isAir())
{
dataSource.isEmpty = false;
break;
}
}
}
LodDataBuilder.putListInTopDownOrder(columnDataPoints);
if (runAdditionalValidation)
{
validateOrThrowApiDataColumn(columnDataPoints);
}
LongArrayList packedDataPoints = convertApiDataPointListToPackedLongArray(columnDataPoints, dataSource, apiChunk.bottomYBlockPos);
LongArrayList packedDataPoints = convertApiDataPointListToPackedLongArray(columnDataPoints, dataSource, apiChunk.bottomYBlockPos, runAdditionalValidation);
// TODO add the ability for API users to define a different compression mode
// or add a "unkown" compression mode
@@ -303,7 +327,6 @@ public class LodDataBuilder
packedDataPoints,
relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ,
EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
dataSource.isEmpty = false;
}
}
return dataSource;
@@ -317,41 +340,97 @@ public class LodDataBuilder
/** @see FullDataPointUtil */
public static LongArrayList convertApiDataPointListToPackedLongArray(
@Nullable List<DhApiTerrainDataPoint> columnDataPoints, FullDataSourceV2 dataSource,
int bottomYBlockPos) throws DataCorruptedException
@Nullable List<DhApiTerrainDataPoint> topDownColumnDataPoints, FullDataSourceV2 dataSource,
int bottomYBlockPos, boolean runAdditionalValidation) throws DataCorruptedException
{
// this null check does 2 nice things at the same time:
// if columnDataPoints is null,
// then packedDataPoints will be of length 0
// AND the below loop won't run.
int size = (columnDataPoints != null) ? columnDataPoints.size() : 0;
// TODO make missing air LODs
// TODO merge duplicate datapoints
LongArrayList packedDataPoints = new LongArrayList(new long[size]);
for (int index = 0; index < size; index++)
if (topDownColumnDataPoints == null
|| topDownColumnDataPoints.size() == 0)
{
DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index);
return new LongArrayList(0);
}
// array to store data
int size = topDownColumnDataPoints.size();
LongArrayList packedDataPoints = new LongArrayList(size);
packedDataPoints.clear();
if (runAdditionalValidation)
{
// check for missing data
int lastTopY = Integer.MAX_VALUE;
for (int i = 0; i < size; i++)
{
DhApiTerrainDataPoint apiDataPoint = topDownColumnDataPoints.get(i);
if (lastTopY != apiDataPoint.topYBlockPos
// the first index won't have a lastTopY value
&& i != 0)
{
throw new DataCorruptedException("LOD data has a gap between ["+lastTopY+"] and ["+apiDataPoint.bottomYBlockPos+"]. Empty areas should be filled with air datapoints so light propagates correctly.");
}
lastTopY = apiDataPoint.bottomYBlockPos;
}
}
// go through data from top down
long lastDataPoint = FullDataPointUtil.EMPTY_DATA_POINT;
for (int i = 0; i < size; i++)
{
DhApiTerrainDataPoint apiDataPoint = topDownColumnDataPoints.get(i);
int id = dataSource.mapping.addIfNotPresentAndGetId(
(IBiomeWrapper) (dataPoint.biomeWrapper),
(IBlockStateWrapper) (dataPoint.blockStateWrapper)
int thisId = dataSource.mapping.addIfNotPresentAndGetId(
(IBiomeWrapper) (apiDataPoint.biomeWrapper),
(IBlockStateWrapper) (apiDataPoint.blockStateWrapper)
);
int thisHeight = (apiDataPoint.topYBlockPos - apiDataPoint.bottomYBlockPos);
packedDataPoints.set(index, FullDataPointUtil.encode(
id,
dataPoint.topYBlockPos - dataPoint.bottomYBlockPos,
dataPoint.bottomYBlockPos - bottomYBlockPos,
(byte) (dataPoint.blockLightLevel),
(byte) (dataPoint.skyLightLevel)
));
int lastId = FullDataPointUtil.getId(lastDataPoint);
byte lastBlockLight = (byte)FullDataPointUtil.getBlockLight(lastDataPoint);
byte lastSkyLight = (byte)FullDataPointUtil.getSkyLight(lastDataPoint);
// if the ID and light are the same, merge the height
if (thisId == lastId
&& apiDataPoint.blockLightLevel == lastBlockLight
&& apiDataPoint.skyLightLevel == lastSkyLight
// the first index should always be added to the list
&& i != 0 )
{
// add adjacent height
int lastHeight = FullDataPointUtil.getHeight(lastDataPoint);
int newHeight = (lastHeight + thisHeight);
lastDataPoint = FullDataPointUtil.setHeight(lastDataPoint, newHeight);
// subtract bottom Y
int lastBottomY = FullDataPointUtil.getBottomY(lastDataPoint);
int newBottomY = lastBottomY - thisHeight;
lastDataPoint = FullDataPointUtil.setBottomY(lastDataPoint, newBottomY);
packedDataPoints.set(packedDataPoints.size()-1, lastDataPoint);
}
else
{
// data changed, create a new datapoint
long dataPoint = FullDataPointUtil.encode(
thisId,
thisHeight,
apiDataPoint.bottomYBlockPos - bottomYBlockPos,
(byte) (apiDataPoint.blockLightLevel),
(byte) (apiDataPoint.skyLightLevel)
);
lastDataPoint = dataPoint;
packedDataPoints.add(dataPoint);
}
}
return packedDataPoints;
}
/** also corrects the order if it's backwards */
public static void correctDataColumnOrder(List<DhApiTerrainDataPoint> dataPoints)
public static void putListInTopDownOrder(List<DhApiTerrainDataPoint> dataPoints)
{
// order doesn't need to be checked if there is 0 or 1 items
if (dataPoints.size() > 1)
@@ -104,7 +104,7 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
}
// write the new data into memory
memoryDataSource.updateFromChunk(inputDataSource);
memoryDataSource.updateFromDataSource(inputDataSource);
// keep track of when the last time we saved something was
pair.updateLastWrittenTimestamp();
}
@@ -47,6 +47,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@@ -80,8 +82,8 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// constructor //
//=============//
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) { super(level, saveStructure); }
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride)
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException { super(level, saveStructure); }
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException
{
super(level, saveStructure, saveDirOverride);
@@ -24,20 +24,22 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.WorldGenModule;
import com.seibel.distanthorizons.core.level.LodRequestModule;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Only handles {@link SyncOnLoadRequestQueue} requests (IE updating existing LODs based on a timestamp).
* Missing data is handled by {@link WorldGenModule} and {@link RemoteWorldRetrievalQueue}.
* Missing data is handled by {@link LodRequestModule} and {@link RemoteWorldRetrievalQueue}.
*/
public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvider
{
@@ -58,7 +60,8 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide
public RemoteFullDataSourceProvider(
IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride,
@Nullable SyncOnLoadRequestQueue syncOnLoadRequestQueue)
@Nullable SyncOnLoadRequestQueue syncOnLoadRequestQueue
) throws SQLException, IOException
{
super(level, saveStructure, saveDirOverride);
this.syncOnLoadRequestQueue = syncOnLoadRequestQueue;
@@ -43,7 +43,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
// constructor //
//=============//
public FullDataSourceProviderV1(TDhLevel level, File saveDir)
public FullDataSourceProviderV1(TDhLevel level, File saveDir) throws SQLException, IOException
{
this.level = level;
this.saveDir = saveDir;
@@ -52,7 +52,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
LOGGER.warn("Unable to create full data folder, file saving may fail.");
}
this.repo = this.createRepo();
this.repo = new FullDataSourceV1Repo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, new File(this.saveDir.getPath() + File.separator + ISaveStructure.DATABASE_NAME));
}
@@ -61,21 +61,6 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
// abstract methods //
//==================//
/** When this is called the parent folders should be created */
protected FullDataSourceV1Repo createRepo()
{
try
{
return new FullDataSourceV1Repo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, new File(this.saveDir.getPath() + File.separator + ISaveStructure.DATABASE_NAME));
}
catch (SQLException e)
{
// should only happen if there is an issue with the database (it's locked or can't be created if missing)
// or the database update failed
throw new RuntimeException(e);
}
}
protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException
{
FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos);
@@ -14,6 +14,8 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
@@ -62,7 +64,8 @@ public class DataMigratorV1 implements IDebugRenderable, AutoCloseable
public DataMigratorV1(
FullDataUpdaterV2 dataUpdater,
IDhLevel level, String levelId, File saveDir)
IDhLevel level, String levelId, File saveDir
) throws SQLException, IOException
{
this.dataUpdater = dataUpdater;
this.saveDir = saveDir;
@@ -96,11 +96,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
// constructor //
//=============//
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure) { this(level, saveStructure, null); }
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride)
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException { this(level, saveStructure, null); }
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException
{
this.saveDir = (saveDirOverride == null) ? saveStructure.getSaveFolder(level.getLevelWrapper()) : saveDirOverride;
this.repo = this.createRepo();
this.repo = new FullDataSourceV2Repo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, new File(this.saveDir.getPath() + File.separator + ISaveStructure.DATABASE_NAME));
this.level = level;
this.levelId = this.level.getLevelWrapper().getDhIdentifier();
@@ -112,19 +112,6 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
}
private FullDataSourceV2Repo createRepo()
{
try
{
return new FullDataSourceV2Repo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, new File(this.saveDir.getPath() + File.separator + ISaveStructure.DATABASE_NAME));
}
catch (SQLException e)
{
// should only happen if there is an issue with the database (it's locked or the folder path is missing)
// or the database update failed
throw new RuntimeException(e);
}
}
@@ -134,14 +121,14 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
public void addDataSourceUpdateListener(IDataSourceUpdateListenerFunc<FullDataSourceV2> listener)
{
synchronized (this.dataUpdater)
synchronized (this.dataUpdater.dateSourceUpdateListeners)
{
this.dataUpdater.dateSourceUpdateListeners.add(listener);
}
}
public void removeDataSourceUpdateListener(IDataSourceUpdateListenerFunc<FullDataSourceV2> listener)
{
synchronized (this.dataUpdater)
synchronized (this.dataUpdater.dateSourceUpdateListeners)
{
this.dataUpdater.dateSourceUpdateListeners.add(listener);
}
@@ -239,7 +226,27 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
catch (InterruptedException ignore) { }
catch (IOException e)
{
LOGGER.warn("File read Error for pos ["+DhSectionPos.toString(pos)+"], error: "+e.getMessage(), e);
String message = e.getMessage();
if (CORRUPT_DATA_ERRORS_LOGGED.add(message))
{
LOGGER.warn("File read Error for pos [" + DhSectionPos.toString(pos) + "], this error message will only be logged once, error: [" + message + "].", e);
}
}
catch (IllegalStateException e)
{
String message = e.getMessage();
if (CORRUPT_DATA_ERRORS_LOGGED.add(message))
{
LOGGER.warn("Incorrectly formatted data for: [" + DhSectionPos.toString(pos) + "], this error message will only be logged once, error: [" + message + "].", e);
}
}
catch (Exception e)
{
String message = e.getMessage();
if (CORRUPT_DATA_ERRORS_LOGGED.add(message))
{
LOGGER.warn("Unexpected error getting: [" + DhSectionPos.toString(pos) + "], this error message will only be logged once, error: [" + message + "].", e);
}
}
// an error occurred
@@ -82,6 +82,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
}
//================//
// parent updates //
//================//
@@ -184,7 +185,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
parentLocked = true;
this.dataUpdater.lockedPosSet.add(parentUpdatePos);
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos))
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos)) // TODO can we cache anything in memory to speed up the propagation process? Compression/Disk IO is by far the slowest part of this process
{
// will return null if the file handler is shutting down
if (parentDataSource != null)
@@ -203,7 +204,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
// can return null when the file handler is being shut down
if (childDataSource != null)
{
parentDataSource.updateFromChunk(childDataSource);
parentDataSource.updateFromDataSource(childDataSource);
}
}
}
@@ -213,6 +214,8 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
}
finally
{
this.provider.repo.setApplyToParent(childPos, false);
childReadLock.unlock();
this.dataUpdater.lockedPosSet.remove(childPos);
}
@@ -225,10 +228,6 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
}
this.dataUpdater.updateDataSource(parentDataSource, false);
for (Long childPos : updatePosByParentPos.get(parentUpdatePos))
{
this.provider.repo.setApplyToParent(childPos, false);
}
}
}
}
@@ -321,7 +320,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
// will return null if the file handler is shutting down
if (childDataSource != null)
{
childDataSource.updateFromChunk(parentDataSource);
childDataSource.updateFromDataSource(parentDataSource);
// don't propagate child updates past the bottom of the tree
if (DhSectionPos.getDetailLevel(childPos) != DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL)
@@ -339,12 +338,12 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
}
finally
{
this.provider.repo.setApplyToChild(parentUpdatePos, false);
childWriteLock.unlock();
this.dataUpdater.lockedPosSet.remove(childPos);
}
}
this.provider.repo.setApplyToChild(parentUpdatePos, false);
}
}
}
@@ -137,7 +137,7 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
{
if (recipientDataSource != null)
{
boolean dataModified = recipientDataSource.updateFromChunk(inputData);
boolean dataModified = recipientDataSource.updateFromDataSource(inputData);
if (dataModified)
{
// save the updated data to the database
@@ -150,11 +150,14 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
}
for (IDataSourceUpdateListenerFunc<FullDataSourceV2> listener : this.dateSourceUpdateListeners)
synchronized (this.dateSourceUpdateListeners)
{
if (listener != null)
for (IDataSourceUpdateListenerFunc<FullDataSourceV2> listener : this.dateSourceUpdateListeners)
{
listener.OnDataSourceUpdated(recipientDataSource);
if (listener != null)
{
listener.OnDataSourceUpdated(recipientDataSource);
}
}
}
}
@@ -24,12 +24,13 @@ import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiW
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvironmentWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.concurrent.CompletableFuture;
@@ -42,10 +43,10 @@ import java.util.function.Consumer;
*/
public class BatchGenerator implements IDhApiWorldGenerator
{
private static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public AbstractBatchGenerationEnvironmentWrapper generationEnvironment;
public IBatchGeneratorEnvironmentWrapper generationEnvironment;
public IDhLevel targetDhLevel;
@@ -57,7 +58,7 @@ public class BatchGenerator implements IDhApiWorldGenerator
public BatchGenerator(IDhLevel targetDhLevel)
{
this.targetDhLevel = targetDhLevel;
this.generationEnvironment = FACTORY.createBatchGenerator(targetDhLevel);
this.generationEnvironment = WRAPPER_FACTORY.createBatchGenerator(targetDhLevel);
LOGGER.info("Batch Chunk Generator initialized");
}
@@ -83,26 +84,26 @@ public class BatchGenerator implements IDhApiWorldGenerator
//===================//
// generator methods //
//===================//
@Override
public CompletableFuture<Void> generateChunks(
int chunkPosMinX, int chunkPosMinZ, int generationRequestChunkWidthCount, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode,
ExecutorService worldGeneratorThreadPool, Consumer<Object[]> resultConsumer)
int chunkPosMinX,
int chunkPosMinZ,
int chunkWidthCount,
byte targetDataDetail,
EDhApiDistantGeneratorMode generatorMode,
ExecutorService worldGeneratorThreadPool,
Consumer<Object[]> resultConsumer)
{
EDhApiWorldGenerationStep targetStep = null;
EDhApiWorldGenerationStep targetStep;
switch (generatorMode)
{
case PRE_EXISTING_ONLY: // Only load in existing chunks. Note: this requires the biome generation step in order for biomes to be properly initialized.
//case BIOME_ONLY: // No blocks. Require fake height in LodBuilder
targetStep = EDhApiWorldGenerationStep.BIOMES;
case PRE_EXISTING_ONLY: // Only load in existing chunks.
targetStep = EDhApiWorldGenerationStep.EMPTY; // special logic
break;
//case BIOME_ONLY_SIMULATE_HEIGHT:
// targetStep = EDhApiWorldGenerationStep.NOISE; // Stone only. Requires a fake surface
// break;
case SURFACE:
targetStep = EDhApiWorldGenerationStep.SURFACE;
break;
@@ -112,20 +113,27 @@ public class BatchGenerator implements IDhApiWorldGenerator
case INTERNAL_SERVER:
targetStep = EDhApiWorldGenerationStep.LIGHT;
break;
default:
throw new IllegalArgumentException("no target step defined for generator mode: ["+generatorMode+"].");
}
// the consumer needs to be wrapped like this because the API can't use DH core objects (and IChunkWrapper can't be easily put into the API project)
Consumer<IChunkWrapper> consumerWrapper = (chunkWrapper) -> resultConsumer.accept(new Object[]{chunkWrapper});
try
{
return this.generationEnvironment.generateChunks(
chunkPosMinX, chunkPosMinZ, generationRequestChunkWidthCount,
return this.generationEnvironment.queueGenEvent(
chunkPosMinX, chunkPosMinZ, chunkWidthCount,
generatorMode, targetStep,
worldGeneratorThreadPool, consumerWrapper);
}
catch (Exception e)
{
if (!LodUtil.isInterruptOrReject(e)) LOGGER.error("Error starting future for chunk generation", e);
if (!ExceptionUtil.isInterruptOrReject(e))
{
LOGGER.error("Error starting future for chunk generation, error: ["+e.getMessage()+"].", e);
}
CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
@@ -144,9 +152,10 @@ public class BatchGenerator implements IDhApiWorldGenerator
@Override
public void close()
{
LOGGER.info(BatchGenerator.class.getSimpleName() + " shutting down...");
this.generationEnvironment.stop();
LOGGER.info("["+BatchGenerator.class.getSimpleName()+"] shutting down...");
this.generationEnvironment.close();
}
}
@@ -154,14 +154,15 @@ public class DhLightingEngine
// and get any necessary info from them
for (int chunkIndex = 0; chunkIndex < nearbyChunkList.size(); chunkIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
{
IChunkWrapper chunk = nearbyChunkList.get(chunkIndex);
if (chunk != null && requestedAdjacentPositions.contains(chunk.getChunkPos()))
IChunkWrapper neighborChunk = nearbyChunkList.get(chunkIndex);
if (neighborChunk != null
&& requestedAdjacentPositions.contains(neighborChunk.getChunkPos()))
{
// remove the newly found position
requestedAdjacentPositions.remove(chunk.getChunkPos());
requestedAdjacentPositions.remove(neighborChunk.getChunkPos());
// add the adjacent chunk
adjacentChunkHolder.add(chunk);
adjacentChunkHolder.add(neighborChunk);
// get and set the adjacent chunk's initial block lights
final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
@@ -174,19 +175,19 @@ public class DhLightingEngine
if (updateBlockLight)
{
ArrayList<DhBlockPos> blockLightPosList = chunk.getWorldBlockLightPosList();
ArrayList<DhBlockPos> blockLightPosList = neighborChunk.getWorldBlockLightPosList();
for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
{
DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex);
blockLightPos.mutateToChunkRelativePos(relLightBlockPos);
// get the light
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos);
IBlockStateWrapper blockState = neighborChunk.getBlockState(relLightBlockPos);
int lightValue = blockState.getLightEmission();
blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue);
// set the light
chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue);
neighborChunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue);
}
}
@@ -198,23 +199,24 @@ public class DhLightingEngine
// get and set the adjacent chunk's initial skylights,
// if the dimension has skylights
if (updateSkyLight && maxSkyLight > 0)
if (updateSkyLight
&& maxSkyLight > 0)
{
IMutableBlockPosWrapper mcBlockPos = chunk.getMutableBlockPosWrapper();
IMutableBlockPosWrapper mcBlockPos = neighborChunk.getMutableBlockPosWrapper();
IBlockStateWrapper previousBlockState = null;
int maxY = chunk.getMaxNonEmptyHeight();
int minY = chunk.getInclusiveMinBuildHeight();
int maxY = neighborChunk.getMaxNonEmptyHeight();
int minY = neighborChunk.getInclusiveMinBuildHeight();
// get the adjacent chunk's sky lights
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) // relative block pos
{
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
{
// set each pos' sky light all the way down until an opaque block is hit
// set each pos sky light all the way down until an opaque block is hit
for (int y = maxY; y >= minY; y--)
{
IBlockStateWrapper block = previousBlockState = chunk.getBlockState(relX, y, relZ, mcBlockPos, previousBlockState);
IBlockStateWrapper block = previousBlockState = neighborChunk.getBlockState(relX, y, relZ, mcBlockPos, previousBlockState);
if (block != null && block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT)
{
// keep moving down until we find a non-transparent block
@@ -223,12 +225,12 @@ public class DhLightingEngine
// add sky light to the queue
DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ);
DhBlockPos skyLightPos = new DhBlockPos(neighborChunk.getMinBlockX() + relX, y, neighborChunk.getMinBlockZ() + relZ);
skyLightWorldPosQueue.push(skyLightPos.getX(), skyLightPos.getY(), skyLightPos.getZ(), maxSkyLight);
// set the chunk's sky light
skyLightPos.mutateToChunkRelativePos(relLightBlockPos);
chunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight);
neighborChunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight);
}
}
}
@@ -247,13 +249,12 @@ public class DhLightingEngine
if (updateBlockLight)
{
// done to prevent a rare issue where the light values are incorrectly set to -1
// TODO why could that happen?
centerChunk.clearDhBlockLighting();
this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
true);
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
true);
}
// sky light
@@ -262,9 +263,9 @@ public class DhLightingEngine
centerChunk.clearDhSkyLighting();
this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
false);
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
false);
}
}
catch (Exception e)
@@ -300,9 +301,25 @@ public class DhLightingEngine
final DhBlockPosMutable neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPosMutable relNeighbourBlockPos = SECONDARY_BLOCK_POS_REF.get();
// it doesn't matter what chunk we get the mutable block pos from
IMutableBlockPosWrapper mcBlockPos = null;
for (int i = 0; i < adjacentChunkHolder.chunkArray.length; i++)
{
IChunkWrapper chunkWrapper = adjacentChunkHolder.chunkArray[i];
if (chunkWrapper != null)
{
mcBlockPos = chunkWrapper.getMutableBlockPosWrapper();
break;
}
}
if (mcBlockPos == null)
{
LodUtil.assertNotReach("How did we try to light a chunk with no chunks?");
}
IBlockStateWrapper previousBlockState = null;
// update each light position
while (!lightPosQueue.isEmpty())
{
@@ -346,15 +363,9 @@ public class DhLightingEngine
}
if (mcBlockPos == null)
{
// it doesn't matter what chunk we get the position object from
// TODO move this getter logic out of ChunkWrapper
mcBlockPos = neighbourChunk.getMutableBlockPosWrapper();
}
IBlockStateWrapper neighbourBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState);
previousBlockState = neighbourBlockState;
IBlockStateWrapper neighbourBlockState = previousBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState);
// Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles.
int targetLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
if (targetLevel > currentBlockLight)
@@ -370,12 +381,14 @@ public class DhLightingEngine
}
// can be enable if troubleshooting lighting issues
if (RENDER_BLOCK_LIGHT_WIREFRAME && propagatingBlockLights)
// can be enabled if troubleshooting lighting issues
if (RENDER_BLOCK_LIGHT_WIREFRAME
&& propagatingBlockLights)
{
RenderDhLightValuesAsWireframe(adjacentChunkHolder, true);
}
else if (RENDER_SKY_LIGHT_WIREFRAME && !propagatingBlockLights)
else if (RENDER_SKY_LIGHT_WIREFRAME
&& !propagatingBlockLights)
{
RenderDhLightValuesAsWireframe(adjacentChunkHolder, false);
}
@@ -462,7 +475,7 @@ public class DhLightingEngine
point = FullDataPointUtil.setSkyLight(point, skylight);
dataPoints.set(index, point);
// now for the propagation.
recursivelyLightAdjacentDataPoints(dataSource, airIDs, x, z, point);
this.recursivelyLightAdjacentDataPoints(dataSource, airIDs, x, z, point);
}
}
}
@@ -596,7 +609,7 @@ public class DhLightingEngine
else if (!airIDs.get(FullDataPointUtil.getId(adjacentDataPoint)))
{
// assume for now that we cannot propagate into non-transparent data points.
continue; // TODO how does this work with water? Do we care?
continue;
}
else
{
@@ -610,7 +623,7 @@ public class DhLightingEngine
adjacentDataPoint = FullDataPointUtil.setSkyLight(adjacentDataPoint, lightLevel - 1);
adjacentDataPoints.set(adjacentIndex, adjacentDataPoint);
// if propagation succeeded, recursively propagate again starting at the adjacent data point.
recursivelyLightAdjacentDataPoints(chunk, airIDs, adjacentX, adjacentZ, adjacentDataPoint);
this.recursivelyLightAdjacentDataPoints(chunk, airIDs, adjacentX, adjacentZ, adjacentDataPoint);
}
}
}
@@ -5,6 +5,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalCause;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
@@ -12,6 +13,7 @@ import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.FormatUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
@@ -28,6 +30,8 @@ public class PregenManager
{
protected static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftSharedWrapper MC_SERVER = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
private final AtomicReference<PregenState> pregenFuture = new AtomicReference<>();
@@ -51,8 +55,11 @@ public class PregenManager
pregenState.completeExceptionally(new IllegalStateException("Pregen is already running."));
return pregenState;
}
MC_SERVER.setPreventAutoPause(true);
pregenState.whenComplete((result, throwable) -> {
this.pregenFuture.set(null);
MC_SERVER.setPreventAutoPause(false);
});
pregenState.fillPendingQueue();
@@ -104,7 +111,7 @@ public class PregenManager
}
long timeSincePreviousTaskFinish = System.currentTimeMillis() - this.lastTaskFinishTime.getAndSet(System.currentTimeMillis());
this.averageTaskCompletionIntervalMs.addValue(timeSincePreviousTaskFinish);
this.averageTaskCompletionIntervalMs.add(timeSincePreviousTaskFinish);
PregenState.this.fillPendingQueue();
})
@@ -69,7 +69,7 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
int chunkWidth = DhSectionPos.getChunkWidth(sectionPos);
int chunkCount = chunkWidth * chunkWidth;
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
this.rollingAverageChunkGenTimeInMs.addValue(timePerChunk);
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
switch (requestResult)
{
@@ -40,6 +40,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
@@ -65,15 +66,6 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
/**
* Defines how many tasks can be queued per thread. <br><br>
*
* TODO the multiplier here should change dynamically based on how fast the generator is vs the queuing thread,
* if this is too high it may cause issues when moving,
* but if it is too low the generator threads won't have enough tasks to work on
*/
private static final int MAX_QUEUED_TASKS_PER_THREAD = 3;
private final IDhApiWorldGenerator generator;
private final IDhServerLevel level;
@@ -240,9 +232,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
return true;
}
// queue more tasks if any of the threads are available
int worldGenThreadCount = Math.max(Config.Common.MultiThreading.numberOfThreads.get(), 1);
int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD;
return executor.getQueueSize() > maxWorldGenTaskCount;
return this.inProgressGenTasksByLodPos.size() > worldGenThreadCount;
}
/**
* @param targetPos the position to center the generation around
@@ -345,7 +337,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
int chunkCount = generationRequestChunkWidthCount * generationRequestChunkWidthCount;
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
this.rollingAverageChunkGenTimeInMs.addValue(timePerChunk);
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
});
newTaskGroup.genFuture = generationFuture;
@@ -358,7 +350,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
if (exception != null)
{
// don't log the shutdown exceptions
if (!LodUtil.isInterruptOrReject(exception))
if (!ExceptionUtil.isInterruptOrReject(exception))
{
LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
}
@@ -412,11 +404,16 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{
IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
// TODO light data should be pulled (if possible) from the ChunkAccess object itself via ChunkFileReader.readLight
// but this should work for now
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<IChunkWrapper>();
nearbyChunkList.add(chunkWrapper);
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
// only light the chunk here if necessary,
// lighting before this point is preferred but for potenial legacy API uses this
// check should be done
if (!chunkWrapper.isDhBlockLightingCorrect())
{
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<>();
nearbyChunkList.add(chunkWrapper);
byte maxSkyLight = this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT;
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, maxSkyLight);
}
try (FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper))
{
@@ -596,9 +593,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
exception = exception.getCause();
}
if (!UncheckedInterruptedException.isInterrupt(exception) && !(exception instanceof CancellationException))
if (!UncheckedInterruptedException.isInterrupt(exception)
&& !(exception instanceof CancellationException))
{
LOGGER.error("Error when terminating data generation for section " + runningTaskGroup.group.pos, exception);
LOGGER.error("Error when terminating data generation for pos: ["+DhSectionPos.toString(runningTaskGroup.group.pos)+"], error: ["+exception.getMessage()+"].", exception);
}
return null;
@@ -623,13 +621,17 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LOGGER.info("Shutting down world generator thread pool...");
AbstractExecutorService executor = ThreadPoolUtil.getWorldGenExecutor();
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getWorldGenExecutor();
if (executor != null)
{
List<Runnable> tasks = executor.shutdownNow();
LOGGER.info("World generator thread pool shutdown with [" + tasks.size() + "] incomplete tasks.");
int queueSize = executor.getQueueSize();
executor.clearQueue();
LOGGER.info("World generator thread pool shutdown with [" + queueSize + "] incomplete tasks.");
}
this.inProgressGenTasksByLodPos.values().forEach((inProgressWorldGenTaskGroup) -> inProgressWorldGenTaskGroup.genFuture.cancel(true));
this.waitingTasks.values().forEach((worldGenTask) -> worldGenTask.future.cancel(true));
this.generator.close();
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue);
@@ -216,7 +216,7 @@ public class JarMain
{
repo = new FullDataSourceV2Repo(FullDataSourceV2Repo.DEFAULT_DATABASE_TYPE, dbFile);
}
catch (SQLException e)
catch (SQLException | IOException e)
{
LOGGER.error("Failed to initialize connection with database: ["+exportFile.getAbsolutePath()+"], error: ["+e.getMessage()+"].", e);
return;
@@ -49,7 +49,7 @@ public class GitlabGetter
/** Commit sha; Commit info */
private static final Map<String, Config> commitInfo = new HashMap<>();
/** Pipeline ID; Pipeline info */
private static final Map<Integer, ArrayList<Config>> pipelineInfo = new HashMap<>();
private static final Map<Number, ArrayList<Config>> pipelineInfo = new HashMap<>();
/** Uses our projectID to init this */
public GitlabGetter()
@@ -88,7 +88,7 @@ public class GitlabGetter
return commitInfo.get(commit);
}
public ArrayList<Config> getPipelineInfo(int pipeline)
public ArrayList<Config> getPipelineInfo(Number pipeline)
{
if (!pipelineInfo.containsKey(pipeline))
{
@@ -111,9 +111,10 @@ public class GitlabGetter
/**
* Gets all the Minecraft download links to a pipeline ID
*
* @param pipelineID Uses {@link Number} instead of a specific value due to the possibility of receiving Integer or Long
* @return Minecraft version; Download URL
*/
public Map<String, URL> getDownloads(int pipelineID)
public Map<String, URL> getDownloads(Number pipelineID)
{
Map<String, URL> downloads = new HashMap<>();
ArrayList<Config> currentPipelineInfo = this.getPipelineInfo(pipelineID);
@@ -55,6 +55,7 @@ import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -106,9 +107,9 @@ public abstract class AbstractDhLevel implements IDhLevel
{
newChunkHashRepo = new ChunkHashRepo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, databaseFile);
}
catch (SQLException e)
catch (SQLException | IOException e)
{
LOGGER.error("Unable to create [ChunkHashRepo], error: ["+e.getMessage()+"].", e);
LOGGER.fatal("Unable to create ["+ChunkHashRepo.class.getSimpleName()+"], error: ["+e.getMessage()+"].", e);
}
this.chunkHashRepo = newChunkHashRepo;
@@ -119,9 +120,9 @@ public abstract class AbstractDhLevel implements IDhLevel
{
newBeaconBeamRepo = new BeaconBeamRepo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, databaseFile);
}
catch (SQLException e)
catch (SQLException | IOException e)
{
LOGGER.error("Unable to create [BeaconBeamRepo], error: ["+e.getMessage()+"].", e);
LOGGER.error("Unable to create ["+BeaconBeamRepo.class.getSimpleName()+"], error: ["+e.getMessage()+"].", e);
}
this.beaconBeamRepo = newBeaconBeamRepo;
}
@@ -29,6 +29,8 @@ import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.*;
@@ -48,23 +50,27 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
*/
protected final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
private final FullDataSourceRequestHandler requestHandler = new FullDataSourceRequestHandler(this);
private final FullDataSourceRequestHandler requestHandler;
//=============//
// constructor //
//=============//
public AbstractDhServerLevel(ISaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager)
{
this(saveStructure, serverLevelWrapper, serverPlayerStateManager, true);
}
public AbstractDhServerLevel(
ISaveStructure saveStructure,
IServerLevelWrapper serverLevelWrapper,
ServerPlayerStateManager serverPlayerStateManager
) throws SQLException, IOException
{ this(saveStructure, serverLevelWrapper, serverPlayerStateManager, true); }
public AbstractDhServerLevel(
ISaveStructure saveStructure,
IServerLevelWrapper serverLevelWrapper,
ServerPlayerStateManager serverPlayerStateManager,
boolean runRepoReliantSetup
)
) throws SQLException, IOException
{
if (saveStructure.getSaveFolder(serverLevelWrapper).mkdirs())
{
@@ -81,6 +87,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
LOGGER.info("Started "+this.getClass().getSimpleName()+" for ["+serverLevelWrapper+"] at ["+saveStructure+"].");
this.serverPlayerStateManager = serverPlayerStateManager;
this.requestHandler = new FullDataSourceRequestHandler(this);
}
@@ -89,12 +96,6 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
// ticks //
//=======//
@Override
public void serverTick()
{
this.requestHandler.tick();
}
@Override
public boolean shouldDoWorldGen()
{ return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); }
@@ -118,9 +119,6 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
return new DhBlockPos2D((int) position.x, (int) position.z);
}
@Override
public void worldGenTick() { this.serverside.worldGenModule.worldGenTick(); }
//==================//
@@ -297,7 +295,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
public void addDebugMenuStringsToList(List<String> messageList)
{
this.serverside.fullDataFileHandler.addDebugMenuStringsToList(messageList);
this.serverside.worldGenModule.addDebugMenuStringsToList(messageList);
this.serverside.lodRequestModule.addDebugMenuStringsToList(messageList);
}
@@ -330,7 +328,10 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
{
super.close();
this.serverside.close();
this.requestHandler.close();
LOGGER.info("Closed DHLevel for [" + this.getLevelWrapper() + "].");
}
}
@@ -49,6 +49,8 @@ import org.jetbrains.annotations.Nullable;
import javax.annotation.CheckForNull;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -83,7 +85,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
.asMap()
);
public final WorldGenModule worldGenModule;
public final LodRequestModule lodRequestModule;
@Nullable
private final SyncOnLoadRequestQueue syncOnLoadRequestQueue;
@@ -94,9 +96,18 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
// constructor //
//=============//
public DhClientLevel(ISaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable ClientNetworkState networkState)
public DhClientLevel(
ISaveStructure saveStructure,
IClientLevelWrapper clientLevelWrapper,
@Nullable ClientNetworkState networkState
) throws SQLException, IOException
{ this(saveStructure, clientLevelWrapper, null, networkState); }
public DhClientLevel(ISaveStructure saveStructure, IClientLevelWrapper clientLevelWrapper, @Nullable File fullDataSaveDirOverride, @Nullable ClientNetworkState networkState)
public DhClientLevel(
ISaveStructure saveStructure,
IClientLevelWrapper clientLevelWrapper,
@Nullable File fullDataSaveDirOverride,
@Nullable ClientNetworkState networkState
) throws SQLException, IOException
{
File saveFolder = saveStructure.getSaveFolder(clientLevelWrapper);
File pre23Folder = saveStructure.getPre23SaveFolder(clientLevelWrapper);
@@ -131,7 +142,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
}
this.remoteDataSourceProvider = new RemoteFullDataSourceProvider(this, saveStructure, fullDataSaveDirOverride, this.syncOnLoadRequestQueue);
this.worldGenModule = new WorldGenModule(this, this.remoteDataSourceProvider, () -> new WorldGenState(this, networkState));
this.lodRequestModule = new LodRequestModule(this,this, this.remoteDataSourceProvider, () -> new LodRequestState(this, networkState));
this.clientside = new ClientLevelModule(this);
@@ -239,11 +250,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Nullable
public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); }
@Override
public void worldGenTick() { this.worldGenModule.worldGenTick(); }
public void startRenderer() { this.clientside.startRenderer(); }
//===========//
@@ -325,7 +331,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
// world gen
this.worldGenModule.addDebugMenuStringsToList(messageList);
this.lodRequestModule.addDebugMenuStringsToList(messageList);
if (this.syncOnLoadRequestQueue != null)
{
assert this.networkState != null;
@@ -348,9 +354,9 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Override
public void close()
{
if (this.worldGenModule != null)
if (this.lodRequestModule != null)
{
this.worldGenModule.close();
this.lodRequestModule.close();
}
if (this.networkEventSource != null)
@@ -371,11 +377,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
// helper classes //
//================//
private static class WorldGenState extends WorldGenModule.AbstractWorldGenState
private static class LodRequestState extends LodRequestModule.AbstractLodRequestState
{
WorldGenState(DhClientLevel level, ClientNetworkState networkState)
LodRequestState(DhClientLevel level, ClientNetworkState networkState)
{
this.worldGenerationQueue = new RemoteWorldRetrievalQueue(networkState, level);
this.retrievalQueue = new RemoteWorldRetrievalQueue(networkState, level);
}
}
@@ -33,6 +33,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapp
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
/** The level used for a singleplayer world */
@@ -48,7 +50,11 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
// constructor //
//=============//
public DhClientServerLevel(ISaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager)
public DhClientServerLevel(
ISaveStructure saveStructure,
IServerLevelWrapper serverLevelWrapper,
ServerPlayerStateManager serverPlayerStateManager
) throws SQLException, IOException
{
super(saveStructure, serverLevelWrapper, serverPlayerStateManager, false);
@@ -27,6 +27,8 @@ import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRend
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
public class DhServerLevel extends AbstractDhServerLevel
@@ -35,10 +37,12 @@ public class DhServerLevel extends AbstractDhServerLevel
// constructor //
//=============//
public DhServerLevel(ISaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager)
{
super(saveStructure, serverLevelWrapper, serverPlayerStateManager);
}
public DhServerLevel(
ISaveStructure saveStructure,
IServerLevelWrapper serverLevelWrapper,
ServerPlayerStateManager serverPlayerStateManager
) throws SQLException, IOException
{ super(saveStructure, serverLevelWrapper, serverPlayerStateManager); }
@@ -56,9 +56,6 @@ import java.util.concurrent.CompletableFuture;
*/
public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener
{
@Deprecated
void worldGenTick();
/**
* May return either a client or server level wrapper. <br>
* Should not return null
@@ -23,8 +23,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapp
public interface IDhServerLevel extends IDhLevel
{
void serverTick();
IServerLevelWrapper getServerLevelWrapper();
}
@@ -45,18 +45,18 @@ import java.util.function.Supplier;
/**
* Handles both single-player/server-side world gen and client side LOD requests.
* TODO rename
*/
public class WorldGenModule implements Closeable
public class LodRequestModule implements Closeable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener;
private final ThreadPoolExecutor tickerThread;
private final GeneratedFullDataSourceProvider dataSourceProvider;
private final Supplier<? extends AbstractWorldGenState> worldGenStateSupplier;
private final Supplier<? extends AbstractLodRequestState> worldGenStateSupplier;
private final AtomicReference<AbstractWorldGenState> worldGenStateRef = new AtomicReference<>();
private final AtomicReference<AbstractLodRequestState> lodRequestStateRef = new AtomicReference<>();
@@ -64,59 +64,41 @@ public class WorldGenModule implements Closeable
// constructor //
//=============//
public WorldGenModule(
public LodRequestModule(
IDhLevel level,
GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener,
GeneratedFullDataSourceProvider dataSourceProvider,
Supplier<? extends AbstractWorldGenState> worldGenStateSupplier
Supplier<? extends AbstractLodRequestState> worldGenStateSupplier
)
{
this.onWorldGenCompleteListener = onWorldGenCompleteListener;
this.dataSourceProvider = dataSourceProvider;
this.worldGenStateSupplier = worldGenStateSupplier;
}
//===================//
// world gen control //
//===================//
public void startWorldGen(GeneratedFullDataSourceProvider dataFileHandler, AbstractWorldGenState newWgs)
{
// create the new world generator
if (!this.worldGenStateRef.compareAndSet(null, newWgs))
{
LOGGER.warn("Failed to start world gen due to concurrency");
newWgs.closeAsync(false);
}
dataFileHandler.addWorldGenCompleteListener(this.onWorldGenCompleteListener);
dataFileHandler.setWorldGenerationQueue(newWgs.worldGenerationQueue);
}
public void stopWorldGen(GeneratedFullDataSourceProvider dataFileHandler)
{
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
LOGGER.warn("Attempted to stop world gen when it was not running");
return;
}
// shut down the world generator
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
String levelId = level.getLevelWrapper().getDhIdentifier();
this.tickerThread = ThreadUtil.makeSingleDaemonThreadPool("Request Module Ticker ["+levelId+"]");
this.tickerThread.execute(this::tickLoop);
}
//=========//
// ticking //
//=========//
private void tickLoop()
{
try
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
while (!Thread.interrupted())
{
return;
Thread.sleep(20);
this.tick();
}
}
dataFileHandler.clearRetrievalQueue();
worldGenState.closeAsync(true).join(); //TODO: Make it async.
dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener);
catch (InterruptedException ignore) { }
}
public void worldGenTick()
private void tick()
{
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
// if the world is read only don't generate anything
@@ -136,13 +118,13 @@ public class WorldGenModule implements Closeable
if (this.isWorldGenRunning())
{
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
if (worldGenState != null)
AbstractLodRequestState lodRequestState = this.lodRequestStateRef.get();
if (lodRequestState != null)
{
DhBlockPos2D targetPosForGeneration = this.onWorldGenCompleteListener.getTargetPosForGeneration();
if (targetPosForGeneration != null)
{
worldGenState.startGenerationQueueAndSetTargetPos(targetPosForGeneration);
lodRequestState.startRequestQueueAndSetTargetPos(targetPosForGeneration);
}
}
}
@@ -150,6 +132,48 @@ public class WorldGenModule implements Closeable
//===================//
// world gen control //
//===================//
public void startWorldGen(GeneratedFullDataSourceProvider dataFileHandler, AbstractLodRequestState newWgs)
{
// create the new world generator
if (!this.lodRequestStateRef.compareAndSet(null, newWgs))
{
LOGGER.warn("Failed to start world gen due to concurrency");
newWgs.closeAsync(false);
}
dataFileHandler.addWorldGenCompleteListener(this.onWorldGenCompleteListener);
dataFileHandler.setWorldGenerationQueue(newWgs.retrievalQueue);
}
public void stopWorldGen(GeneratedFullDataSourceProvider dataFileHandler)
{
AbstractLodRequestState worldGenState = this.lodRequestStateRef.get();
if (worldGenState == null)
{
LOGGER.warn("Attempted to stop world gen when it was not running");
return;
}
// shut down the world generator
while (!this.lodRequestStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.lodRequestStateRef.get();
if (worldGenState == null)
{
return;
}
}
dataFileHandler.clearRetrievalQueue();
worldGenState.closeAsync(true).join(); //TODO: Make it async.
dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener);
}
//=======================//
// base method overrides //
//=======================//
@@ -157,13 +181,15 @@ public class WorldGenModule implements Closeable
@Override
public void close()
{
this.tickerThread.shutdownNow();
// shutdown the world-gen
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
AbstractLodRequestState worldGenState = this.lodRequestStateRef.get();
if (worldGenState != null)
{
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
while (!this.lodRequestStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
worldGenState = this.lodRequestStateRef.get();
if (worldGenState == null)
{
break;
@@ -183,12 +209,12 @@ public class WorldGenModule implements Closeable
// getters //
//=========//
public boolean isWorldGenRunning() { return this.worldGenStateRef.get() != null; }
public boolean isWorldGenRunning() { return this.lodRequestStateRef.get() != null; }
/** mutates a list so it can be added to an existing {@link IDhLevel}'s debug list */
public void addDebugMenuStringsToList(List<String> messageList)
{
AbstractWorldGenState worldGenState = this.worldGenStateRef.get();
AbstractLodRequestState worldGenState = this.lodRequestStateRef.get();
if (worldGenState == null)
{
return;
@@ -196,9 +222,9 @@ public class WorldGenModule implements Closeable
// estimated tasks
String waitingCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getWaitingTaskCount());
String inProgressCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getInProgressTaskCount());
String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getRetrievalEstimatedRemainingChunkCount());
String waitingCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.retrievalQueue.getWaitingTaskCount());
String inProgressCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.retrievalQueue.getInProgressTaskCount());
String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.retrievalQueue.getRetrievalEstimatedRemainingChunkCount());
String message = "World Gen/Import Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress "+inProgressCountStr+")";
// estimated chunks/sec
@@ -210,7 +236,7 @@ public class WorldGenModule implements Closeable
messageList.add(message);
worldGenState.worldGenerationQueue.addDebugMenuStringsToList(messageList);
worldGenState.retrievalQueue.addDebugMenuStringsToList(messageList);
}
@@ -220,40 +246,22 @@ public class WorldGenModule implements Closeable
//================//
/** Handles the {@link IFullDataSourceRetrievalQueue} and any other necessary world gen information. */
public static abstract class AbstractWorldGenState
public static abstract class AbstractLodRequestState
{
/** static so we only send the disable message once per session */
private static long firstProgressMessageSentMs = 0;
public IFullDataSourceRetrievalQueue worldGenerationQueue;
public IFullDataSourceRetrievalQueue retrievalQueue;
private static final ThreadPoolExecutor PROGRESS_UPDATER_THREAD = ThreadUtil.makeSingleDaemonThreadPool("World Gen Progress Updater");
private boolean progressUpdateThreadRunning = false;
CompletableFuture<Void> closeAsync(boolean doInterrupt)
{
// this should stop the updater thread
this.progressUpdateThreadRunning = false;
return this.worldGenerationQueue.startClosingAsync(true, doInterrupt)
.exceptionally(e ->
{
LOGGER.error("Error during first stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e);
return null;
}
).thenRun(this.worldGenerationQueue::close)
.exceptionally(e ->
{
LOGGER.error("Error during second stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e);
return null;
});
}
/** @param targetPosForGeneration the position that world generation should be centered around */
public void startGenerationQueueAndSetTargetPos(DhBlockPos2D targetPosForGeneration)
/** @param targetPosForRequest the position that world generation should be centered around */
public void startRequestQueueAndSetTargetPos(DhBlockPos2D targetPosForRequest)
{
this.worldGenerationQueue.startAndSetTargetPos(targetPosForGeneration);
this.retrievalQueue.startAndSetTargetPos(targetPosForRequest);
this.startProgressUpdateThread();
}
private void startProgressUpdateThread()
@@ -286,8 +294,8 @@ public class WorldGenModule implements Closeable
private void sendRetrievalProgress()
{
// format the remaining chunks
int remainingChunkCount = this.worldGenerationQueue.getRetrievalEstimatedRemainingChunkCount();
remainingChunkCount += this.worldGenerationQueue.getQueuedChunkCount();
int remainingChunkCount = this.retrievalQueue.getRetrievalEstimatedRemainingChunkCount();
remainingChunkCount += this.retrievalQueue.getQueuedChunkCount();
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
String message = "DH is generating chunks. " + remainingChunkCountStr + " left.";
@@ -350,7 +358,7 @@ public class WorldGenModule implements Closeable
/** @return -1 if this method isn't supported or available */
public double getEstimatedChunksPerSecond()
{
RollingAverage avg = this.worldGenerationQueue.getRollingAverageChunkGenTimeInMs();
RollingAverage avg = this.retrievalQueue.getRollingAverageChunkGenTimeInMs();
if (avg == null)
{
return -1;
@@ -373,6 +381,27 @@ public class WorldGenModule implements Closeable
return chunksPerSecond;
}
CompletableFuture<Void> closeAsync(boolean doInterrupt)
{
// this should stop the updater thread
this.progressUpdateThreadRunning = false;
return this.retrievalQueue.startClosingAsync(true, doInterrupt)
.exceptionally(e ->
{
LOGGER.error("Error during first stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e);
return null;
}
).thenRun(this.retrievalQueue::close)
.exceptionally(e ->
{
LOGGER.error("Error during second stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e);
return null;
});
}
}
@@ -28,6 +28,9 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.DependencyInjection.WorldGeneratorInjector;
import com.seibel.distanthorizons.core.logging.DhLogger;
import java.io.IOException;
import java.sql.SQLException;
public class ServerLevelModule implements AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -36,7 +39,7 @@ public class ServerLevelModule implements AutoCloseable
public final ISaveStructure saveStructure;
public final GeneratedFullDataSourceProvider fullDataFileHandler;
public final WorldGenModule worldGenModule;
public final LodRequestModule lodRequestModule;
@@ -44,12 +47,12 @@ public class ServerLevelModule implements AutoCloseable
// constructor //
//=============//
public ServerLevelModule(IDhServerLevel parentServerLevel, ISaveStructure saveStructure)
public ServerLevelModule(IDhServerLevel parentServerLevel, ISaveStructure saveStructure) throws SQLException, IOException
{
this.parentServerLevel = parentServerLevel;
this.saveStructure = saveStructure;
this.fullDataFileHandler = new GeneratedFullDataSourceProvider(parentServerLevel, saveStructure);
this.worldGenModule = new WorldGenModule(this.parentServerLevel, this.fullDataFileHandler, () -> new ServerLevelModule.WorldGenState(this.parentServerLevel));
this.lodRequestModule = new LodRequestModule(this.parentServerLevel, this.parentServerLevel, this.fullDataFileHandler, () -> new LodRequestState(this.parentServerLevel));
}
@@ -62,7 +65,7 @@ public class ServerLevelModule implements AutoCloseable
public void close()
{
// shutdown the world-gen
this.worldGenModule.close();
this.lodRequestModule.close();
this.fullDataFileHandler.close();
}
@@ -72,9 +75,9 @@ public class ServerLevelModule implements AutoCloseable
// helper classes //
//================//
public static class WorldGenState extends WorldGenModule.AbstractWorldGenState
public static class LodRequestState extends LodRequestModule.AbstractLodRequestState
{
WorldGenState(IDhServerLevel level)
LodRequestState(IDhServerLevel level)
{
IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
if (worldGenerator == null)
@@ -85,7 +88,7 @@ public class ServerLevelModule implements AutoCloseable
// since core world generator's should have the lowest override priority
WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator);
}
this.worldGenerationQueue = new WorldGenerationQueue(worldGenerator, level);
this.retrievalQueue = new WorldGenerationQueue(worldGenerator, level);
}
}
@@ -14,6 +14,7 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceR
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import java.util.List;
@@ -21,7 +22,7 @@ import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class FullDataSourceRequestHandler
public class FullDataSourceRequestHandler implements AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder()
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
@@ -29,6 +30,8 @@ public class FullDataSourceRequestHandler
private final AbstractDhServerLevel serverLevel;
private final ThreadPoolExecutor tickerThread;
private String getLevelIdentifier() { return this.serverLevel.getLevelWrapper().getDhIdentifier(); }
private GeneratedFullDataSourceProvider fullDataSourceProvider() { return this.serverLevel.serverside.fullDataFileHandler; }
private List<BeaconBeamDTO> getAllBeamsForPos(long pos) { return this.serverLevel.beaconBeamRepo.getAllBeamsForPos(pos); }
@@ -37,12 +40,22 @@ public class FullDataSourceRequestHandler
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupsByFutureId = new ConcurrentHashMap<>();
//=============//
// constructor //
//=============//
public FullDataSourceRequestHandler(AbstractDhServerLevel serverLevel)
{
this.serverLevel = serverLevel;
String levelId = this.serverLevel.getServerLevelWrapper().getDhIdentifier();
this.tickerThread = ThreadUtil.makeSingleDaemonThreadPool("DataSource Request Ticker ["+levelId+"]");
this.tickerThread.execute(this::tickLoop);
}
//==================//
// network handling //
//==================//
@@ -214,52 +227,6 @@ public class FullDataSourceRequestHandler
}
}
public void tick()
{
// Send finished data source requests
for (Map.Entry<Long, DataSourceRequestGroup> entry : this.requestGroupsByPos.entrySet())
{
DataSourceRequestGroup requestGroup = entry.getValue();
if (requestGroup.fullDataSource == null)
{
continue;
}
LOGGER.debug("[" + this.getLevelIdentifier() + "] Fulfilled request group [" + DhSectionPos.toString(entry.getKey()) + "]");
// Make this group unavailable for adding into
this.requestGroupsByPos.remove(entry.getKey());
if (!requestGroup.tryClose())
{
continue;
}
AbstractExecutorService executor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (executor == null)
{
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
continue;
}
CompletableFuture.runAsync(() ->
{
FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource, this.getAllBeamsForPos(entry.getKey()));
requestGroup.fullDataSource.close();
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
{
this.requestGroupsByFutureId.remove(requestData.futureId());
requestData.serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () -> {
requestData.message.sendResponse(new FullDataSourceResponseMessage(payload));
requestData.rateLimiterSet.generationRequestRateLimiter.release();
});
}
}, executor);
}
}
private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
{
this.fullDataSourceProvider().getAsync(pos).thenAccept(fullDataSource ->
@@ -313,4 +280,80 @@ public class FullDataSourceRequestHandler
}
}
//=========//
// ticking //
//=========//
private void tickLoop()
{
try
{
while (!Thread.interrupted())
{
Thread.sleep(20);
this.tick();
}
}
catch (InterruptedException ignore) { }
}
private void tick()
{
// Send finished data source requests
for (Map.Entry<Long, DataSourceRequestGroup> entry : this.requestGroupsByPos.entrySet())
{
DataSourceRequestGroup requestGroup = entry.getValue();
if (requestGroup.fullDataSource == null)
{
continue;
}
LOGGER.debug("[" + this.getLevelIdentifier() + "] Fulfilled request group [" + DhSectionPos.toString(entry.getKey()) + "]");
// Make this group unavailable for adding into
this.requestGroupsByPos.remove(entry.getKey());
if (!requestGroup.tryClose())
{
continue;
}
AbstractExecutorService executor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (executor == null)
{
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
continue;
}
CompletableFuture.runAsync(() ->
{
FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource, this.getAllBeamsForPos(entry.getKey()));
requestGroup.fullDataSource.close();
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
{
this.requestGroupsByFutureId.remove(requestData.futureId());
requestData.serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () -> {
requestData.message.sendResponse(new FullDataSourceResponseMessage(payload));
requestData.rateLimiterSet.generationRequestRateLimiter.release();
});
}
}, executor);
}
}
//================//
// base overrides //
//================//
@Override
public void close()
{
this.tickerThread.shutdownNow();
}
}
@@ -182,7 +182,8 @@ public class PhantomArrayListPool
"Potential causes: \n" +
"1. your allocated memory isn't high enough \n" +
"2. your DH CPU preset is too high \n" +
"3. your DH quality preset is too high";
"3. your DH quality preset is too high \n" +
"4. you have other memory hungry mod(s)";
LOGGER.warn(message);
if (Config.Common.Logging.Warning.showPoolInsufficientMemoryWarning.get())
@@ -42,6 +42,8 @@ import java.util.function.LongConsumer;
* <strong>Too big</strong>, and the LOD dropoff will be very noticeable.<br>
* With those thoughts in mind we decided on a smallest section size of 64 data points square (IE 4x4 chunks).
*
* TODO absolute vs section detail levels
*
* @author Leetom
*/
public class DhSectionPos
@@ -379,7 +379,7 @@ public class GLBuffer implements AutoCloseable
{
int id = PHANTOM_TO_BUFFER_ID.get(phantomRef);
destroyBufferIdAsync(id);
LOGGER.warn("Buffer Phantom collected, ID: ["+id+"]");
//LOGGER.warn("Buffer Phantom collected, ID: ["+id+"]");
}
phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
@@ -110,8 +110,6 @@ public class DhFadeRenderer
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler)
{
GLState mcState = new GLState();
try
{
profiler.push("Fade Generate");
@@ -149,10 +147,6 @@ public class DhFadeRenderer
}
finally
{
// make sure we always revert to MC's state to prevent GL state corruption
// this is especially important on MC 1.16.5 or when other rendering mods are present
mcState.restore();
profiler.pop();
}
}
@@ -243,7 +243,10 @@ public class LodRenderer
}
// far plane clip fading
if (Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane.get())
if (Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane.get()
// the fade shader messes with the GL state in a way Iris doesn't like,
// so skip it if a shader is active
&& (IRIS_ACCESSOR == null || !IRIS_ACCESSOR.isShaderPackInUse()))
{
profiler.popPush("Fade Far Clip Fade");
DhFadeRenderer.INSTANCE.render(
@@ -121,6 +121,16 @@ public class VanillaFadeRenderer
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level)
{
int depthTextureId = LodRenderer.INSTANCE.getActiveDepthTextureId();
if (depthTextureId == -1)
{
// the renderer hasn't been set up yet
// trying to render fading may cause GL errors
return;
}
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("DH-Vanilla Fade");
@@ -158,7 +168,7 @@ public class VanillaFadeRenderer
FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture;
FadeApplyShader.INSTANCE.readFramebuffer = DhFadeShader.INSTANCE.frameBuffer;
FadeApplyShader.INSTANCE.drawFramebuffer = LodRenderer.INSTANCE.getActiveFramebufferId();
FadeApplyShader.INSTANCE.drawFramebuffer = MC_RENDER.getTargetFramebuffer();
FadeApplyShader.INSTANCE.render(partialTicks);
}
@@ -69,8 +69,7 @@ public class FullDataSourceV1DTO implements IBaseDTO<Long>
/** @return a stream for the data contained in this DTO. */
public DhDataInputStream getInputStream() throws IOException
{
InputStream inputStream = new ByteArrayInputStream(this.dataArray);
DhDataInputStream compressedStream = new DhDataInputStream(inputStream, EDhApiDataCompressionMode.LZ4); // LZ4 was used by DH before 2.1.0 and as such must be used until the render data format is changed to record the compressor
DhDataInputStream compressedStream = DhDataInputStream.create(this.dataArray, EDhApiDataCompressionMode.LZ4); // LZ4 was used by DH before 2.1.0 and as such must be used until the render data format is changed to record the compressor
return compressedStream;
}
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.sql.dto;
import com.github.luben.zstd.Zstd;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
@@ -65,8 +66,6 @@ public class FullDataSourceV2DTO
public long pos;
public int levelMinY;
/** only for the data array */
public int dataChecksum;
@@ -132,7 +131,6 @@ public class FullDataSourceV2DTO
dto.createdUnixDateTime = dataSource.createdUnixDateTime;
dto.applyToParent = dataSource.applyToParent;
dto.applyToChildren = dataSource.applyToChildren;
dto.levelMinY = dataSource.levelMinY;
}
return dto;
@@ -296,8 +294,6 @@ public class FullDataSourceV2DTO
dataSource.lastModifiedUnixDateTime = this.lastModifiedUnixDateTime;
dataSource.createdUnixDateTime = this.createdUnixDateTime;
dataSource.levelMinY = this.levelMinY;
dataSource.isEmpty = false;
if (this.applyToParent != null)
@@ -322,12 +318,7 @@ public class FullDataSourceV2DTO
LongArrayList[] inputDataArray, ByteArrayList outputByteArray,
EDhApiDataCompressionMode compressionModeEnum) throws IOException
{
// write the outputs to a stream to prep for writing to the database
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// normally a DhStream should be the topmost stream to prevent closing the stream accidentally,
// but since this stream will be closed immediately after writing anyway, it won't be an issue
try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
try (DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
{
// write the data
int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH;
@@ -347,20 +338,13 @@ public class FullDataSourceV2DTO
compressedOut.writeLong(dataColumn.getLong(y));
}
}
// generate the checksum
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
}
}
private static void readBlobToDataSourceDataArrayV1(
ByteArrayList inputCompressedDataByteArray, LongArrayList[] outputDataLongArray,
EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements());
try (DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
try (DhDataInputStream compressedIn = DhDataInputStream.create(inputCompressedDataByteArray, compressionModeEnum))
{
// read the data
int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH;
@@ -412,8 +396,7 @@ public class FullDataSourceV2DTO
maxZ = FullDataSourceV2.WIDTH-1;
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
try (DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
{
// this method would be simpler if we allocated a bunch of temporary arrays,
// but we're trying to avoid garbage.
@@ -530,10 +513,6 @@ public class FullDataSourceV2DTO
}
}
}
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
}
}
private static void readBlobToDataSourceDataArrayV2(
@@ -560,8 +539,8 @@ public class FullDataSourceV2DTO
maxZ = FullDataSourceV2.WIDTH-1;
}
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements());
try (DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
try (DhDataInputStream compressedIn = DhDataInputStream.create(inputCompressedDataByteArray, compressionModeEnum))
{
// 1. column counts, preallocate
for (int x = minX; x < maxX; x++)
@@ -687,24 +666,17 @@ public class FullDataSourceV2DTO
private static void writeGenerationStepsToBlob(ByteArrayList inputColumnGenStepByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
try (DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
{
for (int i = 0; i < inputColumnGenStepByteArray.size(); i++)
{
compressedOut.writeByte(inputColumnGenStepByteArray.getByte(i));
}
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
}
}
private static void readBlobToGenerationSteps(ByteArrayList inputCompressedDataByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements());
try(DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
try(DhDataInputStream compressedIn = DhDataInputStream.create(inputCompressedDataByteArray, compressionModeEnum))
{
compressedIn.readFully(outputByteArray.elements(), 0, FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH);
}
@@ -717,24 +689,17 @@ public class FullDataSourceV2DTO
private static void writeWorldCompressionModeToBlob(ByteArrayList inputWorldCompressionModeByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
try (DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
{
for (int i = 0; i < inputWorldCompressionModeByteArray.size(); i++)
{
compressedOut.write(inputWorldCompressionModeByteArray.getByte(i));
}
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
}
}
private static void readBlobToWorldCompressionMode(ByteArrayList inputCompressedDataByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements());
try(DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
try(DhDataInputStream compressedIn = DhDataInputStream.create(inputCompressedDataByteArray, compressionModeEnum))
{
compressedIn.readFully(outputByteArray.elements(), 0, FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH);
}
@@ -747,20 +712,14 @@ public class FullDataSourceV2DTO
private static void writeDataMappingToBlob(FullDataPointIdMap mapping, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try(DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
try(DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
{
mapping.serialize(compressedOut);
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
}
}
private static FullDataPointIdMap readBlobToDataMapping(ByteArrayList compressedMappingByteArray, long pos, @NotNull ILevelWrapper levelWrapper, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException, DataCorruptedException
private static FullDataPointIdMap readBlobToDataMapping(ByteArrayList inputCompressedDataByteArray, long pos, @NotNull ILevelWrapper levelWrapper, EDhApiDataCompressionMode compressionModeEnum) throws IOException, InterruptedException, DataCorruptedException
{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(compressedMappingByteArray.elements());
try (DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
try (DhDataInputStream compressedIn = DhDataInputStream.create(inputCompressedDataByteArray, compressionModeEnum))
{
FullDataPointIdMap mapping = FullDataPointIdMap.deserialize(compressedIn, pos, levelWrapper);
return mapping;
@@ -779,14 +738,27 @@ public class FullDataSourceV2DTO
out.writeLong(this.pos);
out.writeInt(this.dataChecksum);
// data
out.writeInt(this.compressedDataByteArray.size());
out.writeBytes(this.compressedDataByteArray.elements(), 0, this.compressedDataByteArray.size());
// adj data
out.writeInt(this.compressedNorthAdjDataByteArray.size());
out.writeBytes(this.compressedNorthAdjDataByteArray.elements(), 0, this.compressedNorthAdjDataByteArray.size());
out.writeInt(this.compressedSouthAdjDataByteArray.size());
out.writeBytes(this.compressedSouthAdjDataByteArray.elements(), 0, this.compressedSouthAdjDataByteArray.size());
out.writeInt(this.compressedEastAdjDataByteArray.size());
out.writeBytes(this.compressedEastAdjDataByteArray.elements(), 0, this.compressedEastAdjDataByteArray.size());
out.writeInt(this.compressedWestAdjDataByteArray.size());
out.writeBytes(this.compressedWestAdjDataByteArray.elements(), 0, this.compressedWestAdjDataByteArray.size());
// world gen
out.writeInt(this.compressedColumnGenStepByteArray.size());
out.writeBytes(this.compressedColumnGenStepByteArray.elements(), 0, this.compressedColumnGenStepByteArray.size());
out.writeInt(this.compressedWorldCompressionModeByteArray.size());
out.writeBytes(this.compressedWorldCompressionModeByteArray.elements(), 0, this.compressedWorldCompressionModeByteArray.size());
// compression type
out.writeInt(this.compressedMappingByteArray.size());
out.writeBytes(this.compressedMappingByteArray.elements(), 0, this.compressedMappingByteArray.size());
@@ -806,14 +778,27 @@ public class FullDataSourceV2DTO
this.pos = in.readLong();
this.dataChecksum = in.readInt();
// data
this.compressedDataByteArray.size(in.readInt());
in.readBytes(this.compressedDataByteArray.elements(), 0, this.compressedDataByteArray.size());
// adj data
this.compressedNorthAdjDataByteArray.size(in.readInt());
in.readBytes(this.compressedNorthAdjDataByteArray.elements(), 0, this.compressedNorthAdjDataByteArray.size());
this.compressedSouthAdjDataByteArray.size(in.readInt());
in.readBytes(this.compressedSouthAdjDataByteArray.elements(), 0, this.compressedSouthAdjDataByteArray.size());
this.compressedEastAdjDataByteArray.size(in.readInt());
in.readBytes(this.compressedEastAdjDataByteArray.elements(), 0, this.compressedEastAdjDataByteArray.size());
this.compressedWestAdjDataByteArray.size(in.readInt());
in.readBytes(this.compressedWestAdjDataByteArray.elements(), 0, this.compressedWestAdjDataByteArray.size());
// world gen
this.compressedColumnGenStepByteArray.size(in.readInt());
in.readBytes(this.compressedColumnGenStepByteArray.elements(), 0, this.compressedColumnGenStepByteArray.size());
this.compressedWorldCompressionModeByteArray.size(in.readInt());
in.readBytes(this.compressedWorldCompressionModeByteArray.elements(), 0, this.compressedWorldCompressionModeByteArray.size());
// compression type
this.compressedMappingByteArray.size(in.readInt());
in.readBytes(this.compressedMappingByteArray.elements(), 0, this.compressedMappingByteArray.size());
@@ -842,7 +827,6 @@ public class FullDataSourceV2DTO
public String toString()
{
return MoreObjects.toStringHelper(this)
.add("levelMinY", this.levelMinY)
.add("pos", DhSectionPos.toString(this.pos))
.add("dataChecksum", this.dataChecksum)
.add("compressedDataByteArray length", this.compressedDataByteArray.size())
@@ -75,7 +75,7 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
//=============//
/** @throws SQLException if the repo is unable to access the database or has trouble updating said database. */
public AbstractDhRepo(String databaseType, File databaseFile, Class<? extends TDTO> dtoClass) throws SQLException
public AbstractDhRepo(String databaseType, File databaseFile, Class<? extends TDTO> dtoClass) throws SQLException, IOException
{
this.databaseType = databaseType;
this.databaseFile = databaseFile;
@@ -107,7 +107,7 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
{
if (!parentFolder.mkdirs())
{
throw new RuntimeException("Unable to create the necessary parent folders for the database file at location ["+databaseFile.getPath()+"].");
throw new IOException("Unable to create the necessary parent folders for the database file at location ["+databaseFile.getPath()+"].");
}
}
@@ -119,18 +119,18 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
}
catch (IOException e)
{
throw new RuntimeException("Unable to create database file at location ["+databaseFile.getPath()+"] due to error: ["+e.getMessage()+"]", e);
throw new IOException("Unable to create database file at location ["+databaseFile.getPath()+"] due to error: ["+e.getMessage()+"]", e);
}
}
}
if (!databaseFile.canRead())
{
throw new RuntimeException("Unable to read database file at location ["+databaseFile.getPath()+"], please make sure the folder and file has the correct permissions.");
throw new IOException("Unable to read database file at location ["+databaseFile.getPath()+"], please make sure the folder and file has the correct permissions.");
}
if (!databaseFile.canWrite())
{
throw new RuntimeException("Unable to write database file at location ["+databaseFile.getPath()+"], please make sure the folder and file aren't set to read-only.");
throw new IOException("Unable to write database file at location ["+databaseFile.getPath()+"], please make sure the folder and file aren't set to read-only.");
}
@@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -47,7 +48,7 @@ public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
// constructor //
//=============//
public BeaconBeamRepo(String databaseType, File databaseFile) throws SQLException
public BeaconBeamRepo(String databaseType, File databaseFile) throws SQLException, IOException
{
super(databaseType, databaseFile, BeaconBeamDTO.class);
}
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -40,7 +41,7 @@ public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
// constructor //
//=============//
public ChunkHashRepo(String databaseType, File databaseFile) throws SQLException
public ChunkHashRepo(String databaseType, File databaseFile) throws SQLException, IOException
{
super(databaseType, databaseFile, ChunkHashDTO.class);
}
@@ -28,6 +28,7 @@ import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -45,7 +46,7 @@ public class FullDataSourceV1Repo extends AbstractDhRepo<Long, FullDataSourceV1D
// constructor //
//=============//
public FullDataSourceV1Repo(String databaseType, File databaseFile) throws SQLException
public FullDataSourceV1Repo(String databaseType, File databaseFile) throws SQLException, IOException
{
super(databaseType, databaseFile, FullDataSourceV1DTO.class);
}
@@ -51,7 +51,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
// constructor //
//=============//
public FullDataSourceV2Repo(String databaseType, File databaseFile) throws SQLException
public FullDataSourceV2Repo(String databaseType, File databaseFile) throws SQLException, IOException
{
super(databaseType, databaseFile, FullDataSourceV2DTO.class);
}
@@ -98,7 +98,6 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
int posZ = resultSet.getInt("PosZ");
long pos = DhSectionPos.encode(sectionDetailLevel, posX, posZ);
int minY = resultSet.getInt("MinY");
int dataChecksum = resultSet.getInt("DataChecksum");
@@ -145,19 +144,17 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
dto.createdUnixDateTime = createdUnixDateTime;
dto.applyToParent = applyToParent;
dto.applyToChildren = applyToChildren;
dto.levelMinY = minY;
}
return dto;
}
@Nullable
public FullDataSourceV2DTO convertResultSetToAdjDto(long pos, EDhDirection direction, ResultSet resultSet) throws ClassCastException, IOException, SQLException
public FullDataSourceV2DTO convertResultSetToAdjDto(long pos, ResultSet resultSet) throws ClassCastException, IOException, SQLException
{
//======================//
// get statement values //
//======================//
int minY = resultSet.getInt("MinY");
int dataChecksum = resultSet.getInt("DataChecksum");
@@ -178,17 +175,11 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
//===================//
FullDataSourceV2DTO dto = FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding();
// dto.compressedNorthAdjDataByteArray = putAllBytes(resultSet.getBinaryStream("NorthAdjData"), dto.compressedNorthAdjDataByteArray);
// set pooled arrays
dto.compressedDataByteArray = putAllBytes(resultSet.getBinaryStream("AdjData"), dto.compressedDataByteArray);
dto.compressedColumnGenStepByteArray = putAllBytes(resultSet.getBinaryStream("ColumnGenerationStep"), dto.compressedColumnGenStepByteArray);
dto.compressedWorldCompressionModeByteArray = putAllBytes(resultSet.getBinaryStream("ColumnWorldCompressionMode"), dto.compressedWorldCompressionModeByteArray);
dto.compressedMappingByteArray = putAllBytes(resultSet.getBinaryStream("Mapping"), dto.compressedMappingByteArray);
// adjacent full data
//dto.compressedNorthAdjDataByteArray = putAllBytes(resultSet.getBinaryStream("NorthAdjData"), dto.compressedNorthAdjDataByteArray);
//dto.compressedSouthAdjDataByteArray = putAllBytes(resultSet.getBinaryStream("SouthAdjData"), dto.compressedSouthAdjDataByteArray);
//dto.compressedEastAdjDataByteArray = putAllBytes(resultSet.getBinaryStream("EastAdjData"), dto.compressedEastAdjDataByteArray);
//dto.compressedWestAdjDataByteArray = putAllBytes(resultSet.getBinaryStream("WestAdjData"), dto.compressedWestAdjDataByteArray);
// set individual variables
{
@@ -200,7 +191,6 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
dto.createdUnixDateTime = createdUnixDateTime;
dto.applyToParent = applyToParent;
dto.applyToChildren = applyToChildren;
dto.levelMinY = minY;
}
return dto;
}
@@ -237,7 +227,10 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
statement.setInt(i++, DhSectionPos.getX(dto.pos));
statement.setInt(i++, DhSectionPos.getZ(dto.pos));
statement.setInt(i++, dto.levelMinY);
statement.setInt(i++, 0); // deprecated MinY column
// MinY is deprecated due to a bug introduced sometime before 2.3.6 where was always set to "0"
// and due to being unused wasn't noticed until 2.3.7-dev (2025-11-15)
statement.setInt(i++, dto.dataChecksum);
statement.setBinaryStream(i++, new ByteArrayInputStream(dto.compressedDataByteArray.elements()), dto.compressedDataByteArray.size());
@@ -272,8 +265,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
String updateSqlTemplate = (
"UPDATE "+this.getTableName()+" \n" +
"SET \n" +
" MinY = ? \n" +
" ,DataChecksum = ? \n" +
" DataChecksum = ? \n" +
" ,Data = ? \n" +
" ,ColumnGenerationStep = ? \n" +
@@ -303,7 +295,6 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
int i = 1;
statement.setInt(i++, dto.levelMinY);
statement.setInt(i++, dto.dataChecksum);
statement.setBinaryStream(i++, new ByteArrayInputStream(dto.compressedDataByteArray.elements()), dto.compressedDataByteArray.size());
@@ -346,7 +337,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
private final String getAdjForDirectionSqlTemplate =
"SELECT \n" +
" MinY, DataChecksum, \n" +
" DataChecksum, \n" +
" ColumnGenerationStep, ColumnWorldCompressionMode, Mapping, \n" +
" DataFormatVersion, CompressionMode, ApplyToParent, ApplyToChildren, \n" +
" LastModifiedUnixDateTime, CreatedUnixDateTime, \n" +
@@ -400,9 +391,9 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
try(ResultSet resultSet = this.query(statement))
{
if (resultSet != null
&& resultSet.next())
&& resultSet.next())
{
return this.convertResultSetToAdjDto(pos, direction, resultSet);
return this.convertResultSetToAdjDto(pos, resultSet);
}
else
{
@@ -581,9 +572,14 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
EDhApiDataCompressionMode compressionModeEnum = EDhApiDataCompressionMode.getFromValue(compressionModeEnumValue);
// decompress the data
try(DhDataInputStream compressedIn = new DhDataInputStream(result.getBinaryStream("ColumnGenerationStep"), compressionModeEnum))
try
{
putAllBytes(compressedIn, outputByteArray);
ByteArrayList byteArrayList = new ByteArrayList();
putAllBytes(result.getBinaryStream("ColumnGenerationStep"), byteArrayList);
try(DhDataInputStream compressedIn = DhDataInputStream.create(byteArrayList, compressionModeEnum))
{
putAllBytes(compressedIn, outputByteArray);
}
}
catch (IOException e)
{
@@ -13,8 +13,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
public class DhApiTerrainDataPointUtil
{
public static DhApiTerrainDataPoint createApiDatapoint(IDhApiLevelWrapper levelWrapper, FullDataPointIdMap mapping, byte detailLevel, long dataPoint)
{ return createApiDatapoint(levelWrapper.getMinHeight(), mapping, detailLevel, dataPoint); }
public static DhApiTerrainDataPoint createApiDatapoint(int minLevelHeight, FullDataPointIdMap mapping, byte detailLevel, long dataPoint)
{
IBlockStateWrapper blockState = mapping.getBlockStateWrapper(FullDataPointUtil.getId(dataPoint));
@@ -0,0 +1,44 @@
package com.seibel.distanthorizons.core.util;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import java.nio.channels.ClosedByInterruptException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletionException;
import java.util.concurrent.RejectedExecutionException;
public class ExceptionUtil
{
public static boolean isShutdownException(Throwable throwable)
{
Throwable unwrappedCompletion = throwable;
while (unwrappedCompletion instanceof CompletionException)
{
unwrappedCompletion = unwrappedCompletion.getCause();
}
return isThrowableShutdown(throwable)
|| isThrowableShutdown(unwrappedCompletion);
}
private static boolean isThrowableShutdown(Throwable throwable)
{
return throwable instanceof InterruptedException
|| throwable instanceof UncheckedInterruptedException
|| throwable instanceof RejectedExecutionException
|| throwable instanceof ClosedByInterruptException;
}
public static boolean isInterruptOrReject(Throwable t)
{
Throwable unwrapped = ensureUnwrap(t);
return UncheckedInterruptedException.isInterrupt(unwrapped) ||
unwrapped instanceof RejectedExecutionException ||
unwrapped instanceof CancellationException;
}
public static Throwable ensureUnwrap(Throwable t)
{
return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t;
}
}
@@ -21,21 +21,11 @@ package com.seibel.distanthorizons.core.util;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletionException;
import java.util.concurrent.RejectedExecutionException;
import com.seibel.distanthorizons.api.enums.config.EDhApiVanillaOverdraw;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.vertexFormat.DefaultLodVertexFormats;
import com.seibel.distanthorizons.core.render.vertexFormat.LodVertexFormat;
import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger;
/**
@@ -191,17 +181,6 @@ public class LodUtil
throw new AssertFailureException("Assert Not Reach failed:\n " + message);
}
public static Throwable ensureUnwrap(Throwable t)
{
return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t;
}
public static boolean isInterruptOrReject(Throwable t)
{
Throwable unwrapped = LodUtil.ensureUnwrap(t);
return UncheckedInterruptedException.isInterrupt(unwrapped) ||
unwrapped instanceof RejectedExecutionException ||
unwrapped instanceof CancellationException;
}
}
@@ -1,111 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.objects;
import java.time.Duration;
import java.util.ArrayList;
public class EventTimer
{
public static class Event
{
public long timeNs = -1;
public String name;
Event(String name)
{
this.name = name;
}
}
long lastEventNs = -1;
public ArrayList<Event> events = new ArrayList<>();
public EventTimer(String firstEventName)
{
lastEventNs = System.nanoTime();
events.add(new Event(firstEventName));
}
public void nextEvent(String name)
{
long timeNs = System.nanoTime();
if (lastEventNs != -1 && !events.isEmpty() && events.get(events.size() - 1).timeNs == -1)
{
events.get(events.size() - 1).timeNs = timeNs - lastEventNs;
}
lastEventNs = timeNs;
events.add(new Event(name));
}
public void complete()
{
long timeNs = System.nanoTime();
if (lastEventNs != -1 && !events.isEmpty() && events.get(events.size() - 1).timeNs == -1)
{
events.get(events.size() - 1).timeNs = timeNs - lastEventNs;
}
lastEventNs = -1;
}
public long getEventTimeNs(String name)
{
for (Event e : events)
{
if (e.name.equals(name))
{
return e.timeNs;
}
}
return -1;
}
public long getTotalTimeNs()
{
long total = 0;
for (Event e : events)
{
if (e.timeNs != -1)
{
total += e.timeNs;
}
}
return total;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
for (Event e : events)
{
if (e.timeNs != -1)
{
sb.append(e.name).append(": ").append(Duration.ofNanos(e.timeNs)).append('\n');
}
else
{
sb.append(e.name).append(": ").append("N/A").append('\n');
}
}
return sb.toString();
}
}
@@ -41,7 +41,7 @@ public class RollingAverage
// input //
//=======//
public void addValue(double value)
public void add(double value)
{
this.arrayLock.lock();
try
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.util.objects;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import java.util.concurrent.CompletionException;
@@ -76,7 +76,7 @@ public class UncheckedInterruptedException extends RuntimeException
}
public static boolean isInterrupt(Throwable t)
{
Throwable unwrapped = LodUtil.ensureUnwrap(t);
Throwable unwrapped = ExceptionUtil.ensureUnwrap(t);
return unwrapped instanceof InterruptedException || unwrapped instanceof UncheckedInterruptedException;
}
@@ -20,9 +20,11 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.github.luben.zstd.RecyclingBufferPool;
import com.github.luben.zstd.Zstd;
import com.github.luben.zstd.ZstdInputStream;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import net.jpountz.lz4.LZ4FrameInputStream;
import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -48,11 +50,34 @@ public class DhDataInputStream extends DataInputStream
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public DhDataInputStream(InputStream stream, EDhApiDataCompressionMode compressionMode) throws IOException
{
super(warpStream(new BufferedInputStream(stream), compressionMode));
//=============//
// constructor //
//=============//
public static DhDataInputStream create(ByteArrayList byteArrayList, EDhApiDataCompressionMode compressionMode) throws IOException
{ return create(byteArrayList.toByteArray(), compressionMode); }
public static DhDataInputStream create(byte[] byteArray, EDhApiDataCompressionMode compressionMode) throws IOException
{
// Z_Std handling compression outside the stream provides a significant performance boost
ByteArrayInputStream byteArrayInputStream;
if (compressionMode == EDhApiDataCompressionMode.Z_STD)
{
byteArrayInputStream = new ByteArrayInputStream(Zstd.decompress(byteArray));
}
else
{
byteArrayInputStream = new ByteArrayInputStream(byteArray);
}
return new DhDataInputStream(byteArrayInputStream, compressionMode);
}
private static InputStream warpStream(InputStream stream, EDhApiDataCompressionMode compressionMode) throws IOException
private DhDataInputStream(ByteArrayInputStream stream, EDhApiDataCompressionMode compressionMode) throws IOException
{
super(warpStream(stream, compressionMode));
}
@SuppressWarnings("deprecation")
private static InputStream warpStream(ByteArrayInputStream stream, EDhApiDataCompressionMode compressionMode) throws IOException
{
try
{
@@ -63,15 +88,19 @@ public class DhDataInputStream extends DataInputStream
case LZ4:
return new LZ4FrameInputStream(stream);
case Z_STD:
return new ZstdInputStream(stream, RecyclingBufferPool.INSTANCE);
// ZStd compression should be handled before this point
// just return the stream
return stream;
case LZMA2:
// using an array cache significantly reduces GC pressure
ResettableArrayCache arrayCache = LZMA_RESETTABLE_ARRAY_CACHE_GETTER.get();
arrayCache.reset();
// Note: all LZMA/XZ compressors can be decompressed using this same InputStream
return new XZInputStream(stream, arrayCache);
case Z_STD_STREAM: // deprecated, only used for legacy support
return new ZstdInputStream(stream, RecyclingBufferPool.INSTANCE);
default:
throw new IllegalArgumentException("No compressor defined for [" + compressionMode + "]");
}
@@ -85,6 +114,11 @@ public class DhDataInputStream extends DataInputStream
}
//================//
// base overrides //
//================//
@Override
public int read() throws IOException
{
@@ -114,10 +148,6 @@ public class DhDataInputStream extends DataInputStream
}
}
// TODO at one point closing the streams caused errors, is that due to a bug with LZMA streams or some bug in DH's code that was since fixed?
// if streams aren't closed that cause cause higher-than-expected native memory use if the GC decides
// it doesn't want to clear the stream objects
//@Override
//public void close() throws IOException { /* Do nothing. */ }
}
@@ -19,9 +19,11 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.github.luben.zstd.Zstd;
import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FrameOutputStream;
import net.jpountz.xxhash.XXHashFactory;
@@ -41,23 +43,38 @@ public class DhDataOutputStream extends DataOutputStream
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final ThreadLocal<ResettableArrayCache> LZMA_RESETTABLE_ARRAY_CACHE_GETTER = ThreadLocal.withInitial(() -> new ResettableArrayCache(new LzmaArrayCache()));
private final ByteArrayList outputByteArray;
private final ByteArrayOutputStream wrappedByteStream;
private final EDhApiDataCompressionMode compressionMode;
public DhDataOutputStream(OutputStream stream, EDhApiDataCompressionMode compressionMode) throws IOException
//=============//
// constructor //
//=============//
/**
* @param outputByteArray where the contents of this stream will be written to when done
*/
public static DhDataOutputStream create(EDhApiDataCompressionMode compressionMode, ByteArrayList outputByteArray) throws IOException
{ return new DhDataOutputStream(new ByteArrayOutputStream(), compressionMode, outputByteArray); }
private DhDataOutputStream(ByteArrayOutputStream wrappedByteStream, EDhApiDataCompressionMode compressionMode, ByteArrayList outputByteArray) throws IOException
{
super(warpStream(new BufferedOutputStream(stream), compressionMode));
super(warpStream(wrappedByteStream, compressionMode));
this.wrappedByteStream = wrappedByteStream;
this.outputByteArray = outputByteArray;
this.compressionMode = compressionMode;
}
private static OutputStream warpStream(OutputStream stream, EDhApiDataCompressionMode compressionMode) throws IOException
@SuppressWarnings("deprecation")
private static OutputStream warpStream(ByteArrayOutputStream stream, EDhApiDataCompressionMode compressionMode) throws IOException
{
try
{
switch (compressionMode)
{
case UNCOMPRESSED:
return stream;
case Z_STD:
return new ZstdOutputStream(stream, 3, true, true);
return stream;
case LZ4:
return new LZ4FrameOutputStream(stream,
LZ4FrameOutputStream.BLOCKSIZE.SIZE_64KB, -1L,
@@ -68,6 +85,10 @@ public class DhDataOutputStream extends DataOutputStream
//LZ4Factory.nativeInstance().fastCompressor(),
//XXHashFactory.nativeInstance().hash32(),
LZ4FrameOutputStream.FLG.Bits.BLOCK_INDEPENDENCE);
case Z_STD:
// ZStd compression should be handled after the stream is closed
// just return the stream
return stream;
case LZMA2:
// using an array cache significantly reduces GC pressure
ResettableArrayCache arrayCache = LZMA_RESETTABLE_ARRAY_CACHE_GETTER.get();
@@ -77,6 +98,10 @@ public class DhDataOutputStream extends DataOutputStream
return new XZOutputStream(stream, new LZMA2Options(3),
XZ.CHECK_CRC64, arrayCache);
case Z_STD_STREAM: // deprecated, only used for legacy support
//return new ZstdOutputStream(stream, 3, true, true);
throw new UnsupportedOperationException("Z_STD_STREAM is deprecated and shouldn't be used for encoding. The faster block encoding format should be used instead.");
default:
throw new IllegalArgumentException("No compressor defined for ["+compressionMode+"]");
}
@@ -90,10 +115,29 @@ public class DhDataOutputStream extends DataOutputStream
}
// TODO at one point closing the streams caused errors, is that due to a bug with LZMA streams or some bug in DH's code that was since fixed?
// if streams aren't closed that cause cause higher-than-expected native memory use if the GC decides
// it doesn't want to clear the stream objects
//@Override
//public void close() throws IOException { /* Do nothing. */ }
//================//
// base overrides //
//================//
@Override
public void close() throws IOException
{
super.close();
this.outputByteArray.clear();
if (this.compressionMode == EDhApiDataCompressionMode.Z_STD)
{
this.outputByteArray.addElements(0, Zstd.compress(this.wrappedByteStream.toByteArray(), 3));
}
else
{
this.outputByteArray.addElements(0, this.wrappedByteStream.toByteArray());
}
}
}
@@ -190,7 +190,7 @@ public class PriorityTaskPicker
{
return new RateLimitedThreadPoolExecutor(
Config.Common.MultiThreading.numberOfThreads.get(),
new DhThreadFactory(this.name, Thread.MIN_PRIORITY, false),
new DhThreadFactory(this.name, Config.Common.MultiThreading.threadPriority.get(), false),
new ArrayBlockingQueue<>(Runtime.getRuntime().availableProcessors())
);
}
@@ -224,6 +224,13 @@ public class PriorityTaskPicker
@Override
public void execute(@NotNull Runnable command)
{
// needed so we don't try queuing tasks that will never be completed
if (this.threadPoolExecutor.isShutdown())
{
throw new RejectedExecutionException("Thread pool ["+this.name+"] shutdown.");
}
this.taskQueue.add(new TrackedRunnable(this.parentTaskPicker, this, command));
// Attempt to start the task immediately
@@ -249,6 +256,8 @@ public class PriorityTaskPicker
/** Will return NaN if nothing has been submitted yet */
public double getAverageRunTimeInMs() { return this.runTimeInMsRollingAverage.getAverage(); }
public void clearQueue() { this.taskQueue.clear(); }
//==========//
@@ -311,7 +320,7 @@ public class PriorityTaskPicker
finally
{
long timeElapsed = System.nanoTime() - startTime;
this.executor.runTimeInMsRollingAverage.addValue(TimeUnit.NANOSECONDS.toMillis(timeElapsed));
this.executor.runTimeInMsRollingAverage.add(TimeUnit.NANOSECONDS.toMillis(timeElapsed));
// Update variables related to task status
this.parentTaskPicker.occupiedThreadsRef.getAndDecrement();
@@ -54,7 +54,13 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
public void addPlayer(IServerPlayerWrapper serverPlayer)
{
ServerPlayerState playerState = this.serverPlayerStateManager.registerJoinedPlayer(serverPlayer);
((TDhServerLevel) this.getOrLoadServerLevel(serverPlayer.getLevel())).addPlayer(serverPlayer);
AbstractDhServerLevel serverLevel = (AbstractDhServerLevel) this.getOrLoadServerLevel(serverPlayer.getLevel());
if (serverLevel == null)
{
return;
}
serverLevel.addPlayer(serverPlayer);
Iterator<TDhServerLevel> it = this.dhLevelByLevelWrapper.values().stream().distinct().iterator();
while (it.hasNext())
@@ -117,18 +123,6 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
//==============//
// tick methods //
//==============//
@Override
public void serverTick() { this.dhLevelByLevelWrapper.values().forEach(TDhServerLevel::serverTick); }
@Override
public void worldGenTick() { this.dhLevelByLevelWrapper.values().forEach(TDhServerLevel::worldGenTick); }
//================//
// base overrides //
//================//
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.EventLoop;
@@ -67,9 +68,22 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
{
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) ->
{
DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager());
this.dhLevels.add(level);
return level;
try
{
DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager());
this.dhLevels.add(level);
return level;
}
catch (Exception e)
{
LOGGER.fatal("Failed to load client-server level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
"\u00A7c" + "Distant Horizons: ClientServer level loading failed." + "\u00A7r \n" +
"Unable to load level ["+levelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
}
});
}
else
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
@@ -74,8 +75,24 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
return null;
}
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper,
(clientLevelWrapper) -> new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState));
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper,
(clientLevelWrapper) ->
{
try
{
return new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState);
}
catch (Exception e)
{
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
"\u00A7c" + "Distant Horizons: Client level loading failed." + "\u00A7r \n" +
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
}
});
}
@Override
@@ -115,9 +132,6 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
@Override
public void clientTick() { this.eventLoop.tick(); }
@Override
public void worldGenTick() { this.levels.values().forEach(DhClientLevel::worldGenTick); }
@Override
public void addDebugMenuStringsToList(List<String> messageList)
{
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.level.DhServerLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
@@ -51,7 +52,23 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
}
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper,
(serverLevelWrapper) -> new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager()));
(serverLevelWrapper) ->
{
try
{
return new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager());
}
catch (Exception e)
{
LOGGER.fatal("Failed to load server level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
"\u00A7c" + "Distant Horizons: Server level loading failed." + "\u00A7r \n" +
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
}
});
}
@Override
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManag
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.Nullable;
/** Used both for dedicated server and singleplayer worlds */
public interface IDhServerWorld extends IDhWorld
@@ -32,8 +33,8 @@ public interface IDhServerWorld extends IDhWorld
void addPlayer(IServerPlayerWrapper serverPlayer);
void removePlayer(IServerPlayerWrapper serverPlayer);
void changePlayerLevel(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel);
void serverTick();
@Nullable
default IDhServerLevel getOrLoadServerLevel(ILevelWrapper levelWrapper) { return (IDhServerLevel) this.getOrLoadLevel(levelWrapper); }
}
@@ -31,6 +31,7 @@ import java.util.concurrent.CompletableFuture;
public interface IDhWorld extends Closeable
{
@Nullable
IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper);
@Nullable
IDhLevel getLevel(@NotNull ILevelWrapper wrapper);
@@ -39,6 +40,4 @@ public interface IDhWorld extends Closeable
void unloadLevel(@NotNull ILevelWrapper levelWrapper);
void worldGenTick();
}
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvironmentWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import java.io.IOException;
@@ -39,7 +39,7 @@ import java.util.HashSet;
*/
public interface IWrapperFactory extends IDhApiWrapperFactory, IBindable
{
AbstractBatchGenerationEnvironmentWrapper createBatchGenerator(IDhLevel targetLevel);
IBatchGeneratorEnvironmentWrapper createBatchGenerator(IDhLevel targetLevel);
IBiomeWrapper deserializeBiomeWrapper(String str, ILevelWrapper levelWrapper) throws IOException;
IBiomeWrapper getPlainsBiomeWrapper(ILevelWrapper levelWrapper); // TODO it would be nice to remove the level wrapper if possible to put this in line with getAirBlockStateWrapper() but it isn't necessary
@@ -158,6 +158,12 @@ public class ChunkLightStorage
lightSection.set(x, y, z, lightLevel);
}
public boolean isEmpty()
{
return this.lightSections == null
|| this.lightSections.length == 0;
}
public void clear()
{
if (this.lightSections != null)
@@ -32,4 +32,6 @@ public interface IMinecraftSharedWrapper extends IBindable
int getPlayerCount();
void setPreventAutoPause(boolean preventAutoPause);
}
@@ -17,34 +17,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.enums.worldGeneration;
package com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor;
/**
* MULTI_THREADED, <br>
* SINGLE_THREADED, <br>
* SERVER_THREAD, <br>
*
* @author James Seibel
* @version 7-25-2022
*/
public enum EWorldGenThreadMode
public interface IC2meAccessor extends IModAccessor
{
/**
* This world generator can be run on an unlimited number
* of concurrent threads.
*/
MULTI_THREADED,
/**
* This world generator can only be run on one thread at
* a time, however that thread can run concurrently
* to Minecraft's server thread.
*/
SINGLE_THREADED,
/**
* This world generator can only be run on Minecraft's
* server thread.
*/
SERVER_THREAD,
}
@@ -21,25 +21,21 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
public abstract class AbstractBatchGenerationEnvironmentWrapper
public interface IBatchGeneratorEnvironmentWrapper extends AutoCloseable
{
public AbstractBatchGenerationEnvironmentWrapper(IDhLevel level) { }
void updateAllFutures();
public abstract void updateAllFutures();
public abstract int getEventCount();
public abstract void stop();
public abstract CompletableFuture<Void> generateChunks(
int minX, int minZ, int genSize, EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep targetStep,
CompletableFuture<Void> queueGenEvent(
int minX, int minZ, int genSize,
EDhApiDistantGeneratorMode generatorMode, EDhApiWorldGenerationStep targetStep,
ExecutorService worldGeneratorThreadPool, Consumer<IChunkWrapper> resultConsumer);
void close();
}
@@ -678,6 +678,10 @@
"How many threads DH will use.",
"distanthorizons.config.common.multiThreading.threadRunTimeRatio":
"Runtime % for threads",
"distanthorizons.config.common.multiThreading.threadPriority":
"Thread Priority",
"distanthorizons.config.common.multiThreading.threadPriority.@tooltip":
"What Java thread priority should DH's primary thread pools run with?\n\nYou probably don't need to change this unless you are also \nrunning C2ME and are seeing thread starvation in either C2ME or DH.",
@@ -720,6 +724,8 @@
"Show Replay Warning",
"distanthorizons.config.common.logging.warning.showUpdateQueueOverloadedChatWarning":
"Show Update Queue Overloaded Warning",
"distanthorizons.config.common.logging.warning.showSlowWorldGenSettingWarnings":
"Show Slow World Gen Warnings",
"distanthorizons.config.common.logging.warning.showModCompatibilityWarningsOnStartup":
"Show Mod Compatibility Warnings",
@@ -975,7 +981,9 @@
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZ4":
"Fastest/Big - LZ4",
"distanthorizons.config.enum.EDhApiDataCompressionMode.Z_STD":
"Fast/Small - Z_STD",
"Fastest/Small - Z_STD - Block",
"distanthorizons.config.enum.EDhApiDataCompressionMode.Z_STD_STREAM":
"Fast/Small - Z_STD - Stream",
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZMA2":
"Slow/Smallest - LZMA2",
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -33,7 +34,7 @@ import java.util.Map;
public class TestCompoundKeyRepo extends AbstractDhRepo<DhChunkPos, TestCompoundKeyDto>
{
public TestCompoundKeyRepo(String databaseType, File databaseFile) throws SQLException
public TestCompoundKeyRepo(String databaseType, File databaseFile) throws SQLException, IOException
{
super(databaseType, databaseFile, TestCompoundKeyDto.class);
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -32,7 +33,7 @@ import java.util.Map;
public class TestPrimaryKeyRepo extends AbstractDhRepo<Integer, TestSingleKeyDto>
{
public TestPrimaryKeyRepo(String databaseType, File databaseFile) throws SQLException
public TestPrimaryKeyRepo(String databaseType, File databaseFile) throws SQLException, IOException
{
super(databaseType, databaseFile, TestSingleKeyDto.class);
@@ -0,0 +1,40 @@
package testItems.wrappers;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
public class TestBiomeWrapper implements IBiomeWrapper
{
private final String name;
public TestBiomeWrapper(String name)
{ this.name = name; }
@Override
public String getName()
{ return this.name; }
@Override
public String getSerialString()
{ return this.name; }
@Override
public Object getWrappedMcObject()
{ return this; }
@Override
public int hashCode()
{ return this.name.hashCode(); }
@Override
public boolean equals(Object obj)
{
if (!(obj instanceof TestBiomeWrapper))
{
return false;
}
return this.name.equals(((TestBiomeWrapper)obj).name);
}
}
@@ -0,0 +1,76 @@
package testItems.wrappers;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import java.awt.*;
public class TestBlockStateWrapper implements IBlockStateWrapper
{
private final String name;
public TestBlockStateWrapper(String name)
{ this.name = name; }
@Override
public boolean isAir()
{ return false; }
@Override
public boolean isSolid()
{ return true; }
@Override
public boolean isLiquid()
{ return false; }
@Override
public String getSerialString()
{ return this.name; }
@Override
public int getOpacity()
{ return 15; }
@Override
public int getLightEmission()
{ return 0; }
@Override
public byte getMaterialId()
{ return 0; }
@Override
public boolean isBeaconBlock()
{ return false; }
@Override
public boolean isBeaconTintBlock()
{ return false; }
@Override
public boolean allowsBeaconBeamPassage()
{ return false; }
@Override
public boolean isBeaconBaseBlock()
{ return false; }
@Override
public Color getMapColor()
{ return Color.MAGENTA; }
@Override
public Color getBeaconTintColor()
{ return Color.MAGENTA; }
@Override
public Object getWrappedMcObject()
{ return this; }
@Override
public int hashCode()
{ return this.name.hashCode(); }
@Override
public boolean equals(Object obj)
{
if (!(obj instanceof TestBlockStateWrapper))
{
return false;
}
return this.name.equals(((TestBlockStateWrapper)obj).name);
}
}
@@ -30,7 +30,6 @@ import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.junit.Assert;
@@ -50,7 +49,7 @@ import java.util.concurrent.atomic.LongAdder;
* Can also be used to test if there are memory leaks in SQLite
* and if the {@link FullDataSourceV2DTO}/{@link FullDataSourceV2}'s are using pooled objects correctly
*/
public class DhFullDataSourceRepoTests
public class DataSourceRepoTests
{
public static String DATABASE_TYPE = "jdbc:sqlite";
public static String DB_FILE_NAME = "test.sqlite";
@@ -395,4 +394,5 @@ public class DhFullDataSourceRepoTests
}
}
@@ -0,0 +1,236 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package tests;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
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 it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.junit.Assert;
import org.junit.Test;
import testItems.wrappers.TestBiomeWrapper;
import testItems.wrappers.TestBlockStateWrapper;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Random;
/**
*
*/
public class DataSourceUpdateSpeedTest
{
//@Test
public void test() throws DataCorruptedException
{
Random seededRandom = new Random(3);
//===================//
// parent datasource //
//===================//
long parentPos = DhSectionPos.encode((byte)7, 0, 0);
FullDataSourceV2 parentDataSource;
{
FullDataPointIdMap dataMapping = new FullDataPointIdMap(parentPos);
LongArrayList[] fullDataArray = new LongArrayList[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
for (int arrayIndex = 0; arrayIndex < FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH; arrayIndex++)
{
fullDataArray[arrayIndex] = new LongArrayList(1);
// random column heights so we can differentiate
// columns from each other
int columnCount = Math.abs(seededRandom.nextInt() % 31) + 1;
int lastMaxY = 4000;
for (int colIndex = columnCount; colIndex >= 0; colIndex--)
{
int height = (Math.abs(seededRandom.nextInt()) % 20) + 1;
lastMaxY -= height;
long datapoint = FullDataPointUtil.encode(
colIndex, // id
height, // height
lastMaxY, // relative min Y
LodUtil.MIN_MC_LIGHT, // block light
LodUtil.MIN_MC_LIGHT // sky light
);
fullDataArray[arrayIndex].add(datapoint);
dataMapping.addIfNotPresentAndGetId(
new TestBiomeWrapper(colIndex+""),
new TestBlockStateWrapper(colIndex+""));
}
FullDataSourceV2.throwIfDataColumnInWrongOrder(parentPos, fullDataArray[arrayIndex]);
}
byte[] columnGenStep = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
Arrays.fill(columnGenStep, EDhApiWorldGenerationStep.FEATURES.value);
byte[] columnWorldCompressionMode = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
Arrays.fill(columnWorldCompressionMode, EDhApiWorldCompressionMode.VISUALLY_EQUAL.value);
parentDataSource = FullDataSourceV2.createWithData(parentPos, dataMapping, fullDataArray, columnGenStep, columnWorldCompressionMode);
}
//==================//
// child datasource //
//==================//
long childPos = DhSectionPos.encode((byte)6, 0, 0);
FullDataSourceV2 childDataSource;
{
FullDataPointIdMap dataMapping = new FullDataPointIdMap(childPos);
LongArrayList[] fullDataArray = new LongArrayList[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
for (int arrayIndex = 0; arrayIndex < FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH; arrayIndex++)
{
fullDataArray[arrayIndex] = new LongArrayList(1);
// random column heights so we can differentiate
// columns from each other
int columnCount = Math.abs(seededRandom.nextInt() % 31) + 1;
int lastMaxY = 4000;
for (int colIndex = columnCount; colIndex >= 0; colIndex--)
{
int height = (Math.abs(seededRandom.nextInt()) % 20) + 1;
lastMaxY -= height;
long datapoint = FullDataPointUtil.encode(
colIndex, // id
height, // height
lastMaxY, // relative min Y
LodUtil.MAX_MC_LIGHT, // block light
LodUtil.MAX_MC_LIGHT // sky light
);
fullDataArray[arrayIndex].add(datapoint);
dataMapping.addIfNotPresentAndGetId(
new TestBiomeWrapper(colIndex+""),
new TestBlockStateWrapper(colIndex+""));
}
FullDataSourceV2.throwIfDataColumnInWrongOrder(childPos, fullDataArray[arrayIndex]);
}
byte[] columnGenStep = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
Arrays.fill(columnGenStep, EDhApiWorldGenerationStep.FEATURES.value);
byte[] columnWorldCompressionMode = new byte[FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH];
Arrays.fill(columnWorldCompressionMode, EDhApiWorldCompressionMode.VISUALLY_EQUAL.value);
childDataSource = FullDataSourceV2.createWithData(childPos, dataMapping, fullDataArray, columnGenStep, columnWorldCompressionMode);
}
//=========================//
// (optional) loop forever //
//=========================//
// this can be set to "true"
// so we can profile the DTO creation process and
// see if there are any object leaks and how the GC
// handles it
if (true)
{
System.out.println("Starting long update test for time testing...");
long lastLogMsTime = 0;
long totalNano = 0;
NumberFormat numberFormat = NumberFormat.getNumberInstance();
// run for a long time
for (int i = 1; i < 100_000_000; i++)
{
long startNano = System.nanoTime();
boolean updated = parentDataSource.updateFromDataSource(childDataSource);
//Assert.assertTrue(updated);
long updateTimeNano = System.nanoTime() - startNano;
totalNano += updateTimeNano;
long nowMs = System.currentTimeMillis();
if (nowMs - lastLogMsTime > 5_000)
{
lastLogMsTime = nowMs;
double avgMs = ((totalNano / 1_000_000.0)/i);
double totalMs = totalNano / 1_000_000.0;
System.out.println("count: "+numberFormat.format(i)+"\tavg ms: "+numberFormat.format(avgMs)+"\ttotal ms: "+numberFormat.format(totalMs));
}
}
}
}
//================//
// helper methods //
//================//
private static void assertArraysAreEqual(ByteArrayList expectedArray, ByteArrayList actualArray)
{
Assert.assertEquals("size mismatch", expectedArray.size(), actualArray.size());
for (int i = 0; i < expectedArray.size(); i++)
{
byte expectedNumb = expectedArray.getByte(i);
byte actualNumb = actualArray.getByte(i);
Assert.assertEquals("value mismatch at index ["+i+"]", expectedNumb, actualNumb);
}
}
private static void assertArraysAreEqual(LongArrayList expectedArray, LongArrayList actualArray)
{ assertArraysAreEqual(null, expectedArray, actualArray); }
private static void assertArraysAreEqual(String message, LongArrayList expectedArray, LongArrayList actualArray)
{
Assert.assertEquals(message + "size mismatch", expectedArray.size(), actualArray.size());
for (int i = 0; i < expectedArray.size(); i++)
{
long expectedNumb = expectedArray.getLong(i);
long actualNumb = actualArray.getLong(i);
Assert.assertEquals(message + "value mismatch at index ["+i+"]", expectedNumb, actualNumb);
}
}
}
@@ -34,6 +34,7 @@ import testItems.sql.TestPrimaryKeyRepo;
import testItems.sql.TestSingleKeyDto;
import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -137,7 +138,7 @@ public class DhRepoSqliteTest
Assert.assertFalse("DTO exists failed", primaryKeyRepo.existsWithKey(insertDto.getKey()));
}
catch (SQLException e)
catch (SQLException | IOException e)
{
Assert.fail(e.getMessage());
}
@@ -200,7 +201,7 @@ public class DhRepoSqliteTest
Assert.assertFalse("DTO exists failed", compoundKeyRepo.existsWithKey(insertDto.getKey()));
}
catch (SQLException e)
catch (SQLException | IOException e)
{
Assert.fail(e.getMessage());
}
@@ -330,7 +331,7 @@ public class DhRepoSqliteTest
Assert.assertNotEquals(0, primaryKeyRepo.openClosables.size());
}
}
catch (SQLException e)
catch (SQLException | IOException e)
{
Assert.fail(e.getMessage());
}
@@ -367,7 +368,7 @@ public class DhRepoSqliteTest
long endMs = System.currentTimeMillis();
System.out.println("Bulk update took ["+(endMs - startMs)+"] ms");
}
catch (SQLException e)
catch (SQLException | IOException e)
{
Assert.fail(e.getMessage());
}
+6 -7
View File
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.dto.util.VarintUtil;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataOutputStream;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import org.junit.Assert;
import org.junit.Test;
@@ -43,12 +44,12 @@ public class VarintTest
// zig zag encoding is needed for varint handling, so test it first
for (int i = -256; i < 256; i++)
{
//testZigZagEncoding(i);
testZigZagEncoding(i);
}
for (int i = -256; i < 256; i++)
{
//testSingleVarint(i);
testSingleVarint(i);
}
}
@@ -62,8 +63,8 @@ public class VarintTest
private static void testSingleVarint(int value)
{
// write to stream
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (DhDataOutputStream outputStream = new DhDataOutputStream(byteArrayOutputStream, EDhApiDataCompressionMode.UNCOMPRESSED))
ByteArrayList byteArrayList = new ByteArrayList();
try (DhDataOutputStream outputStream = DhDataOutputStream.create(EDhApiDataCompressionMode.UNCOMPRESSED, byteArrayList))
{
int encodedValue = VarintUtil.zigzagEncode(value);
VarintUtil.writeVarint(outputStream, encodedValue); // varint requires zig-zag encoding to function
@@ -76,9 +77,7 @@ public class VarintTest
// read stream
byte[] byteArray = byteArrayOutputStream.toByteArray();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
try (DhDataInputStream inputStream = new DhDataInputStream(byteArrayInputStream, EDhApiDataCompressionMode.UNCOMPRESSED))
try (DhDataInputStream inputStream = DhDataInputStream.create(byteArrayList, EDhApiDataCompressionMode.UNCOMPRESSED))
{
int encodedValue = VarintUtil.readVarint(inputStream);
int decodedValue = VarintUtil.zigzagDecode(encodedValue);