Compare commits

...

45 Commits

Author SHA1 Message Date
s809 034ec7d656 Bump protocol version 2025-08-16 21:01:45 +05:00
s809 fb5e15a2f1 Add a server keys feature 2025-08-16 20:59:28 +05:00
s809 674fc30e77 Replace pooled buffers with unpooled 2025-08-07 17:55:22 +05:00
s809 a05bd307f9 Reduce network logging by default 2025-07-27 23:21:13 +05:00
James Seibel d78a50ce49 up version number 2.3.4 -> 2.3.5 2025-07-19 14:59:14 -05:00
James Seibel 013eab9268 add space to self updater warning log 2025-07-19 14:57:22 -05:00
James Seibel 435cbde238 remove dev from version number 2025-07-19 14:56:53 -05:00
s809 d7040bad13 Load level on player add if missing 2025-07-19 19:05:52 +05:00
James Seibel a588070ce1 up version number 2.3.3 -> 2.3.4 2025-07-12 09:35:05 -05:00
James Seibel d156772438 remove dev from the version number 2025-07-12 09:34:18 -05:00
James Seibel de7ae41769 Fix API config renderingEnabled() changing the user value
Fixes #1083
2025-07-12 08:16:35 -05:00
James Seibel 618ad1938b full data DTO close data source if corrupted 2025-07-10 22:24:33 -05:00
James Seibel 5b10263f82 minor format cleanup 2025-07-10 07:27:23 -05:00
James Seibel 34f914c52f Mark a unit test as deprecated
Done to suppress warnings in compiler log
2025-07-10 07:26:41 -05:00
James Seibel 67b766c674 Fix monoliths when connected to a server 2025-07-10 07:26:14 -05:00
James Seibel a3e7469203 Fixes !1078 (lag due to beacon updating on server) 2025-07-09 07:28:25 -05:00
James Seibel 4ecaa6a9a1 Potentially fix an issue with AMD GPU shader compiling
Fix from Cortex and the Canvas mod
2025-07-08 07:22:36 -05:00
James Seibel 24f9dadc58 Disable vanilla fading when shaders are active 2025-07-07 07:49:36 -05:00
s809 b3ebaffa85 Disable enableAdaptiveTransferSpeed bby default 2025-07-01 22:03:12 +05:00
James Seibel b7ac1909d6 Fix config UI changes not always saving 2025-07-01 07:45:26 -05:00
James Seibel 32c3118afa comment out Z_STD compression 2025-06-30 06:54:27 -05:00
James Seibel 3a525f53f0 Rename world gen mode "Internal Server" -> "Full - Save Chunks" 2025-06-28 13:57:58 -05:00
James Seibel f3947312c1 Re-Add Z_STD compression for testing 2025-06-28 11:37:06 -05:00
James Seibel 839ea1e778 increase ram amount for unit tests
needed for compression tests
2025-06-28 11:36:17 -05:00
James Seibel d2becd2c03 Fix rare fade error when restarting the LodRenderer 2025-06-28 10:45:36 -05:00
James Seibel 7d87347199 Fix multiplayer null pointer 2025-06-28 09:22:23 -05:00
James Seibel f4117751c9 Fix world-gen progress not showing in release builds 2025-06-27 07:29:31 -05:00
James Seibel a8a085f296 Move RenderState to core 2025-06-26 07:50:53 -05:00
James Seibel 317319593e rename renderDeferredLods -> renderDeferredLodsForShaders 2025-06-25 07:47:08 -05:00
James Seibel 4633f90a03 Add null handling to ServerPlayerStateManager.handlePluginMessage() 2025-06-25 07:45:43 -05:00
James Seibel 5802bbb3f3 keep cave culling for medium quality preset
This may be changed back at some point in the future, but depending on the usecase (IE vanilla survival) cave culling is generally better than not having it, and if people see weirdness they can probably guess that increasing the quality preset may fix it.
2025-06-23 07:23:26 -05:00
James Seibel e93d5b90f1 Disable cave culling for medium quality and higher 2025-06-17 07:15:28 -05:00
James Seibel 9be56607a5 Reduce stuttering with fast world gen 2025-06-14 16:17:28 -05:00
James Seibel 91743bf742 Add Api Before/After Text Create events
Deprecate DhApiColorDepthTextureCreatedEvent since it is less obvious when it fires
2025-06-09 07:50:21 -05:00
James Seibel d40d293f54 Fix hash collisions in FullDataPointIdMap 2025-06-06 07:43:38 -05:00
James Seibel a075e60e3e Fix GLMC.glDeleteTextures() calls 2025-06-04 07:07:39 -05:00
s809 d72c7c3695 Check LOD timestamps in file handler threads 2025-06-03 23:41:47 +05:00
Ran 309fa07664 Merge branch 'fix_max_y' into 'main'
Fix max Y validation

