Compare commits

..

43 Commits

Author SHA1 Message Date
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
s809 ad995544f7 Use bytesReceived instead of decreasing multiplicatively 2025-04-20 23:59:34 +05:00
s809 d521e931f4 Change data send tick rate 4 -> 20 2025-04-20 18:26:07 +05:00
s809 dd30a8274a Add a config entry and refactor 2025-04-20 18:25:27 +05:00
s809 3ca5efadc9 Adaptive data transfer speed 2025-04-20 03:02:18 +05:00
Ran 09174c2d2a Improve LodDataBuilder.java
- Use bitwise modulo
- Don't compute certain things 256 times when they can be computed once.
- Removed expressions that are always false
- Improved comments
2025-04-11 11:24:16 +10:00
James Seibel e079b28e77 maybe break n-sized rendering but fix LOD loading getting stuck 2025-04-07 06:56:53 -05:00
James Seibel 136124a703 up version number 2.3.2 -> 2.3.3 2025-04-05 09:11:19 -05:00
45 changed files with 1014 additions and 365 deletions
+1 -2
View File
@@ -4,7 +4,6 @@
[*] [*]
charset = utf-8 charset = utf-8
end_of_line = crlf
indent_size = 4 indent_size = 4
indent_style = space indent_style = space
insert_final_newline = false insert_final_newline = false
@@ -537,7 +536,7 @@ ij_groovy_wrap_chain_calls_after_dot = false
ij_groovy_wrap_long_lines = false ij_groovy_wrap_long_lines = false
[{*.har,*.json,*.png.mcmeta,mcmod.info,pack.mcmeta}] [{*.har,*.json,*.png.mcmeta,mcmod.info,pack.mcmeta}]
indent_size = 2 indent_size = 4
ij_json_array_wrapping = split_into_lines ij_json_array_wrapping = split_into_lines
ij_json_keep_blank_lines_in_code = 0 ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false 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> * Should only be used internally and for unit testing. <br><br>
* *
* Read Speed: 1.64 MS / DTO <br> * Read Speed: 6.09 MS / DTO <br>
* Write Speed: 12.44 MS / DTO <br> * Write Speed: 6.01 MS / DTO <br>
* Compression ratio: 1.0 <br> * Compression ratio: 1.0 <br>
*/ */
@DisallowSelectingViaConfigGui @DisallowSelectingViaConfigGui
@@ -49,28 +49,29 @@ public enum EDhApiDataCompressionMode
/** /**
* Extremely fast (often faster than uncompressed), but generally poor compression. <br><br> * Extremely fast (often faster than uncompressed), but generally poor compression. <br><br>
* *
* Read Speed: 1.85 MS / DTO <br> * Read Speed: 3.25 MS / DTO <br>
* Write Speed: 9.46 MS / DTO <br> * Write Speed: 5.99 MS / DTO <br>
* Compression ratio: 0.3638 <br> * Compression ratio: 0.4513 <br>
*/ */
LZ4(1), LZ4(1),
/* ///**
* Decent speed and good compression. <br><br> // * Decent speed and good compression. <br><br>
* // *
* Read Speed: 11.78 MS / DTO <br> // * Read Speed: 9.31 MS / DTO <br>
* Write Speed: 16.76 MS / DTO <br> // * Write Speed: 15.13 MS / DTO <br>
* Compression ratio: 0.2199 <br> // * Compression ratio: 0.2606 <br>
*/ // */
//@Deprecated ////@DisallowSelectingViaConfigGui
//Z_STD(2), //Z_STD(2),
/** /**
* Extremely slow, but very good compression. <br><br> * Extremely slow, but very good compression. <br><br>
* *
* Read Speed: 12.25 MS / DTO <br> * Read Speed: 13.29 MS / DTO <br>
* Write Speed: 490.07 MS / DTO <br> * Write Speed: 70.95 MS / DTO <br>
* Compression ratio: 0.1242 <br> * Compression ratio: 0.2068 <br>
*/ */
LZMA2(3); LZMA2(3);
@@ -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.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam; 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.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> * the color and depth textures it renders to. <br>
* *
* @author James Seibel * @author James Seibel
* @version 2024-3-2 * @version 2024-3-2
* @since API 2.0.0 * @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> public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<DhApiColorDepthTextureCreatedEvent.EventParam>
{ {
/** Fired before Distant Horizons creates. */ /** Fired before Distant Horizons creates. */
@@ -73,6 +76,15 @@ public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<
this.newHeight = newHeight; this.newHeight = newHeight;
} }
public EventParam(DhApiTextureCreatedParam textureCreatedParam)
{
this.previousWidth = textureCreatedParam.previousWidth;
this.previousHeight = textureCreatedParam.previousHeight;
this.newWidth = textureCreatedParam.newWidth;
this.newHeight = textureCreatedParam.newHeight;
}
@Override @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"; 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 = 10; public static final int PROTOCOL_VERSION = 11;
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.2-b"; public static final String VERSION = "2.3.3-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");
/** This version should only be updated when breaking changes are introduced to the DH API */ /** This version should only be updated when breaking changes are introduced to the DH API */
public static final int API_MAJOR_VERSION = 4; public static final int API_MAJOR_VERSION = 4;
/** This version should be updated whenever new methods are added to the DH API */ /** 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 */ /** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATCH_VERSION = 0; 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> public class RenderModeEnabledConverter implements IConverter<EDhApiRendererMode, Boolean>
{ {
@Override public EDhApiRendererMode convertToCoreType(Boolean renderingEnabled) @Override
{ public EDhApiRendererMode convertToCoreType(Boolean renderingEnabled)
return renderingEnabled ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED; { return renderingEnabled ? EDhApiRendererMode.DEFAULT : EDhApiRendererMode.DISABLED; }
}
@Override public Boolean convertToApiType(EDhApiRendererMode renderingMode) @Override
{ public Boolean convertToApiType(EDhApiRendererMode renderingMode)
return renderingMode == EDhApiRendererMode.DEFAULT; { return renderingMode == EDhApiRendererMode.DEFAULT; }
}
} }
+7 -1
View File
@@ -60,4 +60,10 @@ shadowJar {
def librariesLocation = "DistantHorizons.libraries" def librariesLocation = "DistantHorizons.libraries"
// relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil" // relocate "it.unimi.dsi.fastutil", "${librariesLocation}.unimi.dsi.fastutil"
mergeServiceFiles() 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.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.coreapi.util.converters.RenderModeEnabledConverter;
public class DhApiGraphicsConfig implements IDhApiGraphicsConfig public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
{ {
@@ -60,7 +61,7 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
@Override @Override
public IDhApiConfigValue<Boolean> renderingEnabled() 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 @Override
public IDhApiConfigValue<EDhApiRendererMode> renderingMode() public IDhApiConfigValue<EDhApiRendererMode> renderingMode()
@@ -89,6 +89,15 @@ public class ClientApi
/** this includes the is dev build message and low allocated memory warning */ /** this includes the is dev build message and low allocated memory warning */
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000; 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; private boolean isDevBuildMessagePrinted = false;
@@ -389,7 +398,7 @@ public class ClientApi
// rendering // // 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) public void renderLods(IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
{ this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, false); } { this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, false); }
@@ -397,9 +406,10 @@ public class ClientApi
* Only necessary when Shaders are in use. * Only necessary when Shaders are in use.
* Should be called after {@link ClientApi#renderLods} * 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); } { this.renderLodLayer(levelWrapper, mcModelViewMatrix, mcProjectionMatrix, partialTicks, true); }
private void renderLodLayer( private void renderLodLayer(
IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper levelWrapper, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks,
boolean renderingDeferredLayer) 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 // // render validation //
try try
@@ -555,25 +584,26 @@ public class ClientApi
public void renderFadeOpaque(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level) public void renderFadeOpaque(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level)
{ {
// 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
&& 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 */ /** 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) public void renderFade(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IClientLevelWrapper level)
{ {
// 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
&& 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 FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
if (Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.get() != EDhApiMcRenderingFadeMode.NONE)
{
FadeRenderer.INSTANCE.render(mcModelViewMatrix, mcProjectionMatrix, partialTicks, level);
}
} }
} }
@@ -652,7 +682,8 @@ public class ClientApi
private void detectAndSendBootTimeWarnings() private void detectAndSendBootTimeWarnings()
{ {
// dev build // 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.isDevBuildMessagePrinted = true;
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis(); this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
@@ -669,7 +700,8 @@ public class ClientApi
// memory // memory
if (this.staticStartupMessageSentRecently()) return; 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.lowMemoryWarningPrinted = true;
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis(); this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
@@ -694,7 +726,8 @@ public class ClientApi
// high vanilla render distance // high vanilla render distance
if (this.staticStartupMessageSentRecently()) return; 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 // DH generally doesn't need a vanilla render distance above 12
if (MC_RENDER.getRenderDistance() > 12) if (MC_RENDER.getRenderDistance() > 12)
@@ -721,7 +754,8 @@ public class ClientApi
{ {
if (this.lastStaticWarningMessageSentMsTime == 0) if (this.lastStaticWarningMessageSentMsTime == 0)
{ {
return true; // no static message has ever been sent
return false;
} }
long timeSinceLastMessage = System.currentTimeMillis() - this.lastStaticWarningMessageSentMsTime; long timeSinceLastMessage = System.currentTimeMillis() - this.lastStaticWarningMessageSentMsTime;
@@ -740,4 +774,47 @@ public class ClientApi
*/ */
public void showOverlayMessageNextFrame(String message) { this.overlayMessageQueueForNextFrame.add(message); } 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);
}
}
}
} }
@@ -1342,20 +1342,20 @@ public class Config
+ EDhApiDataCompressionMode.UNCOMPRESSED + " \n" + EDhApiDataCompressionMode.UNCOMPRESSED + " \n"
+ "Should only be used for testing, is worse in every way vs ["+EDhApiDataCompressionMode.LZ4+"].\n" + "Should only be used for testing, is worse in every way vs ["+EDhApiDataCompressionMode.LZ4+"].\n"
+ "Expected Compression Ratio: 1.0\n" + "Expected Compression Ratio: 1.0\n"
+ "Estimated average DTO read speed: 1.64 milliseconds\n" + "Estimated average DTO read speed: 3.25 milliseconds\n"
+ "Estimated average DTO write speed: 12.44 milliseconds\n" + "Estimated average DTO write speed: 5.99 milliseconds\n"
+ "\n" + "\n"
+ EDhApiDataCompressionMode.LZ4 + " \n" + EDhApiDataCompressionMode.LZ4 + " \n"
+ "A good option if you're CPU limited and have plenty of hard drive space.\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 read speed: 1.85 ms\n"
+ "Estimated average DTO write speed: 9.46 ms\n" + "Estimated average DTO write speed: 9.46 ms\n"
+ "\n" + "\n"
+ EDhApiDataCompressionMode.LZMA2 + " \n" + EDhApiDataCompressionMode.LZMA2 + " \n"
+ "Slow but very good compression.\n" + "Slow but very good compression.\n"
+ "Expected Compression Ratio: 0.14\n" + "Expected Compression Ratio: 0.2\n"
+ "Estimated average DTO read speed: 11.89 ms\n" + "Estimated average DTO read speed: 13.29 ms\n"
+ "Estimated average DTO write speed: 192.01 ms\n" + "Estimated average DTO write speed: 70.95 ms\n"
+ "") + "")
.build(); .build();
@@ -1713,6 +1713,15 @@ public class Config
+ "Value of 0 disables the limit." + "Value of 0 disables the limit."
+ "") + "")
.build(); .build();
public static ConfigEntry<Boolean> enableAdaptiveTransferSpeed = new ConfigEntry.Builder<Boolean>()
.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"
+ "If false, transfer speed will remain fixed.\n"
+ "")
.build();
public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build(); public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build();
@@ -105,6 +105,15 @@ public class RenderQualityPresetConfigEventHandler extends AbstractPresetConfigE
this.put(EDhApiQualityPreset.HIGH, true); this.put(EDhApiQualityPreset.HIGH, true);
this.put(EDhApiQualityPreset.EXTREME, 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.ssaoEnabled);
this.configList.add(this.vanillaFade); this.configList.add(this.vanillaFade);
this.configList.add(this.dhDither); this.configList.add(this.dhDither);
this.configList.add(this.caveCulling);
for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList) for (ConfigEntryWithPresetOptions<EDhApiQualityPreset, ?> config : this.configList)
@@ -70,7 +70,12 @@ public class ConfigFileHandling
this.configBase = configBase; this.configBase = configBase;
this.configPath = configPath; 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 * @apiNote This overwrites any value currently stored in the config
*/ */
public void loadNightConfig() public void loadNightConfig() { this.loadNightConfig(this.nightConfig); }
{
loadNightConfig(this.nightConfig);
}
/** /**
* Does {@link CommentedFileConfig#load()} but with error checking * Does {@link CommentedFileConfig#load()} but with error checking
* *
@@ -353,7 +355,7 @@ public class ConfigFileHandling
{ {
System.out.println("Creating file failed"); System.out.println("Creating file failed");
this.logger.error(ex); 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.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; 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.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/** /**
* WARNING: This is not THREAD-SAFE! <br><br> * 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. * 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 * 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. * 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 * @author Leetom
*/ */
@@ -352,14 +350,14 @@ public class FullDataPointIdMap
{ {
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 ConcurrentHashMap<Integer, Entry> ENTRY_BY_HASH = new ConcurrentHashMap<>(); /** two levels are present so we don't need to use a key object */
/** lock is necessary since {@link Int2ReferenceOpenHashMap} isn't concurrent and concurrent threads can cause infinite loops */ private static final ConcurrentHashMap<IBiomeWrapper, ConcurrentHashMap<IBlockStateWrapper, Entry>> ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER = new ConcurrentHashMap<>();
private static final ReentrantReadWriteLock ENTRY_POOL_LOCK = new ReentrantReadWriteLock();
public final IBiomeWrapper biome; public final IBiomeWrapper biome;
public final IBlockStateWrapper blockState; public final IBlockStateWrapper blockState;
private Integer hashCode = null; private int hashCode = 0;
private boolean hashGenerated = false;
private String serialString = null; private String serialString = null;
@@ -370,25 +368,21 @@ public class FullDataPointIdMap
public static Entry getEntry(IBiomeWrapper biome, IBlockStateWrapper blockState) public static Entry getEntry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{ {
int entryHash = generateHashCode(biome, blockState); // check for existing entry
ConcurrentHashMap<IBlockStateWrapper, Entry> entryByBlockState = ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER.get(biome);
// try getting the existing Entry if (entryByBlockState != null)
Entry entry = ENTRY_BY_HASH.get(entryHash);
if (entry != null)
{ {
return entry; Entry entry = entryByBlockState.get(blockState);
if (entry != null)
{
return entry;
}
} }
// create the missing entry // Lazily create the inner map and new Entry
return ENTRY_BY_HASH.compute(entryHash, (Integer newHash, Entry currentEntry) -> return ENTRY_BY_BLOCKSTATE_BY_BIOMEWRAPPER
{ .computeIfAbsent(biome, newBiome -> new ConcurrentHashMap<>())
if (currentEntry != null) .computeIfAbsent(blockState, newBlockState -> new Entry(biome, blockState));
{
return currentEntry;
}
return new Entry(biome, blockState);
});
} }
private Entry(IBiomeWrapper biome, IBlockStateWrapper blockState) private Entry(IBiomeWrapper biome, IBlockStateWrapper blockState)
{ {
@@ -402,13 +396,18 @@ public class FullDataPointIdMap
// overrides // // 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 @Override
public int hashCode() public int hashCode()
{ {
// cache the hash code to improve speed // cache the hash code to improve speed
if (this.hashCode == null) if (!this.hashGenerated)
{ {
this.hashCode = generateHashCode(this); this.hashCode = generateHashCode(this);
this.hashGenerated = true;
} }
return this.hashCode; return this.hashCode;
@@ -430,10 +429,14 @@ public class FullDataPointIdMap
public boolean equals(Object otherObj) public boolean equals(Object otherObj)
{ {
if (otherObj == this) if (otherObj == this)
{
return true; return true;
}
if (!(otherObj instanceof Entry)) if (!(otherObj instanceof Entry))
{
return false; return false;
}
Entry other = (Entry) otherObj; Entry other = (Entry) otherObj;
return other.biome.getSerialString().equals(this.biome.getSerialString()) 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); columnSource.fillDebugFlag(0, 0, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.DebugSourceFlag.FULL);
return columnSource; return columnSource;
} }
@@ -65,8 +65,6 @@ public class LodDataBuilder
// only block lighting is needed here, sky lighting is populated at the data source stage // only block lighting is needed here, sky lighting is populated at the data source stage
LodUtil.assertTrue(chunkWrapper.isDhBlockLightingCorrect()); LodUtil.assertTrue(chunkWrapper.isDhBlockLightingCorrect());
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX()); int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getX());
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ()); int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().getZ());
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ); long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
@@ -80,47 +78,31 @@ public class LodDataBuilder
// compute the chunk dataSource offset // compute the chunk dataSource offset
// this offset is used to determine where in the dataSource this chunk's data should go // this offset is used to determine where in the dataSource this chunk's data should go
int chunkOffsetX = chunkWrapper.getChunkPos().getX();
if (chunkWrapper.getChunkPos().getX() < 0)
{
// expected offset positions:
// chunkPos -> offset
// 5 -> 1
// 4 -> 0 ---
// 3 -> 3
// 2 -> 2
// 1 -> 1
// 0 -> 0 ===
// -1 -> 3
// -2 -> 2
// -3 -> 1
// -4 -> 0 ---
// -5 -> 3
chunkOffsetX = ((chunkOffsetX) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetX != 0)
{
chunkOffsetX += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
}
else
{
chunkOffsetX %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ(); // expected offset positions:
if (chunkWrapper.getChunkPos().getZ() < 0) // chunkPos -> offset
{ // 5 -> 1
chunkOffsetZ = ((chunkOffsetZ) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE); // 4 -> 0 ---
if (chunkOffsetZ != 0) // 3 -> 3
{ // 2 -> 2
chunkOffsetZ += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE; // 1 -> 1
} // 0 -> 0 ===
} // -1 -> 3
else // -2 -> 2
{ // -3 -> 1
chunkOffsetZ %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE; // -4 -> 0 ---
} // -5 -> 3
// Fast modulo calculation using bitwise AND since NUMB_OF_CHUNKS_WIDE is a power of 2 (4)
// For any number n: n & (2^k - 1) is equivalent to Math.floorMod(n, 2^k)
// Original: Math.floorMod(x, 4) - Handles negative numbers, gives non-negative result in range [0,3]
// Bitwise: x & (4-1) - Also gives non-negative result in range [0,3]
// Example: -5 & 3 = 3, which equals Math.floorMod(-5, 4) = 3
int chunkOffsetX = chunkWrapper.getChunkPos().getX() & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1);
int chunkOffsetZ = chunkWrapper.getChunkPos().getZ() & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1);
// Convert from chunk coordinates to block coordinates
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
chunkOffsetZ *= LodUtil.CHUNK_WIDTH; chunkOffsetZ *= LodUtil.CHUNK_WIDTH;
@@ -138,54 +120,49 @@ public class LodDataBuilder
IBlockStateWrapper previousBlockState = null; IBlockStateWrapper previousBlockState = null;
int minBuildHeight = chunkWrapper.getMinNonEmptyHeight(); int minBuildHeight = chunkWrapper.getMinNonEmptyHeight();
int exclusiveMaxBuildHeight = chunkWrapper.getExclusiveMaxBuildHeight();
int inclusiveMinBuildHeight = chunkWrapper.getInclusiveMinBuildHeight();
int dataCapacity = chunkWrapper.getHeight() / 4;
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++) for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{ {
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++) for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
{ {
LongArrayList longs = dataSource.get( // Calculate column position
relBlockX + chunkOffsetX, int columnX = relBlockX + chunkOffsetX;
relBlockZ + chunkOffsetZ); int columnZ = relBlockZ + chunkOffsetZ;
// Get column data
LongArrayList longs = dataSource.get(columnX, columnZ);
if (longs == null) if (longs == null)
{ {
longs = new LongArrayList(chunkWrapper.getHeight() / 4); longs = new LongArrayList(dataCapacity);
} }
else else
{ {
longs.clear(); longs.clear();
} }
int lastY = chunkWrapper.getExclusiveMaxBuildHeight(); int lastY = exclusiveMaxBuildHeight;
IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ); IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
IBlockStateWrapper blockState = AIR; IBlockStateWrapper blockState = AIR;
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState); int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
// Determine lighting (we are at the height limit. There are no torches here, and sky is not obscured.) // TODO: Per face lighting someday?
byte blockLight = LodUtil.MIN_MC_LIGHT;
byte skyLight = LodUtil.MAX_MC_LIGHT;
byte blockLight; // Get the maximum height from both heightmaps
byte skyLight;
if (lastY < chunkWrapper.getExclusiveMaxBuildHeight())
{
// FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting
blockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, lastY + 1, relBlockZ);
skyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, lastY + 1, relBlockZ);
}
else
{
//we are at the height limit. There are no torches here, and sky is not obscured.
blockLight = LodUtil.MIN_MC_LIGHT;
skyLight = LodUtil.MAX_MC_LIGHT;
}
// determine the starting Y Pos
int y = Math.max( int y = Math.max(
// max between both heightmaps to account for solid invisible blocks (glass) // max between both heightmaps to account for solid invisible blocks (glass)
// and non-solid opaque blocks (at one point this was stairs, not sure what would fit this now) // and non-solid opaque blocks (at one point this was stairs, not sure what would fit this now)
chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ), chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ),
chunkWrapper.getSolidHeightMapValue(relBlockX, relBlockZ) chunkWrapper.getSolidHeightMapValue(relBlockX, relBlockZ)
); );
// go up until we reach open air or the world limit
// Go up until we reach open air or the world limit
IBlockStateWrapper topBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState); IBlockStateWrapper topBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState);
while (!topBlockState.isAir() && y < chunkWrapper.getExclusiveMaxBuildHeight()) while (!topBlockState.isAir() && y < exclusiveMaxBuildHeight)
{ {
try try
{ {
@@ -198,7 +175,7 @@ public class LodDataBuilder
{ {
if (!getTopErrorLogged) if (!getTopErrorLogged)
{ {
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getExclusiveMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e); LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + exclusiveMaxBuildHeight + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
getTopErrorLogged = true; getTopErrorLogged = true;
} }
@@ -207,7 +184,7 @@ public class LodDataBuilder
} }
} }
// Process blocks from top to bottom
for (; y >= minBuildHeight; y--) for (; y >= minBuildHeight; y--)
{ {
IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ); IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ);
@@ -215,10 +192,10 @@ public class LodDataBuilder
byte newBlockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, y + 1, relBlockZ); byte newBlockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, y + 1, relBlockZ);
byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ); byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ);
// save the biome/block change // Save the biome/block change if different from previous
if (!newBiome.equals(biome) || !newBlockState.equals(blockState)) if (!newBiome.equals(biome) || !newBlockState.equals(blockState))
{ {
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getInclusiveMinBuildHeight(), blockLight, skyLight)); longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - inclusiveMinBuildHeight, blockLight, skyLight));
biome = newBiome; biome = newBiome;
blockState = newBlockState; blockState = newBlockState;
@@ -228,17 +205,16 @@ public class LodDataBuilder
lastY = y; lastY = y;
} }
} }
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getInclusiveMinBuildHeight(), blockLight, skyLight));
dataSource.setSingleColumn(longs, // Add the final data point
relBlockX + chunkOffsetX, longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - inclusiveMinBuildHeight, blockLight, skyLight));
relBlockZ + chunkOffsetZ,
EDhApiWorldGenerationStep.LIGHT, // Set the column in the data source
worldCompressionMode); dataSource.setSingleColumn(longs, columnX, columnZ, EDhApiWorldGenerationStep.LIGHT, worldCompressionMode);
} }
} }
if (ignoreHiddenBlocks) if (ignoreHiddenBlocks)
{ {
cullHiddenBlocks(dataSource, chunkOffsetX, chunkOffsetZ); cullHiddenBlocks(dataSource, chunkOffsetX, chunkOffsetZ);
} }
@@ -275,16 +251,16 @@ public class LodDataBuilder
{ {
long currentPoint = centerColumn.getLong(centerIndex); long currentPoint = centerColumn.getLong(centerIndex);
// translucent data points are not eligible to be culled. // Translucent data points are not eligible to be culled.
if (isTranslucent(dataSource, currentPoint)) if (isTranslucent(dataSource, currentPoint))
{ {
continue; continue;
} }
// the top segment should never be culled. // the top segment should never be culled.
if (centerIndex == 0 if (centerIndex == 0
|| isTranslucent(dataSource, centerColumn.getLong(centerIndex - 1)) || isTranslucent(dataSource, centerColumn.getLong(centerIndex - 1))
) )
{ {
continue; continue;
} }
@@ -292,9 +268,9 @@ public class LodDataBuilder
// the bottom segment can sometimes be culled. // the bottom segment can sometimes be culled.
// assume it will not be seen from below, // assume it will not be seen from below,
// because this would imply the player is in the void. // because this would imply the player is in the void.
if (centerIndex + 1 < centerColumn.size() if (centerIndex + 1 < centerColumn.size()
&& isTranslucent(dataSource, centerColumn.getLong(centerIndex + 1)) && isTranslucent(dataSource, centerColumn.getLong(centerIndex + 1))
) )
{ {
continue; continue;
} }
@@ -327,9 +303,11 @@ public class LodDataBuilder
continue; continue;
} }
// current point is fully surrounded. remove it. // Current point is fully surrounded. remove it.
centerColumn.removeLong(centerIndex); centerColumn.removeLong(centerIndex);
// make the above data point cover the area that the current point used to occupy.
// Make the above data point cover the area that the current point used to occupy.
// The element that was at `centerIndex - 1` is still at that position even after removal of centerIndex.
long above = centerColumn.getLong(centerIndex - 1); long above = centerColumn.getLong(centerIndex - 1);
above = FullDataPointUtil.setBottomY(above, FullDataPointUtil.getBottomY(currentPoint)); above = FullDataPointUtil.setBottomY(above, FullDataPointUtil.getBottomY(currentPoint));
above = FullDataPointUtil.setHeight(above, FullDataPointUtil.getHeight(currentPoint) + FullDataPointUtil.getHeight(above)); above = FullDataPointUtil.setHeight(above, FullDataPointUtil.getHeight(currentPoint) + FullDataPointUtil.getHeight(above));
@@ -338,31 +316,31 @@ public class LodDataBuilder
} }
} }
} }
/** /**
checks if centerPoint is "covered" by opaque data points in adjacentColumn. checks if centerPoint is "covered" by opaque data points in adjacentColumn.
centerPoint counts as covered if, and only if, for all Y levels in its height range, centerPoint counts as covered if, and only if, for all Y levels in its height range,
there exists an opaque data point in adjacentColumn which overlaps with that Y level. there exists an opaque data point in adjacentColumn which overlaps with that Y level.
@param source used to lookup blocks (and their opacities) based on their IDs. @param source used to lookup blocks (and their opacities) based on their IDs.
@param centerPoint the point being checked to see if it's fully covered. @param centerPoint the point being checked to see if it's fully covered.
@param adjacentColumn the data points which might cover centerPoint. @param adjacentColumn the data points which might cover centerPoint.
@param adjacentIndex the starting index in adjacentColumn to start scanning at. @param adjacentIndex the starting index in adjacentColumn to start scanning at.
indices greater than adjacentIndex have already been checked and confirmed to indices greater than adjacentIndex have already been checked and confirmed to
not overlap or only overlap partially with centerPoint's Y range. not overlap or only overlap partially with centerPoint's Y range.
@return if centerPoint is covered, returns the index of the segment which finishes covering it. @return if centerPoint is covered, returns the index of the segment which finishes covering it.
the start of the covering may be a smaller index. in this case, the returned index may be used the start of the covering may be a smaller index. in this case, the returned index may be used
as the adjacentIndex provided to this method on the next iteration which yields a new centerPoint. as the adjacentIndex provided to this method on the next iteration which yields a new centerPoint.
if centerPoint is NOT covered, returns the bitwise negation of the index of the if centerPoint is NOT covered, returns the bitwise negation of the index of the
segment which did not cover it. this guarantees that the returned value is negative. segment which did not cover it. this guarantees that the returned value is negative.
the caller should check for negative return values and manually un-negate them to proceed with the loop. the caller should check for negative return values and manually un-negate them to proceed with the loop.
in other words, this function returns the index of the next adjacent data in other words, this function returns the index of the next adjacent data
point to use in the loop, AND a boolean indicating whether or not the point to use in the loop, AND a boolean indicating whether or not the
centerPoint is covered; both are packed into the same int, and returned. centerPoint is covered; both are packed into the same int, and returned.
*/ */
private static int checkOcclusion(FullDataSourceV2 source, long centerPoint, LongArrayList adjacentColumn, int adjacentIndex) private static int checkOcclusion(FullDataSourceV2 source, long centerPoint, LongArrayList adjacentColumn, int adjacentIndex)
{ {
int bottomOfCenter = FullDataPointUtil.getBottomY(centerPoint); int bottomOfCenter = FullDataPointUtil.getBottomY(centerPoint);
@@ -387,12 +365,12 @@ public class LodDataBuilder
throw new LodUtil.AssertFailureException("Adjacent column ends before center column does."); throw new LodUtil.AssertFailureException("Adjacent column ends before center column does.");
} }
private static boolean isTranslucent(FullDataSourceV2 source, long point) { private static boolean isTranslucent(FullDataSourceV2 source, long point) {
return source.mapping.getBlockStateWrapper(FullDataPointUtil.getId(point)).getOpacity() < LodUtil.BLOCK_FULLY_OPAQUE; return source.mapping.getBlockStateWrapper(FullDataPointUtil.getId(point)).getOpacity() < LodUtil.BLOCK_FULLY_OPAQUE;
} }
/** @throws ClassCastException if an API user returns the wrong object type(s) */ /** @throws ClassCastException if an API user returns the wrong object type(s) */
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk, boolean runAdditionalValidation) throws ClassCastException, DataCorruptedException, IllegalArgumentException public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk, boolean runAdditionalValidation) throws ClassCastException, DataCorruptedException, IllegalArgumentException
@@ -402,9 +380,13 @@ public class LodDataBuilder
int sectionPosZ = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosZ); int sectionPosZ = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosZ);
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ); long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
// chunk relative block position in the data source // Fast modulo calculation using bitwise AND since NUMB_OF_CHUNKS_WIDE is a power of 2 (4)
int relSourceBlockX = Math.floorMod(apiChunk.chunkPosX, 4) * LodUtil.CHUNK_WIDTH; // For any number n: n & (2^k - 1) is equivalent to Math.floorMod(n, 2^k)
int relSourceBlockZ = Math.floorMod(apiChunk.chunkPosZ, 4) * LodUtil.CHUNK_WIDTH; // Original: Math.floorMod(x, 4) - Handles negative numbers, gives non-negative result in range [0,3]
// Bitwise: x & (4-1) - Also gives non-negative result in range [0,3]
// Example: -5 & 3 = 3, which equals Math.floorMod(-5, 4) = 3
int relSourceBlockX = (apiChunk.chunkPosX & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1)) * LodUtil.CHUNK_WIDTH;
int relSourceBlockZ = (apiChunk.chunkPosZ & (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE - 1)) * LodUtil.CHUNK_WIDTH;
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos); FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++) for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
@@ -423,8 +405,8 @@ public class LodDataBuilder
// 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
dataSource.setSingleColumn( dataSource.setSingleColumn(
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; dataSource.isEmpty = false;
} }
@@ -440,7 +422,7 @@ public class LodDataBuilder
/** @see FullDataPointUtil */ /** @see FullDataPointUtil */
public static LongArrayList convertApiDataPointListToPackedLongArray( public static LongArrayList convertApiDataPointListToPackedLongArray(
@Nullable List<DhApiTerrainDataPoint> columnDataPoints, FullDataSourceV2 dataSource, @Nullable List<DhApiTerrainDataPoint> columnDataPoints, FullDataSourceV2 dataSource,
int bottomYBlockPos) throws DataCorruptedException int bottomYBlockPos) throws DataCorruptedException
{ {
// this null check does 2 nice things at the same time: // this null check does 2 nice things at the same time:
@@ -533,13 +515,13 @@ public class LodDataBuilder
} }
// is there a gap between the last datapoint? // is there a gap between the last datapoint?
if (topYPos != lastBottomYPos if (topYPos != lastBottomYPos
&& lastBottomYPos != Integer.MIN_VALUE) && lastBottomYPos != Integer.MIN_VALUE)
{ {
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] has a gap between it and index ["+(i-1)+"]. Empty spaces should be filled by air, otherwise DH's downsampling won't calculate lighting correctly."); throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] has a gap between it and index ["+(i-1)+"]. Empty spaces should be filled by air, otherwise DH's downsampling won't calculate lighting correctly.");
} }
lastBottomYPos = bottomYPos; lastBottomYPos = bottomYPos;
} }
} }
@@ -113,8 +113,8 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
if (this.networkState.sessionConfig.getGenerationBoundsRadius() > 0) if (this.networkState.sessionConfig.getGenerationBoundsRadius() > 0)
{ {
if (DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, new DhBlockPos2D( if (DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, new DhBlockPos2D(
(int) (this.networkState.sessionConfig.getGenerationBoundsX() / this.level.levelWrapper.getDimensionType().getCoordinateScale()), this.networkState.sessionConfig.getGenerationBoundsX(),
(int) (this.networkState.sessionConfig.getGenerationBoundsZ() / this.level.levelWrapper.getDimensionType().getCoordinateScale()) this.networkState.sessionConfig.getGenerationBoundsZ()
)) > this.networkState.sessionConfig.getGenerationBoundsRadius()) )) > this.networkState.sessionConfig.getGenerationBoundsRadius())
{ {
return false; 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 // 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 try
{ {
lock.lock(); lock.lock();
@@ -151,10 +151,9 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
if (Config.Server.generationBoundsRadius.get() > 0) if (Config.Server.generationBoundsRadius.get() > 0)
{ {
double coordinateScale = this.serverLevelWrapper.getDimensionType().getCoordinateScale();
if (DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D( if (DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D(
(int) (Config.Server.generationBoundsX.get() / coordinateScale), serverPlayerState.sessionConfig.getGenerationBoundsX(),
(int) (Config.Server.generationBoundsZ.get() / coordinateScale) serverPlayerState.sessionConfig.getGenerationBoundsZ()
)) > Config.Server.generationBoundsRadius.get()) )) > Config.Server.generationBoundsRadius.get())
{ {
message.sendResponse(new RequestOutOfRangeException("Section out of allowed bounds")); message.sendResponse(new RequestOutOfRangeException("Section out of allowed bounds"));
@@ -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.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer; import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; 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.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -58,7 +59,7 @@ import java.io.File;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** The level used when connected to a server */ /** The level used when connected to a server */
@@ -162,7 +163,8 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
return; return;
} }
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(message.payload))
try(FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSourceAndReleaseBuffer(message.payload))
{ {
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper); boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
NETWORK_LOGGER.debug("Buffer {} isSameLevel: {}", message.payload.dtoBufferId, isSameLevel); NETWORK_LOGGER.debug("Buffer {} isSameLevel: {}", message.payload.dtoBufferId, isSameLevel);
@@ -171,10 +173,28 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
return; 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); 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) catch (Exception e)
{ {
@@ -0,0 +1,71 @@
package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BooleanSupplier;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
public class ClientCongestionControl
{
private static final double ADDITIVE_INCREASE = 50000;
private static final long INTERVAL_MS = 1000;
private final Runnable rateUpdateHandler;
private final AtomicLong bytesReceived = new AtomicLong(0);
private double desiredRate;
private long lastAdjustTime;
public ClientCongestionControl(
Runnable rateUpdateHandler
)
{
this.rateUpdateHandler = rateUpdateHandler;
this.reset();
}
public void reset()
{
this.desiredRate = ADDITIVE_INCREASE;
this.lastAdjustTime = System.currentTimeMillis();
this.bytesReceived.set(0);
}
public void onPayloadReceived(FullDataSplitMessage message)
{
long now = System.currentTimeMillis();
if (now - this.lastAdjustTime >= INTERVAL_MS)
{
this.adjustRate(now);
}
this.bytesReceived.addAndGet(message.buffer.readableBytes());
}
private void adjustRate(long now)
{
double throughput = this.bytesReceived.getAndSet(0);
if (throughput >= this.desiredRate)
{
this.desiredRate += ADDITIVE_INCREASE;
}
else
{
this.desiredRate = Math.max(throughput - ADDITIVE_INCREASE / 2, 1000);
}
this.lastAdjustTime = now;
this.rateUpdateHandler.run();
}
public int getDesiredRate()
{
return (int) (this.desiredRate / 1000);
}
}
@@ -1,6 +1,7 @@
package com.seibel.distanthorizons.core.multiplayer.client; package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig; import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
@@ -56,6 +57,23 @@ public class ClientNetworkState implements Closeable
private long serverTimeOffset = 0; private long serverTimeOffset = 0;
public long getServerTimeOffset() { return this.serverTimeOffset; } public long getServerTimeOffset() { return this.serverTimeOffset; }
private final ClientCongestionControl congestionControl = new ClientCongestionControl(
() -> {
if (Config.Server.enableAdaptiveTransferSpeed.get())
{
this.sendConfigMessage(false);
}
}
);
private final ConfigChangeListener<Boolean> adaptiveTransferSpeedListener = new ConfigChangeListener<>(Config.Server.enableAdaptiveTransferSpeed, isEnabled -> {
if (isEnabled)
{
this.congestionControl.reset();
}
this.sendConfigMessage();
});
//=============// //=============//
@@ -116,6 +134,7 @@ public class ClientNetworkState implements Closeable
}); });
this.networkSession.registerHandler(FullDataSplitMessage.class, this.fullDataPayloadReceiver::receiveChunk); this.networkSession.registerHandler(FullDataSplitMessage.class, this.fullDataPayloadReceiver::receiveChunk);
this.networkSession.registerHandler(FullDataSplitMessage.class, this.congestionControl::onPayloadReceived);
} }
} }
@@ -127,10 +146,22 @@ public class ClientNetworkState implements Closeable
public void sendConfigMessage() public void sendConfigMessage() { this.sendConfigMessage(true); }
public void sendConfigMessage(boolean blocking)
{ {
this.configReceived = false; SessionConfig sessionConfig = new SessionConfig();
this.getSession().sendMessage(new SessionConfigMessage(new SessionConfig()));
if (Config.Server.enableAdaptiveTransferSpeed.get())
{
sessionConfig.constrainValue(Config.Server.maxDataTransferSpeed, this.congestionControl.getDesiredRate());
}
if (blocking)
{
this.configReceived = false;
}
this.getSession().sendMessage(new SessionConfigMessage(sessionConfig));
} }
@@ -166,6 +197,7 @@ public class ClientNetworkState implements Closeable
public void close() public void close()
{ {
this.fullDataPayloadReceiver.close(); this.fullDataPayloadReceiver.close();
this.adaptiveTransferSpeedListener.close();
this.configAnyChangeListener.close(); this.configAnyChangeListener.close();
this.networkSession.close(); this.networkSession.close();
} }
@@ -18,7 +18,7 @@ public class SessionConfig implements INetworkObject
private static final LinkedHashMap<String, Entry> CONFIG_ENTRIES = new LinkedHashMap<>(); private static final LinkedHashMap<String, Entry> CONFIG_ENTRIES = new LinkedHashMap<>();
private final LinkedHashMap<String, Object> values = new LinkedHashMap<>(); private final HashMap<String, Object> values = new HashMap<>();
public SessionConfig constrainingConfig; public SessionConfig constrainingConfig;
@@ -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) -> x); registerConfigEntry(Config.Server.generationBoundsX, (x, y) -> y);
registerConfigEntry(Config.Server.generationBoundsZ, (x, y) -> x); registerConfigEntry(Config.Server.generationBoundsZ, (x, y) -> y);
registerConfigEntry(Config.Server.generationBoundsRadius, (x, y) -> x); registerConfigEntry(Config.Server.generationBoundsRadius, (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);
@@ -119,10 +119,17 @@ public class SessionConfig implements INetworkObject
} }
return (this.constrainingConfig != null return (this.constrainingConfig != null
? (T) entry.valueConstrainer.apply(value, this.constrainingConfig.getValue(name)) ? (T) entry.valueConstrainer.apply(this.constrainingConfig.getValue(name), value)
: value); : value);
} }
public <T> void constrainValue(ConfigEntry<T> configEntry, T value) { this.constrainValue(configEntry.getChatCommandName(), value); }
private void constrainValue(String name, Object value)
{
Entry entry = CONFIG_ENTRIES.get(name);
this.values.put(name, entry.valueConstrainer.apply(this.getValue(name), value));
}
private Map<String, ?> getValues() private Map<String, ?> getValues()
{ {
return CONFIG_ENTRIES.keySet().stream().collect(Collectors.toMap( return CONFIG_ENTRIES.keySet().stream().collect(Collectors.toMap(
@@ -34,8 +34,6 @@ public class FullDataPayloadReceiver implements AutoCloseable
}) })
.build().asMap(); .build().asMap();
@Override @Override
public void close() public void close()
{ {
@@ -13,7 +13,7 @@ import java.util.function.*;
public class FullDataPayloadSender implements AutoCloseable public class FullDataPayloadSender implements AutoCloseable
{ {
private static final int TICK_RATE = 4; private static final int TICK_RATE = 20;
/** 1 Mebibyte minus 576 bytes for other info */ /** 1 Mebibyte minus 576 bytes for other info */
public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000; public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000;
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.server;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.core.config.Config; 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.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel; import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
@@ -59,45 +60,82 @@ 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; AbstractExecutorService fileHandlerExecutor = ThreadPoolUtil.getFileHandlerExecutor();
// the server timestamp will be null if no LOD data exists for this position if (fileHandlerExecutor == null)
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 // shouldn't normally happen, but just in case
rateLimiterSet.syncOnLoginRateLimiter.release(); LOGGER.warn("Unable to send FullDataSourceResponseMessage - getFileHandlerExecutor() is null");
message.sendResponse(new FullDataSourceResponseMessage(null));
return; return;
} }
AbstractExecutorService networkCompressionExecutor = ThreadPoolUtil.getNetworkCompressionExecutor();
AbstractExecutorService executor = ThreadPoolUtil.getNetworkCompressionExecutor(); if (networkCompressionExecutor == null)
if (executor == null)
{ {
// shouldn't normally happen, but just in case // shouldn't normally happen, but just in case
LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null"); LOGGER.warn("Unable to send FullDataSourceResponseMessage - getNetworkCompressionExecutor() is null");
return; return;
} }
this.fullDataSourceProvider().getAsync(message.sectionPos).thenAcceptAsync(fullDataSource ->
{ // get the data requested by the client
try (FullDataPayload payload = new FullDataPayload(fullDataSource, this.getAllBeamsForPos(message.sectionPos))) CompletableFuture<FullDataSourceV2> getServerDatasourceFuture = CompletableFuture.supplyAsync(() ->
{ {
fullDataSource.close(); try
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
{ {
message.sendResponse(new FullDataSourceResponseMessage(payload)); // the client timestamp will be null if we want to retrieve the LOD regardless of when it was last updated
rateLimiterSet.syncOnLoginRateLimiter.release(); long clientTimestamp = (message.clientTimestamp != null) ? message.clientTimestamp : -1;
});
} // the server timestamp will be null if no LOD data exists for this position
catch (Exception e) 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); try
} {
}, executor); // no server data source found
if (fullDataSource == null)
{
return;
}
// send the found data source to client
try (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) public void queueWorldGenForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
@@ -24,7 +24,7 @@ public class ServerPlayerState implements Closeable
{ {
private final ConfigChangeListener<String> levelKeyPrefixChangeListener private final ConfigChangeListener<String> levelKeyPrefixChangeListener
= new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged); = 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 String lastLevelKey = ""; private String lastLevelKey = "";
@@ -56,8 +56,9 @@ public class ServerPlayerState implements Closeable
this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) -> this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) ->
{ {
this.sessionConfig.constrainingConfig = sessionConfigMessage.config; this.sessionConfig.constrainingConfig = sessionConfigMessage.config;
this.sendLevelKey(); this.sendLevelKey();
this.networkSession.sendMessage(new SessionConfigMessage(this.sessionConfig)); this.sendConfigMessage();
}); });
this.networkSession.registerHandler(CloseInternalEvent.class, event -> { this.networkSession.registerHandler(CloseInternalEvent.class, event -> {
@@ -93,7 +94,14 @@ public class ServerPlayerState implements Closeable
} }
} }
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) 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()); MessageQueueState messageQueue = this.messageQueueByPlayerWrapper.computeIfAbsent(player, k -> new MessageQueueState());
messageQueue.messageQueue.add(message); messageQueue.messageQueue.add(message);
@@ -306,9 +306,9 @@ public class PhantomArrayListPool
{ {
// we only want to log when something has been returned // we only want to log when something has been returned
if (checkoutCount != 0 if (checkoutCount != 0
|| returnedByteArrayCount != 0 || returnedByteArrayCount != 0
|| returnedShortArrayCount != 0 || returnedShortArrayCount != 0
|| returnedLongArrayCount != 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)+"]."); 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)+"].");
@@ -399,9 +399,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// prepare this section for rendering // prepare this section for rendering
if (!renderSection.gpuUploadInProgress() if (!renderSection.gpuUploadInProgress()
&& renderSection.renderBuffer == null && renderSection.renderBuffer == null
// TODO this is commented out since some users reported LODs refusing to
// load at their expected higher-detail levels
// this check is specifically for N-sized world generators where the higher quality // this check is specifically for N-sized world generators where the higher quality
// data source may not exist yet, this is done to prevent holes while waiting for said generator // data source may not exist yet, this is done to prevent holes while waiting for said generator
&& renderSection.getFullDataSourceExists() //&& renderSection.getFullDataSourceExists()
) )
{ {
nodesNeedingLoading.add(renderSection); nodesNeedingLoading.add(renderSection);
@@ -25,9 +25,15 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL32; 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 * This object holds a OpenGL reference to a shader
@@ -63,7 +69,7 @@ public class Shader
} }
StringBuilder source = loadFile(path, absoluteFilePath, new StringBuilder()); StringBuilder source = loadFile(path, absoluteFilePath, new StringBuilder());
GL32.glShaderSource(this.id, source); safeShaderSource(this.id, source);
GL32.glCompileShader(this.id); GL32.glCompileShader(this.id);
// check if the shader compiled // 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+"]."); 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); GL32.glCompileShader(this.id);
// check if the shader compiled // check if the shader compiled
int status = GL32.glGetShaderi(this.id, GL32.GL_COMPILE_STATUS); int status = GL32.glGetShaderi(this.id, GL32.GL_COMPILE_STATUS);
@@ -114,6 +120,37 @@ public class Shader
// helpers // // 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 void free() { GL32.glDeleteShader(this.id); }
public static StringBuilder loadFile(String path, boolean absoluteFilePath, StringBuilder stringBuilder) public static StringBuilder loadFile(String path, boolean absoluteFilePath, StringBuilder stringBuilder)
@@ -151,4 +188,6 @@ public class Shader
return stringBuilder; return stringBuilder;
} }
} }
@@ -1,5 +1,7 @@
package com.seibel.distanthorizons.core.render.glObject.texture; 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.GL11C;
import org.lwjgl.opengl.GL13C; import org.lwjgl.opengl.GL13C;
import org.lwjgl.opengl.GL43C; import org.lwjgl.opengl.GL43C;
@@ -8,12 +10,15 @@ import java.nio.ByteBuffer;
public class DHDepthTexture public class DHDepthTexture
{ {
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private int id; private int id;
public DHDepthTexture(int width, int height, EDhDepthBufferFormat format) public DHDepthTexture(int width, int height, EDhDepthBufferFormat format)
{ {
this.id = GL43C.glGenTextures(); 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_MIN_FILTER, GL11C.GL_NEAREST);
GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_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. // For internal use by Iris for copying data. Do not use this in DH.
public DHDepthTexture(int id) { public DHDepthTexture(int id) { this.id = id; }
this.id = id;
}
public void resize(int width, int height, EDhDepthBufferFormat format) 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, GL43C.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, format.getGlInternalFormat(), width, height, 0,
format.getGlType(), format.getGlFormat(), (ByteBuffer) null); format.getGlType(), format.getGlFormat(), (ByteBuffer) null);
} }
public int getTextureId() public int getTextureId()
{ {
if (id == -1) throw new IllegalStateException("Depth texture does not exist!"); if (this.id == -1)
return id; {
throw new IllegalStateException("Depth texture does not exist!");
}
return this.id;
} }
public void destroy() public void destroy()
{ {
GL43C.glDeleteTextures(getTextureId()); GLMC.glDeleteTextures(this.getTextureId());
this.id = -1; this.id = -1;
} }
} }
@@ -1,5 +1,7 @@
package com.seibel.distanthorizons.core.render.glObject.texture; 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.joml.Vector2i;
import org.lwjgl.opengl.GL11C; import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL13C; import org.lwjgl.opengl.GL13C;
@@ -9,6 +11,9 @@ import java.nio.ByteBuffer;
public class DhColorTexture public class DhColorTexture
{ {
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private final EDhInternalTextureFormat internalFormat; private final EDhInternalTextureFormat internalFormat;
private final EDhPixelFormat format; private final EDhPixelFormat format;
private final EDhPixelType type; private final EDhPixelType type;
@@ -100,7 +105,7 @@ public class DhColorTexture
this.throwIfInvalid(); this.throwIfInvalid();
this.isValid = false; this.isValid = false;
GL43C.glDeleteTextures(this.id); GLMC.glDeleteTextures(this.id);
} }
/** @throws IllegalStateException if the texture isn't valid */ /** @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.interfaces.override.rendering.IDhApiShaderProgram;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; 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.DhApiRenderParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer; import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.ColumnRenderBuffer;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
@@ -692,12 +693,15 @@ public class LodRenderer
this.cachedWidth = MC_RENDER.getTargetFrameBufferViewportWidth(); this.cachedWidth = MC_RENDER.getTargetFrameBufferViewportWidth();
this.cachedHeight = MC_RENDER.getTargetFrameBufferViewportHeight(); 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, ApiEventInjector.INSTANCE.fireAllEvents(DhApiColorDepthTextureCreatedEvent.class, new DhApiColorDepthTextureCreatedEvent.EventParam(textureCreatedParam));
this.cachedWidth, this.cachedHeight ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeColorDepthTextureCreatedEvent.class, textureCreatedParam);
));
// also update the override if present // also update the override if present
@@ -729,6 +733,9 @@ public class LodRenderer
{ {
this.nullableColorTexture = null; this.nullableColorTexture = null;
} }
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterColorDepthTextureCreatedEvent.class, textureCreatedParam);
} }
@@ -797,6 +804,9 @@ public class LodRenderer
if (this.depthTexture != null) if (this.depthTexture != null)
this.depthTexture.destroy(); this.depthTexture.destroy();
this.setActiveDepthTextureId(-1);
this.setActiveColorTextureId(-1);
EVENT_LOGGER.info("Renderer Cleanup Complete"); EVENT_LOGGER.info("Renderer Cleanup Complete");
}); });
} }
@@ -155,6 +155,19 @@ public class FadeShader extends AbstractShaderRenderer
@Override @Override
protected void onRender() 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.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer);
GLMC.disableScissorTest(); GLMC.disableScissorTest();
GLMC.disableDepthTest(); GLMC.disableDepthTest();
@@ -165,8 +178,7 @@ public class FadeShader extends AbstractShaderRenderer
GL32.glUniform1i(this.uMcDepthTexture, 0); GL32.glUniform1i(this.uMcDepthTexture, 0);
GLMC.glActiveTexture(GL32.GL_TEXTURE1); 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(depthTextureId);
GLMC.glBindTexture(LodRenderer.getActiveDepthTextureId());
GL32.glUniform1i(this.uDhDepthTexture, 1); GL32.glUniform1i(this.uDhDepthTexture, 1);
GLMC.glActiveTexture(GL32.GL_TEXTURE2); GLMC.glActiveTexture(GL32.GL_TEXTURE2);
@@ -174,7 +186,7 @@ public class FadeShader extends AbstractShaderRenderer
GL32.glUniform1i(this.uCombinedMcDhColorTexture, 2); GL32.glUniform1i(this.uCombinedMcDhColorTexture, 2);
GLMC.glActiveTexture(GL32.GL_TEXTURE3); GLMC.glActiveTexture(GL32.GL_TEXTURE3);
GLMC.glBindTexture(LodRenderer.getActiveColorTextureId()); GLMC.glBindTexture(colorTextureId);
GL32.glUniform1i(this.uDhColorTexture, 3); GL32.glUniform1i(this.uDhColorTexture, 3);
@@ -142,7 +142,16 @@ public class FullDataSourceV2DTO
public FullDataSourceV2 createDataSource(@NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException public FullDataSourceV2 createDataSource(@NotNull ILevelWrapper levelWrapper) throws IOException, InterruptedException, DataCorruptedException
{ {
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(this.pos); 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; return dataSource;
} }
@@ -73,7 +73,10 @@ public class RenderDataPointUtil
public final static int EMPTY_DATA = 0; 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; public final static int ALPHA_DOWNSIZE_SHIFT = 4;
@@ -20,10 +20,8 @@
package com.seibel.distanthorizons.core.util.objects.dataStreams; package com.seibel.distanthorizons.core.util.objects.dataStreams;
import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode; 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 net.jpountz.lz4.LZ4FrameInputStream;
//import org.apache.commons.compress.compressors.zstandard.ZstdCompressorInputStream;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.tukaani.xz.ResettableArrayCache; import org.tukaani.xz.ResettableArrayCache;
@@ -44,6 +42,7 @@ import java.io.*;
public class DhDataInputStream extends DataInputStream 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<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(); private static final Logger LOGGER = LogManager.getLogger();
@@ -62,6 +61,8 @@ public class DhDataInputStream extends DataInputStream
return stream; return stream;
case LZ4: case LZ4:
return new LZ4FrameInputStream(stream); return new LZ4FrameInputStream(stream);
//case Z_STD:
// return new ZstdCompressorInputStream(stream, ZSTD_RESETTABLE_ARRAY_CACHE_GETTER.get());
case LZMA2: case LZMA2:
// using an array cache significantly reduces GC pressure // using an array cache significantly reduces GC pressure
ResettableArrayCache arrayCache = LZMA_RESETTABLE_ARRAY_CACHE_GETTER.get(); 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.LZ4Factory;
import net.jpountz.lz4.LZ4FrameOutputStream; import net.jpountz.lz4.LZ4FrameOutputStream;
import net.jpountz.xxhash.XXHashFactory; import net.jpountz.xxhash.XXHashFactory;
//import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.tukaani.xz.*; import org.tukaani.xz.*;
@@ -53,6 +54,9 @@ public class DhDataOutputStream extends DataOutputStream
{ {
case UNCOMPRESSED: case UNCOMPRESSED:
return stream; return stream;
//case Z_STD:
// return new ZstdCompressorOutputStream(stream, 3, true, true);
case LZ4: case LZ4:
return new LZ4FrameOutputStream(stream, return new LZ4FrameOutputStream(stream,
LZ4FrameOutputStream.BLOCKSIZE.SIZE_64KB, -1L, LZ4FrameOutputStream.BLOCKSIZE.SIZE_64KB, -1L,
@@ -69,7 +73,7 @@ public class DhDataOutputStream extends DataOutputStream
arrayCache.reset(); arrayCache.reset();
// Note: if the LZMA2Options are changed the array cache may need to be re-tested. // 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 // 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); XZ.CHECK_CRC64, arrayCache);
default: default:
@@ -63,8 +63,16 @@ public class LzmaArrayCache extends ArrayCache
{ {
return new byte[size]; 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; return array;
} }
@@ -107,8 +115,16 @@ public class LzmaArrayCache extends ArrayCache
{ {
return new int[size]; 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; 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()+"].");
}
}
}
}
@@ -746,6 +746,10 @@
"Maximum Data Transfer Speed, KB/s", "Maximum Data Transfer Speed, KB/s",
"distanthorizons.config.server.maxDataTransferSpeed.@tooltip": "distanthorizons.config.server.maxDataTransferSpeed.@tooltip":
"Maximum speed for uploading LODs to the clients, in KB/s.\nValue of 0 disables the limit.", "Maximum speed for uploading LODs to the clients, in KB/s.\nValue of 0 disables the limit.",
"distanthorizons.config.server.enableAdaptiveTransferSpeed":
"Enable Adaptive Transfer Speed",
"distanthorizons.config.server.enableAdaptiveTransferSpeed.@tooltip":
"Enables adaptive transfer speed based on client performance.\nIf true, DH will automatically adjust transfer rate to minimize connection lag.\nIf false, transfer speed will remain fixed.",
"distanthorizons.config.server.experimental": "distanthorizons.config.server.experimental":
@@ -917,7 +921,7 @@
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.FEATURES": "distanthorizons.config.enum.EDhApiDistantGeneratorMode.FEATURES":
"Features", "Features",
"distanthorizons.config.enum.EDhApiDistantGeneratorMode.INTERNAL_SERVER": "distanthorizons.config.enum.EDhApiDistantGeneratorMode.INTERNAL_SERVER":
"Internal Server", "Full - Save Chunks",
"distanthorizons.config.enum.EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY": "distanthorizons.config.enum.EDhApiDistantGeneratorProgressDisplayLocation.OVERLAY":
"Overlay", "Overlay",
@@ -931,9 +935,11 @@
"distanthorizons.config.enum.EDhApiDataCompressionMode.UNCOMPRESSED": "distanthorizons.config.enum.EDhApiDataCompressionMode.UNCOMPRESSED":
"Uncompressed", "Uncompressed",
"distanthorizons.config.enum.EDhApiDataCompressionMode.LZ4": "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": "distanthorizons.config.enum.EDhApiDataCompressionMode.LZMA2":
"Slow/Small - LZMA2", "Slow/Smallest - LZMA2",
"distanthorizons.config.enum.EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS": "distanthorizons.config.enum.EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS":
"1. Merge Same Blocks", "1. Merge Same Blocks",
+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.core.sql.repo.FullDataSourceV2Repo;
import com.seibel.distanthorizons.coreapi.util.StringUtil; import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
//import org.apache.commons.compress.compressors.zstandard.ZstdCompressorOutputStream;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test;
import java.io.*; import java.io.*;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
/** /**
* <strong>Note:</strong> * <strong>Note:</strong>
@@ -47,22 +47,14 @@ import java.text.StringCharacterIterator;
*/ */
public class CompressionTest 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 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 */ /** -1 will test all of them */
public static int MAX_DTO_TEST_COUNT = -1; 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 // collapse the following commented out code when looking at tests
//@Test //@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 ////@Test
//public void ZstdDictionary() throws SQLException // isn't any better than normal Zstd //public void ZstdDictionary() throws SQLException // isn't any better than normal Zstd
//{ //{
@@ -205,6 +186,13 @@ public class CompressionTest
// this.testCompressor(compressorName, createInputStreamFunc, createOutputStreamFunc); // this.testCompressor(compressorName, createInputStreamFunc, createOutputStreamFunc);
//} //}
//@Test
public void NoCompression()
{
String compressorName = "Uncompressed";
this.testCompressor(compressorName, EDhApiDataCompressionMode.UNCOMPRESSED);
}
//@Test //@Test
public void Lz4() // fast, poor compression public void Lz4() // fast, poor compression
{ {
@@ -213,11 +201,11 @@ public class CompressionTest
} }
//@Test //@Test
//public void Zstd() // middle of the road public void Zstd() // middle of the road
//{ {
// String compressorName = "Zstd"; //String compressorName = "Zstd";
// this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD); //this.testCompressor(compressorName, EDhApiDataCompressionMode.Z_STD);
//} }
//@Test //@Test
public void LZMA2() // very slow, very good compression though public void LZMA2() // very slow, very good compression though
@@ -254,13 +242,15 @@ public class CompressionTest
long totalCompressedFileSizeInBytes; long totalCompressedFileSizeInBytes;
FullDataSourceV2Repo uncompressedRepo = null;
FullDataSourceV2Repo compressedRepo = null;
try try
{ {
String uncompressedDatabaseFilePath = TEST_DIR + "/" + UNCOMPRESSED_DB_FILE_NAME; String uncompressedDatabaseFilePath = TEST_DIR + "/" + UNCOMPRESSED_DB_FILE_NAME;
File uncompressedDatabaseFile = new File(uncompressedDatabaseFilePath); File uncompressedDatabaseFile = new File(uncompressedDatabaseFilePath);
Assert.assertTrue(uncompressedDatabaseFile.exists()); 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"; String compressedDatabaseFilePath = TEST_DIR + "/output/" + DB_FILE_NAME_PREFIX + "_" + compressorName + ".sqlite";
@@ -268,7 +258,7 @@ public class CompressionTest
compressedDatabaseFile.mkdirs(); compressedDatabaseFile.mkdirs();
compressedDatabaseFile.delete(); compressedDatabaseFile.delete();
Assert.assertTrue(!compressedDatabaseFile.exists()); 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 // // uncompressed input //
FullDataSourceV2DTO uncompressedDto = uncompressedRepo.getByKey(pos); try (FullDataSourceV2DTO uncompressedDto = uncompressedRepo.getByKey(pos))
Assert.assertEquals(uncompressedDto.compressionModeValue, EDhApiDataCompressionMode.UNCOMPRESSED.value); {
FullDataSourceV2 uncompressedDataSource = uncompressedDto.createUnitTestDataSource(); Assert.assertEquals(uncompressedDto.compressionModeValue, EDhApiDataCompressionMode.UNCOMPRESSED.value);
FullDataSourceV2 uncompressedDataSource = uncompressedDto.createUnitTestDataSource();
long uncompressedDtoSize = uncompressedRepo.getDataSizeInBytes(pos);
minUncompressedDtoSizeInBytes = Math.min(uncompressedDtoSize, minUncompressedDtoSizeInBytes); long uncompressedDtoSize = uncompressedRepo.getDataSizeInBytes(pos);
maxUncompressedDtoSizeInBytes = Math.max(uncompressedDtoSize, maxUncompressedDtoSizeInBytes); minUncompressedDtoSizeInBytes = Math.min(uncompressedDtoSize, minUncompressedDtoSizeInBytes);
avgUncompressedDtoSizeInBytes += uncompressedDtoSize; maxUncompressedDtoSizeInBytes = Math.max(uncompressedDtoSize, maxUncompressedDtoSizeInBytes);
avgUncompressedDtoSizeInBytes += uncompressedDtoSize;
// compress file // // compress file //
long startWriteNanoTime = System.nanoTime(); long startWriteNanoTime = System.nanoTime();
FullDataSourceV2DTO compressedDto = FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, compressionMode); FullDataSourceV2DTO compressedDto = FullDataSourceV2DTO.CreateFromDataSource(uncompressedDataSource, compressionMode);
compressedRepo.save(compressedDto); compressedRepo.save(compressedDto);
long endWriteNanoTime = System.nanoTime(); long endWriteNanoTime = System.nanoTime();
totalWriteTimeInNano += (endWriteNanoTime - startWriteNanoTime); totalWriteTimeInNano += (endWriteNanoTime - startWriteNanoTime);
long compressedDtoSize = compressedRepo.getDataSizeInBytes(pos); long compressedDtoSize = compressedRepo.getDataSizeInBytes(pos);
minCompressedDtoSizeInBytes = Math.min(compressedDtoSize, minCompressedDtoSizeInBytes); minCompressedDtoSizeInBytes = Math.min(compressedDtoSize, minCompressedDtoSizeInBytes);
maxCompressedDtoSizeInBytes = Math.max(compressedDtoSize, maxCompressedDtoSizeInBytes); maxCompressedDtoSizeInBytes = Math.max(compressedDtoSize, maxCompressedDtoSizeInBytes);
avgCompressedDtoSizeInBytes += compressedDtoSize; avgCompressedDtoSizeInBytes += compressedDtoSize;
// read compressed file //
// read compressed file //
long startReadNanoTime = System.nanoTime();
long startReadNanoTime = System.nanoTime();
compressedDto = compressedRepo.getByKey(pos);
compressedDto = compressedRepo.getByKey(pos); FullDataSourceV2 compressedDataSource = compressedDto.createUnitTestDataSource();
FullDataSourceV2 compressedDataSource = compressedDto.createUnitTestDataSource();
long endReadMsTime = System.nanoTime();
long endReadMsTime = System.nanoTime(); totalReadTimeInNano += (endReadMsTime - startReadNanoTime);
totalReadTimeInNano += (endReadMsTime - startReadNanoTime);
processedDtoCount++;
processedDtoCount++; }
} }
catch (Exception | Error e) catch (Exception | Error e)
{ {
@@ -371,6 +361,18 @@ public class CompressionTest
e.printStackTrace(); e.printStackTrace();
Assert.fail(e.getMessage()); 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.Assert;
import org.junit.Test; import org.junit.Test;
@Deprecated
public class SquareIntersectTest public class SquareIntersectTest
{ {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final Logger LOGGER = DhLoggerBuilder.getLogger();