Compare commits

..

67 Commits

Author SHA1 Message Date
James Seibel a2c61ed278 up version number 2.3.7 -> 2.4.0 2025-12-13 10:19:50 -06:00
James Seibel 99eb4ac8a1 Fix infinite loop in DhSectionPos 2025-12-13 09:10:12 -06:00
James Seibel c75902d9d6 debug particle cleanup 2025-12-13 08:50:15 -06:00
James Seibel 1743949ba5 fix GeneratedFullDataSourceProvider not adding update listener 2025-12-13 08:49:45 -06:00
James Seibel a74a37a0e8 world gen queue refactoring 2025-12-13 08:49:31 -06:00
James Seibel 4ed7941288 fix missing localization 2025-12-12 07:45:12 -06:00
James Seibel ec59a5f754 comment cleanup and enum renaming for API use 2025-12-11 07:35:37 -06:00
James Seibel 895e04b7cc Remove unused wrapper functions and refactor 2025-12-10 18:50:35 -06:00
James Seibel 8f0930fa02 Allow world gen limits on singleplayer 2025-12-10 07:09:29 -06:00
James Seibel c1c4328fa5 rename API getSoftCache -> createSoftCahe 2025-12-09 20:57:27 -06:00
James Seibel 91240e4f7a disable mip-mapping on textures
necessary to fix MC 1.21.11 rendering
2025-12-09 20:57:09 -06:00
James Seibel 17c61a97cc revert long windows filepath char 2025-12-09 07:21:40 -06:00
James Seibel b78b852ffb Merge branch 'batchGenRefactor' 2025-12-09 07:16:18 -06:00
James Seibel 26d4220967 Add logging/messaging for corrupted DB files 2025-12-09 07:12:33 -06:00
James Seibel 5edc73cc03 enable long file paths for the config file 2025-12-06 12:28:22 -06:00
James Seibel 6fcfc9379e Fix repo unit tests 2025-12-06 12:27:53 -06:00
James Seibel 149fbccfa5 Merge branch 'batchGenRefactor' 2025-12-06 12:19:17 -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 4e9559f230 enable long file paths on windows for the DB 2025-12-02 07:07:17 -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
131 changed files with 2078 additions and 1614 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ insert_final_newline = false
max_line_length = 1000 max_line_length = 1000
tab_width = 4 tab_width = 4
trim_trailing_whitespace = false trim_trailing_whitespace = false
ij_continuation_indent_size = 8 ij_continuation_indent_size = 4
ij_formatter_off_tag = @formatter:off ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = true ij_formatter_tags_enabled = true
@@ -39,10 +39,6 @@ package com.seibel.distanthorizons.api.enums;
*/ */
public enum EDhApiDetailLevel public enum EDhApiDetailLevel
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** /**
* detail level: 0 <Br> * detail level: 0 <Br>
* width in Blocks: 1 * width in Blocks: 1
@@ -28,10 +28,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/ */
public enum EDhApiBlocksToAvoid public enum EDhApiBlocksToAvoid
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
NONE(false), NONE(false),
NON_COLLIDING(true); NON_COLLIDING(true);
@@ -23,6 +23,7 @@ package com.seibel.distanthorizons.api.enums.config;
* UNCOMPRESSED <br> * UNCOMPRESSED <br>
* LZ4 <br> * LZ4 <br>
* Z_STD <br> * Z_STD <br>
* Z_STD_STREAM <br>
* LZMA2 <br><br> * LZMA2 <br><br>
* *
* Note: speed and compression ratios are examples * Note: speed and compression ratios are examples
@@ -33,10 +34,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/ */
public enum EDhApiDataCompressionMode public enum EDhApiDataCompressionMode
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** /**
* Should only be used internally and for unit testing. <br><br> * Should only be used internally and for unit testing. <br><br>
* *
@@ -57,17 +54,31 @@ public enum EDhApiDataCompressionMode
LZ4(1), LZ4(1),
/** /**
* Decent speed and good compression. <br><br> * Great speed and good compression. <br><br>
*
* Read Speed: 2.1 MS / DTO <br>
* Write Speed: 4.9 MS / DTO <br>
* Compression ratio: 0.2606 <br>
*/
Z_STD_BLOCK(4),
/**
* Similar to {@link EDhApiDataCompressionMode#Z_STD_BLOCK}
* except slower. <br><br>
*
* This option is only provided for legacy support when processing old databases. <br><br>
* *
* Read Speed: 9.31 MS / DTO <br> * Read Speed: 9.31 MS / DTO <br>
* Write Speed: 15.13 MS / DTO <br> * Write Speed: 15.13 MS / DTO <br>
* Compression ratio: 0.2606 <br> * Compression ratio: 0.2606 <br>
*/ */
Z_STD(2), @Deprecated
@DisallowSelectingViaConfigGui
Z_STD_STREAM(2),
/** /**
* Extremely slow, but very good compression. <br><br> * Extremely slow, but very good compression. <br>
* Often causes whole computer stuttering due to memory bandwidth saturation. <br><br>
* *
* Read Speed: 13.29 MS / DTO <br> * Read Speed: 13.29 MS / DTO <br>
* Write Speed: 70.95 MS / DTO <br> * Write Speed: 70.95 MS / DTO <br>
@@ -44,9 +44,9 @@ public enum EDhApiGpuUploadMethod
/** Fast rendering but may stutter when uploading. */ /** Fast rendering but may stutter when uploading. */
SUB_DATA(false, false), SUB_DATA(false, false),
/** Don't upload, only should be used for debugging */ ///** Don't upload, only should be used for debugging */
@Deprecated // TODO remove before release //@Deprecated
NONE(false, false), //NONE(false, false),
/** /**
* May end up storing buffers in System memory. <br> * May end up storing buffers in System memory. <br>
@@ -29,10 +29,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/ */
public enum EDhApiGrassSideRendering public enum EDhApiGrassSideRendering
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
AS_GRASS, AS_GRASS,
FADE_TO_DIRT, FADE_TO_DIRT,
AS_DIRT; AS_DIRT;
@@ -31,11 +31,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/ */
public enum EDhApiHorizontalQuality public enum EDhApiHorizontalQuality
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
// Note: any quadraticBase less than 2.0f has issues with DetailDistanceUtil, and will always return the lowest detail level. // Note: any quadraticBase less than 2.0f has issues with DetailDistanceUtil, and will always return the lowest detail level.
// So for now we are limiting the lowest value to 2.0 // So for now we are limiting the lowest value to 2.0
// LOWEST was originally 1.0f and LOW was 1.5f // LOWEST was originally 1.0f and LOW was 1.5f
@@ -29,10 +29,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/ */
public enum EDhApiLodShading public enum EDhApiLodShading
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** /**
* Uses Minecraft's shading for LODs. <Br> * Uses Minecraft's shading for LODs. <Br>
* This means if Minecraft's shading is disabled DH's shading will be as well. * This means if Minecraft's shading is disabled DH's shading will be as well.
@@ -33,10 +33,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/ */
public enum EDhApiMcRenderingFadeMode public enum EDhApiMcRenderingFadeMode
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** /**
* No fading is done, there will be a pronounced border between * No fading is done, there will be a pronounced border between
* Minecraft and Distant Horizons. <br> * Minecraft and Distant Horizons. <br>
@@ -35,11 +35,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/ */
public enum EDhApiServerFolderNameMode public enum EDhApiServerFolderNameMode
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** Only use the server name */ /** Only use the server name */
NAME_ONLY, NAME_ONLY,
@@ -34,11 +34,6 @@ package com.seibel.distanthorizons.api.enums.config;
@Deprecated // not currently in use, if the config this enum represents is re-implemented, the deprecated flag can be removed @Deprecated // not currently in use, if the config this enum represents is re-implemented, the deprecated flag can be removed
public enum EDhApiVanillaOverdraw public enum EDhApiVanillaOverdraw
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** /**
* Don't draw LODs where a minecraft chunk could be. * Don't draw LODs where a minecraft chunk could be.
* Use Overdraw Offset to tweak the border thickness. * Use Overdraw Offset to tweak the border thickness.
@@ -28,10 +28,6 @@ package com.seibel.distanthorizons.api.enums.config;
*/ */
public enum EDhApiWorldCompressionMode public enum EDhApiWorldCompressionMode
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** /**
* Every block/biome change is recorded in the database. <br> * Every block/biome change is recorded in the database. <br>
* This is what DH 2.0 and 2.0.1 all used by default and will store a lot of data. * This is what DH 2.0 and 2.0.1 all used by default and will store a lot of data.
@@ -35,10 +35,6 @@ import com.seibel.distanthorizons.api.enums.config.DisallowSelectingViaConfigGui
*/ */
public enum EDhApiQualityPreset public enum EDhApiQualityPreset
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
@DisallowSelectingViaConfigGui @DisallowSelectingViaConfigGui
CUSTOM, CUSTOM,
@@ -34,10 +34,6 @@ import com.seibel.distanthorizons.api.enums.config.DisallowSelectingViaConfigGui
*/ */
public enum EDhApiThreadPreset public enum EDhApiThreadPreset
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
@DisallowSelectingViaConfigGui @DisallowSelectingViaConfigGui
CUSTOM, CUSTOM,
@@ -33,11 +33,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/ */
public enum EDhApiDebugRendering public enum EDhApiDebugRendering
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** LODs are rendered normally */ /** LODs are rendered normally */
OFF, OFF,
@@ -33,10 +33,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
@Deprecated @Deprecated
public enum EDhApiFogDrawMode public enum EDhApiFogDrawMode
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** /**
* Use whatever Fog setting Optifine is using. * Use whatever Fog setting Optifine is using.
* If Optifine isn't installed this defaults to {@link EDhApiFogDrawMode#FOG_ENABLED}. * If Optifine isn't installed this defaults to {@link EDhApiFogDrawMode#FOG_ENABLED}.
@@ -30,11 +30,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/ */
public enum EDhApiFogFalloff public enum EDhApiFogFalloff
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
LINEAR(0), LINEAR(0),
EXPONENTIAL(1), EXPONENTIAL(1),
EXPONENTIAL_SQUARED(2); EXPONENTIAL_SQUARED(2);
@@ -33,11 +33,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/ */
public enum EDhApiHeightFogDirection public enum EDhApiHeightFogDirection
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
ABOVE_CAMERA (true, true, false), ABOVE_CAMERA (true, true, false),
BELOW_CAMERA (true, false, true), BELOW_CAMERA (true, false, true),
ABOVE_AND_BELOW_CAMERA (true, true, true), ABOVE_AND_BELOW_CAMERA (true, true, true),
@@ -29,11 +29,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/ */
public enum EDhApiRendererMode public enum EDhApiRendererMode
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
DEFAULT, DEFAULT,
DEBUG, DEBUG,
DISABLED; DISABLED;
@@ -29,11 +29,6 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/ */
public enum EDhApiTransparency public enum EDhApiTransparency
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
DISABLED(false, false), DISABLED(false, false),
FAKE(true, true), FAKE(true, true),
COMPLETE(true, false); COMPLETE(true, false);
@@ -34,11 +34,6 @@ package com.seibel.distanthorizons.api.enums.worldGeneration;
*/ */
public enum EDhApiDistantGeneratorMode public enum EDhApiDistantGeneratorMode
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
/** Don't generate any new terrain, just generate LODs for already generated chunks. */ /** Don't generate any new terrain, just generate LODs for already generated chunks. */
PRE_EXISTING_ONLY((byte) 1), PRE_EXISTING_ONLY((byte) 1),
@@ -31,11 +31,6 @@ package com.seibel.distanthorizons.api.enums.worldGeneration;
*/ */
public enum EDhApiDistantGeneratorProgressDisplayLocation public enum EDhApiDistantGeneratorProgressDisplayLocation
{ {
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
OVERLAY, OVERLAY,
CHAT, CHAT,
LOG, LOG,
@@ -104,7 +104,7 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
* 2 = blending of 5x5 <br> * 2 = blending of 5x5 <br>
* ... <br> * ... <br>
*/ */
// IDhApiConfigValue<Integer> getBiomeBlending(); IDhApiConfigValue<Integer> getBiomeBlending();
@@ -161,9 +161,16 @@ public interface IDhApiTerrainDataRepo
//=========// //=========//
/** /**
* Creates a new cache you manage that can be used to speed up repeat
* read operations. <br>
* Without a cache each operation must: hit the backing database file,
* decompress it, and parse it; which is a fairly slow process. <br>
*
* @return a {@link IDhApiTerrainDataCache} backed by {@link java.lang.ref.SoftReference}'s. * @return a {@link IDhApiTerrainDataCache} backed by {@link java.lang.ref.SoftReference}'s.
* @since API 3.0.0 * @since API 5.0.0
*/ */
IDhApiTerrainDataCache getSoftCache(); IDhApiTerrainDataCache createSoftCache();
} }
@@ -41,6 +41,7 @@ public interface IDhApiEventInjector extends IDependencyInjector<IDhApiEvent>
* @throws IllegalArgumentException if the implementation object doesn't implement the interface * @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 // 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; boolean unbind(Class<? extends IDhApiEvent> dependencyInterface, Class<? extends IDhApiEvent> dependencyClassToRemove) throws IllegalArgumentException;
@@ -31,7 +31,7 @@ import java.util.List;
* Contains a list of {@link DhApiTerrainDataPoint} representing the blocks in a Minecraft chunk. * Contains a list of {@link DhApiTerrainDataPoint} representing the blocks in a Minecraft chunk.
* *
* @author Builderb0y, James Seibel * @author Builderb0y, James Seibel
* @version 2024-7-21 * @version 2025-12-11
* @since API 2.0.0 * @since API 2.0.0
* *
* @see IDhApiWrapperFactory * @see IDhApiWrapperFactory
@@ -54,27 +54,12 @@ public class DhApiChunk
// constructors // // constructors //
//==============// //==============//
/** /** @since API 3.0.0 */
* 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
*/
public static DhApiChunk create(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos) 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 */
* Only visible to internal DH methods private DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
* @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)
{ {
this.chunkPosX = chunkPosX; this.chunkPosX = chunkPosX;
this.chunkPosZ = chunkPosZ; this.chunkPosZ = chunkPosZ;
@@ -29,7 +29,7 @@ import java.util.ArrayList;
* Holds a single datapoint of terrain data. * Holds a single datapoint of terrain data.
* *
* @author James Seibel * @author James Seibel
* @version 2024-7-20 * @version 2025-11-15
* @since API 1.0.0 * @since API 1.0.0
*/ */
public class DhApiTerrainDataPoint public class DhApiTerrainDataPoint
@@ -47,6 +47,10 @@ public class DhApiTerrainDataPoint
public final int blockLightLevel; public final int blockLightLevel;
public final int skyLightLevel; 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 bottomYBlockPos;
public final int topYBlockPos; public final int topYBlockPos;
@@ -59,28 +63,7 @@ public class DhApiTerrainDataPoint
// constructors // // constructors //
//==============// //==============//
/** /** @since API 3.0.0 */
* 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
*/
public static DhApiTerrainDataPoint create( public static DhApiTerrainDataPoint create(
byte detailLevel, byte detailLevel,
int blockLightLevel, int skyLightLevel, int blockLightLevel, int skyLightLevel,
@@ -91,20 +74,15 @@ public class DhApiTerrainDataPoint
return new DhApiTerrainDataPoint( return new DhApiTerrainDataPoint(
detailLevel, blockLightLevel, skyLightLevel, detailLevel, blockLightLevel, skyLightLevel,
bottomYBlockPos, topYBlockPos, bottomYBlockPos, topYBlockPos,
blockStateWrapper, biomeWrapper, blockStateWrapper, biomeWrapper);
false);
} }
/** /** Only visible to internal DH methods */
* Only visible to internal DH methods
* @param ignoredParameter is only present to differentiate the two constructors and isn't actually used
*/
private DhApiTerrainDataPoint( private DhApiTerrainDataPoint(
byte detailLevel, byte detailLevel,
int blockLightLevel, int skyLightLevel, int blockLightLevel, int skyLightLevel,
int bottomYBlockPos, int topYBlockPos, int bottomYBlockPos, int topYBlockPos,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper, IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper
boolean ignoredParameter
) )
{ {
this.detailLevel = detailLevel; this.detailLevel = detailLevel;
@@ -118,4 +96,24 @@ public class DhApiTerrainDataPoint
this.biomeWrapper = biomeWrapper; 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,14 +31,14 @@ public final class ModInfo
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial"; 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. */ /** 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"; public static final String WRAPPER_PACKET_PATH = "message";
/** The internal mod name */ /** The internal mod name */
public static final String NAME = "DistantHorizons"; public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */ /** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons"; public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.3.7-b-dev"; public static final String VERSION = "2.4.0-b";
/** Returns true if the current build is an unstable developer build, false otherwise. */ /** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
@@ -97,10 +97,9 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks() public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks); } { return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks); }
// TODO re-implement @Override
// @Override public IDhApiConfigValue<Integer> getBiomeBlending()
// public IDhApiConfigValue<Integer> getBiomeBlending() { return new DhApiConfigValue<Integer, Integer>(Config.Client.Advanced.Graphics.Quality.lodBiomeBlending); }
// { return new DhApiConfigValue<Integer, Integer>(Quality.lodBiomeBlending); }
@@ -277,7 +277,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
if (!getSpecificYCoordinate) if (!getSpecificYCoordinate)
{ {
// if we aren't look for a specific datapoint, add each datapoint to the return array // 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 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 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 // 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}); return DhApiResult.createSuccess(new DhApiTerrainDataPoint[]{apiTerrainData});
} }
} }
@@ -345,7 +345,10 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
@Nullable @Nullable
IDhApiTerrainDataCache dataCache) 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(); rayDirection.normalize();
int minBlockHeight = levelWrapper.getMinHeight(); int minLevelBlockHeight = levelWrapper.getMinHeight();
int maxBlockHeight = levelWrapper.getMaxHeight(); int maxLevelBlockHeight = levelWrapper.getMaxHeight();
@@ -380,7 +383,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
DhApiRaycastResult closetFoundDataPoint = null; DhApiRaycastResult closetFoundDataPoint = null;
while (blockPos.y >= minBlockHeight && blockPos.y < maxBlockHeight while (blockPos.y >= minLevelBlockHeight
&& blockPos.y < maxLevelBlockHeight
&& currentLength <= maxRayBlockLength) && currentLength <= maxRayBlockLength)
{ {
// get the LOD columns around this position // get the LOD columns around this position
@@ -403,7 +407,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{ {
// does this LOD contain the given Y position? // does this LOD contain the given Y position?
Vec3i dataPointPos = new Vec3i(columnPos.x, dataPoint.bottomYBlockPos, columnPos.z); 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) if (closetFoundDataPoint == null)
{ {
@@ -516,7 +521,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//=============// //=============//
@Override @Override
public IDhApiTerrainDataCache getSoftCache() { return new DhApiTerrainDataCache(); } public IDhApiTerrainDataCache createSoftCache() { return new DhApiTerrainDataCache(); }
@@ -572,12 +577,16 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
} }
// draw raycast position // draw raycast position
if (rayCast.success && rayCast.payload != null) if (rayCast.success
&& rayCast.payload != null)
{ {
DebugRenderer.makeParticle( DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle( new DebugRenderer.BoxParticle(
new DebugRenderer.Box( 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 1.0, 0f
) )
); );
@@ -29,9 +29,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry; import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.renderer.VanillaFadeRenderer; import com.seibel.distanthorizons.core.render.renderer.*;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.RenderParams;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
@@ -45,9 +43,7 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel; import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.renderer.TestRenderer;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld; import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.world.IDhClientWorld; import com.seibel.distanthorizons.core.world.IDhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -338,12 +334,6 @@ public class ClientApi
if (clientWorld != null) if (clientWorld != null)
{ {
clientWorld.clientTick(); clientWorld.clientTick();
// Ignore local world gen, as it's managed by server ticking
if (!(clientWorld instanceof DhClientServerWorld))
{
SharedApi.worldGenTick(clientWorld::worldGenTick);
}
} }
} }
catch (Exception e) catch (Exception e)
@@ -570,8 +560,13 @@ public class ClientApi
{ {
// only fade when DH is rendering // only fade when DH is rendering
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
&&
(
// only fade when requested // only fade when requested
&& Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() == EDhApiMcRenderingFadeMode.DOUBLE_PASS 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 // don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering()) && !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{ {
@@ -9,6 +9,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -89,7 +90,7 @@ public class ClientPluginChannelApi
LOGGER.info("Server level key received: [" + msg.levelKey + "]."); LOGGER.info("Server level key received: [" + msg.levelKey + "].");
MC.executeOnRenderThread(() -> GLProxy.getInstance().queueRunningOnRenderThread(() ->
{ {
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true); IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(); IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
@@ -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 // // server events //
//===============// //===============//
@@ -141,16 +141,6 @@ public class SharedApi
} }
} }
public static void worldGenTick(Runnable worldGenRunnable)
{
lastWorldGenTickDelta--;
if (lastWorldGenTickDelta <= 0)
{
worldGenRunnable.run();
lastWorldGenTickDelta = 20;
}
}
@Nullable @Nullable
public static AbstractDhWorld getAbstractDhWorld() { return currentWorld; } 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.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class ChunkUpdateQueueManager public class ChunkUpdateQueueManager
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -15,6 +19,7 @@ public class ChunkUpdateQueueManager
public final ChunkPosQueue updateQueue; public final ChunkPosQueue updateQueue;
public final ChunkPosQueue preUpdateQueue; public final ChunkPosQueue preUpdateQueue;
public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
public int maxSize = 500; public int maxSize = 500;
@@ -38,12 +43,18 @@ public class ChunkUpdateQueueManager
// list/set methods // // 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() public void clear()
{ {
this.updateQueue.clear(); this.updateQueue.clear();
this.preUpdateQueue.clear(); this.preUpdateQueue.clear();
this.ignoredChunkPosSet.clear();
} }
public int getQueuedCount() { return this.updateQueue.getQueuedCount() + this.preUpdateQueue.getQueuedCount(); } public int getQueuedCount() { return this.updateQueue.getQueuedCount() + this.preUpdateQueue.getQueuedCount(); }
public boolean isEmpty() 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 // // position methods //
//==================// //==================//
@@ -103,10 +103,7 @@ public class Config
public static ConfigUiLinkedEntry quickEnableWorldGenerator = new ConfigUiLinkedEntry(Common.WorldGenerator.enableDistantGeneration); public static ConfigUiLinkedEntry quickEnableWorldGenerator = new ConfigUiLinkedEntry(Common.WorldGenerator.enableDistantGeneration);
public static ConfigEntry<Boolean> quickShowWorldGenProgress = new ConfigEntry.Builder<Boolean>() public static ConfigUiLinkedEntry quickShowWorldGenProgress = new ConfigUiLinkedEntry(Common.WorldGenerator.showGenerationProgress);
.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 quickLodCloudRendering = new ConfigUiLinkedEntry(Advanced.Graphics.GenericRendering.enableCloudRendering); public static ConfigUiLinkedEntry quickLodCloudRendering = new ConfigUiLinkedEntry(Advanced.Graphics.GenericRendering.enableCloudRendering);
@@ -126,7 +123,6 @@ public class Config
{ {
// common config links need to have their destination // common config links need to have their destination
// since they aren't part of "client" config class // since they aren't part of "client" config class
// TODO determine their destination programically instead of hard coding the value
public static ConfigUIComment advancedHeader = new ConfigUIComment.Builder().setParentConfigClass(Advanced.class).build(); public static ConfigUIComment advancedHeader = new ConfigUIComment.Builder().setParentConfigClass(Advanced.class).build();
@@ -172,6 +168,20 @@ public class Config
public static ConfigCategory culling = new ConfigCategory.Builder().set(Culling.class).build(); public static ConfigCategory culling = new ConfigCategory.Builder().set(Culling.class).build();
public static ConfigUISpacer cullingSpacer = new ConfigUISpacer.Builder().build(); public static ConfigUISpacer cullingSpacer = new ConfigUISpacer.Builder().build();
public static ConfigEntry<Boolean> overrideVanillaGraphicsSettings = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment("" +
"If true some vanilla graphics settings will be automatically changed \n" +
"during DH setup to provide a better experience. \n" +
" \n" +
"IE disabling vanilla clouds (which render on top of DH LODs), \n" +
" and chunk fading (DH already fades MC chunks) \n" +
"")
.build();
public static ConfigUISpacer overrideVanillaSpacer = new ConfigUISpacer.Builder().build();
public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build(); public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build();
@@ -263,7 +273,7 @@ public class Config
public static ConfigEntry<Double> lodBias = new ConfigEntry.Builder<Double>() public static ConfigEntry<Double> lodBias = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0d, 0d, null) .setMinDefaultMax(0d, 0d, null)
.comment("" .comment(""
+ "What the value should vanilla Minecraft's texture LodBias be? \n" + "What value should vanilla Minecraft's texture LodBias be? \n"
+ "If set to 0 the mod wont overwrite vanilla's default (which so happens to also be 0)") + "If set to 0 the mod wont overwrite vanilla's default (which so happens to also be 0)")
.build(); .build();
@@ -1365,6 +1375,37 @@ public class Config
+ "") + "")
.build(); .build();
public static ConfigEntry<Integer> generationCenterChunkX = new ConfigEntry.Builder<Integer>()
.setChatCommandName("generation.bounds.centerChunk.x")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.setMinDefaultMax(Integer.MIN_VALUE, 0, Integer.MAX_VALUE)
.comment("" +
"The center X chunk position that the world gen max radius is centered around. \n" +
"")
.build();
public static ConfigEntry<Integer> generationCenterChunkZ = new ConfigEntry.Builder<Integer>()
.setChatCommandName("generation.bounds.centerChunk.z")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.setMinDefaultMax(Integer.MIN_VALUE, 0, Integer.MAX_VALUE)
.comment("" +
"The center Z chunk position that the world gen max radius is centered around. \n" +
"")
.build();
public static ConfigEntry<Integer> generationMaxChunkRadius = new ConfigEntry.Builder<Integer>()
.setChatCommandName("generation.bounds.radiusInChunks")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.setMinDefaultMax(0, 0, Integer.MAX_VALUE)
.comment("" +
"The max radius in chunks around the central point where world generation is allowed. \n" +
"If this value is set to 0, generation bounds are disabled and the render distance will be used. \n" +
"\n" +
"This should only be set if you have a pre-generated world that has a very limited size. \n" +
"Setting this on a normal MC world will prevent the world generator from filling \n" +
"out your render distance. \n" +
"")
.build();
} }
public static class LodBuilding public static class LodBuilding
@@ -1390,37 +1431,9 @@ public class Config
.build(); .build();
public static ConfigEntry<EDhApiDataCompressionMode> dataCompression = new ConfigEntry.Builder<EDhApiDataCompressionMode>() public static ConfigEntry<EDhApiDataCompressionMode> dataCompression = new ConfigEntry.Builder<EDhApiDataCompressionMode>()
.set(EDhApiDataCompressionMode.Z_STD) .set(EDhApiDataCompressionMode.Z_STD_BLOCK)
.comment("" // only visible via the API since there is no reason to use any compressor except ZStandard as of 2025-11-24
+ "What algorithm should be used to compress new LOD data? \n" .setAppearance(EConfigEntryAppearance.ONLY_IN_API)
+ "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"
+ "")
.build(); .build();
public static ConfigEntry<EDhApiWorldCompressionMode> worldCompression = new ConfigEntry.Builder<EDhApiWorldCompressionMode>() public static ConfigEntry<EDhApiWorldCompressionMode> worldCompression = new ConfigEntry.Builder<EDhApiWorldCompressionMode>()
@@ -1443,49 +1456,6 @@ public class Config
+ "") + "")
.build(); .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(); public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build();
@@ -1527,6 +1497,7 @@ public class Config
+ "How many threads should be used by Distant Horizons? \n" + "How many threads should be used by Distant Horizons? \n"
+ "") + "")
.build(); .build();
public static final ConfigEntry<Double> threadRunTimeRatio = new ConfigEntry.Builder<Double>() public static final ConfigEntry<Double> threadRunTimeRatio = new ConfigEntry.Builder<Double>()
.setChatCommandName("threading.threadRunTimeRatio") .setChatCommandName("threading.threadRunTimeRatio")
.setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getDefaultRunTimeRatio(), 1.0) .setMinDefaultMax(0.01, ThreadPresetConfigEventHandler.getDefaultRunTimeRatio(), 1.0)
@@ -1540,6 +1511,19 @@ public class Config
"") "")
.build(); .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 +1560,6 @@ public class Config
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .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>() public static ConfigEntry<EDhApiLoggerLevel> logWorldGenChunkLoadEventToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.logWorldGenLoadEvent") .setChatCommandName("logging.logWorldGenLoadEvent")
.set(EDhApiLoggerLevel.INFO) .set(EDhApiLoggerLevel.INFO)
@@ -1669,6 +1645,14 @@ public class Config
+ "") + "")
.build(); .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>() public static ConfigEntry<Boolean> showModCompatibilityWarningsOnStartup = new ConfigEntry.Builder<Boolean>()
.set(true) .set(true)
.comment("" .comment(""
@@ -1746,32 +1730,6 @@ public class Config
"") "")
.build(); .build();
public static ConfigEntry<Integer> generationBoundsX = new ConfigEntry.Builder<Integer>()
.setChatCommandName("generation.bounds.x")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.setMinDefaultMax(Integer.MIN_VALUE, 0, Integer.MAX_VALUE)
.comment("" +
"Defines the X-coordinate of the central point for generation boundaries, in blocks. \n" +
"")
.build();
public static ConfigEntry<Integer> generationBoundsZ = new ConfigEntry.Builder<Integer>()
.setChatCommandName("generation.bounds.z")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.setMinDefaultMax(Integer.MIN_VALUE, 0, Integer.MAX_VALUE)
.comment("" +
"Defines the Z-coordinate of the central point for generation boundaries, in blocks. \n" +
"")
.build();
public static ConfigEntry<Integer> generationBoundsRadius = new ConfigEntry.Builder<Integer>()
.setChatCommandName("generation.bounds.radius")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.setMinDefaultMax(0, 0, Integer.MAX_VALUE)
.comment("" +
"Defines the radius around the central point within which generation is allowed, in blocks. \n" +
"If this value is set to 0, generation bounds are disabled." +
"")
.build();
// Real-time updates // Real-time updates
public static ConfigEntry<Boolean> enableRealTimeUpdates = new ConfigEntry.Builder<Boolean>() public static ConfigEntry<Boolean> enableRealTimeUpdates = new ConfigEntry.Builder<Boolean>()
@@ -1892,7 +1850,6 @@ public class Config
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickShowWorldGenProgressConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
} }
catch (Exception e) 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);
}
}
@@ -24,12 +24,14 @@ import com.seibel.distanthorizons.core.config.ConfigHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.config.types.AbstractConfigBase; import com.seibel.distanthorizons.core.config.types.AbstractConfigBase;
import com.seibel.distanthorizons.core.config.types.ConfigEntry; import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.jar.EPlatform;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -268,7 +268,7 @@ public class FullDataPointIdMap
// necessary to prevent issues with deserializing objects after the level has been closed // necessary to prevent issues with deserializing objects after the level has been closed
if (Thread.interrupted()) 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.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -92,8 +93,6 @@ public class FullDataSourceV2
public long lastModifiedUnixDateTime; public long lastModifiedUnixDateTime;
public long createdUnixDateTime; public long createdUnixDateTime;
public int levelMinY;
/** /**
* stores how far each column has been generated should start with {@link EDhApiWorldGenerationStep#EMPTY} * 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} * returns {@link FullDataPointUtil#EMPTY_DATA_POINT} if the given {@link DhBlockPos}
* is outside this data source's boundaries. * 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 // 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 // search for a datapoint that contains the given block y position
int relBlockPosY = blockPosY - levelMinY;
long dataPoint; long dataPoint;
for (int i = 0; i < dataColumn.size(); i++) for (int i = 0; i < dataColumn.size(); i++)
{ {
@@ -345,14 +345,13 @@ public class FullDataSourceV2
int requestedY = blockPos.getY(); int bottomY = FullDataPointUtil.getBottomY(dataPoint);
int bottomY = FullDataPointUtil.getBottomY(dataPoint) + this.levelMinY;
int height = FullDataPointUtil.getHeight(dataPoint); int height = FullDataPointUtil.getHeight(dataPoint);
int topY = bottomY + height; int topY = bottomY + height;
// does this datapoint contain the requested Y position? // does this datapoint contain the requested Y position?
if (bottomY <= requestedY if (bottomY <= relBlockPosY
&& requestedY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY && relBlockPosY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY
{ {
return dataPoint; return dataPoint;
} }
@@ -367,7 +366,7 @@ public class FullDataSourceV2
// updating // // updating //
//==========// //==========//
public boolean updateFromChunk(@NotNull FullDataSourceV2 inputDataSource) public boolean updateFromDataSource(@NotNull FullDataSourceV2 inputDataSource)
{ {
// don't try updating if the input is empty // don't try updating if the input is empty
if (inputDataSource.mapping.isEmpty()) if (inputDataSource.mapping.isEmpty())
@@ -470,7 +469,7 @@ public class FullDataSourceV2
return dataChanged; 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 // both data sources should have the same detail level
if (DhSectionPos.getDetailLevel(inputDataSource.pos) != DhSectionPos.getDetailLevel(this.pos)) if (DhSectionPos.getDetailLevel(inputDataSource.pos) != DhSectionPos.getDetailLevel(this.pos))
@@ -486,10 +485,14 @@ public class FullDataSourceV2
for (int z = 0; z < WIDTH; z++) for (int z = 0; z < WIDTH; z++)
{ {
int index = relativePosToIndex(x, z); int index = relativePosToIndex(x, z);
LongArrayList inputDataArray = inputDataSource.dataPoints[index]; LongArrayList inputDataArray = inputDataSource.dataPoints[index];
if (inputDataArray != null) if (inputDataArray == null)
{ {
continue;
}
byte thisGenState = this.columnGenerationSteps.getByte(index); byte thisGenState = this.columnGenerationSteps.getByte(index);
byte inputGenState = inputDataSource.columnGenerationSteps.getByte(index); byte inputGenState = inputDataSource.columnGenerationSteps.getByte(index);
@@ -515,9 +518,13 @@ public class FullDataSourceV2
genStateAllowsUpdating = true; genStateAllowsUpdating = true;
} }
if (!genStateAllowsUpdating)
if (genStateAllowsUpdating)
{ {
continue;
}
// check if the data changed // check if the data changed
if (this.dataPoints[index] == null) if (this.dataPoints[index] == null)
{ {
@@ -570,12 +577,11 @@ public class FullDataSourceV2
this.isEmpty = false; this.isEmpty = false;
} }
} }
}
}
return dataChanged; 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)) if (DhSectionPos.getDetailLevel(inputDataSource.pos) + 1 != DhSectionPos.getDetailLevel(this.pos))
{ {
@@ -711,176 +717,221 @@ public class FullDataSourceV2
{ {
LongArrayList newColumnList = new LongArrayList(); 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 };
int lastId = 0; //=========================//
byte lastBlockLight = 0; // get the 4 input columns //
byte lastSkyLight = 0; //=========================//
int height = 0;
int minY = 0;
LongArrayList[] inputColumns = new LongArrayList[4];
// 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++)
{
// 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; int colIndex = 0;
for (int inputX = x; inputX < x + 2; inputX++) for (int inputX = x; inputX < x + 2; inputX++)
{ {
for (int inputZ = z; inputZ < z + 2; inputZ++, colIndex++) 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... inputColumns[colIndex] = inputDataSource.dataPoints[relativePosToIndex(inputX, inputZ)];
LongArrayList inputDataArray = inputDataSource.dataPoints[relativePosToIndex(inputX, inputZ)]; if (inputColumns[colIndex] != null
if (inputDataArray == null || inputDataArray.size() == 0) && 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())
{ {
currentDatapointIndex[colIndex] = -1;
continue; continue;
} }
// determine the last index (the lowest data point) for each column for (int j = 0; j < inputColumns[i].size(); j++)
if (currentDatapointIndex[colIndex] == -2)
{ {
currentDatapointIndex[colIndex] = inputDataArray.size() - 1; long datapoint = inputColumns[i].getLong(j);
int minY = FullDataPointUtil.getBottomY(datapoint);
int maxY = minY + FullDataPointUtil.getHeight(datapoint);
if (RUN_DATA_ORDER_VALIDATION) if (!yTransitions.contains(minY))
{ {
throwIfDataColumnInWrongOrder(inputDataSource.pos, inputDataArray); yTransitions.add(minY);
}
} }
if (!yTransitions.contains(maxY))
int dataPointIndex = currentDatapointIndex[colIndex];
if (dataPointIndex == -1)
{ {
// went over the end yTransitions.add(maxY);
continue; }
}
} }
long datapoint = inputDataArray.getLong(dataPointIndex);
int datapointMinY = FullDataPointUtil.getBottomY(datapoint); // can happen if the columns are empty
int numbOfBlocksTall = FullDataPointUtil.getHeight(datapoint); if (yTransitions.isEmpty())
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 return newColumnList;
continue;
} }
else if (blockY >= datapointMaxY)
// 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++)
{ {
// this y-slice is above the current datapoint, if (inputColumns[i] != null
// try the next data point && !inputColumns[i].isEmpty())
int newDatapointIndex = currentDatapointIndex[colIndex] - 1;
if (newDatapointIndex < 0)
{ {
// went to far, no additional data present currentIndices[i] = inputColumns[i].size() - 1;
newDatapointIndex = -1;
} }
currentDatapointIndex[colIndex] = newDatapointIndex; else
{
currentIndices[i] = -1;
// try again with the next data point
inputZ--;
colIndex--;
continue;
}
datapointsForYSlice[colIndex] = datapoint;
} }
} }
//=======================//
// process each Y change //
//=======================//
int lastId = 0;
byte lastBlockLight = 0;
byte lastSkyLight = 0;
int currentMinY = yTransitions.getInt(0);
int accumulatedHeight = 0;
int[] mergeIds = new int[4];
int[] mergeBlockLights = new int[4];
int[] mergeSkyLights = new int[4];
for (int yIndex = 0; yIndex < yTransitions.size() - 1; yIndex++)
{
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(mergeIds, 0);
Arrays.fill(mergeBlockLights, 0); Arrays.fill(mergeBlockLights, 0);
Arrays.fill(mergeSkyLights, 0); Arrays.fill(mergeSkyLights, 0);
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
mergeIds[i] = FullDataPointUtil.getId(datapointsForYSlice[i]); // skip columns that are empty or where we have already reached the bottom
mergeBlockLights[i] = FullDataPointUtil.getBlockLight(datapointsForYSlice[i]); if (currentIndices[i] == -1)
mergeSkyLights[i] = FullDataPointUtil.getSkyLight(datapointsForYSlice[i]); {
continue;
} }
// determine the most common values for this slice LongArrayList column = inputColumns[i];
int id = determineMostValueInColumnSlice(mergeIds, inputDataSource.mapping); 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 merged values for this slice
int id = determineMostCommonValueInColumnSlice(mergeIds, inputDataSource.mapping);
byte blockLight = (byte) determineAverageValueInColumnSlice(mergeBlockLights); byte blockLight = (byte) determineAverageValueInColumnSlice(mergeBlockLights);
byte skyLight = (byte) determineAverageValueInColumnSlice(mergeSkyLights); byte skyLight = (byte) determineAverageValueInColumnSlice(mergeSkyLights);
// if this slice is different then the last one, create a new one // Check if we need to start a new datapoint
if (id != lastId if (accumulatedHeight == 0)
// block and sky light might not be necessary {
// first datapoint
lastId = id;
lastBlockLight = blockLight;
lastSkyLight = skyLight;
currentMinY = sliceMinY;
accumulatedHeight = sliceHeight;
}
else if (id != lastId
|| blockLight != lastBlockLight || blockLight != lastBlockLight
|| skyLight != lastSkyLight) || skyLight != lastSkyLight)
{ {
if (height != 0) // the data changed, create a new datapoint
{
try try
{ {
long datapoint = FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight); long datapoint = FullDataPointUtil.encode(lastId, accumulatedHeight, currentMinY, lastBlockLight, lastSkyLight);
newColumnList.add(datapoint); newColumnList.add(datapoint);
} }
catch (DataCorruptedException e) 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["+accumulatedHeight+"], minY["+currentMinY+"], 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["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
}
} }
// start the next datapoint
lastId = id; lastId = id;
lastBlockLight = blockLight; lastBlockLight = blockLight;
lastSkyLight = skyLight; lastSkyLight = skyLight;
height = 0; currentMinY = sliceMinY;
minY = blockY; 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 try
{ {
newColumnList.add(FullDataPointUtil.encode(lastId, height, minY, lastBlockLight, lastSkyLight)); newColumnList.add(FullDataPointUtil.encode(lastId, accumulatedHeight, currentMinY, lastBlockLight, lastSkyLight));
} }
catch (DataCorruptedException e) 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["+accumulatedHeight+"], minY["+currentMinY+"], 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["+height+"], minY["+minY+"], lastBlockLight["+lastBlockLight+"], lastSkyLight["+lastSkyLight+"].");
} }
} }
// flip the array if necessary // confirm the array is in the correct order
// 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?
ensureDataColumnOrder(newColumnList); ensureDataColumnOrder(newColumnList);
return newColumnList; return newColumnList;
@@ -898,23 +949,8 @@ public class FullDataSourceV2
dataColumn.set(i, FullDataPointUtil.remap(remappedIds, dataColumn.getLong(i))); 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 */ /** @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) 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))); int maxCount = Math.max(count0, Math.max(count1, Math.max(count2, count3)));
if (maxCount == count0) if (maxCount == count0)
// if the max count is 1 then we'll just go with the first column // if the max count is 1 then we'll just go with the first column
@@ -1271,13 +1307,13 @@ public class FullDataSourceV2
{ {
try try
{ {
LodDataBuilder.correctDataColumnOrder(columnDataPoints); LodDataBuilder.putListInTopDownOrder(columnDataPoints);
if (this.runApiChunkValidation) if (this.runApiChunkValidation)
{ {
LodDataBuilder.validateOrThrowApiDataColumn(columnDataPoints); 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 // 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); this.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.SURFACE, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
@@ -1300,7 +1336,7 @@ public class FullDataSourceV2
{ {
long datapoint = dataColumn.getLong(i); 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); apiList.add(apiDataPoint);
} }
@@ -157,8 +157,8 @@ public final class BufferQuad
return false; return false;
// make sure these quads share the same perpendicular axis // make sure these quads share the same perpendicular axis
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y) || if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y)
(mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown && this.x != quad.x)) || (mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown && this.x != quad.x))
{ {
return false; return false;
} }
@@ -24,19 +24,19 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; 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.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class ColumnBox public class ColumnBox
{ {
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
/** /**
* if the skylight has this value that means * if the skylight has this value that means
@@ -46,7 +46,6 @@ public class ColumnBox
//=========// //=========//
// builder // // builder //
//=========// //=========//
@@ -124,7 +123,7 @@ public class ColumnBox
&& !isTopTransparent; && !isTopTransparent;
if (!skipTop) if (!skipTop)
{ {
builder.addQuadUp(minX, maxY, minZ, width, width, ColorUtil.applyShade(color, MC.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); builder.addQuadUp(minX, maxY, minZ, width, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight);
} }
} }
@@ -135,7 +134,7 @@ public class ColumnBox
&& !isBottomTransparent; && !isBottomTransparent;
if (!skipBottom) if (!skipBottom)
{ {
builder.addQuadDown(minX, minY, minZ, width, width, ColorUtil.applyShade(color, MC.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); builder.addQuadDown(minX, minY, minZ, width, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight);
} }
} }
@@ -265,7 +264,7 @@ public class ColumnBox
// no adjacent data // // no adjacent data //
//==================// //==================//
color = ColorUtil.applyShade(color, MC.getShade(direction)); color = ColorUtil.applyShade(color, MC_RENDER.getShade(direction));
if (adjColumnView.size == 0 if (adjColumnView.size == 0
|| RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0))) || RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0)))
@@ -311,7 +310,8 @@ public class ColumnBox
long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA; long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA;
boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint); boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint);
boolean adjTransparent = !adjOverVoid boolean adjTransparent =
!adjOverVoid
&& RenderDataPointUtil.getAlpha(adjPoint) < 255 && RenderDataPointUtil.getAlpha(adjPoint) < 255
&& transparencyEnabled; && transparencyEnabled;
@@ -112,7 +112,8 @@ public class LodBufferContainer implements AutoCloseable
try try
{ {
// skip this event if requested // skip this event if requested
if (Thread.interrupted() || this.uploadFuture.isCancelled()) if (Thread.interrupted()
|| this.uploadFuture.isCancelled())
{ {
throw new InterruptedException(); throw new InterruptedException();
} }
@@ -32,6 +32,7 @@ import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
@@ -44,7 +45,7 @@ import org.lwjgl.system.MemoryUtil;
public class LodQuadBuilder public class LodQuadBuilder
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6]; private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
@@ -148,8 +149,18 @@ public class LodQuadBuilder
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!"); 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); 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() if (!quadList.isEmpty()
&& ( && (
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
@@ -369,7 +380,7 @@ public class LodQuadBuilder
// for horizontal and bottom faces of grass blocks, use the dirt color to // for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls // prevent green cliff walls
color = this.clientLevelWrapper.getDirtBlockColor(); color = this.clientLevelWrapper.getDirtBlockColor();
color = ColorUtil.applyShade(color, MC.getShade(quad.direction)); color = ColorUtil.applyShade(color, MC_RENDER.getShade(quad.direction));
} }
} }
} }
@@ -31,11 +31,9 @@ import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable; 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.util.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; 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.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; 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 DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); 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> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper);
HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(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(); int caveCullingMaxY = Config.Client.Advanced.Graphics.Culling.caveCullingHeight.get() - levelWrapper.getMinHeight();
boolean caveCullingEnabled = boolean caveCullingEnabled =
Config.Client.Advanced.Graphics.Culling.enableCaveCulling.get() Config.Client.Advanced.Graphics.Culling.enableCaveCulling.get()
@@ -252,9 +260,9 @@ public class FullDataToRenderDataTransformer
} }
catch (IndexOutOfBoundsException e) 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(); String levelId = levelWrapper.getDhIdentifier();
LOGGER.warn("Unable to get data point with id ["+id+"] " + LOGGER.warn("Unable to get data point with id ["+id+"] " +
"(Max possible ID: ["+fullDataMapping.getMaxValidId()+"]) " + "(Max possible ID: ["+fullDataMapping.getMaxValidId()+"]) " +
@@ -324,10 +332,26 @@ public class FullDataToRenderDataTransformer
// non-solid block check // // non-solid block check //
//=======================// //=======================//
if (ignoreNonCollidingBlocks boolean ignoreNonSolidBlock =
ignoreNonCollidingBlocks
&& !block.isSolid() && !block.isSolid()
&& !block.isLiquid() && !block.isLiquid()
&& block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE) && 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) if (colorBelowWithAvoidedBlocks)
{ {
@@ -74,7 +74,7 @@ public class LodDataBuilder
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ); long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos); 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 // chunk updates always propagate up
dataSource.applyToParent = true; dataSource.applyToParent = true;
@@ -244,6 +244,15 @@ public class LodDataBuilder
blockLight = newBlockLight; blockLight = newBlockLight;
skyLight = newSkyLight; skyLight = newSkyLight;
lastY = y; 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; return null;
} }
LodUtil.assertTrue(!dataSource.isEmpty);
return dataSource; return dataSource;
} }
@@ -289,13 +297,29 @@ public class LodDataBuilder
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++) for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{ {
List<DhApiTerrainDataPoint> columnDataPoints = apiChunk.getDataPoints(relBlockX, relBlockZ); 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) if (runAdditionalValidation)
{ {
validateOrThrowApiDataColumn(columnDataPoints); 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 // TODO add the ability for API users to define a different compression mode
// or add a "unkown" compression mode // or add a "unkown" compression mode
@@ -303,7 +327,6 @@ public class LodDataBuilder
packedDataPoints, packedDataPoints,
relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ, relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ,
EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS); EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
dataSource.isEmpty = false;
} }
} }
return dataSource; return dataSource;
@@ -317,41 +340,97 @@ public class LodDataBuilder
/** @see FullDataPointUtil */ /** @see FullDataPointUtil */
public static LongArrayList convertApiDataPointListToPackedLongArray( public static LongArrayList convertApiDataPointListToPackedLongArray(
@Nullable List<DhApiTerrainDataPoint> columnDataPoints, FullDataSourceV2 dataSource, @Nullable List<DhApiTerrainDataPoint> topDownColumnDataPoints, FullDataSourceV2 dataSource,
int bottomYBlockPos) throws DataCorruptedException int bottomYBlockPos, boolean runAdditionalValidation) throws DataCorruptedException
{ {
// this null check does 2 nice things at the same time: if (topDownColumnDataPoints == null
// if columnDataPoints is null, || topDownColumnDataPoints.size() == 0)
// 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++)
{ {
DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index); return new LongArrayList(0);
}
int id = dataSource.mapping.addIfNotPresentAndGetId(
(IBiomeWrapper) (dataPoint.biomeWrapper),
(IBlockStateWrapper) (dataPoint.blockStateWrapper) // 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 thisId = dataSource.mapping.addIfNotPresentAndGetId(
(IBiomeWrapper) (apiDataPoint.biomeWrapper),
(IBlockStateWrapper) (apiDataPoint.blockStateWrapper)
); );
int thisHeight = (apiDataPoint.topYBlockPos - apiDataPoint.bottomYBlockPos);
packedDataPoints.set(index, FullDataPointUtil.encode( int lastId = FullDataPointUtil.getId(lastDataPoint);
id, byte lastBlockLight = (byte)FullDataPointUtil.getBlockLight(lastDataPoint);
dataPoint.topYBlockPos - dataPoint.bottomYBlockPos, byte lastSkyLight = (byte)FullDataPointUtil.getSkyLight(lastDataPoint);
dataPoint.bottomYBlockPos - bottomYBlockPos,
(byte) (dataPoint.blockLightLevel),
(byte) (dataPoint.skyLightLevel) // 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; return packedDataPoints;
} }
/** also corrects the order if it's backwards */ public static void putListInTopDownOrder(List<DhApiTerrainDataPoint> dataPoints)
public static void correctDataColumnOrder(List<DhApiTerrainDataPoint> dataPoints)
{ {
// order doesn't need to be checked if there is 0 or 1 items // order doesn't need to be checked if there is 0 or 1 items
if (dataPoints.size() > 1) if (dataPoints.size() > 1)
@@ -0,0 +1,12 @@
package com.seibel.distanthorizons.core.enums;
/**
* TODO
* might be deprecated in the future? in that case we'll probably want a wrapper
* function to handle colors for new MC versions
*
* source: https://minecraft.wiki/w/Formatting_codes
*/
public class EMinecraftColor
{
}
@@ -104,7 +104,7 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
} }
// write the new data into memory // write the new data into memory
memoryDataSource.updateFromChunk(inputDataSource); memoryDataSource.updateFromDataSource(inputDataSource);
// keep track of when the last time we saved something was // keep track of when the last time we saved something was
pair.updateLastWrittenTimestamp(); pair.updateLastWrittenTimestamp();
} }
@@ -47,6 +47,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -80,8 +82,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// constructor // // constructor //
//=============// //=============//
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) { super(level, saveStructure); } public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) { this(level, saveStructure, null); }
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException
{ {
super(level, saveStructure, saveDirOverride); 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.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue; import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
import com.seibel.distanthorizons.core.level.IDhLevel; 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.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue; import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Only handles {@link SyncOnLoadRequestQueue} requests (IE updating existing LODs based on a timestamp). * 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 public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvider
{ {
@@ -58,7 +60,8 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide
public RemoteFullDataSourceProvider( public RemoteFullDataSourceProvider(
IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride, IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride,
@Nullable SyncOnLoadRequestQueue syncOnLoadRequestQueue) @Nullable SyncOnLoadRequestQueue syncOnLoadRequestQueue
) throws SQLException, IOException
{ {
super(level, saveStructure, saveDirOverride); super(level, saveStructure, saveDirOverride);
this.syncOnLoadRequestQueue = syncOnLoadRequestQueue; this.syncOnLoadRequestQueue = syncOnLoadRequestQueue;
@@ -43,7 +43,7 @@ public class FullDataSourceProviderV1<TDhLevel extends IDhLevel>
// constructor // // constructor //
//=============// //=============//
public FullDataSourceProviderV1(TDhLevel level, File saveDir) public FullDataSourceProviderV1(TDhLevel level, File saveDir) throws SQLException, IOException
{ {
this.level = level; this.level = level;
this.saveDir = saveDir; 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."); 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 // // 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 protected FullDataSourceV1 createDataSourceFromDto(FullDataSourceV1DTO dto) throws InterruptedException, IOException, DataCorruptedException
{ {
FullDataSourceV1 dataSource = FullDataSourceV1.createEmpty(dto.pos); 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 com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -62,7 +64,8 @@ public class DataMigratorV1 implements IDebugRenderable, AutoCloseable
public DataMigratorV1( public DataMigratorV1(
FullDataUpdaterV2 dataUpdater, FullDataUpdaterV2 dataUpdater,
IDhLevel level, String levelId, File saveDir) IDhLevel level, String levelId, File saveDir
) throws SQLException, IOException
{ {
this.dataUpdater = dataUpdater; this.dataUpdater = dataUpdater;
this.saveDir = saveDir; this.saveDir = saveDir;
@@ -96,11 +96,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
// constructor // // constructor //
//=============// //=============//
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure) { this(level, saveStructure, null); } public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException { this(level, saveStructure, null); }
public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) public FullDataSourceProviderV2(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException
{ {
this.saveDir = (saveDirOverride == null) ? saveStructure.getSaveFolder(level.getLevelWrapper()) : saveDirOverride; 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.level = level;
this.levelId = this.level.getLevelWrapper().getDhIdentifier(); 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); 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) public void addDataSourceUpdateListener(IDataSourceUpdateListenerFunc<FullDataSourceV2> listener)
{ {
synchronized (this.dataUpdater) synchronized (this.dataUpdater.dateSourceUpdateListeners)
{ {
this.dataUpdater.dateSourceUpdateListeners.add(listener); this.dataUpdater.dateSourceUpdateListeners.add(listener);
} }
} }
public void removeDataSourceUpdateListener(IDataSourceUpdateListenerFunc<FullDataSourceV2> listener) public void removeDataSourceUpdateListener(IDataSourceUpdateListenerFunc<FullDataSourceV2> listener)
{ {
synchronized (this.dataUpdater) synchronized (this.dataUpdater.dateSourceUpdateListeners)
{ {
this.dataUpdater.dateSourceUpdateListeners.add(listener); this.dataUpdater.dateSourceUpdateListeners.add(listener);
} }
@@ -239,7 +226,27 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
catch (InterruptedException ignore) { } catch (InterruptedException ignore) { }
catch (IOException e) 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 // an error occurred
@@ -82,6 +82,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
} }
//================// //================//
// parent updates // // parent updates //
//================// //================//
@@ -184,7 +185,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
parentLocked = true; parentLocked = true;
this.dataUpdater.lockedPosSet.add(parentUpdatePos); 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 // will return null if the file handler is shutting down
if (parentDataSource != null) if (parentDataSource != null)
@@ -203,7 +204,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
// can return null when the file handler is being shut down // can return null when the file handler is being shut down
if (childDataSource != null) if (childDataSource != null)
{ {
parentDataSource.updateFromChunk(childDataSource); parentDataSource.updateFromDataSource(childDataSource);
} }
} }
} }
@@ -213,6 +214,8 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
} }
finally finally
{ {
this.provider.repo.setApplyToParent(childPos, false);
childReadLock.unlock(); childReadLock.unlock();
this.dataUpdater.lockedPosSet.remove(childPos); this.dataUpdater.lockedPosSet.remove(childPos);
} }
@@ -224,11 +227,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
parentDataSource.applyToParent = true; parentDataSource.applyToParent = true;
} }
this.dataUpdater.updateDataSource(parentDataSource, false); this.dataUpdater.updateDataSource(parentDataSource);
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 // will return null if the file handler is shutting down
if (childDataSource != null) if (childDataSource != null)
{ {
childDataSource.updateFromChunk(parentDataSource); childDataSource.updateFromDataSource(parentDataSource);
// don't propagate child updates past the bottom of the tree // don't propagate child updates past the bottom of the tree
if (DhSectionPos.getDetailLevel(childPos) != DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL) if (DhSectionPos.getDetailLevel(childPos) != DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL)
@@ -329,7 +328,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
childDataSource.applyToChildren = true; childDataSource.applyToChildren = true;
} }
this.dataUpdater.updateDataSource(childDataSource, false); this.dataUpdater.updateDataSource(childDataSource);
} }
} }
} }
@@ -339,12 +338,12 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
} }
finally finally
{ {
this.provider.repo.setApplyToChild(parentUpdatePos, false);
childWriteLock.unlock(); childWriteLock.unlock();
this.dataUpdater.lockedPosSet.remove(childPos); this.dataUpdater.lockedPosSet.remove(childPos);
} }
} }
this.provider.repo.setApplyToChild(parentUpdatePos, false);
} }
} }
} }
@@ -63,7 +63,7 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
/** /**
* Can be used if you don't want to lock the current thread * Can be used if you don't want to lock the current thread
* Otherwise the sync version {@link FullDataUpdaterV2#updateDataSource(FullDataSourceV2, boolean)} may be a better choice. * Otherwise the sync version {@link FullDataUpdaterV2#updateDataSource(FullDataSourceV2)} may be a better choice.
*/ */
public CompletableFuture<Void> updateDataSourceAsync(@NotNull FullDataSourceV2 inputDataSource) public CompletableFuture<Void> updateDataSourceAsync(@NotNull FullDataSourceV2 inputDataSource)
{ {
@@ -86,7 +86,7 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
{ {
try try
{ {
this.updateDataSource(inputDataSource, true); this.updateDataSource(inputDataSource);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -107,7 +107,7 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
} }
/** After this method returns the inputData will be written to file. */ /** After this method returns the inputData will be written to file. */
public void updateDataSource(@NotNull FullDataSourceV2 inputData, boolean lockOnUpdatePos) public void updateDataSource(@NotNull FullDataSourceV2 inputData)
{ {
if (this.isShutdownRef.get()) if (this.isShutdownRef.get())
{ {
@@ -117,27 +117,22 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
long updatePos = inputData.getPos(); long updatePos = inputData.getPos();
boolean methodLocked = false;
// a lock is necessary to prevent two threads from writing to the same position at once, // a lock is necessary to prevent two threads from writing to the same position at once,
// if that happens only the second update will apply and the LOD will end up with hole(s) // if that happens only the second update will apply and the LOD will end up with hole(s)
ReentrantLock updateLock = this.updateLockProvider.getLock(updatePos); ReentrantLock updateLock = this.updateLockProvider.getLock(updatePos);
try try
{ {
if (lockOnUpdatePos)
{
methodLocked = true;
updateLock.lock(); updateLock.lock();
this.lockedPosSet.add(updatePos); this.lockedPosSet.add(updatePos);
}
// get or create the data source // get or create the data source
try (FullDataSourceV2 recipientDataSource = this.provider.get(updatePos)) try (FullDataSourceV2 recipientDataSource = this.provider.get(updatePos))
{ {
if (recipientDataSource != null) if (recipientDataSource != null) // will be null if the repo was shut down
{ {
boolean dataModified = recipientDataSource.updateFromChunk(inputData); boolean dataModified = recipientDataSource.updateFromDataSource(inputData);
if (dataModified) if (dataModified)
{ {
// save the updated data to the database // save the updated data to the database
@@ -150,6 +145,8 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
} }
synchronized (this.dateSourceUpdateListeners)
{
for (IDataSourceUpdateListenerFunc<FullDataSourceV2> listener : this.dateSourceUpdateListeners) for (IDataSourceUpdateListenerFunc<FullDataSourceV2> listener : this.dateSourceUpdateListeners)
{ {
if (listener != null) if (listener != null)
@@ -161,19 +158,17 @@ public class FullDataUpdaterV2 implements IDebugRenderable, AutoCloseable
} }
} }
} }
}
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Error updating pos ["+DhSectionPos.toString(updatePos)+"], error: "+e.getMessage(), e); LOGGER.error("Error updating pos ["+DhSectionPos.toString(updatePos)+"], error: "+e.getMessage(), e);
} }
finally finally
{
if (methodLocked)
{ {
updateLock.unlock(); updateLock.unlock();
this.lockedPosSet.remove(updatePos); this.lockedPosSet.remove(updatePos);
} }
} }
}
private FullDataSourceV2DTO createDtoFromDataSource(FullDataSourceV2 dataSource) private FullDataSourceV2DTO createDtoFromDataSource(FullDataSourceV2 dataSource)
{ {
@@ -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.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; 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.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.IBatchGeneratorEnvironmentWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.worldGeneration.AbstractBatchGenerationEnvironmentWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -42,10 +43,10 @@ import java.util.function.Consumer;
*/ */
public class BatchGenerator implements IDhApiWorldGenerator 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(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public AbstractBatchGenerationEnvironmentWrapper generationEnvironment; public IBatchGeneratorEnvironmentWrapper generationEnvironment;
public IDhLevel targetDhLevel; public IDhLevel targetDhLevel;
@@ -57,7 +58,7 @@ public class BatchGenerator implements IDhApiWorldGenerator
public BatchGenerator(IDhLevel targetDhLevel) public BatchGenerator(IDhLevel targetDhLevel)
{ {
this.targetDhLevel = targetDhLevel; this.targetDhLevel = targetDhLevel;
this.generationEnvironment = FACTORY.createBatchGenerator(targetDhLevel); this.generationEnvironment = WRAPPER_FACTORY.createBatchGenerator(targetDhLevel);
LOGGER.info("Batch Chunk Generator initialized"); LOGGER.info("Batch Chunk Generator initialized");
} }
@@ -83,26 +84,26 @@ public class BatchGenerator implements IDhApiWorldGenerator
//===================// //===================//
// generator methods // // generator methods //
//===================// //===================//
@Override @Override
public CompletableFuture<Void> generateChunks( public CompletableFuture<Void> generateChunks(
int chunkPosMinX, int chunkPosMinZ, int generationRequestChunkWidthCount, byte targetDataDetail, EDhApiDistantGeneratorMode generatorMode, int chunkPosMinX,
ExecutorService worldGeneratorThreadPool, Consumer<Object[]> resultConsumer) int chunkPosMinZ,
int chunkWidthCount,
byte targetDataDetail,
EDhApiDistantGeneratorMode generatorMode,
ExecutorService worldGeneratorThreadPool,
Consumer<Object[]> resultConsumer)
{ {
EDhApiWorldGenerationStep targetStep = null; EDhApiWorldGenerationStep targetStep;
switch (generatorMode) 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 PRE_EXISTING_ONLY: // Only load in existing chunks.
//case BIOME_ONLY: // No blocks. Require fake height in LodBuilder targetStep = EDhApiWorldGenerationStep.EMPTY; // special logic
targetStep = EDhApiWorldGenerationStep.BIOMES;
break; break;
//case BIOME_ONLY_SIMULATE_HEIGHT:
// targetStep = EDhApiWorldGenerationStep.NOISE; // Stone only. Requires a fake surface
// break;
case SURFACE: case SURFACE:
targetStep = EDhApiWorldGenerationStep.SURFACE; targetStep = EDhApiWorldGenerationStep.SURFACE;
break; break;
@@ -112,20 +113,27 @@ public class BatchGenerator implements IDhApiWorldGenerator
case INTERNAL_SERVER: case INTERNAL_SERVER:
targetStep = EDhApiWorldGenerationStep.LIGHT; targetStep = EDhApiWorldGenerationStep.LIGHT;
break; 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) // 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}); Consumer<IChunkWrapper> consumerWrapper = (chunkWrapper) -> resultConsumer.accept(new Object[]{chunkWrapper});
try try
{ {
return this.generationEnvironment.generateChunks( return this.generationEnvironment.queueGenEvent(
chunkPosMinX, chunkPosMinZ, generationRequestChunkWidthCount, chunkPosMinX, chunkPosMinZ, chunkWidthCount,
generatorMode, targetStep, generatorMode, targetStep,
worldGeneratorThreadPool, consumerWrapper); worldGeneratorThreadPool, consumerWrapper);
} }
catch (Exception e) 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<>(); CompletableFuture<Void> future = new CompletableFuture<>();
future.completeExceptionally(e); future.completeExceptionally(e);
return future; return future;
@@ -144,9 +152,10 @@ public class BatchGenerator implements IDhApiWorldGenerator
@Override @Override
public void close() public void close()
{ {
LOGGER.info(BatchGenerator.class.getSimpleName() + " shutting down..."); LOGGER.info("["+BatchGenerator.class.getSimpleName()+"] shutting down...");
this.generationEnvironment.stop(); this.generationEnvironment.close();
} }
} }
@@ -154,14 +154,15 @@ public class DhLightingEngine
// and get any necessary info from them // 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 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); IChunkWrapper neighborChunk = nearbyChunkList.get(chunkIndex);
if (chunk != null && requestedAdjacentPositions.contains(chunk.getChunkPos())) if (neighborChunk != null
&& requestedAdjacentPositions.contains(neighborChunk.getChunkPos()))
{ {
// remove the newly found position // remove the newly found position
requestedAdjacentPositions.remove(chunk.getChunkPos()); requestedAdjacentPositions.remove(neighborChunk.getChunkPos());
// add the adjacent chunk // add the adjacent chunk
adjacentChunkHolder.add(chunk); adjacentChunkHolder.add(neighborChunk);
// get and set the adjacent chunk's initial block lights // get and set the adjacent chunk's initial block lights
final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get(); final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get();
@@ -174,19 +175,19 @@ public class DhLightingEngine
if (updateBlockLight) 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 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); DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex);
blockLightPos.mutateToChunkRelativePos(relLightBlockPos); blockLightPos.mutateToChunkRelativePos(relLightBlockPos);
// get the light // get the light
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos); IBlockStateWrapper blockState = neighborChunk.getBlockState(relLightBlockPos);
int lightValue = blockState.getLightEmission(); int lightValue = blockState.getLightEmission();
blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue); blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue);
// set the light // 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, // get and set the adjacent chunk's initial skylights,
// if the dimension has 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; IBlockStateWrapper previousBlockState = null;
int maxY = chunk.getMaxNonEmptyHeight(); int maxY = neighborChunk.getMaxNonEmptyHeight();
int minY = chunk.getInclusiveMinBuildHeight(); int minY = neighborChunk.getInclusiveMinBuildHeight();
// get the adjacent chunk's sky lights // get the adjacent chunk's sky lights
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) // relative block pos for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) // relative block pos
{ {
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) 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--) 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) if (block != null && block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT)
{ {
// keep moving down until we find a non-transparent block // keep moving down until we find a non-transparent block
@@ -223,12 +225,12 @@ public class DhLightingEngine
// add sky light to the queue // 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); skyLightWorldPosQueue.push(skyLightPos.getX(), skyLightPos.getY(), skyLightPos.getZ(), maxSkyLight);
// set the chunk's sky light // set the chunk's sky light
skyLightPos.mutateToChunkRelativePos(relLightBlockPos); skyLightPos.mutateToChunkRelativePos(relLightBlockPos);
chunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight); neighborChunk.setDhSkyLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), maxSkyLight);
} }
} }
} }
@@ -247,7 +249,6 @@ public class DhLightingEngine
if (updateBlockLight) if (updateBlockLight)
{ {
// done to prevent a rare issue where the light values are incorrectly set to -1 // done to prevent a rare issue where the light values are incorrectly set to -1
// TODO why could that happen?
centerChunk.clearDhBlockLighting(); centerChunk.clearDhBlockLighting();
this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder, this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
@@ -300,9 +301,25 @@ public class DhLightingEngine
final DhBlockPosMutable neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get(); final DhBlockPosMutable neighbourBlockPos = PRIMARY_BLOCK_POS_REF.get();
final DhBlockPosMutable relNeighbourBlockPos = SECONDARY_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; 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; IBlockStateWrapper previousBlockState = null;
// update each light position // update each light position
while (!lightPosQueue.isEmpty()) while (!lightPosQueue.isEmpty())
{ {
@@ -346,15 +363,9 @@ public class DhLightingEngine
} }
if (mcBlockPos == null) IBlockStateWrapper neighbourBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState);
{ previousBlockState = neighbourBlockState;
// 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 = 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. // 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()); int targetLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
if (targetLevel > currentBlockLight) if (targetLevel > currentBlockLight)
@@ -370,12 +381,14 @@ public class DhLightingEngine
} }
// can be enable if troubleshooting lighting issues // can be enabled if troubleshooting lighting issues
if (RENDER_BLOCK_LIGHT_WIREFRAME && propagatingBlockLights) if (RENDER_BLOCK_LIGHT_WIREFRAME
&& propagatingBlockLights)
{ {
RenderDhLightValuesAsWireframe(adjacentChunkHolder, true); RenderDhLightValuesAsWireframe(adjacentChunkHolder, true);
} }
else if (RENDER_SKY_LIGHT_WIREFRAME && !propagatingBlockLights) else if (RENDER_SKY_LIGHT_WIREFRAME
&& !propagatingBlockLights)
{ {
RenderDhLightValuesAsWireframe(adjacentChunkHolder, false); RenderDhLightValuesAsWireframe(adjacentChunkHolder, false);
} }
@@ -462,7 +475,7 @@ public class DhLightingEngine
point = FullDataPointUtil.setSkyLight(point, skylight); point = FullDataPointUtil.setSkyLight(point, skylight);
dataPoints.set(index, point); dataPoints.set(index, point);
// now for the propagation. // 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))) else if (!airIDs.get(FullDataPointUtil.getId(adjacentDataPoint)))
{ {
// assume for now that we cannot propagate into non-transparent data points. // 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 else
{ {
@@ -610,7 +623,7 @@ public class DhLightingEngine
adjacentDataPoint = FullDataPointUtil.setSkyLight(adjacentDataPoint, lightLevel - 1); adjacentDataPoint = FullDataPointUtil.setSkyLight(adjacentDataPoint, lightLevel - 1);
adjacentDataPoints.set(adjacentIndex, adjacentDataPoint); adjacentDataPoints.set(adjacentIndex, adjacentDataPoint);
// if propagation succeeded, recursively propagate again starting at the adjacent data point. // 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.google.common.cache.RemovalCause;
import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config; 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.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos; 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.FormatUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; 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.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -28,6 +30,8 @@ public class PregenManager
{ {
protected static final DhLogger LOGGER = new DhLoggerBuilder().build(); 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<>(); private final AtomicReference<PregenState> pregenFuture = new AtomicReference<>();
@@ -51,6 +55,7 @@ public class PregenManager
pregenState.completeExceptionally(new IllegalStateException("Pregen is already running.")); pregenState.completeExceptionally(new IllegalStateException("Pregen is already running."));
return pregenState; return pregenState;
} }
pregenState.whenComplete((result, throwable) -> { pregenState.whenComplete((result, throwable) -> {
this.pregenFuture.set(null); this.pregenFuture.set(null);
}); });
@@ -104,7 +109,7 @@ public class PregenManager
} }
long timeSincePreviousTaskFinish = System.currentTimeMillis() - this.lastTaskFinishTime.getAndSet(System.currentTimeMillis()); long timeSincePreviousTaskFinish = System.currentTimeMillis() - this.lastTaskFinishTime.getAndSet(System.currentTimeMillis());
this.averageTaskCompletionIntervalMs.addValue(timeSincePreviousTaskFinish); this.averageTaskCompletionIntervalMs.add(timeSincePreviousTaskFinish);
PregenState.this.fillPendingQueue(); PregenState.this.fillPendingQueue();
}) })
@@ -11,6 +11,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.WorldGenUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -69,7 +70,7 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
int chunkWidth = DhSectionPos.getChunkWidth(sectionPos); int chunkWidth = DhSectionPos.getChunkWidth(sectionPos);
int chunkCount = chunkWidth * chunkWidth; int chunkCount = chunkWidth * chunkWidth;
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount; double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
this.rollingAverageChunkGenTimeInMs.addValue(timePerChunk); this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
switch (requestResult) switch (requestResult)
{ {
@@ -110,12 +111,14 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
@Override @Override
protected boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos) protected boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos)
{ {
if (this.networkState.sessionConfig.getGenerationBoundsRadius() > 0) if (this.networkState.sessionConfig.getGenerationMaxChunkRadius() > 0)
{ {
if (DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, new DhBlockPos2D( boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
this.networkState.sessionConfig.getGenerationBoundsX(), sectionPos,
this.networkState.sessionConfig.getGenerationBoundsZ() this.networkState.sessionConfig.getGenerationCenterChunkX(), this.networkState.sessionConfig.getGenerationCenterChunkZ(),
)) > this.networkState.sessionConfig.getGenerationBoundsRadius()) this.networkState.sessionConfig.getGenerationMaxChunkRadius()
);
if (!posInRange)
{ {
return false; return false;
} }
@@ -40,6 +40,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder; import com.seibel.distanthorizons.core.dataObjects.transformers.LodDataBuilder;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; 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.LodUtil.AssertFailureException;
import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; 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 DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); 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 IDhApiWorldGenerator generator;
private final IDhServerLevel level; private final IDhServerLevel level;
@@ -218,7 +210,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
while (!this.isGeneratorBusy() while (!this.isGeneratorBusy()
&& taskStarted) && taskStarted)
{ {
taskStarted = this.startNextWorldGenTask(this.generationTargetPos); taskStarted = this.tryStartNextWorldGenTask(this.generationTargetPos);
} }
} }
catch (Exception e) catch (Exception e)
@@ -240,15 +232,15 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
return true; return true;
} }
// queue more tasks if any of the threads are available
int worldGenThreadCount = Math.max(Config.Common.MultiThreading.numberOfThreads.get(), 1); int worldGenThreadCount = Math.max(Config.Common.MultiThreading.numberOfThreads.get(), 1);
int maxWorldGenTaskCount = worldGenThreadCount * MAX_QUEUED_TASKS_PER_THREAD; return this.inProgressGenTasksByLodPos.size() > worldGenThreadCount;
return executor.getQueueSize() > maxWorldGenTaskCount;
} }
/** /**
* @param targetPos the position to center the generation around * @param targetPos the position to center the generation around
* @return false if no tasks were found to generate * @return false if no tasks were found to generate
*/ */
private boolean startNextWorldGenTask(DhBlockPos2D targetPos) private boolean tryStartNextWorldGenTask(DhBlockPos2D targetPos)
{ {
if (this.waitingTasks.isEmpty()) if (this.waitingTasks.isEmpty())
{ {
@@ -257,23 +249,23 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
Mapper closestTaskMap = this.waitingTasks.reduceEntries(1024, TaskDistancePair closestTaskPair = this.waitingTasks.reduceEntries(1024,
entry -> new Mapper(entry.getValue(), DhSectionPos.getSectionBBoxPos(entry.getValue().pos).getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())), entry -> new TaskDistancePair(entry.getValue(), DhSectionPos.getSectionBBoxPos(entry.getValue().pos).getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())),
(aMapper, bMapper) -> aMapper.dist < bMapper.dist ? aMapper : bMapper); (TaskDistancePair aTaskPair, TaskDistancePair bTaskPair) -> (aTaskPair.dist < bTaskPair.dist) ? aTaskPair : bTaskPair);
if (closestTaskMap == null) if (closestTaskPair == null)
{ {
// FIXME concurrency issue // FIXME concurrency issue
return false; return false;
} }
WorldGenTask closestTask = closestTaskMap.task; WorldGenTask closestTask = closestTaskPair.task;
// remove the task we found, we are going to start it and don't want to run it multiple times // remove the task we found, we are going to start it and don't want to run it multiple times
this.waitingTasks.remove(closestTask.pos, closestTask); this.waitingTasks.remove(closestTask.pos, closestTask);
// do we need to modify this task to generate it? // do we need to modify this task to generate it?
if (this.canGeneratePos(closestTask.pos)) if (this.canGenerateDetailLevel(DhSectionPos.getDetailLevel(closestTask.pos)))
{ {
// detail level is correct for generation, start generation // detail level is correct for generation, start generation
@@ -284,11 +276,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{ {
// no task exists for this position, start one // no task exists for this position, start one
InProgressWorldGenTaskGroup newTaskGroup = new InProgressWorldGenTaskGroup(closestTaskGroup); InProgressWorldGenTaskGroup newTaskGroup = new InProgressWorldGenTaskGroup(closestTaskGroup);
boolean taskStarted = this.tryStartingWorldGenTaskGroup(newTaskGroup); this.startWorldGenTaskGroup(newTaskGroup);
if (!taskStarted)
{
//LOGGER.trace("Unable to start task: "+closestTask.pos+", skipping. Task position may have already been generated.");
}
} }
else else
{ {
@@ -297,7 +285,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// the newly selected task, we cannot use it, // the newly selected task, we cannot use it,
// as some chunks may have already been written into. // as some chunks may have already been written into.
//LOGGER.trace("A task already exists for this position, todo: "+closestTask.pos); //LOGGER.warn("A task already exists for this position, todo: "+DhSectionPos.toString(closestTask.pos));
} }
// a task has been started // a task has been started
@@ -329,8 +317,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
return true; return true;
} }
} }
/** @return true if the task was started, false otherwise */ private void startWorldGenTaskGroup(InProgressWorldGenTaskGroup newTaskGroup)
private boolean tryStartingWorldGenTaskGroup(InProgressWorldGenTaskGroup newTaskGroup)
{ {
byte taskDetailLevel = newTaskGroup.group.dataDetail; byte taskDetailLevel = newTaskGroup.group.dataDetail;
long taskPos = newTaskGroup.group.pos; long taskPos = newTaskGroup.group.pos;
@@ -345,7 +332,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime; long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
int chunkCount = generationRequestChunkWidthCount * generationRequestChunkWidthCount; int chunkCount = generationRequestChunkWidthCount * generationRequestChunkWidthCount;
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount; double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
this.rollingAverageChunkGenTimeInMs.addValue(timePerChunk); this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
}); });
newTaskGroup.genFuture = generationFuture; newTaskGroup.genFuture = generationFuture;
@@ -358,7 +345,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
if (exception != null) if (exception != null)
{ {
// don't log the shutdown exceptions // 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); LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
} }
@@ -383,7 +370,6 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
}); });
this.inProgressGenTasksByLodPos.put(taskPos, newTaskGroup); this.inProgressGenTasksByLodPos.put(taskPos, newTaskGroup);
return true;
} }
private CompletableFuture<Void> startGenerationEvent( private CompletableFuture<Void> startGenerationEvent(
long requestPos, long requestPos,
@@ -412,11 +398,16 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{ {
IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray); IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
// TODO light data should be pulled (if possible) from the ChunkAccess object itself via ChunkFileReader.readLight // only light the chunk here if necessary,
// but this should work for now // lighting before this point is preferred but for potenial legacy API uses this
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<IChunkWrapper>(); // check should be done
if (!chunkWrapper.isDhBlockLightingCorrect())
{
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<>();
nearbyChunkList.add(chunkWrapper); nearbyChunkList.add(chunkWrapper);
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); 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)) try (FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper))
{ {
@@ -596,9 +587,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
exception = exception.getCause(); 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; return null;
@@ -623,13 +615,17 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LOGGER.info("Shutting down world generator thread pool..."); LOGGER.info("Shutting down world generator thread pool...");
AbstractExecutorService executor = ThreadPoolUtil.getWorldGenExecutor(); PriorityTaskPicker.Executor executor = ThreadPoolUtil.getWorldGenExecutor();
if (executor != null) if (executor != null)
{ {
List<Runnable> tasks = executor.shutdownNow(); int queueSize = executor.getQueueSize();
LOGGER.info("World generator thread pool shutdown with [" + tasks.size() + "] incomplete tasks."); 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(); this.generator.close();
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue); DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showWorldGenQueue);
@@ -687,9 +683,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// helper methods // // helper methods //
//================// //================//
private boolean canGeneratePos(long taskPos) private boolean canGenerateDetailLevel(byte taskDetailLevel)
{ {
byte requestedDetailLevel = (byte) (DhSectionPos.getDetailLevel(taskPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); byte requestedDetailLevel = (byte) (taskDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
return (this.highestDataDetail <= requestedDetailLevel && requestedDetailLevel <= this.lowestDataDetail); return (this.highestDataDetail <= requestedDetailLevel && requestedDetailLevel <= this.lowestDataDetail);
} }
@@ -699,11 +695,12 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// helper classes // // helper classes //
//================// //================//
private static class Mapper private static class TaskDistancePair
{ {
public final WorldGenTask task; public final WorldGenTask task;
public final int dist; public final int dist;
public Mapper(WorldGenTask task, int dist)
public TaskDistancePair(WorldGenTask task, int dist)
{ {
this.task = task; this.task = task;
this.dist = dist; this.dist = dist;
@@ -216,7 +216,7 @@ public class JarMain
{ {
repo = new FullDataSourceV2Repo(FullDataSourceV2Repo.DEFAULT_DATABASE_TYPE, dbFile); 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); LOGGER.error("Failed to initialize connection with database: ["+exportFile.getAbsolutePath()+"], error: ["+e.getMessage()+"].", e);
return; return;
@@ -49,7 +49,7 @@ public class GitlabGetter
/** Commit sha; Commit info */ /** Commit sha; Commit info */
private static final Map<String, Config> commitInfo = new HashMap<>(); private static final Map<String, Config> commitInfo = new HashMap<>();
/** Pipeline ID; Pipeline info */ /** 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 */ /** Uses our projectID to init this */
public GitlabGetter() public GitlabGetter()
@@ -88,7 +88,7 @@ public class GitlabGetter
return commitInfo.get(commit); return commitInfo.get(commit);
} }
public ArrayList<Config> getPipelineInfo(int pipeline) public ArrayList<Config> getPipelineInfo(Number pipeline)
{ {
if (!pipelineInfo.containsKey(pipeline)) if (!pipelineInfo.containsKey(pipeline))
{ {
@@ -111,9 +111,10 @@ public class GitlabGetter
/** /**
* Gets all the Minecraft download links to a pipeline ID * 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 * @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<>(); Map<String, URL> downloads = new HashMap<>();
ArrayList<Config> currentPipelineInfo = this.getPipelineInfo(pipelineID); ArrayList<Config> currentPipelineInfo = this.getPipelineInfo(pipelineID);
@@ -55,6 +55,7 @@ import org.jetbrains.annotations.Nullable;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -106,9 +107,9 @@ public abstract class AbstractDhLevel implements IDhLevel
{ {
newChunkHashRepo = new ChunkHashRepo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, databaseFile); 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; this.chunkHashRepo = newChunkHashRepo;
@@ -119,9 +120,9 @@ public abstract class AbstractDhLevel implements IDhLevel
{ {
newBeaconBeamRepo = new BeaconBeamRepo(AbstractDhRepo.DEFAULT_DATABASE_TYPE, databaseFile); 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; this.beaconBeamRepo = newBeaconBeamRepo;
} }
@@ -21,6 +21,7 @@ import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.WorldGenUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -29,6 +30,8 @@ import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
@@ -48,23 +51,27 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
*/ */
protected final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>(); protected final ConcurrentLinkedQueue<IServerPlayerWrapper> worldGenPlayerCenteringQueue = new ConcurrentLinkedQueue<>();
private final FullDataSourceRequestHandler requestHandler = new FullDataSourceRequestHandler(this); private final FullDataSourceRequestHandler requestHandler;
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
public AbstractDhServerLevel(ISaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager) public AbstractDhServerLevel(
{ ISaveStructure saveStructure,
this(saveStructure, serverLevelWrapper, serverPlayerStateManager, true); IServerLevelWrapper serverLevelWrapper,
} ServerPlayerStateManager serverPlayerStateManager
) throws SQLException, IOException
{ this(saveStructure, serverLevelWrapper, serverPlayerStateManager, true); }
public AbstractDhServerLevel( public AbstractDhServerLevel(
ISaveStructure saveStructure, ISaveStructure saveStructure,
IServerLevelWrapper serverLevelWrapper, IServerLevelWrapper serverLevelWrapper,
ServerPlayerStateManager serverPlayerStateManager, ServerPlayerStateManager serverPlayerStateManager,
boolean runRepoReliantSetup boolean runRepoReliantSetup
) ) throws SQLException, IOException
{ {
if (saveStructure.getSaveFolder(serverLevelWrapper).mkdirs()) if (saveStructure.getSaveFolder(serverLevelWrapper).mkdirs())
{ {
@@ -81,6 +88,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
LOGGER.info("Started "+this.getClass().getSimpleName()+" for ["+serverLevelWrapper+"] at ["+saveStructure+"]."); LOGGER.info("Started "+this.getClass().getSimpleName()+" for ["+serverLevelWrapper+"] at ["+saveStructure+"].");
this.serverPlayerStateManager = serverPlayerStateManager; this.serverPlayerStateManager = serverPlayerStateManager;
this.requestHandler = new FullDataSourceRequestHandler(this);
} }
@@ -89,12 +97,6 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
// ticks // // ticks //
//=======// //=======//
@Override
public void serverTick()
{
this.requestHandler.tick();
}
@Override @Override
public boolean shouldDoWorldGen() public boolean shouldDoWorldGen()
{ return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); } { return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); }
@@ -118,9 +120,6 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
return new DhBlockPos2D((int) position.x, (int) position.z); return new DhBlockPos2D((int) position.x, (int) position.z);
} }
@Override
public void worldGenTick() { this.serverside.worldGenModule.worldGenTick(); }
//==================// //==================//
@@ -149,17 +148,16 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
return; return;
} }
if (Config.Server.generationBoundsRadius.get() > 0) boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
{ message.sectionPos,
if (DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D( Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(),
serverPlayerState.sessionConfig.getGenerationBoundsX(), Config.Common.WorldGenerator.generationMaxChunkRadius.get()
serverPlayerState.sessionConfig.getGenerationBoundsZ() );
)) > Config.Server.generationBoundsRadius.get()) if (!posInRange)
{ {
message.sendResponse(new RequestOutOfRangeException("Section out of allowed bounds")); message.sendResponse(new RequestOutOfRangeException("Section out of allowed bounds"));
return; return;
} }
}
if (!Config.Server.Experimental.enableNSizedGeneration.get() && DhSectionPos.getDetailLevel(message.sectionPos) != DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) if (!Config.Server.Experimental.enableNSizedGeneration.get() && DhSectionPos.getDetailLevel(message.sectionPos) != DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
{ {
@@ -297,7 +295,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
public void addDebugMenuStringsToList(List<String> messageList) public void addDebugMenuStringsToList(List<String> messageList)
{ {
this.serverside.fullDataFileHandler.addDebugMenuStringsToList(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(); super.close();
this.serverside.close(); this.serverside.close();
this.requestHandler.close();
LOGGER.info("Closed DHLevel for [" + this.getLevelWrapper() + "]."); LOGGER.info("Closed DHLevel for [" + this.getLevelWrapper() + "].");
} }
} }
@@ -49,6 +49,8 @@ import org.jetbrains.annotations.Nullable;
import javax.annotation.CheckForNull; import javax.annotation.CheckForNull;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -83,7 +85,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
.asMap() .asMap()
); );
public final WorldGenModule worldGenModule; public final LodRequestModule lodRequestModule;
@Nullable @Nullable
private final SyncOnLoadRequestQueue syncOnLoadRequestQueue; private final SyncOnLoadRequestQueue syncOnLoadRequestQueue;
@@ -94,9 +96,18 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
// constructor // // 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); } { 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 saveFolder = saveStructure.getSaveFolder(clientLevelWrapper);
File pre23Folder = saveStructure.getPre23SaveFolder(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.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); this.clientside = new ClientLevelModule(this);
@@ -239,11 +250,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Nullable @Nullable
public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); } 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 // world gen
this.worldGenModule.addDebugMenuStringsToList(messageList); this.lodRequestModule.addDebugMenuStringsToList(messageList);
if (this.syncOnLoadRequestQueue != null) if (this.syncOnLoadRequestQueue != null)
{ {
assert this.networkState != null; assert this.networkState != null;
@@ -348,9 +354,9 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Override @Override
public void close() public void close()
{ {
if (this.worldGenModule != null) if (this.lodRequestModule != null)
{ {
this.worldGenModule.close(); this.lodRequestModule.close();
} }
if (this.networkEventSource != null) if (this.networkEventSource != null)
@@ -371,11 +377,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
// helper classes // // 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 org.jetbrains.annotations.Nullable;
import java.awt.*; import java.awt.*;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List; import java.util.List;
/** The level used for a singleplayer world */ /** The level used for a singleplayer world */
@@ -48,7 +50,11 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
// constructor // // 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); 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 com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List; import java.util.List;
public class DhServerLevel extends AbstractDhServerLevel public class DhServerLevel extends AbstractDhServerLevel
@@ -35,10 +37,12 @@ public class DhServerLevel extends AbstractDhServerLevel
// constructor // // constructor //
//=============// //=============//
public DhServerLevel(ISaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, ServerPlayerStateManager serverPlayerStateManager) public DhServerLevel(
{ ISaveStructure saveStructure,
super(saveStructure, serverLevelWrapper, serverPlayerStateManager); 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 public interface IDhLevel extends AutoCloseable, GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener
{ {
@Deprecated
void worldGenTick();
/** /**
* May return either a client or server level wrapper. <br> * May return either a client or server level wrapper. <br>
* Should not return null * Should not return null
@@ -23,8 +23,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapp
public interface IDhServerLevel extends IDhLevel public interface IDhServerLevel extends IDhLevel
{ {
void serverTick();
IServerLevelWrapper getServerLevelWrapper(); IServerLevelWrapper getServerLevelWrapper();
} }
@@ -45,18 +45,18 @@ import java.util.function.Supplier;
/** /**
* Handles both single-player/server-side world gen and client side LOD requests. * 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 static final DhLogger LOGGER = new DhLoggerBuilder().build();
private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener; private final GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener;
private final ThreadPoolExecutor tickerThread;
private final GeneratedFullDataSourceProvider dataSourceProvider; 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 // // constructor //
//=============// //=============//
public WorldGenModule( public LodRequestModule(
IDhLevel level,
GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener, GeneratedFullDataSourceProvider.IOnWorldGenCompleteListener onWorldGenCompleteListener,
GeneratedFullDataSourceProvider dataSourceProvider, GeneratedFullDataSourceProvider dataSourceProvider,
Supplier<? extends AbstractWorldGenState> worldGenStateSupplier Supplier<? extends AbstractLodRequestState> worldGenStateSupplier
) )
{ {
this.onWorldGenCompleteListener = onWorldGenCompleteListener; this.onWorldGenCompleteListener = onWorldGenCompleteListener;
this.dataSourceProvider = dataSourceProvider; this.dataSourceProvider = dataSourceProvider;
this.worldGenStateSupplier = worldGenStateSupplier; this.worldGenStateSupplier = worldGenStateSupplier;
String levelId = level.getLevelWrapper().getDhIdentifier();
this.tickerThread = ThreadUtil.makeSingleDaemonThreadPool("Request Module Ticker ["+levelId+"]");
this.tickerThread.execute(this::tickLoop);
} }
//===================// //=========//
// world gen control // // ticking //
//===================// //=========//
public void startWorldGen(GeneratedFullDataSourceProvider dataFileHandler, AbstractWorldGenState newWgs) private void tickLoop()
{ {
// create the new world generator try
if (!this.worldGenStateRef.compareAndSet(null, newWgs))
{ {
LOGGER.warn("Failed to start world gen due to concurrency"); while (!Thread.interrupted())
newWgs.closeAsync(false);
}
dataFileHandler.addWorldGenCompleteListener(this.onWorldGenCompleteListener);
dataFileHandler.setWorldGenerationQueue(newWgs.worldGenerationQueue);
}
public void stopWorldGen(GeneratedFullDataSourceProvider dataFileHandler)
{ {
AbstractWorldGenState worldGenState = this.worldGenStateRef.get(); Thread.sleep(20);
if (worldGenState == null) this.tick();
{
LOGGER.warn("Attempted to stop world gen when it was not running");
return;
}
// shut down the world generator
while (!this.worldGenStateRef.compareAndSet(worldGenState, null))
{
worldGenState = this.worldGenStateRef.get();
if (worldGenState == null)
{
return;
} }
} }
dataFileHandler.clearRetrievalQueue(); catch (InterruptedException ignore) { }
worldGenState.closeAsync(true).join(); //TODO: Make it async.
dataFileHandler.removeWorldGenCompleteListener(this.onWorldGenCompleteListener);
} }
private void tick()
public void worldGenTick()
{ {
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen(); boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
// if the world is read only don't generate anything // if the world is read only don't generate anything
@@ -136,13 +118,13 @@ public class WorldGenModule implements Closeable
if (this.isWorldGenRunning()) if (this.isWorldGenRunning())
{ {
AbstractWorldGenState worldGenState = this.worldGenStateRef.get(); AbstractLodRequestState lodRequestState = this.lodRequestStateRef.get();
if (worldGenState != null) if (lodRequestState != null)
{ {
DhBlockPos2D targetPosForGeneration = this.onWorldGenCompleteListener.getTargetPosForGeneration(); DhBlockPos2D targetPosForGeneration = this.onWorldGenCompleteListener.getTargetPosForGeneration();
if (targetPosForGeneration != null) 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 // // base method overrides //
//=======================// //=======================//
@@ -157,13 +181,15 @@ public class WorldGenModule implements Closeable
@Override @Override
public void close() public void close()
{ {
this.tickerThread.shutdownNow();
// shutdown the world-gen // shutdown the world-gen
AbstractWorldGenState worldGenState = this.worldGenStateRef.get(); AbstractLodRequestState worldGenState = this.lodRequestStateRef.get();
if (worldGenState != null) 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) if (worldGenState == null)
{ {
break; break;
@@ -183,12 +209,12 @@ public class WorldGenModule implements Closeable
// getters // // 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 */ /** mutates a list so it can be added to an existing {@link IDhLevel}'s debug list */
public void addDebugMenuStringsToList(List<String> messageList) public void addDebugMenuStringsToList(List<String> messageList)
{ {
AbstractWorldGenState worldGenState = this.worldGenStateRef.get(); AbstractLodRequestState worldGenState = this.lodRequestStateRef.get();
if (worldGenState == null) if (worldGenState == null)
{ {
return; return;
@@ -196,9 +222,9 @@ public class WorldGenModule implements Closeable
// estimated tasks // estimated tasks
String waitingCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getWaitingTaskCount()); String waitingCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.retrievalQueue.getWaitingTaskCount());
String inProgressCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getInProgressTaskCount()); String inProgressCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.retrievalQueue.getInProgressTaskCount());
String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getRetrievalEstimatedRemainingChunkCount()); String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.retrievalQueue.getRetrievalEstimatedRemainingChunkCount());
String message = "World Gen/Import Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress "+inProgressCountStr+")"; String message = "World Gen/Import Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress "+inProgressCountStr+")";
// estimated chunks/sec // estimated chunks/sec
@@ -210,7 +236,7 @@ public class WorldGenModule implements Closeable
messageList.add(message); 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. */ /** 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 */ /** static so we only send the disable message once per session */
private static long firstProgressMessageSentMs = 0; 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 static final ThreadPoolExecutor PROGRESS_UPDATER_THREAD = ThreadUtil.makeSingleDaemonThreadPool("World Gen Progress Updater");
private boolean progressUpdateThreadRunning = false; private boolean progressUpdateThreadRunning = false;
CompletableFuture<Void> closeAsync(boolean doInterrupt)
{
// this should stop the updater thread
this.progressUpdateThreadRunning = false;
return this.worldGenerationQueue.startClosingAsync(true, doInterrupt) /** @param targetPosForRequest the position that world generation should be centered around */
.exceptionally(e -> public void startRequestQueueAndSetTargetPos(DhBlockPos2D targetPosForRequest)
{ {
LOGGER.error("Error during first stage of generation queue shutdown, Error: ["+e.getMessage()+"].", e); this.retrievalQueue.startAndSetTargetPos(targetPosForRequest);
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)
{
this.worldGenerationQueue.startAndSetTargetPos(targetPosForGeneration);
this.startProgressUpdateThread(); this.startProgressUpdateThread();
} }
private void startProgressUpdateThread() private void startProgressUpdateThread()
@@ -286,8 +294,8 @@ public class WorldGenModule implements Closeable
private void sendRetrievalProgress() private void sendRetrievalProgress()
{ {
// format the remaining chunks // format the remaining chunks
int remainingChunkCount = this.worldGenerationQueue.getRetrievalEstimatedRemainingChunkCount(); int remainingChunkCount = this.retrievalQueue.getRetrievalEstimatedRemainingChunkCount();
remainingChunkCount += this.worldGenerationQueue.getQueuedChunkCount(); remainingChunkCount += this.retrievalQueue.getQueuedChunkCount();
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount); String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
String message = "DH is generating chunks. " + remainingChunkCountStr + " left."; 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 */ /** @return -1 if this method isn't supported or available */
public double getEstimatedChunksPerSecond() public double getEstimatedChunksPerSecond()
{ {
RollingAverage avg = this.worldGenerationQueue.getRollingAverageChunkGenTimeInMs(); RollingAverage avg = this.retrievalQueue.getRollingAverageChunkGenTimeInMs();
if (avg == null) if (avg == null)
{ {
return -1; return -1;
@@ -373,6 +381,27 @@ public class WorldGenModule implements Closeable
return chunksPerSecond; 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.coreapi.DependencyInjection.WorldGeneratorInjector;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.io.IOException;
import java.sql.SQLException;
public class ServerLevelModule implements AutoCloseable public class ServerLevelModule implements AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
@@ -36,7 +39,7 @@ public class ServerLevelModule implements AutoCloseable
public final ISaveStructure saveStructure; public final ISaveStructure saveStructure;
public final GeneratedFullDataSourceProvider fullDataFileHandler; public final GeneratedFullDataSourceProvider fullDataFileHandler;
public final WorldGenModule worldGenModule; public final LodRequestModule lodRequestModule;
@@ -44,12 +47,12 @@ public class ServerLevelModule implements AutoCloseable
// constructor // // constructor //
//=============// //=============//
public ServerLevelModule(IDhServerLevel parentServerLevel, ISaveStructure saveStructure) public ServerLevelModule(IDhServerLevel parentServerLevel, ISaveStructure saveStructure) throws SQLException, IOException
{ {
this.parentServerLevel = parentServerLevel; this.parentServerLevel = parentServerLevel;
this.saveStructure = saveStructure; this.saveStructure = saveStructure;
this.fullDataFileHandler = new GeneratedFullDataSourceProvider(parentServerLevel, 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() public void close()
{ {
// shutdown the world-gen // shutdown the world-gen
this.worldGenModule.close(); this.lodRequestModule.close();
this.fullDataFileHandler.close(); this.fullDataFileHandler.close();
} }
@@ -72,9 +75,9 @@ public class ServerLevelModule implements AutoCloseable
// helper classes // // 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()); IDhApiWorldGenerator worldGenerator = WorldGeneratorInjector.INSTANCE.get(level.getLevelWrapper());
if (worldGenerator == null) if (worldGenerator == null)
@@ -85,7 +88,7 @@ public class ServerLevelModule implements AutoCloseable
// since core world generator's should have the lowest override priority // since core world generator's should have the lowest override priority
WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator); WorldGeneratorInjector.INSTANCE.bind(level.getLevelWrapper(), worldGenerator);
} }
this.worldGenerationQueue = new WorldGenerationQueue(worldGenerator, level); this.retrievalQueue = new WorldGenerationQueue(worldGenerator, level);
} }
} }
@@ -276,7 +276,7 @@ public class DhLogger implements IConfigListener
if (mc_client != null) if (mc_client != null)
{ {
mc_client.logToChat(level, msgStr); this.logToChat(level, msgStr);
messageLogged = true; messageLogged = true;
} }
} }
@@ -296,6 +296,41 @@ public class DhLogger implements IConfigListener
} }
private static boolean loggingLevelIsLessSpecificThan(Level thisLoggingLevel, Level requestedLogLevel) private static boolean loggingLevelIsLessSpecificThan(Level thisLoggingLevel, Level requestedLogLevel)
{ return thisLoggingLevel.intLevel() >= requestedLogLevel.intLevel(); } { return thisLoggingLevel.intLevel() >= requestedLogLevel.intLevel(); }
/** Sends the given message to chat with a formatted prefix and color based on the log level. */
private void logToChat(Level logLevel, String message)
{
String prefix = "[" + ModInfo.READABLE_NAME + "] ";
if (logLevel == Level.ERROR)
{
prefix += "\u00A74";
}
else if (logLevel == Level.WARN)
{
prefix += "\u00A76";
}
else if (logLevel == Level.INFO)
{
prefix += "\u00A7f";
}
else if (logLevel == Level.DEBUG)
{
prefix += "\u00A77";
}
else if (logLevel == Level.TRACE)
{
prefix += "\u00A78";
}
else
{
prefix += "\u00A7f";
}
prefix += "\u00A7l\u00A7u";
prefix += logLevel.name();
prefix += ":\u00A7r ";
mc_client.sendChatMessage(prefix + message);
}
@@ -33,9 +33,9 @@ public class SessionConfig implements INetworkObject
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd); registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd);
registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min); registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min);
registerConfigEntry(Config.Server.generationBoundsX, (x, y) -> y); registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkX, (x, y) -> y);
registerConfigEntry(Config.Server.generationBoundsZ, (x, y) -> y); registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkZ, (x, y) -> y);
registerConfigEntry(Config.Server.generationBoundsRadius, (x, y) -> y); registerConfigEntry(Config.Common.WorldGenerator.generationMaxChunkRadius, (x, y) -> y);
registerConfigEntry(Config.Server.generationRequestRateLimit, Math::min); registerConfigEntry(Config.Server.generationRequestRateLimit, Math::min);
registerConfigEntry(Config.Server.enableRealTimeUpdates, Boolean::logicalAnd); registerConfigEntry(Config.Server.enableRealTimeUpdates, Boolean::logicalAnd);
@@ -68,9 +68,9 @@ public class SessionConfig implements INetworkObject
public boolean isDistantGenerationEnabled() { return this.getValue(Config.Common.WorldGenerator.enableDistantGeneration); } public boolean isDistantGenerationEnabled() { return this.getValue(Config.Common.WorldGenerator.enableDistantGeneration); }
public int getMaxGenerationRequestDistance() { return this.getValue(Config.Server.maxGenerationRequestDistance); } public int getMaxGenerationRequestDistance() { return this.getValue(Config.Server.maxGenerationRequestDistance); }
public Integer getGenerationBoundsX() { return this.getValue(Config.Server.generationBoundsX); } public Integer getGenerationCenterChunkX() { return this.getValue(Config.Common.WorldGenerator.generationCenterChunkX); }
public Integer getGenerationBoundsZ() { return this.getValue(Config.Server.generationBoundsZ); } public Integer getGenerationCenterChunkZ() { return this.getValue(Config.Common.WorldGenerator.generationCenterChunkZ); }
public Integer getGenerationBoundsRadius() { return this.getValue(Config.Server.generationBoundsRadius); } public Integer getGenerationMaxChunkRadius() { return this.getValue(Config.Common.WorldGenerator.generationMaxChunkRadius); }
public int getGenerationRequestRateLimit() { return this.getValue(Config.Server.generationRequestRateLimit); } public int getGenerationRequestRateLimit() { return this.getValue(Config.Server.generationRequestRateLimit); }
public boolean isRealTimeUpdatesEnabled() { return this.getValue(Config.Server.enableRealTimeUpdates); } public boolean isRealTimeUpdatesEnabled() { return this.getValue(Config.Server.enableRealTimeUpdates); }
@@ -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.network.messages.fullData.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import java.util.List; import java.util.List;
@@ -21,7 +22,7 @@ import java.util.Map;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class FullDataSourceRequestHandler public class FullDataSourceRequestHandler implements AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder() private static final DhLogger LOGGER = new DhLoggerBuilder()
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile) .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
@@ -29,6 +30,8 @@ public class FullDataSourceRequestHandler
private final AbstractDhServerLevel serverLevel; private final AbstractDhServerLevel serverLevel;
private final ThreadPoolExecutor tickerThread;
private String getLevelIdentifier() { return this.serverLevel.getLevelWrapper().getDhIdentifier(); } private String getLevelIdentifier() { return this.serverLevel.getLevelWrapper().getDhIdentifier(); }
private GeneratedFullDataSourceProvider fullDataSourceProvider() { return this.serverLevel.serverside.fullDataFileHandler; } private GeneratedFullDataSourceProvider fullDataSourceProvider() { return this.serverLevel.serverside.fullDataFileHandler; }
private List<BeaconBeamDTO> getAllBeamsForPos(long pos) { return this.serverLevel.beaconBeamRepo.getAllBeamsForPos(pos); } 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<>(); private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupsByFutureId = new ConcurrentHashMap<>();
//=============//
// constructor //
//=============//
public FullDataSourceRequestHandler(AbstractDhServerLevel serverLevel) public FullDataSourceRequestHandler(AbstractDhServerLevel serverLevel)
{ {
this.serverLevel = 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 // // 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) private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
{ {
this.fullDataSourceProvider().getAsync(pos).thenAccept(fullDataSource -> 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();
}
} }
@@ -102,8 +102,8 @@ public class ServerPlayerState implements Closeable
private void sendConfigMessage() private void sendConfigMessage()
{ {
double coordinateScale = this.getServerPlayer().getLevel().getDimensionType().getCoordinateScale(); double coordinateScale = this.getServerPlayer().getLevel().getDimensionType().getCoordinateScale();
this.sessionConfig.constrainValue(Config.Server.generationBoundsX, (int) (Config.Server.generationBoundsX.get() / coordinateScale)); this.sessionConfig.constrainValue(Config.Common.WorldGenerator.generationCenterChunkX, (int) (Config.Common.WorldGenerator.generationCenterChunkX.get() / coordinateScale));
this.sessionConfig.constrainValue(Config.Server.generationBoundsZ, (int) (Config.Server.generationBoundsZ.get() / coordinateScale)); this.sessionConfig.constrainValue(Config.Common.WorldGenerator.generationCenterChunkZ, (int) (Config.Common.WorldGenerator.generationCenterChunkZ.get() / coordinateScale));
this.networkSession.sendMessage(new SessionConfigMessage(this.sessionConfig)); this.networkSession.sendMessage(new SessionConfigMessage(this.sessionConfig));
} }
@@ -182,7 +182,8 @@ public class PhantomArrayListPool
"Potential causes: \n" + "Potential causes: \n" +
"1. your allocated memory isn't high enough \n" + "1. your allocated memory isn't high enough \n" +
"2. your DH CPU preset is too high \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); LOGGER.warn(message);
if (Config.Common.Logging.Warning.showPoolInsufficientMemoryWarning.get()) 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> * <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). * 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 * @author Leetom
*/ */
public class DhSectionPos public class DhSectionPos
@@ -279,17 +281,24 @@ public class DhSectionPos
+ Math.abs(getCenterBlockPosZ(pos) - blockPos.z); + Math.abs(getCenterBlockPosZ(pos) - blockPos.z);
} }
/** see: {@link DhSectionPos#getChebyshevSignedBlockDistance(long, int, int)} */
public static int getChebyshevSignedBlockDistance(long pos, DhBlockPos blockPos)
{ return getChebyshevSignedBlockDistance(pos, blockPos.getX(), blockPos.getZ()); }
/** see: {@link DhSectionPos#getChebyshevSignedBlockDistance(long, int, int)} */
public static int getChebyshevSignedBlockDistance(long pos, DhBlockPos2D blockPos)
{ return getChebyshevSignedBlockDistance(pos, blockPos.x, blockPos.z); }
/** /**
* Returns the signed distance from a given block to a given section. <br> * Returns the signed distance from a given block to a given section. <br>
* Essentially acts like a distance from the block to the nearest edge of the section, * Essentially acts like a distance from the block to the nearest edge of the section,
* except inside the section it's negative. <br> * except inside the section it's negative. <br>
* Useful for detail level insensitive distance comparisons. * Useful for detail level insensitive distance comparisons.
*/ */
public static int getChebyshevSignedBlockDistance(long pos, DhBlockPos2D blockPos) public static int getChebyshevSignedBlockDistance(long pos, int blockPosX, int blockPosZ)
{ {
return Math.max( return Math.max(
Math.abs(getCenterBlockPosX(pos) - blockPos.x), Math.abs(getCenterBlockPosX(pos) - blockPosX),
Math.abs(getCenterBlockPosZ(pos) - blockPos.z) Math.abs(getCenterBlockPosZ(pos) - blockPosZ)
) - getBlockWidth(pos) / 2; ) - getBlockWidth(pos) / 2;
} }
@@ -762,7 +762,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
{ {
// walk through each node // walk through each node
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator(); Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
ArrayList<CompletableFuture<Void>> renderDataBuildFutures = new ArrayList<>();
while (nodeIterator.hasNext()) while (nodeIterator.hasNext())
{ {
QuadNode<LodRenderSection> quadNode = nodeIterator.next(); QuadNode<LodRenderSection> quadNode = nodeIterator.next();
@@ -42,7 +42,7 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler; import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.util.PerfRecorder; import com.seibel.distanthorizons.core.util.WorldGenUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -528,6 +528,18 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
} }
long pos = missingGenerationPos.removeLong(i); long pos = missingGenerationPos.removeLong(i);
boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
pos,
Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(),
Config.Common.WorldGenerator.generationMaxChunkRadius.get()
);
if (!posInRange)
{
continue;
}
boolean positionQueued = (this.fullDataSourceProvider.queuePositionForRetrieval(pos) != null); boolean positionQueued = (this.fullDataSourceProvider.queuePositionForRetrieval(pos) != null);
if (!positionQueued) if (!positionQueued)
{ {
@@ -198,8 +198,8 @@ public class GLBuffer implements AutoCloseable
switch (uploadMethod) switch (uploadMethod)
{ {
case NONE: //case NONE:
return; // return;
case AUTO: case AUTO:
LodUtil.assertNotReach("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!"); LodUtil.assertNotReach("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!");
case BUFFER_STORAGE: case BUFFER_STORAGE:
@@ -379,7 +379,7 @@ public class GLBuffer implements AutoCloseable
{ {
int id = PHANTOM_TO_BUFFER_ID.get(phantomRef); int id = PHANTOM_TO_BUFFER_ID.get(phantomRef);
destroyBufferIdAsync(id); destroyBufferIdAsync(id);
LOGGER.warn("Buffer Phantom collected, ID: ["+id+"]"); //LOGGER.warn("Buffer Phantom collected, ID: ["+id+"]");
} }
phantomRef = PHANTOM_REFERENCE_QUEUE.poll(); phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
@@ -25,6 +25,10 @@ public class DHDepthTexture
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_S, GL13C.GL_CLAMP_TO_EDGE); GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_S, GL13C.GL_CLAMP_TO_EDGE);
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_T, GL13C.GL_CLAMP_TO_EDGE); GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_T, GL13C.GL_CLAMP_TO_EDGE);
// disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, 0); GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, 0);
} }
@@ -46,7 +46,7 @@ public class DhColorTexture
this.id = GL43C.glGenTextures(); this.id = GL43C.glGenTextures();
boolean isPixelFormatInteger = builder.internalFormat.getPixelFormat().isInteger(); boolean isPixelFormatInteger = builder.internalFormat.getPixelFormat().isInteger();
this.setupTexture(this.id, builder.width, builder.height, !isPixelFormatInteger); this.setupTexture(this.id, builder.width, builder.height, !isPixelFormatInteger); // this binds the texture
// Clean up after ourselves // Clean up after ourselves
// This is strictly defensive to ensure that other buggy code doesn't tamper with our textures // This is strictly defensive to ensure that other buggy code doesn't tamper with our textures
@@ -67,6 +67,10 @@ public class DhColorTexture
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, allowsLinear ? GL11C.GL_LINEAR : GL11C.GL_NEAREST); GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, allowsLinear ? GL11C.GL_LINEAR : GL11C.GL_NEAREST);
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_S, GL13C.GL_CLAMP_TO_EDGE); GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_S, GL13C.GL_CLAMP_TO_EDGE);
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_T, GL13C.GL_CLAMP_TO_EDGE); GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_T, GL13C.GL_CLAMP_TO_EDGE);
// disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
} }
private void resizeTexture(int texture, int width, int height) private void resizeTexture(int texture, int width, int height)
@@ -38,7 +38,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRen
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f; import com.seibel.distanthorizons.core.util.math.Vec3f;
import org.apache.logging.log4j.LogManager;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
@@ -213,7 +212,7 @@ public class DebugRenderer
// particle rendering // particle rendering
BoxParticle head = null; BoxParticle head = null;
while ((head = this.particles.poll()) != null && head.isDead(System.nanoTime())) while ((head = this.particles.poll()) != null && head.isDead())
{ /* remove dead particles */ } { /* remove dead particles */ }
if (head != null) if (head != null)
{ {
@@ -311,52 +310,50 @@ public class DebugRenderer
public static final class BoxParticle implements Comparable<BoxParticle> public static final class BoxParticle implements Comparable<BoxParticle>
{ {
public Box box; public Box box;
public long startTime; public long startMsTime;
public long duration; public long durationInMs;
public float yChange; public float yChange;
public BoxParticle(Box box, long startTime, long duration, float yChange) private BoxParticle(Box box, long startMsTime, long durationInMs, float yChange)
{ {
this.box = box; this.box = box;
this.startTime = startTime; this.startMsTime = startMsTime;
this.duration = duration; this.durationInMs = durationInMs;
this.yChange = yChange; this.yChange = yChange;
} }
public BoxParticle(Box box, long nanoSecondDuratoin, float yChange) { this(box, System.nanoTime(), nanoSecondDuratoin, yChange); } public BoxParticle(Box box, double secondDuration, float yChange) { this(box, System.currentTimeMillis(), (long) (secondDuration * 1_000), yChange); }
public BoxParticle(Box box, double secondDuration, float yChange) { this(box, System.nanoTime(), (long) (secondDuration * 1000000000), yChange); }
@Override @Override
public int compareTo(@NotNull BoxParticle particle) public int compareTo(@NotNull BoxParticle particle)
{ {
return Long.compare(this.startTime + this.duration, particle.startTime + particle.duration); return Long.compare(this.startMsTime + this.durationInMs, particle.startMsTime + particle.durationInMs);
} }
public Box getBox() public Box getBox()
{ {
long now = System.nanoTime(); long nowMs = System.currentTimeMillis();
float percent = (now - this.startTime) / (float) this.duration; float percent = (nowMs - this.startMsTime) / (float) this.durationInMs;
percent = (float) Math.pow(percent, 4); percent = (float) Math.pow(percent, 4);
float yDiff = this.yChange * percent; float yDiff = this.yChange * percent;
return new Box(new Vec3f(this.box.minPos.x, this.box.minPos.y + yDiff, this.box.minPos.z), new Vec3f(this.box.maxPos.x, this.box.maxPos.y + yDiff, this.box.maxPos.z), this.box.color); return new Box(new Vec3f(this.box.minPos.x, this.box.minPos.y + yDiff, this.box.minPos.z), new Vec3f(this.box.maxPos.x, this.box.maxPos.y + yDiff, this.box.maxPos.z), this.box.color);
} }
public boolean isDead(long time) { return (time - this.startTime) > this.duration; } public boolean isDead() { return (System.currentTimeMillis() - this.startMsTime) > this.durationInMs; }
} }
public static final class BoxWithLife implements IDebugRenderable, Closeable public static final class BoxWithLife implements IDebugRenderable, Closeable
{ {
public Box box; public Box box;
public BoxParticle particaleOnClose; public BoxParticle particleOnClose;
public BoxWithLife(Box box, long ns, float yChange, Color deathColor) public BoxWithLife(Box box, long ns, float yChange, Color deathColor)
{ {
this.box = box; this.box = box;
this.particaleOnClose = new BoxParticle(new Box(box.minPos, box.maxPos, deathColor), -1, ns, yChange); this.particleOnClose = new BoxParticle(new Box(box.minPos, box.maxPos, deathColor), -1, ns, yChange);
register(this, null); register(this, null);
} }
@@ -366,7 +363,7 @@ public class DebugRenderer
public BoxWithLife(Box box, double s, float yChange, Color deathColor) public BoxWithLife(Box box, double s, float yChange, Color deathColor)
{ {
this.box = box; this.box = box;
this.particaleOnClose = new BoxParticle(new Box(box.minPos, box.maxPos, deathColor), s, yChange); this.particleOnClose = new BoxParticle(new Box(box.minPos, box.maxPos, deathColor), s, yChange);
} }
public BoxWithLife(Box box, double s, float yChange) { this(box, s, yChange, box.color); } public BoxWithLife(Box box, double s, float yChange) { this(box, s, yChange, box.color); }
@@ -377,7 +374,7 @@ public class DebugRenderer
@Override @Override
public void close() public void close()
{ {
makeParticle(new BoxParticle(this.particaleOnClose.getBox(), System.nanoTime(), this.particaleOnClose.duration, this.particaleOnClose.yChange)); makeParticle(new BoxParticle(this.particleOnClose.getBox(), System.nanoTime(), this.particleOnClose.durationInMs, this.particleOnClose.yChange));
unregister(this, null); unregister(this, null);
} }
@@ -31,6 +31,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLW
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL43C;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -99,6 +100,11 @@ public class DhFadeRenderer
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null); GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
// disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0); GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0);
} }
@@ -110,8 +116,6 @@ public class DhFadeRenderer
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler)
{ {
GLState mcState = new GLState();
try try
{ {
profiler.push("Fade Generate"); profiler.push("Fade Generate");
@@ -149,10 +153,6 @@ public class DhFadeRenderer
} }
finally 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(); profiler.pop();
} }
} }
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL43C;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -92,6 +93,10 @@ public class FogRenderer
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fogTexture, 0); GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fogTexture, 0);
// disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
} }
@@ -42,6 +42,7 @@ import com.seibel.distanthorizons.core.render.renderer.shaders.*;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.objects.SortedArraySet; import com.seibel.distanthorizons.core.util.objects.SortedArraySet;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -69,6 +70,7 @@ public class LodRenderer
.maxCountPerSecond(4) .maxCountPerSecond(4)
.build(); .build();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class); private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private static final IIrisAccessor IRIS_ACCESSOR = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class); private static final IIrisAccessor IRIS_ACCESSOR = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class);
@@ -174,6 +176,13 @@ public class LodRenderer
return; return;
} }
// only do this once, that way they can still be reverted if desired
if (Config.Client.Advanced.Graphics.overrideVanillaGraphicsSettings.get())
{
MC.disableVanillaClouds();
MC.disableVanillaChunkFadeIn();
}
this.renderObjectsCreated = true; this.renderObjectsCreated = true;
} }
@@ -243,7 +252,10 @@ public class LodRenderer
} }
// far plane clip fading // 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"); profiler.popPush("Fade Far Clip Fade");
DhFadeRenderer.INSTANCE.render( DhFadeRenderer.INSTANCE.render(
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLW
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.Mat4f;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL43C;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -91,6 +92,11 @@ public class SSAORenderer
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_R16F, width, height, 0, GL32.GL_RED, GL32.GL_HALF_FLOAT, (ByteBuffer) null); GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_R16F, width, height, 0, GL32.GL_RED, GL32.GL_HALF_FLOAT, (ByteBuffer) null);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
// disable mip-mapping since DH is just going to draw straight to the screen
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.ssaoTexture, 0); GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.ssaoTexture, 0);
} }
@@ -121,6 +121,16 @@ public class VanillaFadeRenderer
public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level) 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(); IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.pop(); // get out of "terrain" profiler.pop(); // get out of "terrain"
profiler.push("DH-Vanilla Fade"); profiler.push("DH-Vanilla Fade");
@@ -158,7 +168,7 @@ public class VanillaFadeRenderer
FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture; FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture;
FadeApplyShader.INSTANCE.readFramebuffer = DhFadeShader.INSTANCE.frameBuffer; FadeApplyShader.INSTANCE.readFramebuffer = DhFadeShader.INSTANCE.frameBuffer;
FadeApplyShader.INSTANCE.drawFramebuffer = LodRenderer.INSTANCE.getActiveFramebufferId(); FadeApplyShader.INSTANCE.drawFramebuffer = MC_RENDER.getTargetFramebuffer();
FadeApplyShader.INSTANCE.render(partialTicks); FadeApplyShader.INSTANCE.render(partialTicks);
} }
@@ -164,10 +164,11 @@ public class FogShader extends AbstractShaderRenderer
// Fog uniforms // Fog uniforms
this.shader.setUniform(this.uFogColor, MC_RENDER.isFogStateSpecial() ? this.getSpecialFogColor(partialTicks) : this.getFogColor(partialTicks)); this.shader.setUniform(this.uFogColor, this.getFogColor(partialTicks));
this.shader.setUniform(this.uFogScale, 1.f / lodDrawDistance); this.shader.setUniform(this.uFogScale, 1.f / lodDrawDistance);
this.shader.setUniform(this.uFogVerticalScale, 1.f / MC.getWrappedClientLevel().getMaxHeight()); this.shader.setUniform(this.uFogVerticalScale, 1.f / MC.getWrappedClientLevel().getMaxHeight());
this.shader.setUniform(this.uFullFogMode, MC_RENDER.isFogStateSpecial() ? 1 : 0); // only used for debugging
this.shader.setUniform(this.uFullFogMode, 0); // 1 = render everything with fog color // 7 = use debug rendering
// fog config // fog config
@@ -229,7 +230,6 @@ public class FogShader extends AbstractShaderRenderer
return fogColor; return fogColor;
} }
private Color getSpecialFogColor(float partialTicks) { return MC_RENDER.getSpecialFogColor(partialTicks); }
public void setProjectionMatrix(Mat4f projectionMatrix) public void setProjectionMatrix(Mat4f projectionMatrix)
{ {
@@ -69,8 +69,7 @@ public class FullDataSourceV1DTO implements IBaseDTO<Long>
/** @return a stream for the data contained in this DTO. */ /** @return a stream for the data contained in this DTO. */
public DhDataInputStream getInputStream() throws IOException public DhDataInputStream getInputStream() throws IOException
{ {
InputStream inputStream = new ByteArrayInputStream(this.dataArray); 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
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
return compressedStream; return compressedStream;
} }
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.sql.dto; package com.seibel.distanthorizons.core.sql.dto;
import com.github.luben.zstd.Zstd;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode; import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode; import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
@@ -65,8 +66,6 @@ public class FullDataSourceV2DTO
public long pos; public long pos;
public int levelMinY;
/** only for the data array */ /** only for the data array */
public int dataChecksum; public int dataChecksum;
@@ -132,7 +131,6 @@ public class FullDataSourceV2DTO
dto.createdUnixDateTime = dataSource.createdUnixDateTime; dto.createdUnixDateTime = dataSource.createdUnixDateTime;
dto.applyToParent = dataSource.applyToParent; dto.applyToParent = dataSource.applyToParent;
dto.applyToChildren = dataSource.applyToChildren; dto.applyToChildren = dataSource.applyToChildren;
dto.levelMinY = dataSource.levelMinY;
} }
return dto; return dto;
@@ -296,8 +294,6 @@ public class FullDataSourceV2DTO
dataSource.lastModifiedUnixDateTime = this.lastModifiedUnixDateTime; dataSource.lastModifiedUnixDateTime = this.lastModifiedUnixDateTime;
dataSource.createdUnixDateTime = this.createdUnixDateTime; dataSource.createdUnixDateTime = this.createdUnixDateTime;
dataSource.levelMinY = this.levelMinY;
dataSource.isEmpty = false; dataSource.isEmpty = false;
if (this.applyToParent != null) if (this.applyToParent != null)
@@ -322,12 +318,7 @@ public class FullDataSourceV2DTO
LongArrayList[] inputDataArray, ByteArrayList outputByteArray, LongArrayList[] inputDataArray, ByteArrayList outputByteArray,
EDhApiDataCompressionMode compressionModeEnum) throws IOException EDhApiDataCompressionMode compressionModeEnum) throws IOException
{ {
// write the outputs to a stream to prep for writing to the database try (DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
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))
{ {
// write the data // write the data
int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH; int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH;
@@ -347,20 +338,13 @@ public class FullDataSourceV2DTO
compressedOut.writeLong(dataColumn.getLong(y)); compressedOut.writeLong(dataColumn.getLong(y));
} }
} }
// generate the checksum
compressedOut.flush();
byteArrayOutputStream.close();
outputByteArray.addElements(0, byteArrayOutputStream.toByteArray());
} }
} }
private static void readBlobToDataSourceDataArrayV1( private static void readBlobToDataSourceDataArrayV1(
ByteArrayList inputCompressedDataByteArray, LongArrayList[] outputDataLongArray, ByteArrayList inputCompressedDataByteArray, LongArrayList[] outputDataLongArray,
EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{ {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements()); try (DhDataInputStream compressedIn = DhDataInputStream.create(inputCompressedDataByteArray, compressionModeEnum))
try (DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
{ {
// read the data // read the data
int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH; int dataArrayLength = FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH;
@@ -412,8 +396,7 @@ public class FullDataSourceV2DTO
maxZ = FullDataSourceV2.WIDTH-1; maxZ = FullDataSourceV2.WIDTH-1;
} }
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try (DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
{ {
// this method would be simpler if we allocated a bunch of temporary arrays, // this method would be simpler if we allocated a bunch of temporary arrays,
// but we're trying to avoid garbage. // 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( private static void readBlobToDataSourceDataArrayV2(
@@ -560,8 +539,8 @@ public class FullDataSourceV2DTO
maxZ = FullDataSourceV2.WIDTH-1; 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 // 1. column counts, preallocate
for (int x = minX; x < maxX; x++) 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 private static void writeGenerationStepsToBlob(ByteArrayList inputColumnGenStepByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
{ {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try (DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
{ {
for (int i = 0; i < inputColumnGenStepByteArray.size(); i++) for (int i = 0; i < inputColumnGenStepByteArray.size(); i++)
{ {
compressedOut.writeByte(inputColumnGenStepByteArray.getByte(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 private static void readBlobToGenerationSteps(ByteArrayList inputCompressedDataByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{ {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements()); try(DhDataInputStream compressedIn = DhDataInputStream.create(inputCompressedDataByteArray, compressionModeEnum))
try(DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
{ {
compressedIn.readFully(outputByteArray.elements(), 0, FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH); 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 private static void writeWorldCompressionModeToBlob(ByteArrayList inputWorldCompressionModeByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
{ {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try (DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
try (DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
{ {
for (int i = 0; i < inputWorldCompressionModeByteArray.size(); i++) for (int i = 0; i < inputWorldCompressionModeByteArray.size(); i++)
{ {
compressedOut.write(inputWorldCompressionModeByteArray.getByte(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 private static void readBlobToWorldCompressionMode(ByteArrayList inputCompressedDataByteArray, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException, DataCorruptedException
{ {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputCompressedDataByteArray.elements()); try(DhDataInputStream compressedIn = DhDataInputStream.create(inputCompressedDataByteArray, compressionModeEnum))
try(DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
{ {
compressedIn.readFully(outputByteArray.elements(), 0, FullDataSourceV2.WIDTH * FullDataSourceV2.WIDTH); 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 private static void writeDataMappingToBlob(FullDataPointIdMap mapping, ByteArrayList outputByteArray, EDhApiDataCompressionMode compressionModeEnum) throws IOException
{ {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try(DhDataOutputStream compressedOut = DhDataOutputStream.create(compressionModeEnum, outputByteArray))
try(DhDataOutputStream compressedOut = new DhDataOutputStream(byteArrayOutputStream, compressionModeEnum))
{ {
mapping.serialize(compressedOut); 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 = DhDataInputStream.create(inputCompressedDataByteArray, compressionModeEnum))
try (DhDataInputStream compressedIn = new DhDataInputStream(byteArrayInputStream, compressionModeEnum))
{ {
FullDataPointIdMap mapping = FullDataPointIdMap.deserialize(compressedIn, pos, levelWrapper); FullDataPointIdMap mapping = FullDataPointIdMap.deserialize(compressedIn, pos, levelWrapper);
return mapping; return mapping;
@@ -779,14 +738,27 @@ public class FullDataSourceV2DTO
out.writeLong(this.pos); out.writeLong(this.pos);
out.writeInt(this.dataChecksum); out.writeInt(this.dataChecksum);
// data
out.writeInt(this.compressedDataByteArray.size()); out.writeInt(this.compressedDataByteArray.size());
out.writeBytes(this.compressedDataByteArray.elements(), 0, 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.writeInt(this.compressedColumnGenStepByteArray.size());
out.writeBytes(this.compressedColumnGenStepByteArray.elements(), 0, this.compressedColumnGenStepByteArray.size()); out.writeBytes(this.compressedColumnGenStepByteArray.elements(), 0, this.compressedColumnGenStepByteArray.size());
out.writeInt(this.compressedWorldCompressionModeByteArray.size()); out.writeInt(this.compressedWorldCompressionModeByteArray.size());
out.writeBytes(this.compressedWorldCompressionModeByteArray.elements(), 0, this.compressedWorldCompressionModeByteArray.size()); out.writeBytes(this.compressedWorldCompressionModeByteArray.elements(), 0, this.compressedWorldCompressionModeByteArray.size());
// compression type
out.writeInt(this.compressedMappingByteArray.size()); out.writeInt(this.compressedMappingByteArray.size());
out.writeBytes(this.compressedMappingByteArray.elements(), 0, this.compressedMappingByteArray.size()); out.writeBytes(this.compressedMappingByteArray.elements(), 0, this.compressedMappingByteArray.size());
@@ -806,14 +778,27 @@ public class FullDataSourceV2DTO
this.pos = in.readLong(); this.pos = in.readLong();
this.dataChecksum = in.readInt(); this.dataChecksum = in.readInt();
// data
this.compressedDataByteArray.size(in.readInt()); this.compressedDataByteArray.size(in.readInt());
in.readBytes(this.compressedDataByteArray.elements(), 0, this.compressedDataByteArray.size()); 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()); this.compressedColumnGenStepByteArray.size(in.readInt());
in.readBytes(this.compressedColumnGenStepByteArray.elements(), 0, this.compressedColumnGenStepByteArray.size()); in.readBytes(this.compressedColumnGenStepByteArray.elements(), 0, this.compressedColumnGenStepByteArray.size());
this.compressedWorldCompressionModeByteArray.size(in.readInt()); this.compressedWorldCompressionModeByteArray.size(in.readInt());
in.readBytes(this.compressedWorldCompressionModeByteArray.elements(), 0, this.compressedWorldCompressionModeByteArray.size()); in.readBytes(this.compressedWorldCompressionModeByteArray.elements(), 0, this.compressedWorldCompressionModeByteArray.size());
// compression type
this.compressedMappingByteArray.size(in.readInt()); this.compressedMappingByteArray.size(in.readInt());
in.readBytes(this.compressedMappingByteArray.elements(), 0, this.compressedMappingByteArray.size()); in.readBytes(this.compressedMappingByteArray.elements(), 0, this.compressedMappingByteArray.size());
@@ -842,7 +827,6 @@ public class FullDataSourceV2DTO
public String toString() public String toString()
{ {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
.add("levelMinY", this.levelMinY)
.add("pos", DhSectionPos.toString(this.pos)) .add("pos", DhSectionPos.toString(this.pos))
.add("dataChecksum", this.dataChecksum) .add("dataChecksum", this.dataChecksum)
.add("compressedDataByteArray length", this.compressedDataByteArray.size()) .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. */ /** @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.databaseType = databaseType;
this.databaseFile = databaseFile; this.databaseFile = databaseFile;
@@ -107,7 +107,7 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
{ {
if (!parentFolder.mkdirs()) 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) 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()) 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()) 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.awt.*;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -47,7 +48,7 @@ public class BeaconBeamRepo extends AbstractDhRepo<DhBlockPos, BeaconBeamDTO>
// constructor // // constructor //
//=============// //=============//
public BeaconBeamRepo(String databaseType, File databaseFile) throws SQLException public BeaconBeamRepo(String databaseType, File databaseFile) throws SQLException, IOException
{ {
super(databaseType, databaseFile, BeaconBeamDTO.class); super(databaseType, databaseFile, BeaconBeamDTO.class);
} }
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -40,7 +41,7 @@ public class ChunkHashRepo extends AbstractDhRepo<DhChunkPos, ChunkHashDTO>
// constructor // // constructor //
//=============// //=============//
public ChunkHashRepo(String databaseType, File databaseFile) throws SQLException public ChunkHashRepo(String databaseType, File databaseFile) throws SQLException, IOException
{ {
super(databaseType, databaseFile, ChunkHashDTO.class); super(databaseType, databaseFile, ChunkHashDTO.class);
} }
@@ -28,6 +28,7 @@ import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -45,7 +46,7 @@ public class FullDataSourceV1Repo extends AbstractDhRepo<Long, FullDataSourceV1D
// constructor // // constructor //
//=============// //=============//
public FullDataSourceV1Repo(String databaseType, File databaseFile) throws SQLException public FullDataSourceV1Repo(String databaseType, File databaseFile) throws SQLException, IOException
{ {
super(databaseType, databaseFile, FullDataSourceV1DTO.class); super(databaseType, databaseFile, FullDataSourceV1DTO.class);
} }
@@ -51,7 +51,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
// constructor // // constructor //
//=============// //=============//
public FullDataSourceV2Repo(String databaseType, File databaseFile) throws SQLException public FullDataSourceV2Repo(String databaseType, File databaseFile) throws SQLException, IOException
{ {
super(databaseType, databaseFile, FullDataSourceV2DTO.class); super(databaseType, databaseFile, FullDataSourceV2DTO.class);
} }
@@ -98,7 +98,6 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
int posZ = resultSet.getInt("PosZ"); int posZ = resultSet.getInt("PosZ");
long pos = DhSectionPos.encode(sectionDetailLevel, posX, posZ); long pos = DhSectionPos.encode(sectionDetailLevel, posX, posZ);
int minY = resultSet.getInt("MinY");
int dataChecksum = resultSet.getInt("DataChecksum"); int dataChecksum = resultSet.getInt("DataChecksum");
@@ -145,19 +144,17 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
dto.createdUnixDateTime = createdUnixDateTime; dto.createdUnixDateTime = createdUnixDateTime;
dto.applyToParent = applyToParent; dto.applyToParent = applyToParent;
dto.applyToChildren = applyToChildren; dto.applyToChildren = applyToChildren;
dto.levelMinY = minY;
} }
return dto; return dto;
} }
@Nullable @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 // // get statement values //
//======================// //======================//
int minY = resultSet.getInt("MinY");
int dataChecksum = resultSet.getInt("DataChecksum"); int dataChecksum = resultSet.getInt("DataChecksum");
@@ -178,17 +175,11 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
//===================// //===================//
FullDataSourceV2DTO dto = FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(); FullDataSourceV2DTO dto = FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding();
// dto.compressedNorthAdjDataByteArray = putAllBytes(resultSet.getBinaryStream("NorthAdjData"), dto.compressedNorthAdjDataByteArray);
// set pooled arrays // set pooled arrays
dto.compressedDataByteArray = putAllBytes(resultSet.getBinaryStream("AdjData"), dto.compressedDataByteArray); dto.compressedDataByteArray = putAllBytes(resultSet.getBinaryStream("AdjData"), dto.compressedDataByteArray);
dto.compressedColumnGenStepByteArray = putAllBytes(resultSet.getBinaryStream("ColumnGenerationStep"), dto.compressedColumnGenStepByteArray); dto.compressedColumnGenStepByteArray = putAllBytes(resultSet.getBinaryStream("ColumnGenerationStep"), dto.compressedColumnGenStepByteArray);
dto.compressedWorldCompressionModeByteArray = putAllBytes(resultSet.getBinaryStream("ColumnWorldCompressionMode"), dto.compressedWorldCompressionModeByteArray); dto.compressedWorldCompressionModeByteArray = putAllBytes(resultSet.getBinaryStream("ColumnWorldCompressionMode"), dto.compressedWorldCompressionModeByteArray);
dto.compressedMappingByteArray = putAllBytes(resultSet.getBinaryStream("Mapping"), dto.compressedMappingByteArray); 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 // set individual variables
{ {
@@ -200,7 +191,6 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
dto.createdUnixDateTime = createdUnixDateTime; dto.createdUnixDateTime = createdUnixDateTime;
dto.applyToParent = applyToParent; dto.applyToParent = applyToParent;
dto.applyToChildren = applyToChildren; dto.applyToChildren = applyToChildren;
dto.levelMinY = minY;
} }
return dto; return dto;
} }
@@ -237,7 +227,10 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
statement.setInt(i++, DhSectionPos.getX(dto.pos)); statement.setInt(i++, DhSectionPos.getX(dto.pos));
statement.setInt(i++, DhSectionPos.getZ(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.setInt(i++, dto.dataChecksum);
statement.setBinaryStream(i++, new ByteArrayInputStream(dto.compressedDataByteArray.elements()), dto.compressedDataByteArray.size()); statement.setBinaryStream(i++, new ByteArrayInputStream(dto.compressedDataByteArray.elements()), dto.compressedDataByteArray.size());
@@ -272,8 +265,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
String updateSqlTemplate = ( String updateSqlTemplate = (
"UPDATE "+this.getTableName()+" \n" + "UPDATE "+this.getTableName()+" \n" +
"SET \n" + "SET \n" +
" MinY = ? \n" + " DataChecksum = ? \n" +
" ,DataChecksum = ? \n" +
" ,Data = ? \n" + " ,Data = ? \n" +
" ,ColumnGenerationStep = ? \n" + " ,ColumnGenerationStep = ? \n" +
@@ -303,7 +295,6 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
int i = 1; int i = 1;
statement.setInt(i++, dto.levelMinY);
statement.setInt(i++, dto.dataChecksum); statement.setInt(i++, dto.dataChecksum);
statement.setBinaryStream(i++, new ByteArrayInputStream(dto.compressedDataByteArray.elements()), dto.compressedDataByteArray.size()); 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 = private final String getAdjForDirectionSqlTemplate =
"SELECT \n" + "SELECT \n" +
" MinY, DataChecksum, \n" + " DataChecksum, \n" +
" ColumnGenerationStep, ColumnWorldCompressionMode, Mapping, \n" + " ColumnGenerationStep, ColumnWorldCompressionMode, Mapping, \n" +
" DataFormatVersion, CompressionMode, ApplyToParent, ApplyToChildren, \n" + " DataFormatVersion, CompressionMode, ApplyToParent, ApplyToChildren, \n" +
" LastModifiedUnixDateTime, CreatedUnixDateTime, \n" + " LastModifiedUnixDateTime, CreatedUnixDateTime, \n" +
@@ -402,7 +393,7 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
if (resultSet != null if (resultSet != null
&& resultSet.next()) && resultSet.next())
{ {
return this.convertResultSetToAdjDto(pos, direction, resultSet); return this.convertResultSetToAdjDto(pos, resultSet);
} }
else else
{ {
@@ -581,10 +572,15 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
EDhApiDataCompressionMode compressionModeEnum = EDhApiDataCompressionMode.getFromValue(compressionModeEnumValue); EDhApiDataCompressionMode compressionModeEnum = EDhApiDataCompressionMode.getFromValue(compressionModeEnumValue);
// decompress the data // decompress the data
try(DhDataInputStream compressedIn = new DhDataInputStream(result.getBinaryStream("ColumnGenerationStep"), compressionModeEnum)) try
{
ByteArrayList byteArrayList = new ByteArrayList();
putAllBytes(result.getBinaryStream("ColumnGenerationStep"), byteArrayList);
try(DhDataInputStream compressedIn = DhDataInputStream.create(byteArrayList, compressionModeEnum))
{ {
putAllBytes(compressedIn, outputByteArray); putAllBytes(compressedIn, outputByteArray);
} }
}
catch (IOException e) catch (IOException e)
{ {
LOGGER.warn("Decompression issue when getting column gen steps for pos: [" + DhSectionPos.toString(pos) + "], deleting corrupted data.", e); LOGGER.warn("Decompression issue when getting column gen steps for pos: [" + DhSectionPos.toString(pos) + "], deleting corrupted data.", e);
@@ -13,8 +13,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
public class DhApiTerrainDataPointUtil 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) public static DhApiTerrainDataPoint createApiDatapoint(int minLevelHeight, FullDataPointIdMap mapping, byte detailLevel, long dataPoint)
{ {
IBlockStateWrapper blockState = mapping.getBlockStateWrapper(FullDataPointUtil.getId(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.Arrays;
import java.util.List; 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.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.vertexFormat.DefaultLodVertexFormats; import com.seibel.distanthorizons.core.render.vertexFormat.DefaultLodVertexFormats;
import com.seibel.distanthorizons.core.render.vertexFormat.LodVertexFormat; 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.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; import com.seibel.distanthorizons.core.logging.DhLogger;
/** /**
@@ -191,17 +181,6 @@ public class LodUtil
throw new AssertFailureException("Assert Not Reach failed:\n " + message); 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;
}
} }
@@ -0,0 +1,32 @@
package com.seibel.distanthorizons.core.util;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
public class WorldGenUtil
{
/** will always return true if a generation max radius isn't set */
public static boolean isPosInWorldGenRange(
long requestedPos,
int centerChunkX, int centerChunkZ,
int maxChunkRadius)
{
if (Config.Common.WorldGenerator.generationMaxChunkRadius.get() <= 0)
{
return true;
}
DhBlockPos centerBlockPos = new DhChunkPos(centerChunkX, centerChunkZ).centerBlockPos();
int blockDistanceFromCenter = DhSectionPos.getChebyshevSignedBlockDistance(requestedPos, centerBlockPos);
int maxBlockRadius = maxChunkRadius * LodUtil.CHUNK_WIDTH;
boolean requestInRadius = (blockDistanceFromCenter <= maxBlockRadius);
return requestInRadius;
}
}

Some files were not shown because too many files have changed in this diff Show More