See merge request distant-horizons-team/distant-horizons-core!85
2025-05-18 00:32:51 +00:00
Stewart Borle 0a017567c4 Fix max Y validation 2025-05-18 00:32:51 +00:00
James Seibel e01261da5c Remove line ending from editorconfig
Done to fix some issues with some devs on linux
2025-05-17 11:47:00 -05:00
James Seibel a0879d07c5 json indent 2 -> 4
for consistency
2025-05-17 11:25:18 -05:00
Ran bbb15263f2 Fix gradle versioning 2025-05-03 11:21:05 +10:00
s809 5ca3563c66 Bump protocol version 2025-05-03 00:08:03 +05:00
s809 30256a2779 Send scaled generation bounds coordinates 2025-05-03 00:08:03 +05:00
Ran 4b4f10f5e6 Fix gradle versioning for core application 2025-05-02 12:44:25 +10:00
52 changed files with 889 additions and 345 deletions
+1 -2
View File
@@ -4,7 +4,6 @@
[*]
charset = utf-8
end_of_line = crlf
indent_size = 4
indent_style = space
insert_final_newline = false
@@ -537,7 +536,7 @@ ij_groovy_wrap_chain_calls_after_dot = false
ij_groovy_wrap_long_lines = false
[{*.har,*.json,*.png.mcmeta,mcmod.info,pack.mcmeta}]
indent_size = 2
indent_size = 4
ij_json_array_wrapping = split_into_lines
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
@@ -39,8 +39,8 @@ public enum EDhApiDataCompressionMode
/**
* Should only be used internally and for unit testing. <br><br>
*
* Read Speed: 1.64 MS / DTO <br>
* Write Speed: 12.44 MS / DTO <br>
* Read Speed: 6.09 MS / DTO <br>
* Write Speed: 6.01 MS / DTO <br>
* Compression ratio: 1.0 <br>
*/
@DisallowSelectingViaConfigGui
@@ -49,28 +49,29 @@ public enum EDhApiDataCompressionMode
/**
* Extremely fast (often faster than uncompressed), but generally poor compression. <br><br>
*
* Read Speed: 1.85 MS / DTO <br>
* Write Speed: 9.46 MS / DTO <br>
* Compression ratio: 0.3638 <br>
* Read Speed: 3.25 MS / DTO <br>
* Write Speed: 5.99 MS / DTO <br>
* Compression ratio: 0.4513 <br>
*/
LZ4(1),
/*
* Decent speed and good compression. <br><br>
*
* Read Speed: 11.78 MS / DTO <br>
* Write Speed: 16.76 MS / DTO <br>
* Compression ratio: 0.2199 <br>
*/
//@Deprecated
///**
// * Decent speed and good compression. <br><br>
// *
// * Read Speed: 9.31 MS / DTO <br>
// * Write Speed: 15.13 MS / DTO <br>
// * Compression ratio: 0.2606 <br>
// */
////@DisallowSelectingViaConfigGui
//Z_STD(2),
/**
* Extremely slow, but very good compression. <br><br>
*
* Read Speed: 12.25 MS / DTO <br>
* Write Speed: 490.07 MS / DTO <br>
* Compression ratio: 0.1242 <br>
* Read Speed: 13.29 MS / DTO <br>
* Write Speed: 70.95 MS / DTO <br>
* Compression ratio: 0.2068 <br>
*/
LZMA2(3);
@@ -40,6 +40,7 @@ public enum EDhApiLoggerMode
LOG_DEBUG_TO_CHAT_AND_FILE(Level.DEBUG, Level.DEBUG),
LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.WARN),
LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE(Level.INFO, Level.ERROR),
LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE(Level.ERROR, Level.WARN),
;
public final Level levelForFile;
@@ -0,0 +1,48 @@
/*
* 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.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
/**
* Called after Distant Horizons (re)creates
* the color and depth textures it renders to. <br>
*
* @author James Seibel
* @version 2025-6-9
* @since API 4.1.0
*/
public abstract class DhApiAfterColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiTextureCreatedParam>
{
/** Fired before Distant Horizons creates. */
public abstract void onResize(DhApiEventParam<DhApiTextureCreatedParam> event);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiEventParam<DhApiTextureCreatedParam> event) { this.onResize(event); }
}
@@ -0,0 +1,49 @@
/*
* 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.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
/**
* Called before Distant Horizons (re)creates
* the color and depth textures it renders to. <br>
*
* @author James Seibel
* @version 2025-6-9
* @since API 4.1.0
*/
public abstract class DhApiBeforeColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiTextureCreatedParam>
{
/** Fired before Distant Horizons creates. */
public abstract void onResize(DhApiEventParam<DhApiTextureCreatedParam> event);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiEventParam<DhApiTextureCreatedParam> event) { this.onResize(event); }
}
@@ -22,15 +22,18 @@ package com.seibel.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
/**
* Called whenever Distant Horizons (re)creates
* Called before Distant Horizons (re)creates
* the color and depth textures it renders to. <br>
*
* @author James Seibel
* @version 2024-3-2
* @since API 2.0.0
* @deprecated Replaced by {@link DhApiBeforeColorDepthTextureCreatedEvent} since this event's name isn't obvious when it fires.
*/
@Deprecated
public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam>
{
/** Fired before Distant Horizons creates. */
@@ -73,6 +76,15 @@ public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<
this.newHeight = newHeight;
}
public EventParam(DhApiTextureCreatedParam textureCreatedParam)
{
this.previousWidth = textureCreatedParam.previousWidth;
this.previousHeight = textureCreatedParam.previousHeight;
this.newWidth = textureCreatedParam.newWidth;
this.newHeight = textureCreatedParam.newHeight;
}
@Override
@@ -0,0 +1,68 @@
/*
* 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.api.methods.events.sharedParameterObjects;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
/**
* Contains information relevant to when Distant Horizons (re)creates
* depth/color textures for rendering.
*
* @author James Seibel
* @version 2025-6-9
* @since API 4.1.0
*/
public class DhApiTextureCreatedParam implements IDhApiEventParam
{
/** Measured in pixels */
public final int previousWidth;
/** Measured in pixels */
public final int previousHeight;
/** Measured in pixels */
public final int newWidth;
/** Measured in pixels */
public final int newHeight;
public DhApiTextureCreatedParam(
int previousWidth, int previousHeight,
int newWidth, int newHeight)
{
this.previousWidth = previousWidth;
this.previousHeight = previousHeight;
this.newWidth = newWidth;
this.newHeight = newHeight;
}
@Override
public DhApiTextureCreatedParam copy()
{
return new DhApiTextureCreatedParam(
this.previousWidth, this.previousHeight,
this.newWidth, this.newHeight
);
}
}
@@ -31,21 +31,21 @@ public final class ModInfo
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
public static final int PROTOCOL_VERSION = 10;
public static final int PROTOCOL_VERSION = 12;
public static final String WRAPPER_PACKET_PATH = "message";
/** The internal mod name */
public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.3.3-b-dev";
public static final String VERSION = "2.3.5-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
/** This version should only be updated when breaking changes are introduced to the DH API */
public static final int API_MAJOR_VERSION = 4;
/** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 0;
public static final int API_MINOR_VERSION = 1;
/** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATCH_VERSION = 0;
@@ -31,14 +31,12 @@ import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
public class RenderModeEnabledConverter implements IConverter<EDhApiRendererMode, Boolean>
{
@Override public EDhApiRendererMode convertToCoreType(Boolean renderingEnabled)
{
return renderingEnabled ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED;
}
@Override
public EDhApiRendererMode convertToCoreType(Boolean renderingEnabled)
{ return renderingEnabled ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED; }
@Override public Boolean convertToApiType(EDhApiRendererMode renderingMode)
{
return renderingMode == EDhApiRendererMode.DEFAULT;
}
@Override
public Boolean convertToApiType(EDhApiRendererMode renderingMode)
{ return renderingMode == EDhApiRendererMode.DEFAULT; }
}
+7 -1
View File
@@ -60,4 +60,10 @@ shadowJar {
def librariesLocation = "DistantHorizons.libraries"
// relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
mergeServiceFiles()
}
}
test {
// this is necessary specifically for the Compression tests since those
// need more than the default 512 MB of RAM
jvmArgs '-Xmx4096m'
}
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.api.interfaces.config.client.*;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.coreapi.util.converters.RenderModeEnabledConverter;
public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
{
@@ -60,7 +61,7 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
@Override
public IDhApiConfigValue<Boolean> renderingEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.quickEnableRendering); }
{ return new DhApiConfigValue<EDhApiRendererMode, Boolean>(Config.Client.Advanced.Debugging.rendererMode, new RenderModeEnabledConverter()); }
@Override
public IDhApiConfigValue<EDhApiRendererMode> renderingMode()
@@ -89,6 +89,15 @@ public class ClientApi
/** this includes the is dev build message and low allocated memory warning */
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000;
/**
* This isn't the cleanest way of storing variables before passing them to the LOD renderer,
* but due to how mixins work and the inconsistency between MC versions,
* having a static object that stores a single frame's data
* is often the easiest solution. <br><br>
*
* Only downside is making sure each variable is populated before rendering.
*/
public static final RenderState RENDER_STATE = new RenderState();
private boolean isDevBuildMessagePrinted = false;
@@ -389,7 +398,7 @@ public class ClientApi
// rendering //
//===========//
/** Should be called before {@link ClientApi#renderDeferredLods} */
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
public void renderLods(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
{ this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, false); }
@@ -397,9 +406,10 @@ public class ClientApi
* Only necessary when Shaders are in use.
* Should be called after {@link ClientApi#renderLods}
*/
public void renderDeferredLods(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
public void renderDeferredLodsForShaders(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
{ this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, true); }
private void renderLodLayer(
IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks,
boolean renderingDeferredLayer)
@@ -445,6 +455,25 @@ public class ClientApi
//Mat4f mcCombined = mcModelViewMatrix.copy();
//mcCombined.multiply(mcProjectionMatrix);
//
//com.seibel.distanthorizons.api.objects.math.DhApiMat4f dhCombined = renderEventParam.dhModelViewMatrix.copy();
//dhCombined.multiply(renderEventParam.dhProjectionMatrix);
//
//LOGGER.info("\n\n" +
// "API\n" +
// "Mc MVM: \n" + mcModelViewMatrix.toString() + "\n" +
// "Mc Proj: \n" + mcProjectionMatrix + "\n" +
// "Mc Combined:\n" + mcCombined.toString() + "\n" +
// "\n" +
// "DH MVM: \n" + renderEventParam.dhModelViewMatrix.toString() + "\n" +
// "DH Proj: \n" + renderEventParam.dhProjectionMatrix + "\n" +
// "DH Combined:\n" + mcCombined.toString()
//);
// render validation //
try
@@ -555,25 +584,26 @@ public class ClientApi
public void renderFadeOpaque(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level)
{
// 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
&& Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() == EDhApiMcRenderingFadeMode.DOUBLE_PASS
// don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{
if (Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() == EDhApiMcRenderingFadeMode.DOUBLE_PASS)
{
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
}
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
}
}
/** should be called after DH and MC finish rendering so we can smooth the transition between the two */
public void renderFade(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level)
{
// 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
&& Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() != EDhApiMcRenderingFadeMode.NONE
// don't fade when Iris shaders are active, otherwise the rendering can get weird
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{
// fade if any level fading is active
if (Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() != EDhApiMcRenderingFadeMode.NONE)
{
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
}
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
}
}
@@ -652,7 +682,8 @@ public class ClientApi
private void detectAndSendBootTimeWarnings()
{
// dev build
if (ModInfo.IS_DEV_BUILD && !this.isDevBuildMessagePrinted && MC_CLIENT.playerExists())
if (ModInfo.IS_DEV_BUILD
&& !this.isDevBuildMessagePrinted && MC_CLIENT.playerExists())
{
this.isDevBuildMessagePrinted = true;
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
@@ -669,7 +700,8 @@ public class ClientApi
// memory
if (this.staticStartupMessageSentRecently()) return;
if (!this.lowMemoryWarningPrinted && Config.Common.Logging.Warning.showLowMemoryWarningOnStartup.get())
if (!this.lowMemoryWarningPrinted
&& Config.Common.Logging.Warning.showLowMemoryWarningOnStartup.get())
{
this.lowMemoryWarningPrinted = true;
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
@@ -694,7 +726,8 @@ public class ClientApi
// high vanilla render distance
if (this.staticStartupMessageSentRecently()) return;
if (!this.highVanillaRenderDistanceWarningPrinted && Config.Common.Logging.Warning.showHighVanillaRenderDistanceWarning.get())
if (!this.highVanillaRenderDistanceWarningPrinted
&& Config.Common.Logging.Warning.showHighVanillaRenderDistanceWarning.get())
{
// DH generally doesn't need a vanilla render distance above 12
if (MC_RENDER.getRenderDistance() > 12)
@@ -721,7 +754,8 @@ public class ClientApi
{
if (this.lastStaticWarningMessageSentMsTime == 0)
{
return true;
// no static message has ever been sent
return false;
}
long timeSinceLastMessage = System.currentTimeMillis() - this.lastStaticWarningMessageSentMsTime;
@@ -740,4 +774,47 @@ public class ClientApi
*/
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); }
//================//
// helper classes //
//================//
public static class RenderState
{
public Mat4f mcModelViewMatrix = null;
public Mat4f mcProjectionMatrix = null;
public float frameTime = -1;
public void canRenderOrThrow() throws IllegalStateException
{
String errorReasons = "";
if (this.mcModelViewMatrix == null)
{
errorReasons += "no MVM Matrix, ";
}
if (this.mcProjectionMatrix == null)
{
errorReasons += "no Projection Matrix, ";
}
if (this.frameTime == -1)
{
errorReasons += "no Frame Time, ";
}
if (!errorReasons.isEmpty())
{
throw new IllegalStateException(errorReasons);
}
}
}
}
@@ -75,7 +75,12 @@ public class ClientPluginChannelApi
private void onLevelInitMessage(LevelInitMessage msg)
{
if (!msg.levelKey.matches(LevelInitMessage.VALIDATION_REGEX))
if (!msg.serverKey.matches(LevelInitMessage.SERVER_KEY_REGEX))
{
throw new IllegalArgumentException("Server sent invalid server key.");
}
if (!msg.levelKey.matches(LevelInitMessage.LEVEL_KEY_REGEX))
{
throw new IllegalArgumentException("Server sent invalid level key.");
}
@@ -105,10 +110,12 @@ public class ClientPluginChannelApi
this.levelUnloadHandler.accept(clientLevel);
}
if (existingKeyedClientLevel == null || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
if (existingKeyedClientLevel == null
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
|| !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{
LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.levelKey);
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel);
}
});
@@ -1342,20 +1342,20 @@ public class Config
+ 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: 1.64 milliseconds\n"
+ "Estimated average DTO write speed: 12.44 milliseconds\n"
+ "Estimated average DTO read speed: 3.25 milliseconds\n"
+ "Estimated average DTO write speed: 5.99 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.36\n"
+ "Expected Compression Ratio: 0.26\n"
+ "Estimated average DTO read speed: 1.85 ms\n"
+ "Estimated average DTO write speed: 9.46 ms\n"
+ "\n"
+ EDhApiDataCompressionMode.LZMA2 + " \n"
+ "Slow but very good compression.\n"
+ "Expected Compression Ratio: 0.14\n"
+ "Estimated average DTO read speed: 11.89 ms\n"
+ "Estimated average DTO write speed: 192.01 ms\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();
@@ -1520,7 +1520,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setChatCommandName("logging.logNetworkEvent")
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE)
.comment(""
+ "If enabled, the mod will log information about network operations. \n"
+ "This can be useful for debugging.")
@@ -1599,6 +1599,28 @@ public class Config
+ "")
.build();
public static ConfigEntry<Integer> serverId = new ConfigEntry.Builder<Integer>()
.set(new Random().nextInt())
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.comment(""
+ "DO NOT CHANGE UNLESS YOU KNOW WHAT YOU'RE DOING.\n"
+ "Autogenerated ID used to prevent multiple independent servers from accidentally\n"
+ "writing over each other's LODs when the same serverKey is set on both.\n"
+ "")
.build();
public static ConfigEntry<String> serverKey = new ConfigEntry.Builder<String>()
.setChatCommandName("levelKeys.serverKey")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.set("")
.comment(""
+ "Custom server key used which can be used to always reuse the same LOD data folder,\n"
+ "for cases when the server doesn't have a static IP for some reason.\n"
+ "If this value is empty, the client itself decides which folder name to use.\n"
+ "Requires rejoining the server to apply after changing.\n"
+ "")
.build();
public static ConfigEntry<String> levelKeyPrefix = new ConfigEntry.Builder<String>()
.setChatCommandName("levelKeys.prefix")
.set("")
@@ -1714,7 +1736,7 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> enableAdaptiveTransferSpeed = new ConfigEntry.Builder<Boolean>()
.set(true)
.set(false)
.comment(""
+ "Enables adaptive transfer speed based on client performance.\n"
+ "If true, DH will automatically adjust transfer rate to minimize connection lag.\n"
@@ -105,6 +105,15 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, true);
this.put(EDhApiQualityPreset.EXTREME, true);
}});
private final ConfigEntryWithPresetOptions<EDhApiQualityPreset, Boolean> caveCulling = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.Culling.enableCaveCulling,
new HashMap<EDhApiQualityPreset, Boolean>()
{{
this.put(EDhApiQualityPreset.MINIMUM, true);
this.put(EDhApiQualityPreset.LOW, true);
this.put(EDhApiQualityPreset.MEDIUM, true);
this.put(EDhApiQualityPreset.HIGH, false);
this.put(EDhApiQualityPreset.EXTREME, false);
}});
@@ -123,6 +132,7 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.configList.add(this.ssaoEnabled);
this.configList.add(this.vanillaFade);
this.configList.add(this.dhDither);
this.configList.add(this.caveCulling);
for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList)
@@ -70,7 +70,12 @@ public class ConfigFileHandling
this.configBase = configBase;
this.configPath = configPath;
this.nightConfig = CommentedFileConfig.builder(this.configPath.toFile()).build();
this.nightConfig = CommentedFileConfig
.builder(this.configPath.toFile())
// sync is needed so file reading/writing only happens during locked sections,
// otherwise some GUI changes may be lost when changing screens
.sync()
.build();
}
@@ -321,10 +326,7 @@ public class ConfigFileHandling
*
* @apiNote This overwrites any value currently stored in the config
*/
public void loadNightConfig()
{
loadNightConfig(this.nightConfig);
}
public void loadNightConfig() { this.loadNightConfig(this.nightConfig); }
/**
* Does {@link CommentedFileConfig#load()} but with error checking
*
@@ -353,7 +355,7 @@ public class ConfigFileHandling
{
System.out.println("Creating file failed");
this.logger.error(ex);
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Loading file and resetting config file failed at path [" + configPath + "]. Please check the file is ok and you have the permissions", ex);
SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).crashMinecraft("Loading file and resetting config file failed at path [" + this.configPath + "]. Please check the file is ok and you have the permissions", ex);
}
}
@@ -29,14 +29,12 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* WARNING: This is not THREAD-SAFE! <br><br>
@@ -47,7 +45,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
* since it stringifies every block and biome name, which is quite bulky.
* It might be worth while to have a biome and block ID that then both get mapped
* to the data point ID to reduce file size.
* And/or it would be good to dynamically remove IDs that aren't currently in use.
* And/or it would be good to dynamically remove IDs that aren't currently in use.
*
* @author Leetom
*/
@@ -352,14 +350,14 @@ public class FullDataPointIdMap
{
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private static final ConcurrentHashMap<Integer, Entry> ENTRY_BY_HASH = new ConcurrentHashMap<>();
/** lock is necessary since {@link Int2ReferenceOpenHashMap} isn't concurrent and concurrent threads can cause infinite loops */
private static final ReentrantReadWriteLock ENTRY_POOL_LOCK = new ReentrantReadWriteLock();
/** two levels are present so we don't need to use a key object */
private static final ConcurrentHashMap<IBiomeWrapper, ConcurrentHashMap<IBlockStateWrapper, Entry>> ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER = new ConcurrentHashMap<>();
public final IBiomeWrapper biome;
public final IBlockStateWrapper blockState;
private Integer hashCode = null;
private int hashCode = 0;
private boolean hashGenerated = false;
private String serialString = null;
@@ -370,25 +368,21 @@ public class FullDataPointIdMap
public static Entry getEntry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
int entryHash = generateHashCode(biome, blockState);
// try getting the existing Entry
Entry entry = ENTRY_BY_HASH.get(entryHash);
if (entry != null)
// check for existing entry
ConcurrentHashMap<IBlockStateWrapper, Entry> entryByBlockState = ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER.get(biome);
if (entryByBlockState != null)
{
return entry;
Entry entry = entryByBlockState.get(blockState);
if (entry != null)
{
return entry;
}
}
// create the missing entry
return ENTRY_BY_HASH.compute(entryHash, (Integer newHash, Entry currentEntry) ->
{
if (currentEntry != null)
{
return currentEntry;
}
return new Entry(biome, blockState);
});
// Lazily create the inner map and new Entry
return ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER
.computeIfAbsent(biome, newBiome -> new ConcurrentHashMap<>())
.computeIfAbsent(blockState, newBlockState -> new Entry(biome, blockState));
}
private Entry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{
@@ -402,13 +396,18 @@ public class FullDataPointIdMap
// overrides //
//===========//
/**
* Reminder: this hash code won't always be unique, collisions can occur;
* because of that this hash shouldn't be the only unique identifier for this object.
*/
@Override
public int hashCode()
{
// cache the hash code to improve speed
if (this.hashCode == null)
if (!this.hashGenerated)
{
this.hashCode = generateHashCode(this);
this.hashGenerated = true;
}
return this.hashCode;
@@ -430,10 +429,14 @@ public class FullDataPointIdMap
public boolean equals(Object otherObj)
{
if (otherObj == this)
{
return true;
}
if (!(otherObj instanceof Entry))
{
return false;
}
Entry other = (Entry) otherObj;
return other.biome.getSerialString().equals(this.biome.getSerialString())
@@ -139,7 +139,7 @@ public class FullDataToRenderDataTransformer
}
columnSource.fillDebugFlag(0, 0, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.DebugSourceFlag.FULL);
return columnSource;
}
@@ -81,13 +81,20 @@ public class ClientOnlySaveStructure implements ISaveStructure
{
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
LOGGER.info("Loading level [" + newLevelWrapper.getDhIdentifier() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "].");
// This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = getSaveFolderByLevelId(keyedClientLevel.getServerLevelKey());
String serverKey = keyedClientLevel.getServerKey();
if (serverKey.isEmpty())
{
serverKey = getServerFolderName();
}
// This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = getSaveFolderByLevelId(serverKey, keyedClientLevel.getServerLevelKey());
}
else
{
// get the default folder
saveFolder = getSaveFolderByLevelId(levelWrapper.getDhIdentifier());
saveFolder = getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDhIdentifier());
}
// Allow API users to override the save folder
@@ -116,7 +123,7 @@ public class ClientOnlySaveStructure implements ISaveStructure
return this.getSaveFolder(levelWrapper);
}
return getSaveFolderByLevelId(levelWrapper.getDimensionType().getName());
return getSaveFolderByLevelId(getServerFolderName(), levelWrapper.getDimensionType().getName());
}
@@ -173,11 +180,11 @@ public class ClientOnlySaveStructure implements ISaveStructure
}
private static File getSaveFolderByLevelId(String dimensionName)
private static File getSaveFolderByLevelId(String folderName, String dimensionName)
{
String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar
+ SERVER_DATA_FOLDER_NAME + File.separatorChar
+ getServerFolderName() + File.separatorChar
+ folderName + File.separatorChar
+ dimensionName.replaceAll(":", "@@");
return new File(path);
@@ -113,8 +113,8 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
if (this.networkState.sessionConfig.getGenerationBoundsRadius() > 0)
{
if (DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, new DhBlockPos2D(
(int) (this.networkState.sessionConfig.getGenerationBoundsX() / this.level.levelWrapper.getDimensionType().getCoordinateScale()),
(int) (this.networkState.sessionConfig.getGenerationBoundsZ() / this.level.levelWrapper.getDimensionType().getCoordinateScale())
this.networkState.sessionConfig.getGenerationBoundsX(),
this.networkState.sessionConfig.getGenerationBoundsZ()
)) > this.networkState.sessionConfig.getGenerationBoundsRadius())
{
return false;
@@ -111,7 +111,7 @@ public class SelfUpdater
}
if (!ModrinthGetter.mcVersions.contains(mcVersion))
{
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Modrinth, only findable versions are ["+ StringUtil.join(",", ModrinthGetter.mcVersions) +"]");
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Modrinth, only findable versions are ["+ StringUtil.join(", ", ModrinthGetter.mcVersions) +"]");
return false;
}
@@ -277,7 +277,7 @@ public abstract class AbstractDhLevel implements IDhLevel
// locked to prevent two threads from updating the same section at the same time
ReentrantLock lock = this.beaconUpdateLockContainer.getLockForPos(sectionPosForLock);
ReentrantLock lock = this.beaconUpdateLockContainer.getLockForPos(sectionPosForLock); // TODO this can cause a lot of slow-downs
try
{
lock.lock();
@@ -151,10 +151,9 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
if (Config.Server.generationBoundsRadius.get() > 0)
{
double coordinateScale = this.serverLevelWrapper.getDimensionType().getCoordinateScale();
if (DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D(
(int) (Config.Server.generationBoundsX.get() / coordinateScale),
(int) (Config.Server.generationBoundsZ.get() / coordinateScale)
serverPlayerState.sessionConfig.getGenerationBoundsX(),
serverPlayerState.sessionConfig.getGenerationBoundsZ()
)) > Config.Server.generationBoundsRadius.get())
{
message.sendResponse(new RequestOutOfRangeException("Section out of allowed bounds"));
@@ -262,29 +261,27 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
}
LodUtil.assertTrue(this.beaconBeamRepo != null, "beaconBeamRepo should not be null");
try (FullDataPayload payload = new FullDataPayload(data, this.beaconBeamRepo.getAllBeamsForPos(data.getPos())))
FullDataPayload payload = new FullDataPayload(data, this.beaconBeamRepo.getAllBeamsForPos(data.getPos()));
for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getReadyPlayers())
{
for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getReadyPlayers())
if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
{
if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
continue;
}
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
{
continue;
}
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
{
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
{
continue;
}
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
{
continue;
}
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
{
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
{
serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
});
}
serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
});
}
}
});
@@ -42,6 +42,7 @@ import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRend
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -58,7 +59,7 @@ import java.io.File;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/** The level used when connected to a server */
@@ -162,7 +163,8 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
return;
}
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(message.payload))
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload))
{
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
NETWORK_LOGGER.debug("Buffer {} isSameLevel: {}", message.payload.dtoBufferId, isSameLevel);
@@ -171,10 +173,28 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
return;
}
this.updateBeaconBeamsForSectionPos(dataSourceDto.pos, message.payload.beaconBeams);
Executor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null)
{
executor.execute(() ->
{
try
{
// TODO this has a lock which can cause stuttering/lag issues
this.updateBeaconBeamsForSectionPos(dataSourceDto.pos, message.payload.beaconBeams);
}
catch (Exception e)
{
LOGGER.error("Unexpected erorr while updating full data source, error: ["+e.getMessage()+"].", e);
}
});
}
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.levelWrapper);
this.updateDataSourcesAsync(fullDataSource).whenComplete((result, e) -> fullDataSource.close());
this.updateDataSourcesAsync(fullDataSource)
.whenComplete((result, e) -> fullDataSource.close());
}
catch (Exception e)
{
@@ -30,7 +30,7 @@ public interface IKeyedClientLevelManager extends IBindable
{
IServerKeyedClientLevel getServerKeyedLevel();
/** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */
IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String levelKey);
IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey);
void clearKeyedLevel();
boolean isEnabled();
@@ -24,6 +24,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
/** Enhances a {@link IClientLevelWrapper} with server provided level information. */
public interface IServerKeyedClientLevel extends IClientLevelWrapper
{
/** Returns the folder name the server wants the player to use. */
String getServerKey();
/** Returns the level key, which is used to select the correct folder on the client. */
String getServerLevelKey();
@@ -256,7 +256,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
if (response.payload != null)
{
FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(response.payload);
FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(response.payload);
// set application flags based on the received detail level,
// this is needed so the data sources propagate correctly
@@ -33,9 +33,9 @@ public class SessionConfig implements INetworkObject
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd);
registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min);
registerConfigEntry(Config.Server.generationBoundsX, (x, y) -> x);
registerConfigEntry(Config.Server.generationBoundsZ, (x, y) -> x);
registerConfigEntry(Config.Server.generationBoundsRadius, (x, y) -> x);
registerConfigEntry(Config.Server.generationBoundsX, (x, y) -> y);
registerConfigEntry(Config.Server.generationBoundsZ, (x, y) -> y);
registerConfigEntry(Config.Server.generationBoundsRadius, (x, y) -> y);
registerConfigEntry(Config.Server.generationRequestRateLimit, Math::min);
registerConfigEntry(Config.Server.enableRealTimeUpdates, Boolean::logicalAnd);
@@ -119,7 +119,7 @@ public class SessionConfig implements INetworkObject
}
return (this.constrainingConfig != null
? (T) entry.valueConstrainer.apply(value, this.constrainingConfig.getValue(name))
? (T) entry.valueConstrainer.apply(this.constrainingConfig.getValue(name), value)
: value);
}
@@ -9,18 +9,17 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMe
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
* @see FullDataSplitMessage
*/
public class FullDataPayload implements INetworkObject, AutoCloseable
public class FullDataPayload implements INetworkObject
{
private static final AtomicInteger lastBufferId = new AtomicInteger();
@@ -47,7 +46,7 @@ public class FullDataPayload implements INetworkObject, AutoCloseable
EDhApiDataCompressionMode compressionMode = Config.Common.LodBuilding.dataCompression.get();
try (FullDataSourceV2DTO dataSourceDto = FullDataSourceV2DTO.CreateFromDataSource(fullDataSource, compressionMode))
{
this.dtoBuffer = ByteBufAllocator.DEFAULT.buffer();
this.dtoBuffer = Unpooled.buffer();
dataSourceDto.encode(this.dtoBuffer);
}
}
@@ -85,12 +84,6 @@ public class FullDataPayload implements INetworkObject, AutoCloseable
// base overrides //
//================//
@Override
public void close()
{
this.dtoBuffer.release();
}
@Override
public String toString()
{
@@ -9,8 +9,8 @@ import com.seibel.distanthorizons.core.network.INetworkObject;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import org.apache.logging.log4j.LogManager;
import java.util.Objects;
@@ -24,15 +24,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.removalListener((RemovalNotification<Integer, CompositeByteBuf> notification) ->
{
// If an entry was replaced without removing, the buffer has to be released manually
if (notification.getCause() != RemovalCause.REPLACED)
{
Objects.requireNonNull(notification.getValue()).release();
}
})
.build().asMap();
.<Integer, CompositeByteBuf>build().asMap();
@Override
public void close()
@@ -46,13 +38,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
{
if (message.isFirst)
{
if (composite != null)
{
composite.release();
LOGGER.debug("Released existing full data buffer [" + message.bufferId + "]");
}
composite = ByteBufAllocator.DEFAULT.compositeBuffer();
composite = Unpooled.compositeBuffer();
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
}
else if (composite == null)
@@ -67,7 +53,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
});
}
public FullDataSourceV2DTO decodeDataSourceAndReleaseBuffer(FullDataPayload payload)
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
{
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
LodUtil.assertTrue(compositeByteBuffer != null);
@@ -38,12 +38,6 @@ public class FullDataPayloadSender implements AutoCloseable
public void close()
{
this.tickTimerTask.cancel();
PendingTransfer pendingTransfer;
while ((pendingTransfer = this.transferQueue.poll()) != null)
{
pendingTransfer.close();
}
}
@@ -78,36 +72,25 @@ public class FullDataPayloadSender implements AutoCloseable
if (pendingTransfer.buffer.readableBytes() == 0)
{
pendingTransfer.sendFinalMessage.run();
pendingTransfer.close();
this.transferQueue.poll();
}
}
}
private static class PendingTransfer implements AutoCloseable
private static class PendingTransfer
{
public final int bufferId;
public final ByteBuf buffer;
public final Runnable sendFinalMessage;
private final AtomicBoolean isClosed = new AtomicBoolean();
private PendingTransfer(FullDataPayload payload, Runnable sendFinalMessage)
{
this.bufferId = payload.dtoBufferId;
this.buffer = payload.dtoBuffer.retainedDuplicate().readerIndex(0);
this.buffer = payload.dtoBuffer.duplicate().readerIndex(0);
this.sendFinalMessage = sendFinalMessage;
}
@Override
public void close()
{
if (this.isClosed.compareAndSet(false, true))
{
this.buffer.release();
}
}
}
}
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.server;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
@@ -59,45 +60,80 @@ public class FullDataSourceRequestHandler
}
// the client timestamp will be null if we want to retrieve the LOD regardless of when it was last updated
long clientTimestamp = (message.clientTimestamp != null) ? message.clientTimestamp : -1;
// the server timestamp will be null if no LOD data exists for this position
Long serverTimestamp = this.fullDataSourceProvider().getTimestampForPos(message.sectionPos);
if (serverTimestamp == null
|| serverTimestamp <= clientTimestamp)
AbstractExecutorService fileHandlerExecutor = ThreadPoolUtil.getFileHandlerExecutor();
if (fileHandlerExecutor == null)
{
// either no data exists to sync, or the client is already up to date
rateLimiterSet.syncOnLoginRateLimiter.release();
message.sendResponse(new FullDataSourceResponseMessage(null));
// shouldn't normally happen, but just in case
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getFileHandlerExecutor() is null");
return;
}
AbstractExecutorService executor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (executor == null)
AbstractExecutorService networkCompressionExecutor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (networkCompressionExecutor == null)
{
// shouldn't normally happen, but just in case
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
return;
}
this.fullDataSourceProvider().getAsync(message.sectionPos).thenAcceptAsync(fullDataSource ->
{
try (FullDataPayload payload = new FullDataPayload(fullDataSource, this.getAllBeamsForPos(message.sectionPos)))
// get the data requested by the client
CompletableFuture<FullDataSourceV2> getServerDatasourceFuture = CompletableFuture.supplyAsync(() ->
{
fullDataSource.close();
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
try
{
message.sendResponse(new FullDataSourceResponseMessage(payload));
rateLimiterSet.syncOnLoginRateLimiter.release();
});
}
catch (Exception e)
// the client timestamp will be null if we want to retrieve the LOD regardless of when it was last updated
long clientTimestamp = (message.clientTimestamp != null) ? message.clientTimestamp : -1;
// the server timestamp will be null if no LOD data exists for this position
Long serverTimestamp = this.fullDataSourceProvider().getTimestampForPos(message.sectionPos);
if (serverTimestamp == null
|| serverTimestamp <= clientTimestamp)
{
// either no data exists to sync, or the client is already up to date
rateLimiterSet.syncOnLoginRateLimiter.release();
message.sendResponse(new FullDataSourceResponseMessage(null));
return null;
}
// get the server's datasource
return this.fullDataSourceProvider().get(message.sectionPos);
}
catch (Exception e)
{
LOGGER.error("Unexpected issue getting server-side LOD for request at pos [" + DhSectionPos.toString(message.sectionPos) + "], error: [" + e.getMessage() + "].", e);
return null;
}
}, fileHandlerExecutor);
// send the found data
getServerDatasourceFuture.thenAcceptAsync(fullDataSource ->
{
LOGGER.error("Unexpected issue getting request for pos ["+DhSectionPos.toString(message.sectionPos)+"], error: ["+e.getMessage()+"].", e);
}
}, executor);
try
{
// no server data source found
if (fullDataSource == null)
{
return;
}
// send the found data source to client
FullDataPayload payload = new FullDataPayload(fullDataSource, this.getAllBeamsForPos(message.sectionPos));
fullDataSource.close();
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
{
message.sendResponse(new FullDataSourceResponseMessage(payload));
rateLimiterSet.syncOnLoginRateLimiter.release();
});
}
catch (Exception e)
{
LOGGER.error("Unexpected issue sending request for pos [" + DhSectionPos.toString(message.sectionPos) + "], error: [" + e.getMessage() + "].", e);
}
}, networkCompressionExecutor);
}
public void queueWorldGenForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
@@ -207,19 +243,17 @@ public class FullDataSourceRequestHandler
}
CompletableFuture.runAsync(() ->
{
try (FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource, this.getAllBeamsForPos(entry.getKey())))
FullDataPayload payload = new FullDataPayload(requestGroup.fullDataSource, this.getAllBeamsForPos(entry.getKey()));
requestGroup.fullDataSource.close();
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
{
requestGroup.fullDataSource.close();
this.requestGroupsByFutureId.remove(requestData.futureId());
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();
});
}
requestData.serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () -> {
requestData.message.sendResponse(new FullDataSourceResponseMessage(payload));
requestData.rateLimiterSet.generationRequestRateLimiter.release();
});
}
}, executor);
}
@@ -24,9 +24,13 @@ public class ServerPlayerState implements Closeable
{
private final ConfigChangeListener<String> levelKeyPrefixChangeListener
= new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged);
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::onSessionConfigChanged);
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
private final String serverKeyWithoutId = Config.Server.serverKey.get();
private final String serverKey = (this.serverKeyWithoutId.isEmpty() ? "" : Config.Server.serverId.get() + "_" + this.serverKeyWithoutId.trim())
.replaceAll("[^" + LevelInitMessage.PART_ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll(" ", "_");
private String lastLevelKey = "";
@@ -56,8 +60,9 @@ public class ServerPlayerState implements Closeable
this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) ->
{
this.sessionConfig.constrainingConfig = sessionConfigMessage.config;
this.sendLevelKey();
this.networkSession.sendMessage(new SessionConfigMessage(this.sessionConfig));
this.sendConfigMessage();
});
this.networkSession.registerHandler(CloseInternalEvent.class, event -> {
@@ -88,12 +93,19 @@ public class ServerPlayerState implements Closeable
if (!levelKey.equals(this.lastLevelKey))
{
this.lastLevelKey = levelKey;
this.networkSession.sendMessage(new LevelInitMessage(levelKey));
this.networkSession.sendMessage(new LevelInitMessage(this.serverKey, levelKey));
}
}
}
private void onSessionConfigChanged() { this.networkSession.sendMessage(new SessionConfigMessage(this.sessionConfig)); }
private void sendConfigMessage()
{
double coordinateScale = this.getServerPlayer().getLevel().getDimensionType().getCoordinateScale();
this.sessionConfig.constrainValue(Config.Server.generationBoundsX, (int) (Config.Server.generationBoundsX.get() / coordinateScale));
this.sessionConfig.constrainValue(Config.Server.generationBoundsZ, (int) (Config.Server.generationBoundsZ.get() / coordinateScale));
this.networkSession.sendMessage(new SessionConfigMessage(this.sessionConfig));
}
@@ -46,6 +46,13 @@ public class ServerPlayerStateManager
public void handlePluginMessage(IServerPlayerWrapper player, AbstractNetworkMessage message)
{
// done to prevent a rare null-pointer on Neo/Forge
if (player == null || message == null)
{
return;
}
MessageQueueState messageQueue = this.messageQueueByPlayerWrapper.computeIfAbsent(player, k -> new MessageQueueState());
messageQueue.messageQueue.add(message);
@@ -10,12 +10,18 @@ public class LevelInitMessage extends AbstractNetworkMessage
public static final String PART_ALLOWED_CHARS_REGEX = "a-zA-Z0-9-_";
// A plain string of characters
// 1-150 characters in total
public static final String SERVER_KEY_REGEX = String.format("^(?=.{1,%s}$)[%s]+$",
MAX_LENGTH, PART_ALLOWED_CHARS_REGEX);
// prefix@namespace:path
// 1-150 characters in total, all parts except namespace can be omitted
public static final String VALIDATION_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$",
public static final String LEVEL_KEY_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$",
MAX_LENGTH, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX);
public String serverKey;
public String levelKey;
public long serverTime;
@@ -26,8 +32,9 @@ public class LevelInitMessage extends AbstractNetworkMessage
//==============//
public LevelInitMessage() { }
public LevelInitMessage(String levelKey)
public LevelInitMessage(String serverKey, String levelKey)
{
this.serverKey = serverKey;
this.levelKey = levelKey;
this.serverTime = System.currentTimeMillis();
}
@@ -41,6 +48,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override
public void encode(ByteBuf out)
{
this.writeString(this.serverKey, out);
this.writeString(this.levelKey, out);
out.writeLong(this.serverTime);
}
@@ -48,6 +56,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override
public void decode(ByteBuf in)
{
this.serverKey = this.readString(in);
this.levelKey = this.readString(in);
this.serverTime = in.readLong();
}
@@ -62,6 +71,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("serverKey", this.serverKey)
.add("levelKey", this.levelKey)
.add("serverTime", this.serverTime);
}
@@ -22,8 +22,8 @@ package com.seibel.distanthorizons.core.network.messages.fullData;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayload;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.util.TimerUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.Timer;
@@ -34,16 +34,10 @@ import java.util.Timer;
*/
public class FullDataSplitMessage extends AbstractNetworkMessage
{
private static final long BUFFER_RELEASE_DELAY_MS = 5000L;
public int bufferId;
public ByteBuf buffer;
public boolean isFirst;
// Reference counting is unreliable here for some reason so this is a "fix"
private static final Timer bufferReleaseTimer = TimerUtil.CreateTimer("FullDataBufferCleanupTimer");
private boolean releaseScheduled = false;
//==============//
// constructors //
@@ -72,12 +66,6 @@ public class FullDataSplitMessage extends AbstractNetworkMessage
out.writeBytes(this.buffer.readerIndex(0));
out.writeBoolean(this.isFirst);
if (!this.releaseScheduled)
{
bufferReleaseTimer.schedule(TimerUtil.createTimerTask(this.buffer::release), BUFFER_RELEASE_DELAY_MS);
this.releaseScheduled = true;
}
}
@Override
@@ -86,7 +74,7 @@ public class FullDataSplitMessage extends AbstractNetworkMessage
this.bufferId = in.readInt();
int bufferSize = in.readInt();
this.buffer = in.readBytes(bufferSize);
this.buffer = Unpooled.copiedBuffer(in.readSlice(bufferSize));
this.isFirst = in.readBoolean();
}
@@ -306,9 +306,9 @@ public class PhantomArrayListPool
{
// we only want to log when something has been returned
if (checkoutCount != 0
|| returnedByteArrayCount != 0
|| returnedShortArrayCount != 0
|| returnedLongArrayCount != 0)
|| returnedByteArrayCount != 0
|| returnedShortArrayCount != 0
|| returnedLongArrayCount != 0)
{
LOGGER.warn("Pool: ["+ pool.name+"] phantom recovery. Returned checkouts:["+F3Screen.NUMBER_FORMAT.format(checkoutCount)+"], byte:["+F3Screen.NUMBER_FORMAT.format(returnedByteArrayCount)+"], short:["+F3Screen.NUMBER_FORMAT.format(returnedShortArrayCount)+"], long:["+F3Screen.NUMBER_FORMAT.format(returnedLongArrayCount)+"].");
@@ -25,9 +25,15 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL32C;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.system.NativeType;
/**
* This object holds a OpenGL reference to a shader
@@ -63,7 +69,7 @@ public class Shader
}
StringBuilder source = loadFile(path, absoluteFilePath, new StringBuilder());
GL32.glShaderSource(this.id, source);
safeShaderSource(this.id, source);
GL32.glCompileShader(this.id);
// check if the shader compiled
@@ -93,7 +99,7 @@ public class Shader
throw new IllegalArgumentException("Failed to create shader with type ["+type+"] and Source: \n["+sourceString+"].");
}
GL32.glShaderSource(this.id, sourceString);
safeShaderSource(this.id, sourceString);
GL32.glCompileShader(this.id);
// check if the shader compiled
int status = GL32.glGetShaderi(this.id, GL32.GL_COMPILE_STATUS);
@@ -114,6 +120,37 @@ public class Shader
// helpers //
//=========//
/**
* Identical in function to {@link GL32C#glShaderSource(int, CharSequence)} but
* passes a null pointer for string length to force the driver to rely on the null
* terminator for string length. This is a workaround for an apparent flaw with some
* AMD drivers that don't receive or interpret the length correctly, resulting in
* an access violation when the driver tries to read past the string memory.
*
* <p>Hat tip to fewizz for the find and the fix.
*
* <p>Source: https://github.com/vram-guild/canvas/commit/820bf754092ccaf8d0c169620c2ff575722d7d96
*/
private static void safeShaderSource(@NativeType("GLuint") int glId, @NativeType("GLchar const **") CharSequence source)
{
final MemoryStack stack = MemoryStack.stackGet();
final int stackPointer = stack.getPointer();
try
{
final ByteBuffer sourceBuffer = MemoryUtil.memUTF8(source, true);
final PointerBuffer pointers = stack.mallocPointer(1);
pointers.put(sourceBuffer);
GL32.nglShaderSource(glId, 1, pointers.address0(), 0);
org.lwjgl.system.APIUtil.apiArrayFree(pointers.address0(), 1);
}
finally
{
stack.setPointer(stackPointer);
}
}
public void free() { GL32.glDeleteShader(this.id); }
public static StringBuilder loadFile(String path, boolean absoluteFilePath, StringBuilder stringBuilder)
@@ -151,4 +188,6 @@ public class Shader
return stringBuilder;
}
}
@@ -1,5 +1,7 @@
package com.seibel.distanthorizons.core.render.glObject.texture;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL13C;
import org.lwjgl.opengl.GL43C;
@@ -8,12 +10,15 @@ import java.nio.ByteBuffer;
public class DHDepthTexture
{
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private int id;
public DHDepthTexture(int width, int height, EDhDepthBufferFormat format)
{
this.id = GL43C.glGenTextures();
resize(width, height, format);
this.resize(width, height, format);
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
@@ -24,27 +29,31 @@ public class DHDepthTexture
}
// For internal use by Iris for copying data. Do not use this in DH.
public DHDepthTexture(int id) {
this.id = id;
}
public DHDepthTexture(int id) { this.id = id; }
public void resize(int width, int height, EDhDepthBufferFormat format)
{
GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, getTextureId());
GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, this.getTextureId());
GL43C.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, format.getGlInternalFormat(), width, height, 0,
format.getGlType(), format.getGlFormat(), (ByteBuffer) null);
}
public int getTextureId()
{
if (id == -1) throw new IllegalStateException("Depth texture does not exist!");
return id;
if (this.id == -1)
{
throw new IllegalStateException("Depth texture does not exist!");
}
return this.id;
}
public void destroy()
{
GL43C.glDeleteTextures(getTextureId());
GLMC.glDeleteTextures(this.getTextureId());
this.id = -1;
}
}
@@ -1,5 +1,7 @@
package com.seibel.distanthorizons.core.render.glObject.texture;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import org.joml.Vector2i;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL13C;
@@ -9,6 +11,9 @@ import java.nio.ByteBuffer;
public class DhColorTexture
{
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private final EDhInternalTextureFormat internalFormat;
private final EDhPixelFormat format;
private final EDhPixelType type;
@@ -100,7 +105,7 @@ public class DhColorTexture
this.throwIfInvalid();
this.isValid = false;
GL43C.glDeleteTextures(this.id);
GLMC.glDeleteTextures(this.id);
}
/** @throws IllegalStateException if the texture isn't valid */
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiFrameb
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShaderProgram;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
@@ -692,12 +693,15 @@ public class LodRenderer
this.cachedWidth = MC_RENDER.getTargetFrameBufferViewportWidth();
this.cachedHeight = MC_RENDER.getTargetFrameBufferViewportHeight();
DhApiTextureCreatedParam textureCreatedParam = new DhApiTextureCreatedParam(
oldWidth, oldHeight,
this.cachedWidth, this.cachedHeight
);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiColorDepthTextureCreatedEvent.class,
new DhApiColorDepthTextureCreatedEvent.EventParam(
oldWidth, oldHeight,
this.cachedWidth, this.cachedHeight
));
ApiEventInjector.INSTANCE.fireAllEvents(DhApiColorDepthTextureCreatedEvent.class, new DhApiColorDepthTextureCreatedEvent.EventParam(textureCreatedParam));
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeColorDepthTextureCreatedEvent.class, textureCreatedParam);
// also update the override if present
@@ -729,6 +733,9 @@ public class LodRenderer
{
this.nullableColorTexture = null;
}
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterColorDepthTextureCreatedEvent.class, textureCreatedParam);
}
@@ -797,6 +804,9 @@ public class LodRenderer
if (this.depthTexture != null)
this.depthTexture.destroy();
this.setActiveDepthTextureId(-1);
this.setActiveColorTextureId(-1);
EVENT_LOGGER.info("Renderer Cleanup Complete");
});
}
@@ -155,6 +155,19 @@ public class FadeShader extends AbstractShaderRenderer
@Override
protected void onRender()
{
int depthTextureId = LodRenderer.getActiveDepthTextureId();
int colorTextureId = LodRenderer.getActiveColorTextureId();
if (depthTextureId == -1
|| colorTextureId == -1)
{
// the renderer is currently being re-built and/or inactive,
// we don't need to/can't render fading
return;
}
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer);
GLMC.disableScissorTest();
GLMC.disableDepthTest();
@@ -165,8 +178,7 @@ public class FadeShader extends AbstractShaderRenderer
GL32.glUniform1i(this.uMcDepthTexture, 0);
GLMC.glActiveTexture(GL32.GL_TEXTURE1);
// FIXME it's possible for this to return an invalid texture ID if the renderer is being re-built at the same time
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
GLMC.glBindTexture(depthTextureId);
GL32.glUniform1i(this.uDhDepthTexture, 1);
GLMC.glActiveTexture(GL32.GL_TEXTURE2);
@@ -174,7 +186,7 @@ public class FadeShader extends AbstractShaderRenderer
GL32.glUniform1i(this.uCombinedMcDhColorTexture, 2);
GLMC.glActiveTexture(GL32.GL_TEXTURE3);
GLMC.glBindTexture(LodRenderer.getActiveColorTextureId());
GLMC.glBindTexture(colorTextureId);
GL32.glUniform1i(this.uDhColorTexture, 3);
@@ -142,7 +142,16 @@ public class FullDataSourceV2DTO
public FullDataSourceV2 createDataSource(@NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(this.pos);
this.internalPopulateDataSource(dataSource, levelWrapper, false);
try
{
this.internalPopulateDataSource(dataSource, levelWrapper, false);
}
catch (Exception e)
{
dataSource.close();
throw e;
}
return dataSource;
}
@@ -73,7 +73,10 @@ public class RenderDataPointUtil
public final static int EMPTY_DATA = 0;
public final static int MAX_WORLD_Y_SIZE = 4096;
// the maximum valid Y value is the maximum min y + world height.
// min y is [-2032, 2031], height is < 4064.
public final static int MAX_WORLD_Y_SIZE = 2031 + 4064;
public final static int ALPHA_DOWNSIZE_SHIFT = 4;
@@ -20,10 +20,8 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.Initializer;
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
import com.seibel.distanthorizons.coreapi.ModInfo;
import net.jpountz.lz4.LZ4FrameInputStream;
//import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.tukaani.xz.ResettableArrayCache;
@@ -44,6 +42,7 @@ import java.io.*;
public class DhDataInputStream extends DataInputStream
{
private static final ThreadLocal<ResettableArrayCache> LZMA_RESETTABLE_ARRAY_CACHE_GETTER = ThreadLocal.withInitial(() -> new ResettableArrayCache(new LzmaArrayCache()));
//private static final ThreadLocal<ZstdArrayCache> ZSTD_RESETTABLE_ARRAY_CACHE_GETTER = ThreadLocal.withInitial(() -> new ZstdArrayCache());
private static final Logger LOGGER = LogManager.getLogger();
@@ -62,6 +61,8 @@ public class DhDataInputStream extends DataInputStream
return stream;
case LZ4:
return new LZ4FrameInputStream(stream);
//case Z_STD:
// return new ZstdCompressorInputStream(stream, ZSTD_RESETTABLE_ARRAY_CACHE_GETTER.get());
case LZMA2:
// using an array cache significantly reduces GC pressure
ResettableArrayCache arrayCache = LZMA_RESETTABLE_ARRAY_CACHE_GETTER.get();
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FrameOutputStream;
import net.jpountz.xxhash.XXHashFactory;
//import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.tukaani.xz.*;
@@ -53,6 +54,9 @@ public class DhDataOutputStream extends DataOutputStream
{
case UNCOMPRESSED:
return stream;
//case Z_STD:
// return new ZstdCompressorOutputStream(stream, 3, true, true);
case LZ4:
return new LZ4FrameOutputStream(stream,
LZ4FrameOutputStream.BLOCKSIZE.SIZE_64KB, -1L,
@@ -69,7 +73,7 @@ public class DhDataOutputStream extends DataOutputStream
arrayCache.reset();
// Note: if the LZMA2Options are changed the array cache may need to be re-tested.
// the array cache was specifically tested and tuned for LZMA preset 3/4
return new XZOutputStream(stream, new LZMA2Options(3),
return new XZOutputStream(stream, new LZMA2Options(3),
XZ.CHECK_CRC64, arrayCache);
default:
@@ -63,8 +63,16 @@ public class LzmaArrayCache extends ArrayCache
{
return new byte[size];
}
// the array needs to be cleared to prevent accidentally sending dirty data
Arrays.fill(array, (byte)0);
// the array only sometimes needs to be cleared,
// clearing all the time results in unnecessary slowdowns
if (fillWithZeros)
{
// TODO it appears that this can prevent the CPU from working on
// other tasks, thus causing render thread lag even when run on a separate thread
Arrays.fill(array, (byte) 0);
}
return array;
}
@@ -107,8 +115,16 @@ public class LzmaArrayCache extends ArrayCache
{
return new int[size];
}
// the array needs to be cleared to prevent accidentally sending dirty data
Arrays.fill(array, (byte)0);
// the array only sometimes needs to be cleared,
// clearing all the time results in unnecessary slowdowns
if (fillWithZeros)
{
// TODO it appears that this can prevent the CPU from working on
// other tasks, thus causing render thread lag even when run on a separate thread
Arrays.fill(array, (byte) 0);
}
return array;
}
@@ -0,0 +1,85 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams;
//import com.github.luben.zstd.BufferPool;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
import org.apache.logging.log4j.Logger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;
/**
* LZMA requires a custom object to cache it's backend arrays.
*/
public class ZstdArrayCache //implements BufferPool
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
/**
* In James' testing the byte and int caches only ever had to store 2 and 4 arrays respectively.
* With the in mind we could take a few shortcuts, but if that changes then we need to be
* notified as it might cause issues with the current logic.
*/
public static final int WARN_CACHE_LENGTH_EXCEEDED = 10;
public static final AtomicInteger MAX_BYTE_CACHE_LENGTH_REF = new AtomicInteger(WARN_CACHE_LENGTH_EXCEEDED);
public final IntUnaryOperator maxByteCacheSizeUnaryOperator = (x) -> Math.max(this.bufferCache.size(), x);
/**
* generally only 2 items long <br>
* {@link Int2ReferenceArrayMap} can be used since the cache should only be a few items long.
* If the array ends up being longer then this design will need to be changed.
*/
public final Int2ReferenceArrayMap<ArrayList<ByteBuffer>> bufferCache = new Int2ReferenceArrayMap<>();
//=============//
// byte arrays //
//=============//
//@Override
public ByteBuffer get(int size)
{
ArrayList<ByteBuffer> cacheList = this.bufferCache.computeIfAbsent(size, (newSize) -> new ArrayList<>(4));
if (cacheList.isEmpty())
{
return ByteBuffer.allocate(size);
}
ByteBuffer array = cacheList.remove(cacheList.size()-1);
if (array == null)
{
return ByteBuffer.allocate(size);
}
return array;
}
//@Override
public void release(ByteBuffer buffer)
{
int size = buffer.array().length;
this.bufferCache.computeIfAbsent(size, (newSize) -> new ArrayList<>());
this.bufferCache.get(size).add(buffer);
if (this.bufferCache.size() > WARN_CACHE_LENGTH_EXCEEDED)
{
int previousMax = MAX_BYTE_CACHE_LENGTH_REF.getAndUpdate(this.maxByteCacheSizeUnaryOperator);
int newMax = MAX_BYTE_CACHE_LENGTH_REF.get();
if (newMax > previousMax)
{
LOGGER.warn("LZMA byte array cache expected size exceeded. Expected max length ["+WARN_CACHE_LENGTH_EXCEEDED+"], actual length ["+this.bufferCache.size()+"].");
}
}
}
}
@@ -52,7 +52,7 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
public void addPlayer(IServerPlayerWrapper serverPlayer)
{
ServerPlayerState playerState = this.serverPlayerStateManager.registerJoinedPlayer(serverPlayer);
this.getLevel(serverPlayer.getLevel()).addPlayer(serverPlayer);
((TDhServerLevel) this.getOrLoadServerLevel(serverPlayer.getLevel())).addPlayer(serverPlayer);
Iterator<TDhServerLevel> it = this.dhLevelByLevelWrapper.values().stream().distinct().iterator();
while (it.hasNext())
@@ -921,7 +921,7 @@
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.FEATURES":
"Features",
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.INTERNAL_SERVER":
"Internal Server",
"Full - Save Chunks",
"distanthorizons.config.enum.EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY":
"Overlay",
@@ -935,9 +935,11 @@
"distanthorizons.config.enum.EDhApiDataCompressionMode.UNCOMPRESSED":
"Uncompressed",
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZ4":
"Fast/Big - LZ4",
"Fastest/Big - LZ4",
"distanthorizons.config.enum.EDhApiDataCompressionMode.Z_STD":
"Fast/Small - Z_STD",
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZMA2":
"Slow/Small - LZMA2",
"Slow/Smallest - LZMA2",
"distanthorizons.config.enum.EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS":
"1. Merge Same Blocks",
@@ -1020,6 +1022,8 @@
"File: Info, Chat: Warning",
"distanthorizons.config.enum.EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_INFO_TO_FILE":
"File: Info, Chat: Error",
"distanthorizons.config.enum.EDhApiLoggerMode.LOG_ERROR_TO_CHAT_AND_WARNING_TO_FILE":
"File: Warning, Chat: Error",
"distanthorizons.config.enum.EDhApiGpuUploadMethod.AUTO":
"Auto",
+73 -71
View File
@@ -25,11 +25,11 @@ import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
//import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
import org.junit.Assert;
import org.junit.Test;
import java.io.*;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
/**
* <strong>Note:</strong>
@@ -47,22 +47,14 @@ import java.text.StringCharacterIterator;
*/
public class CompressionTest
{
public static String TEST_DIR = "C:\\DistantHorizonsWorkspace\\distant-horizons\\run\\saves\\Arcapelago\\data";
public static String TEST_DIR = "C:\\DistantHorizonsWorkspace\\distant-horizons\\run\\client\\saves\\Archipelego\\data";
public static String DB_FILE_NAME_PREFIX = "DistantHorizons";
public static String UNCOMPRESSED_DB_FILE_NAME = "DistantHorizons.sqlite";
public static String UNCOMPRESSED_DB_FILE_NAME = "DistantHorizons_uncompressed.sqlite";
/** -1 will test all of them */
public static int MAX_DTO_TEST_COUNT = -1;
//@Test
public void NoCompression()
{
String compressorName = "Uncompressed";
this.testCompressor(compressorName, EDhApiDataCompressionMode.UNCOMPRESSED);
}
// collapse the following commented out code when looking at tests
//@Test
@@ -132,17 +124,6 @@ public class CompressionTest
//}
//@Test
//public void Zstd()
//{
// String compressorName = "Zstd";
//
// DhDataInputStream.CreateInputStreamFunc createInputStreamFunc = (inputStream) -> new ZstdInputStream(inputStream);
// DhDataOutputStream.CreateOutputStreamFunc createOutputStreamFunc = (outputStream) -> new ZstdOutputStream(outputStream);
//
// this.testCompressor(compressorName, createInputStreamFunc, createOutputStreamFunc);
//}
////@Test
//public void ZstdDictionary() throws SQLException // isn't any better than normal Zstd
//{
@@ -205,6 +186,13 @@ public class CompressionTest
// this.testCompressor(compressorName, createInputStreamFunc, createOutputStreamFunc);
//}
//@Test
public void NoCompression()
{
String compressorName = "Uncompressed";
this.testCompressor(compressorName, EDhApiDataCompressionMode.UNCOMPRESSED);
}
//@Test
public void Lz4() // fast, poor compression
{
@@ -213,11 +201,11 @@ public class CompressionTest
}
//@Test
//public void Zstd() // middle of the road
//{
// String compressorName = "Zstd";
// this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD);
//}
public void Zstd() // middle of the road
{
//String compressorName = "Zstd";
//this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD);
}
//@Test
public void LZMA2() // very slow, very good compression though
@@ -254,13 +242,15 @@ public class CompressionTest
long totalCompressedFileSizeInBytes;
FullDataSourceV2Repo uncompressedRepo = null;
FullDataSourceV2Repo compressedRepo = null;
try
{
String uncompressedDatabaseFilePath = TEST_DIR + "/" + UNCOMPRESSED_DB_FILE_NAME;
File uncompressedDatabaseFile = new File(uncompressedDatabaseFilePath);
Assert.assertTrue(uncompressedDatabaseFile.exists());
FullDataSourceV2Repo uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile);
uncompressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile);
String compressedDatabaseFilePath = TEST_DIR + "/output/" + DB_FILE_NAME_PREFIX + "_" + compressorName + ".sqlite";
@@ -268,7 +258,7 @@ public class CompressionTest
compressedDatabaseFile.mkdirs();
compressedDatabaseFile.delete();
Assert.assertTrue(!compressedDatabaseFile.exists());
FullDataSourceV2Repo compressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", uncompressedDatabaseFile);
compressedRepo = new FullDataSourceV2Repo("jdbc:sqlite", compressedDatabaseFile);
@@ -292,47 +282,47 @@ public class CompressionTest
// uncompressed input //
FullDataSourceV2DTO uncompressedDto = uncompressedRepo.getByKey(pos);
Assert.assertEquals(uncompressedDto.compressionModeValue, EDhApiDataCompressionMode.UNCOMPRESSED.value);
FullDataSourceV2 uncompressedDataSource = uncompressedDto.createUnitTestDataSource();
long uncompressedDtoSize = uncompressedRepo.getDataSizeInBytes(pos);
minUncompressedDtoSizeInBytes = Math.min(uncompressedDtoSize, minUncompressedDtoSizeInBytes);
maxUncompressedDtoSizeInBytes = Math.max(uncompressedDtoSize, maxUncompressedDtoSizeInBytes);
avgUncompressedDtoSizeInBytes += uncompressedDtoSize;
// compress file //
long startWriteNanoTime = System.nanoTime();
FullDataSourceV2DTO compressedDto = FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, compressionMode);
compressedRepo.save(compressedDto);
long endWriteNanoTime = System.nanoTime();
totalWriteTimeInNano += (endWriteNanoTime - startWriteNanoTime);
long compressedDtoSize = compressedRepo.getDataSizeInBytes(pos);
minCompressedDtoSizeInBytes = Math.min(compressedDtoSize, minCompressedDtoSizeInBytes);
maxCompressedDtoSizeInBytes = Math.max(compressedDtoSize, maxCompressedDtoSizeInBytes);
avgCompressedDtoSizeInBytes += compressedDtoSize;
// read compressed file //
long startReadNanoTime = System.nanoTime();
compressedDto = compressedRepo.getByKey(pos);
FullDataSourceV2 compressedDataSource = compressedDto.createUnitTestDataSource();
long endReadMsTime = System.nanoTime();
totalReadTimeInNano += (endReadMsTime - startReadNanoTime);
processedDtoCount++;
try (FullDataSourceV2DTO uncompressedDto = uncompressedRepo.getByKey(pos))
{
Assert.assertEquals(uncompressedDto.compressionModeValue, EDhApiDataCompressionMode.UNCOMPRESSED.value);
FullDataSourceV2 uncompressedDataSource = uncompressedDto.createUnitTestDataSource();
long uncompressedDtoSize = uncompressedRepo.getDataSizeInBytes(pos);
minUncompressedDtoSizeInBytes = Math.min(uncompressedDtoSize, minUncompressedDtoSizeInBytes);
maxUncompressedDtoSizeInBytes = Math.max(uncompressedDtoSize, maxUncompressedDtoSizeInBytes);
avgUncompressedDtoSizeInBytes += uncompressedDtoSize;
// compress file //
long startWriteNanoTime = System.nanoTime();
FullDataSourceV2DTO compressedDto = FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, compressionMode);
compressedRepo.save(compressedDto);
long endWriteNanoTime = System.nanoTime();
totalWriteTimeInNano += (endWriteNanoTime - startWriteNanoTime);
long compressedDtoSize = compressedRepo.getDataSizeInBytes(pos);
minCompressedDtoSizeInBytes = Math.min(compressedDtoSize, minCompressedDtoSizeInBytes);
maxCompressedDtoSizeInBytes = Math.max(compressedDtoSize, maxCompressedDtoSizeInBytes);
avgCompressedDtoSizeInBytes += compressedDtoSize;
// read compressed file //
long startReadNanoTime = System.nanoTime();
compressedDto = compressedRepo.getByKey(pos);
FullDataSourceV2 compressedDataSource = compressedDto.createUnitTestDataSource();
long endReadMsTime = System.nanoTime();
totalReadTimeInNano += (endReadMsTime - startReadNanoTime);
processedDtoCount++;
}
}
catch (Exception | Error e)
{
@@ -371,6 +361,18 @@ public class CompressionTest
e.printStackTrace();
Assert.fail(e.getMessage());
}
finally
{
if(uncompressedRepo != null)
{
uncompressedRepo.close();
}
if(compressedRepo != null)
{
compressedRepo.close();
}
}
}
@@ -27,6 +27,7 @@ import org.apache.logging.log4j.core.config.Configurator;
import org.junit.Assert;
import org.junit.Test;
@Deprecated
public class SquareIntersectTest
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();