Compare commits

..

58 Commits

Author SHA1 Message Date
James Seibel 4a3c24f39e Add proof-of-concept dynamic fade 2026-01-17 10:16:35 -06:00
James Seibel 7b6fd03d78 Change render wrapper get Texture error returns 2026-01-17 09:56:21 -06:00
s809 1a540cf2bc Make sure payload chunk is readable 2026-01-14 22:17:46 +05:00
James Seibel 20fc2efb46 Improve concurrent iterating in QuadTree 2026-01-10 17:03:43 -06:00
James Seibel d8beba2498 minor cleanup in LodBufferContainer cleanup 2026-01-10 17:02:56 -06:00
James Seibel 9f0cb5a394 Add forge specific icon/logo
Done to fix a forge limitation where logos can't contain a file pathhttps://github.com/MinecraftForge/MinecraftForge/issues/7348
2026-01-10 11:56:08 -06:00
James Seibel df63401d11 DB updater use correct classloader 2026-01-10 08:21:09 -06:00
James Seibel db95951ade minor reformat and comment 2026-01-10 08:20:44 -06:00
s809 1e020f93a6 Reapply "Run plugin messages on a DH thread"
This reverts commit ff3145336d.
2026-01-09 20:29:23 +05:00
James Seibel 7aee6dfb44 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2026-01-07 07:50:25 -06:00
James Seibel 546a51a295 expand distant beacon beams for visiblity 2026-01-07 07:50:22 -06:00
James Seibel ec7e791e9f Change EMinecraftColor -> MinecraftTextFormat
No need for an enum when all the values are strings
2026-01-06 07:10:40 -06:00
James Seibel d60dec3d82 Merge branch 'main' into 'main'
Fix typo in high vanilla render distance warning

See merge request distant-horizons-team/distant-horizons-core!94
2026-01-05 13:00:17 +00:00
s809 89a80103f0 Wrong message target 2026-01-04 20:04:30 +05:00
s809 8e14a7223c Add a chat message for incompatible messages 2026-01-04 19:36:24 +05:00
meanwhile131 7cf1e901f5 Fix typo in high vanilla render distance warning 2026-01-01 15:06:30 +04:00
James Seibel ba923fa829 Fix neoforge thread causing resource loading to fail 2025-12-26 14:13:27 -06:00
s809 505dbe2f62 Replace the failure state with future exceptions 2025-12-27 00:51:30 +05:00
James Seibel 48c5828e8f up version number 2.4.5 -> 2.4.6-dev 2025-12-24 22:41:27 -06:00
James Seibel eb2317934f up version number 2.4.4 -> 2.4.5 2025-12-24 22:06:53 -06:00
James Seibel 60537cda1b Replace MC color code strings with an enum 2025-12-24 22:04:50 -06:00
James Seibel 508ff2b776 Fix null pointer in ChunkUpdateQueueManager 2025-12-24 21:53:39 -06:00
James Seibel 7c4ac2bd7e remove dev from version number 2025-12-23 22:55:40 -06:00
James Seibel 8c13c2cf47 Fix toggling world gen not recreating queue 2025-12-23 22:55:40 -06:00
James Seibel 802019ff72 up DH api version 5.0.0 -> 5.1.0 2025-12-23 20:01:06 -06:00
James Seibel 141890556c Revert "remove deprecated getHeight() from DhApiLevelWrapper"
This reverts commit 50bdb73a52.
2025-12-23 19:56:28 -06:00
James Seibel 353838db41 add experimental option to ignore rendering dimensions by name 2025-12-23 12:22:00 -06:00
James Seibel f1547477c9 add clientLevelWrapper to DhApiRenderParam 2025-12-23 12:20:42 -06:00
James Seibel 535a645a84 minor internal API cleanup 2025-12-23 12:19:14 -06:00
James Seibel 2dc7f02b32 Remove experimental option onlyLoadCenterLods
option is now merged into main
2025-12-23 12:18:28 -06:00
James Seibel 50bdb73a52 remove deprecated getHeight() from DhApiLevelWrapper
use getMaxHeight() instead
2025-12-23 12:06:15 -06:00
James Seibel 53e6c95432 commenting DhTerrainShaderProgram 2025-12-23 08:57:51 -06:00
James Seibel 36f0029e45 Fix earth curvature shader compiling 2025-12-23 08:47:44 -06:00
s809 5067e970a2 Use another method to create a buffer 2025-12-23 12:50:02 +05:00
James Seibel 167ca94e69 Remove deprecated disableVanillaFog config 2025-12-22 20:31:24 -06:00
James Seibel 8d94b86bfd Hide network config changes by default 2025-12-22 14:51:29 -06:00
James Seibel a29567430e Net only log changed config values 2025-12-22 14:37:34 -06:00
James Seibel fb2dae48e2 re-enable remote timestamp getting 2025-12-22 14:21:12 -06:00
James Seibel 948b4bfd9c comment out debug log 2025-12-22 14:17:57 -06:00
James Seibel ca44256ca9 disable full data debug phantom array stacks 2025-12-22 14:17:47 -06:00
James Seibel a29b6a5aab remove unnecessary config appearance check 2025-12-22 14:17:13 -06:00
James Seibel 868254ccc8 try fixing rare leak in delayed data source cache
Didn't fix the problem, but shouldn't hurt
2025-12-22 14:16:34 -06:00
James Seibel 195fde8d73 quad tree spilt request cleanup 2025-12-22 13:58:26 -06:00
James Seibel ce7b9b94b6 fix/improve world gen/retrieval error handling 2025-12-22 13:58:26 -06:00
James Seibel 1f0c2e286a fix network splitting requests 2025-12-22 13:58:26 -06:00
James Seibel f79fd5e06f error handling in AbstactDhLevel chunk update 2025-12-22 13:58:26 -06:00
James Seibel 47c1d3955f failed attmpt to fix leaks
Breaks split world gen requests
2025-12-22 13:57:49 -06:00
James Seibel 2c5f5a3d4c minor refactors 2025-12-22 09:46:21 -06:00
James Seibel 81c533051e close errored data sources in full data provider 2025-12-22 08:35:15 -06:00
James Seibel 5cbe5ecfd8 Fix dis/re-enabling world gen queuing 2025-12-21 19:44:48 -06:00
James Seibel d4b4d28c9f Fix null error log in Data source provider 2025-12-21 08:53:27 -06:00
James Seibel b8e653b5f7 Fix phantom checkout not updating stack trace 2025-12-21 08:52:44 -06:00
James Seibel 80fea09598 Fix concurrency error in LodQuadTree 2025-12-21 08:52:25 -06:00
James Seibel 1d4f914a9f Merge branch 'worldGenRefactor' 2025-12-20 10:53:39 -06:00
James Seibel bf92dea2eb reduce stuttering at the cost of lighting quality 2025-12-20 10:52:51 -06:00
James Seibel 1c30213aca up version number 2.4.3 -> 2.4.4-dev 2025-12-18 10:04:41 -06:00
James Seibel e9a044308f remove dev from version number 2025-12-18 09:35:07 -06:00
James Seibel 1aabc0c792 remove chunkWrapper.isStillValid() 2025-12-18 09:35:02 -06:00
69 changed files with 1751 additions and 1009 deletions
@@ -20,6 +20,8 @@
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldLoadEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
@@ -27,7 +29,7 @@ import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
* Contains information relevant to Distant Horizons and Minecraft rendering.
*
* @author James Seibel
* @version 2024-1-31
* @version 2025-12-23
* @since API 1.0.0
*/
public class DhApiRenderParam implements IDhApiEventParam
@@ -61,6 +63,13 @@ public class DhApiRenderParam implements IDhApiEventParam
public final int worldYOffset;
/**
* The level currently being rendered.
*
* @since API 5.1.0
*/
public final IDhApiLevelWrapper clientLevelWrapper;
//==============//
@@ -70,12 +79,13 @@ public class DhApiRenderParam implements IDhApiEventParam
public DhApiRenderParam(DhApiRenderParam parent)
{
this(
parent.renderPass,
parent.partialTicks,
parent.nearClipPlane, parent.farClipPlane,
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
parent.worldYOffset
parent.renderPass,
parent.partialTicks,
parent.nearClipPlane, parent.farClipPlane,
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
parent.worldYOffset,
parent.clientLevelWrapper
);
}
public DhApiRenderParam(
@@ -84,7 +94,8 @@ public class DhApiRenderParam implements IDhApiEventParam
float nearClipPlane, float farClipPlane,
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
int worldYOffset
int worldYOffset,
IDhApiLevelWrapper clientLevelWrapper
)
{
this.renderPass = renderPass;
@@ -101,6 +112,7 @@ public class DhApiRenderParam implements IDhApiEventParam
this.dhModelViewMatrix = newDhModelViewMatrix;
this.worldYOffset = worldYOffset;
this.clientLevelWrapper = clientLevelWrapper;
}
@@ -38,14 +38,14 @@ public final class ModInfo
public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.4.3-b-dev";
public static final String VERSION = "2.4.6-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
/** This version should only be updated when breaking changes are introduced to the DH API */
public static final int API_MAJOR_VERSION = 5;
/** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 0;
public static final int API_MINOR_VERSION = 1;
/** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATCH_VERSION = 0;
@@ -20,7 +20,9 @@
package com.seibel.distanthorizons.core;
import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
@@ -171,6 +173,8 @@ public class Initializer
}
}
DhApi.events.bind(DhApiBeforeRenderEvent.class, IgnoredDimensionCsvHandler.INSTANCE);
}
}
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiHeightFogCo
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.api.converters.ApiFogDrawModeConverter;
import com.seibel.distanthorizons.core.config.api.converters.InvertedBoolConverter;
public class DhApiFogConfig implements IDhApiFogConfig
{
@@ -67,7 +68,7 @@ public class DhApiFogConfig implements IDhApiFogConfig
@Override
@Deprecated
public IDhApiConfigValue<Boolean> disableVanillaFog()
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.disableVanillaFog); }
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog, new InvertedBoolConverter()); }
@Override
public IDhApiConfigValue<Boolean> enableVanillaFog()
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog); }
@@ -23,7 +23,9 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.objects.DhApiResult;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
@@ -31,21 +33,21 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.renderer.*;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.world.IDhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -53,13 +55,23 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL46;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Function;
/**
* This holds the methods that should be called
@@ -95,7 +107,6 @@ public class ClientApi
private boolean isDevBuildMessagePrinted = false;
private boolean lowMemoryWarningPrinted = false;
private boolean highVanillaRenderDistanceWarningPrinted = false;
private boolean g1GarbageCollectorWarningPrinted = false;
private long lastStaticWarningMessageSentMsTime = 0L;
@@ -156,10 +167,10 @@ public class ClientApi
if (Config.Common.Logging.Warning.showReplayWarningOnStartup.get())
{
MC_CLIENT.sendChatMessage("\u00A76" + "Distant Horizons: Replay detected." + "\u00A7r"); // gold color
MC_CLIENT.sendChatMessage(MinecraftTextFormat.ORANGE + "Distant Horizons: Replay detected." + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage("DH may behave strangely or have missing functionality.");
MC_CLIENT.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:");
MC_CLIENT.sendChatMessage("\u00A77"+".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+"\u00A7r"); // light gray color
MC_CLIENT.sendChatMessage(MinecraftTextFormat.GRAY +".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+ MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
MC_CLIENT.sendChatMessage("");
}
@@ -245,7 +256,7 @@ public class ClientApi
}
}
public void clientLevelLoadEvent(IClientLevelWrapper levelWrapper)
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
{
// wait a moment before loading the level to give the server a chance to handle the client's login request
if (MC_CLIENT.clientConnectedToDedicatedServer())
@@ -332,10 +343,27 @@ public class ClientApi
*/
public void pluginMessageReceived(@NotNull AbstractNetworkMessage message)
{
NetworkSession networkSession = this.pluginChannelApi.networkSession;
if (networkSession != null)
@Nullable ThreadPoolExecutor executor = ThreadPoolUtil.networkClientHandlerExecutor();
if (executor == null)
{
networkSession.tryHandleMessage(message);
LOGGER.warn("warn");
return;
}
try
{
executor.execute(() ->
{
NetworkSession networkSession = this.pluginChannelApi.networkSession;
if (networkSession != null)
{
networkSession.tryHandleMessage(message);
}
});
}
catch (RejectedExecutionException e)
{
LOGGER.warn("Plugin message executor rejected");
}
}
@@ -511,10 +539,10 @@ public class ClientApi
this.rendererDisabledBecauseOfExceptions = true;
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
MC_CLIENT.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
MC_CLIENT.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption.");
MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
MC_CLIENT.sendChatMessage("\u00A74Error: " + e);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "" + MinecraftTextFormat.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Renderer disabled to try preventing GL state corruption." + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Toggle DH rendering via the config UI to re-activate DH rendering." + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
}
@@ -533,8 +561,11 @@ public class ClientApi
* The first fade pass.
* Called after MC finishes rendering the opaque passes.
*/
public void renderFadeOpaque()
public void renderFadeOpaque() // TODO this is actually the transparent pass
{
DepthCalculator.INSTANCE.getMcTransparentDepthTexture();
DepthCalculator.INSTANCE.tryCalculateAsync();
// only fade when DH is rendering
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
&&
@@ -555,8 +586,10 @@ public class ClientApi
* Called after MC finishes rendering both opaque
* and transparent passes.
*/
public void renderFadeTransparent()
public void renderFadeTransparent() // TODO this is actually the opaque pass
{
DepthCalculator.INSTANCE.getMcOpaqueDepthTexture();
// only fade when DH is rendering
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
{
@@ -585,27 +618,30 @@ public class ClientApi
/** Trigger once on key press, with CLIENT PLAYER. */
public void keyPressedEvent(int glfwKey)
{
if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get())
{
// keybindings are disabled
return;
}
//if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get())
//{
// // keybindings are disabled
// return;
//}
if (glfwKey == GLFW.GLFW_KEY_F8)
{
Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
DepthCalculator.INSTANCE.pause = true;
//Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
//MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
}
else if (glfwKey == GLFW.GLFW_KEY_F6)
{
Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
DepthCalculator.INSTANCE.pause = true;
//Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
//MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
}
else if (glfwKey == GLFW.GLFW_KEY_P)
{
prefLoggerEnabled = !prefLoggerEnabled;
MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
DepthCalculator.INSTANCE.pause = true;
//prefLoggerEnabled = !prefLoggerEnabled;
//MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
}
}
@@ -659,8 +695,7 @@ public class ClientApi
// remind the user that this is a development build
String message =
// green text
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" +
MinecraftTextFormat.DARK_GREEN + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Issues may occur with this version.\n" +
"Here be dragons!\n";
MC_CLIENT.sendChatMessage(message);
@@ -684,7 +719,7 @@ public class ClientApi
{
String message =
// orange text
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" +
MinecraftTextFormat.ORANGE + "Distant Horizons: Low memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Stuttering or low FPS may occur. \n" +
"Please increase Minecraft's available memory to 4 GB or more. \n" +
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
@@ -706,15 +741,13 @@ public class ClientApi
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
String message =
// yellow text
"\u00A7e" + "Distant Horizons: High vanilla render distance detected." + "\u00A7r \n" +
"Using a high vanilla render distance uses a lot of CPU power \n" +
"and doesn't improve graphics much after about 12.\n" +
"Lowing your vanilla render distance will give you better FPS\n" +
"and reduce stuttering at a similar visual quality.\n" +
// gray text
"\u00A77" + "A vanilla render distance of 8 is recommended." + "\u00A7r \n" +
"This message can be disabled in DH's config under Advanced -> Logging.\n";
MinecraftTextFormat.YELLOW + "Distant Horizons: High vanilla render distance detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Using a high vanilla render distance uses a lot of CPU power \n" +
"and doesn't improve graphics much after about 12.\n" +
"Lowering your vanilla render distance will give you better FPS\n" +
"and reduce stuttering at a similar visual quality.\n" +
MinecraftTextFormat.GRAY + "A vanilla render distance of 8 is recommended." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"This message can be disabled in DH's config under Advanced -> Logging.\n";
MC_CLIENT.sendChatMessage(message);
}
}
@@ -82,15 +82,15 @@ public class ServerApi
// level events //
//==============//
public void serverLevelLoadEvent(IServerLevelWrapper level)
public void serverLevelLoadEvent(IServerLevelWrapper levelWrapper)
{
LOGGER.debug("Server Level " + level + " loading");
LOGGER.debug("Server Level " + levelWrapper + " loading");
AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld();
if (serverWorld != null)
{
serverWorld.getOrLoadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
serverWorld.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
}
}
public void serverLevelUnloadEvent(IServerLevelWrapper level)
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.Initializer;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.DhClientLevel;
@@ -184,12 +185,11 @@ public class SharedApi
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, false); }
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, true); }
//public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks) { this.applyChunkUpdate(chunkWrapper, level, canGetNeighboringChunks, false); }
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded)
{
//========================//
// world and level checks //
//========================//
//===================//
// validation checks //
//===================//
if (chunkWrapper == null)
{
@@ -217,7 +217,6 @@ public class SharedApi
return;
}
// only continue if the level is loaded
IDhLevel dhLevel = dhWorld.getLevel(level);
if (dhLevel == null)
@@ -232,6 +231,7 @@ public class SharedApi
return;
}
// ignore chunk updates if the network should handle them
if (dhLevel instanceof DhClientLevel)
{
if (!((DhClientLevel) dhLevel).shouldProcessChunkUpdate(chunkWrapper.getChunkPos()))
@@ -240,7 +240,14 @@ public class SharedApi
}
}
// shoudln't normally happen, but just in case
// ignore chunk updates for non-rendered levels
String dimName = dhLevel.getLevelWrapper().getDimensionName();
if (IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName))
{
return;
}
// shouldn't normally happen, but just in case
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
{
// TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded
@@ -248,94 +255,20 @@ public class SharedApi
}
//===============================//
// update the necessary chunk(s) //
//===============================//
if (!canGetNeighboringChunks)
{
// only update the center chunk
queueChunkUpdate(chunkWrapper, null, dhLevel, false);
return;
}
ArrayList<IChunkWrapper> neighboringChunkList = getNeighborChunkListForChunk(chunkWrapper, dhLevel);
if (newlyLoaded)
{
// this means this chunkWrapper is a newly loaded chunk
// which may be missing some neighboring chunk data
// because it is bordering the render distance
// thus, only the chunks neighboring this chunkWrapper will get updated
// because those are more likely to have their full neighboring chunk data
//TODO this does not prevent those neighboring chunks from updating
// this newly loaded chunk that were just skipped
// leading to occasional lighting issues
for (IChunkWrapper neighboringChunk : neighboringChunkList)
{
if (neighboringChunk == chunkWrapper)
{
continue;
}
this.applyChunkUpdate(neighboringChunk, level, true, false);
}
}
else
{
// if not all neighboring chunk data is available, do not try to update
if (neighboringChunkList.size() < 9)
{
return;
}
// update the center with any existing neighbour chunks.
// this is done so lighting changes are propagated correctly
queueChunkUpdate(chunkWrapper, neighboringChunkList, dhLevel, true);
}
}
private static ArrayList<IChunkWrapper> getNeighborChunkListForChunk(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{
// get the neighboring chunk list
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
for (int xOffset = -1; xOffset <= 1; xOffset++)
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
if (xOffset == 0 && zOffset == 0)
{
// center chunk
neighborChunkList.add(chunkWrapper);
}
else
{
// neighboring chunk
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighborChunk = dhLevel.getLevelWrapper().tryGetChunk(neighborPos);
if (neighborChunk != null)
{
neighborChunkList.add(neighborChunk);
}
}
}
}
return neighborChunkList;
queueChunkUpdate(chunkWrapper, dhLevel);
}
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighboringChunks)
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{
// return if the chunk is already queued
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
{
return;
}
// add chunk update data to preUpdate queue
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, neighborChunkList, dhLevel, canGetNeighboringChunks);
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel);
CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData);
@@ -343,7 +276,8 @@ public class SharedApi
// (this prevents doing extra work queuing tasks that may not be necessary)
// and makes sure the chunks closest to the player are updated first
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null && executor.getQueueSize() < executor.getPoolSize())
if (executor != null
&& executor.getQueueSize() < executor.getPoolSize())
{
try
{
@@ -383,10 +317,7 @@ public class SharedApi
// update the necessary chunk(s) //
//===============================//
// process preUpdate queue
processQueuedChunkPreUpdate();
// process update queue
processQueuedChunkUpdate();
// queue the next position if there are still positions to process
@@ -415,8 +346,7 @@ public class SharedApi
IDhLevel dhLevel = preUpdateData.dhLevel;
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
boolean canGetNeighboringChunks = preUpdateData.canGetNeighboringChunks;
ArrayList<IChunkWrapper> neighborChunkList = preUpdateData.neighborChunkList;
chunkWrapper.createDhHeightMaps();
try
{
@@ -433,34 +363,6 @@ public class SharedApi
// do not update the chunk if the hash is the same
return;
}
// if this chunk will update and can get neighbors
// then queue neighboring chunks to update as well
// neighboring chunk will get added directly to the update queue
// so they won't queue further chunk updates
if (neighborChunkList != null
&& !neighborChunkList.isEmpty())
{
for (IChunkWrapper adjacentChunk : neighborChunkList)
{
// pulling a new chunkWrapper is necessary to prevent concurrent modification on the existing chunkWrappers
IChunkWrapper newCenterChunk = dhLevel.getLevelWrapper().tryGetChunk(adjacentChunk.getChunkPos());
if (newCenterChunk != null)
{
ChunkUpdateData newUpdateData;
if (canGetNeighboringChunks)
{
newUpdateData = new ChunkUpdateData(newCenterChunk, getNeighborChunkListForChunk(newCenterChunk, dhLevel), dhLevel, true);
}
else
{
newUpdateData = new ChunkUpdateData(newCenterChunk, null, dhLevel, false);
}
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(newCenterChunk.getChunkPos(), newUpdateData);
}
}
}
}
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
@@ -473,8 +375,6 @@ public class SharedApi
private static void processQueuedChunkUpdate()
{
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
if (updateData == null)
{
@@ -484,15 +384,11 @@ public class SharedApi
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
IDhLevel dhLevel = updateData.dhLevel;
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
// having a list of the nearby chunks is needed for lighting and beacon generation
@Nullable ArrayList<IChunkWrapper> nearbyChunkList = updateData.neighborChunkList;
// a non-null list is needed for the lighting engine
if (nearbyChunkList == null)
{
nearbyChunkList = new ArrayList<IChunkWrapper>();
nearbyChunkList.add(chunkWrapper);
}
// having a list of the nearby chunks is needed for lighting and beacon generation
ArrayList<IChunkWrapper> nearbyChunkList = tryGetNeighborChunkListForChunk(chunkWrapper);
try
{
@@ -508,6 +404,35 @@ public class SharedApi
{
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
}
CHUNK_UPDATE_QUEUE_MANAGER.queuedChunkWrapperByChunkPos.remove(updateData.chunkWrapper.getChunkPos());
}
private static ArrayList<IChunkWrapper> tryGetNeighborChunkListForChunk(IChunkWrapper chunkWrapper)
{
// get the neighboring chunk list
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
for (int xOffset = -1; xOffset <= 1; xOffset++)
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
if (xOffset == 0 && zOffset == 0)
{
// center chunk
neighborChunkList.add(chunkWrapper);
}
else
{
// neighboring chunk
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighborChunk = CHUNK_UPDATE_QUEUE_MANAGER.tryGetChunk(neighborPos);
if (neighborChunk != null)
{
neighborChunkList.add(neighborChunk);
}
}
}
}
return neighborChunkList;
}
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import org.jetbrains.annotations.Nullable;
import java.util.Comparator;
import java.util.concurrent.ConcurrentHashMap;
@@ -105,6 +106,7 @@ public class ChunkPosQueue
this.furthestQueue.remove(closest);
return this.updateDataByChunkPos.remove(closest);
}
@Nullable
public ChunkUpdateData popFurthest()
{
if (this.furthestQueue.isEmpty())
@@ -9,18 +9,13 @@ import java.util.ArrayList;
public class ChunkUpdateData
{
public IChunkWrapper chunkWrapper;
@Nullable
public ArrayList<IChunkWrapper> neighborChunkList;
public IDhLevel dhLevel;
public boolean canGetNeighboringChunks;
public ChunkUpdateData(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighborChunks)
public ChunkUpdateData(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{
this.chunkWrapper = chunkWrapper;
this.neighborChunkList = neighborChunkList;
this.dhLevel = dhLevel;
this.canGetNeighboringChunks = canGetNeighborChunks;
}
}
@@ -1,16 +1,22 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
public class ChunkUpdateQueueManager
{
@@ -21,6 +27,12 @@ public class ChunkUpdateQueueManager
public final ChunkPosQueue preUpdateQueue;
public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final ConcurrentMap<DhChunkPos, IChunkWrapper> queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.SECONDS)
.<DhChunkPos, IChunkWrapper>build()
.asMap();
/** dynamically changes based on the number of threads currently available */
public int maxSize = 500;
private static long lastOverloadedLogMessageMsTime = 0;
@@ -68,22 +80,27 @@ public class ChunkUpdateQueueManager
* If there are no more slots, replaces the item furthest from the center in the update queue.
*/
public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
{ this.addItemToQueue(pos, updateData, this.preUpdateQueue); }
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
{ this.addItemToQueue(pos, updateData, this.updateQueue); }
private void addItemToQueue(DhChunkPos pos, ChunkUpdateData updateData, ChunkPosQueue queue)
{
int remainingSlots = this.maxSize - this.getQueuedCount();
// If no slots are left, get one by removing the item furthest from the center
if (remainingSlots <= 0)
{
if (!this.updateQueue.isEmpty())
ChunkUpdateData removedData = queue.popFurthest();
if (removedData != null)
{
this.updateQueue.popFurthest();
}
else
{
this.preUpdateQueue.popFurthest();
this.queuedChunkWrapperByChunkPos.remove(removedData.chunkWrapper.getChunkPos());
}
}
this.preUpdateQueue.addItem(pos, updateData);
queue.addItem(pos,updateData);
this.queuedChunkWrapperByChunkPos.putIfAbsent(pos, updateData.chunkWrapper);
remainingSlots = this.maxSize - this.getQueuedCount();
if (remainingSlots <= 0)
@@ -92,24 +109,6 @@ public class ChunkUpdateQueueManager
}
}
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
{
int remainingSlots = this.maxSize - this.getQueuedCount();
// If no slots are left, get one by removing the item furthest from the center
if (remainingSlots <= 0)
{
this.updateQueue.popFurthest();
}
this.updateQueue.addItem(pos,updateData);
remainingSlots = this.maxSize - this.getQueuedCount();
if (remainingSlots <= 0)
{
this.sendOverloadMessage();
}
}
private void sendOverloadMessage()
{
@@ -119,7 +118,7 @@ public class ChunkUpdateQueueManager
{
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
String message = "\u00A76" + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + "\u00A7r" +
String message = MinecraftTextFormat.ORANGE + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + MinecraftTextFormat.CLEAR_FORMATTING +
"\nThis may result in holes in your LODs. " +
"\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " +
"\nMax queue count [" + SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.maxSize + "] ([" + SharedApi.MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players).";
@@ -140,6 +139,26 @@ public class ChunkUpdateQueueManager
}
}
/**
* Tries to return a cloned chunk wrapper from memory.
* Returns null if no chunk is available.
* <br><br>
* This is done instead of accessing the MC level since
* accessing the level often requires running on the render or server
* thread, which causes stuttering.
*/
@Nullable
public IChunkWrapper tryGetChunk(DhChunkPos pos)
{
IChunkWrapper existingWrapper = this.queuedChunkWrapperByChunkPos.get(pos);
if (existingWrapper == null)
{
return null;
}
return existingWrapper.copy();
}
//=========//
@@ -118,6 +118,20 @@ public class Config
public static ConfigEntry<Boolean> dynamicFadeUseOpaqueMcDepth = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "")
.build();
public static ConfigEntry<String> dynamicFadeExportPath = new ConfigEntry.Builder<String>()
.set("C:/Users/James_Seibel/Desktop/")
.comment(""
+ "")
.build();
public static class Advanced
{
// common config links need to have their destination
@@ -457,6 +471,15 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> expandDistantBeacons = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If true LOD beacon beams will be rendered wider at extreme distances, \n"
+ "making them easier to see. \n"
+ "If false all LOD beacon beams will only ever be 1 block wide. \n"
+ "")
.build();
public static ConfigEntry<Boolean> enableCloudRendering = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
@@ -505,11 +528,6 @@ public class Config
+ "Note: Other mods may conflict with this setting. \n"
+ "")
.build();
@Deprecated
public static ConfigEntry<Boolean> disableVanillaFog = new ConfigEntry.Builder<Boolean>()
.set(!enableVanillaFog.get())
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.build();
@@ -571,14 +589,6 @@ public class Config
static
{
disableVanillaFog.addListener(
new ConfigChangeListener<Boolean>(disableVanillaFog,
(disableVanillaFog) -> enableVanillaFog.setApiValue(disableVanillaFog))
);
}
public static class HeightFog
{
public static ConfigUIComment heightFogHeader = new ConfigUIComment.Builder().setParentConfigClass(HeightFog.class).build();
@@ -846,7 +856,7 @@ public class Config
public static ConfigUIComment experimentalHeader = new ConfigUIComment.Builder().setParentConfigClass(Experimental.class).build();
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 0, 5000)
.setMinDefaultMax(-5000, 0, 5000)
.comment(""
+ "This is the earth size ratio when applying the curvature shader effect. \n"
+ "Note: Enabling this feature may cause rendering bugs. \n"
@@ -856,24 +866,26 @@ public class Config
+ "100 = 1 to 100 (63,710 blocks) \n"
+ "10000 = 1 to 10000 (637.1 blocks) \n"
+ "\n"
+ "Note: Due to current limitations, the min value is 50 \n"
+ "Note: Due to current limitations, the min value is ["+WorldCurvatureConfigEventHandler.MIN_VALID_CURVE_VALUE+"] \n"
+ "and the max value is 5000. Any values outside this range \n"
+ "will be set to 0 (disabled).")
.addListener(WorldCurvatureConfigEventHandler.INSTANCE)
.build();
// TODO should be replaced with a better long-term solution
@Deprecated
public static ConfigEntry<Boolean> onlyLoadCenterLods = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "For internal testing:\n"
+ "Skips loading adjacent LODs to significantly reduce load times (~5x)\n"
+ "but causes lighting on LOD borders to appear as full-bright\n"
+ "and other graphical bugs.\n"
+ "")
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
.build();
public static ConfigEntry<String> ignoredDimensionCsv = new ConfigEntry.Builder<String>()
.set("")
.comment(""
+ "A comma separated list of dimension resource locations where DH won't render. \n"
+ "\n"
+ "Example: \"minecraft:the_nether,minecraft:the_end\"\n"
+ "\n"
+ "Note:\n"
+ "Some DH settings will be disabled and/or changed to improve \n"
+ "visuals when DH rendering is disabled. \n"
+ "")
.addListener(IgnoredDimensionCsvHandler.INSTANCE)
.build();
}
}
@@ -1422,7 +1434,7 @@ public class Config
.set(false)
// enabling this can be quite detrimental to performance,
// so hiding it in the config file should reduce people accidentally enabling it
.setAppearance(isRunningInDevEnvironment() ? EConfigEntryAppearance.ALL : EConfigEntryAppearance.ONLY_IN_FILE)
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.comment(""
+ "Enabling this will drastically increase chunk processing time\n"
+ "and you may need to increase your CPU load to handle it.\n"
@@ -1603,6 +1615,14 @@ public class Config
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerLevel> logConnectionConfigChangesToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.logConnectionConfigChanges")
.set(EDhApiLoggerLevel.WARN)
.comment(""
+ "If enabled, config changes sent by the server will be logged. \n"
+ "")
.build();
public static ConfigCategory warning = new ConfigCategory.Builder().set(Warning.class).build();
@@ -1865,6 +1885,8 @@ public class Config
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
IgnoredDimensionCsvHandler.INSTANCE.onConfigValueSet();
}
catch (Exception e)
{
@@ -0,0 +1,43 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.config.api.converters;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
/**
* Used to support deprecated config options that may be identical
* in implementation but with the On/Off values flipped.
*
* @author James Seibel
* @version 2025-12-22
*/
public class InvertedBoolConverter implements IConverter<Boolean, Boolean>
{
@Override
public Boolean convertToCoreType(Boolean core)
{ return !core; }
@Override
public Boolean convertToApiType(Boolean api)
{ return !api; }
}
@@ -0,0 +1,125 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
public class IgnoredDimensionCsvHandler extends DhApiBeforeRenderEvent implements IConfigListener
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static IgnoredDimensionCsvHandler INSTANCE = new IgnoredDimensionCsvHandler();
private String[] dimensionNames = null;
//=============//
// constructor //
//=============//
/** private since we only ever need one handler at a time */
private IgnoredDimensionCsvHandler() { }
//=================//
// config handling //
//=================//
@Override
public void onConfigValueSet()
{
String ignoredDimensionCsvString = Config.Client.Advanced.Graphics.Experimental.ignoredDimensionCsv.get();
if (ignoredDimensionCsvString == null
|| ignoredDimensionCsvString.isEmpty())
{
LOGGER.info("Dimension ignoring disabled, DH will render all dimensions.");
this.dimensionNames = null;
}
else
{
try
{
this.dimensionNames = ignoredDimensionCsvString.split(",");
LOGGER.info("DH set to ignore dimensions: ["+ StringUtil.join(", ", this.dimensionNames)+"].");
}
catch (Exception e)
{
LOGGER.error("Failed to separate ignored dimensions from CSV string, error: ["+e.getMessage()+"].", e);
this.dimensionNames = null;
}
}
}
//===================//
// external handling //
//===================//
@Override
public void beforeRender(DhApiCancelableEventParam<DhApiRenderParam> event)
{
String dimName = event.value.clientLevelWrapper.getDimensionName();
if (IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName))
{
event.cancelEvent();
Config.Client.Advanced.Graphics.Fog.enableVanillaFog.setApiValue(true);
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.setApiValue(EDhApiMcRenderingFadeMode.NONE);
}
else
{
Config.Client.Advanced.Graphics.Fog.enableVanillaFog.setApiValue(null);
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.setApiValue(null);
}
}
public boolean dimensionNameShouldBeIgnored(String dimName)
{
if (this.dimensionNames == null
|| this.dimensionNames.length == 0)
{
return false;
}
for (int i = 0; i < this.dimensionNames.length; i++)
{
String dimNameToIgnore = this.dimensionNames[i];
if (dimName.equalsIgnoreCase(dimNameToIgnore))
{
return true;
}
}
return false;
}
}
@@ -35,7 +35,7 @@ public class WorldCurvatureConfigEventHandler implements IConfigListener
{
public static WorldCurvatureConfigEventHandler INSTANCE = new WorldCurvatureConfigEventHandler();
private static final int MIN_VALID_CURVE_VALUE = 50;
public static final int MIN_VALID_CURVE_VALUE = 50;
/** private since we only ever need one handler at a time */
@@ -52,6 +52,11 @@ public class WorldCurvatureConfigEventHandler implements IConfigListener
// shouldn't update the UI, otherwise we may end up fighting the user
Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(MIN_VALID_CURVE_VALUE);
}
else if (curveRatio < 0 && curveRatio > -MIN_VALID_CURVE_VALUE)
{
// same as above, but in the negative direction
Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(-MIN_VALID_CURVE_VALUE);
}
}
@@ -202,8 +202,9 @@ public class FullDataSourceV2
public static FullDataSourceV2 createEmpty(long pos)
{
FullDataPointIdMap map = new FullDataPointIdMap(pos);
return new FullDataSourceV2(
pos, new FullDataPointIdMap(pos),
pos, map,
// data points, genSteps, and columnCompression are all null since
// nothing has been generated yet.
// Using the default value of all 0's is adequate
@@ -295,24 +295,21 @@ public class LodBufferContainer implements AutoCloseable
{
this.buffersUploaded = false;
GLProxy.queueRunningOnRenderThread(() ->
for (GLVertexBuffer buffer : this.vbos)
{
for (GLVertexBuffer buffer : this.vbos)
if (buffer != null)
{
if (buffer != null)
{
buffer.destroyAsync();
}
buffer.destroyAsync();
}
for (GLVertexBuffer buffer : this.vbosTransparent)
}
for (GLVertexBuffer buffer : this.vbosTransparent)
{
if (buffer != null)
{
if (buffer != null)
{
buffer.destroyAsync();
}
buffer.destroyAsync();
}
});
}
}
}
@@ -372,10 +372,10 @@ public class LodQuadBuilder
if (quad.direction.axis.isHorizontal() || quad.direction == EDhDirection.DOWN)
{
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
// if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN)
// if we want the color to fade, only apply the dirt color to the bottom vertices
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN)
{
// for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls
@@ -1,12 +0,0 @@
package com.seibel.distanthorizons.core.enums;
/**
* TODO
* might be deprecated in the future? in that case we'll probably want a wrapper
* function to handle colors for new MC versions
*
* source: https://minecraft.wiki/w/Formatting_codes
*/
public class EMinecraftColor
{
}
@@ -0,0 +1,35 @@
package com.seibel.distanthorizons.core.enums;
/**
* might be deprecated in the future? in that case we'll probably want a wrapper
* function to handle colors for new MC versions
* <br><br>
* source: https://minecraft.wiki/w/Formatting_codes
*/
public class MinecraftTextFormat
{
public static final String BLACK = "\u00A70";
public static final String DARK_BLUE = "\u00A71";
public static final String DARK_GREEN = "\u00A72";
public static final String DARK_AQUA = "\u00A73";
public static final String DARK_RED = "\u00A74";
public static final String DARK_PURPLE = "\u00A75";
public static final String ORANGE = "\u00A76";
public static final String GRAY = "\u00A77";
public static final String DARK_GRAY = "\u00A78";
public static final String BLUE = "\u00A79";
public static final String GREEN = "\u00A7a";
public static final String AQUA = "\u00A7b";
public static final String RED = "\u00A7c";
public static final String LIGHT_PURPLE = "\u00A7d";
public static final String YELLOW = "\u00A7e";
public static final String WHITE = "\u00A7f";
public static final String OBFUSCATED = "\u00A7k";
public static final String BOLD = "\u00A7l";
public static final String STRIKETHROUGH = "\u00A7m";
public static final String UNDERLINE = "\u00A7n";
public static final String ITALIC = "\u00A7o";
public static final String CLEAR_FORMATTING = "\u00A7r";
}
@@ -96,7 +96,12 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
// no data currently in the memory cache for this position
memoryDataSource = FullDataSourceV2.createEmpty(inputPos);
pair = new DataSourceSavedTimePair(memoryDataSource);
this.dataSourceByPosition.put(inputPos, pair);
DataSourceSavedTimePair oldPair = this.dataSourceByPosition.put(inputPos, pair);
if (oldPair != null)
{
// shouldn't happen, but just in case
this.handleDataSourceRemoval(oldPair.dataSource);
}
}
else
{
@@ -29,6 +29,7 @@ import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -43,6 +44,7 @@ import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
@@ -115,37 +117,43 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// events //
//========//
private void onWorldGenTaskComplete(DataSourceRetrievalResult genTaskResult, Throwable exception)
private void onWorldGenTaskComplete(@NotNull Long genPos, @Nullable DataSourceRetrievalResult genTaskResult, @Nullable Throwable exception)
{
if (exception != null)
try
{
// don't log shutdown exceptions
if (!ExceptionUtil.isInterruptOrReject(exception))
if (exception != null)
{
LOGGER.error("Uncaught Gen Task Exception at ["+genTaskResult.pos+"], error: ["+exception.getMessage()+"].", exception);
// don't log shutdown exceptions
if (!ExceptionUtil.isInterruptOrReject(exception))
{
LOGGER.error("Uncaught Gen Task Exception at [" + genPos + "], error: [" + exception.getMessage() + "].", exception);
}
return;
}
Objects.requireNonNull(genTaskResult);
if (genTaskResult.state == ERetrievalResultState.SUCCESS)
{
LodUtil.assertTrue(genTaskResult.dataSource != null, "Successful retrieval object should have a datasource.");
this.dataUpdater.updateDataSource(genTaskResult.dataSource);
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
genTaskResult.dataSource.close();
}
else if (genTaskResult.state == ERetrievalResultState.REQUIRES_SPLITTING)
{
// task was split
LodUtil.assertTrue(genTaskResult.dataSource == null, "Split retrieval object should not have a datasource.");
}
else
{
// shouldn't happen, but just in case
LOGGER.warn("Unexpected gen Task state at: [" + DhSectionPos.toString(genTaskResult.pos) + "], state: [" + genTaskResult.state + "], datasource: NULL, exception: NULL.");
}
}
else if (genTaskResult.generatedDataSource != null)
catch (Exception e)
{
this.dataUpdater.updateDataSource(genTaskResult.generatedDataSource);
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
}
else if (exception == null && !genTaskResult.success) // TODO use enum to check type
{
// task was split
}
else
{
// shouldn't happen, but just in case
// TODO is definitely happening
LOGGER.warn("Unexpected gen Task state at: [" + DhSectionPos.toString(genTaskResult.pos) + "], success: ["+genTaskResult.success+"], datasource: NULL, exception: NULL.");
}
// if the generation task was split up into smaller positions, add the on-complete event to them
for (CompletableFuture<DataSourceRetrievalResult> siblingFuture : genTaskResult.childFutures)
{
siblingFuture.whenComplete((siblingGenTaskResult, siblingEx) -> this.onWorldGenTaskComplete(siblingGenTaskResult, siblingEx));
LOGGER.error("Unexpected issue during onWorldGenTaskComplete, error: ["+e.getMessage()+"].", e);
}
}
@@ -268,12 +276,15 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount();
if (availableTaskSlots <= 0)
if (availableTaskSlots == 0)
{
return false;
}
else if (availableTaskSlots < 0)
{
//if (false)
if (pruneWaitingTasksAboveLimit)
{
AtomicInteger tasksToCancel = new AtomicInteger(-availableTaskSlots + 1);
AtomicInteger tasksToCancel = new AtomicInteger(availableTaskSlots * -1);
worldGenQueue.removeRetrievalRequestIf(taskPos -> tasksToCancel.getAndDecrement() > 0);
}
else
@@ -296,7 +307,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
worldGenFuture.whenComplete(this::onWorldGenTaskComplete);
worldGenFuture.whenComplete((r, e) -> this.onWorldGenTaskComplete(genPos, r, e));
return worldGenFuture;
}
@@ -23,11 +23,11 @@ import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.LodRequestModule;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.ENetRequestState;
import com.seibel.distanthorizons.core.multiplayer.client.NetRequestResult;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
@@ -105,20 +105,17 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide
if (timestamp != null)
{
this.syncOnLoadRequestQueue.submitRequest(pos, timestamp)
.thenAccept((NetRequestResult netRequestResult) ->
.thenAccept((DataSourceRetrievalResult result) ->
{
if (netRequestResult.state == ENetRequestState.SUCCESS)
if (result.state == ERetrievalResultState.SUCCESS
&& result.dataSource != null)
{
FullDataSourceV2 fullDataSource = netRequestResult.receivedDataSource;
if (fullDataSource != null)
{
this.updateDataSourceAsync(fullDataSource)
.handle((voidObj, throwable) ->
{
fullDataSource.close();
return null;
});
}
this.updateDataSourceAsync(result.dataSource)
.handle((voidObj, throwable) ->
{
result.dataSource.close();
return null;
});
}
});
}
@@ -200,9 +200,10 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
return FullDataSourceV2.createEmpty(pos);
}
FullDataSourceV2 dataSource = null;
try
{
FullDataSourceV2 dataSource = this.createDataSourceFromDto(dto);
dataSource = this.createDataSourceFromDto(dto);
// automatically create and save adjacent data if missing
if (dto.dataFormatVersion == FullDataSourceV2DTO.DATA_FORMAT.V1_NO_ADJACENT_DATA)
@@ -221,6 +222,15 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
this.tryLogCorruptedDataError(DhSectionPos.toString(pos), e);
this.repo.deleteWithKey(pos);
}
catch (Exception e)
{
if (dataSource != null)
{
dataSource.close();
}
throw e;
}
}
catch (InterruptedException ignore) { }
catch (IOException e)
@@ -242,6 +252,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
catch (Exception e)
{
String message = e.getMessage();
if (message == null)
{
message = "NULL";
}
if (CORRUPT_DATA_ERRORS_LOGGED.add(message))
{
LOGGER.warn("Unexpected error getting: [" + DhSectionPos.toString(pos) + "], this error message will only be logged once, error: [" + message + "].", e);
@@ -185,7 +185,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
parentLocked = true;
this.dataUpdater.lockedPosSet.add(parentUpdatePos);
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos)) // TODO can we cache anything in memory to speed up the propagation process? Compression/Disk IO is by far the slowest part of this process
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos))
{
// will return null if the file handler is shutting down
if (parentDataSource != null)
@@ -7,7 +7,9 @@ import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.FormatUtil;
@@ -154,14 +156,16 @@ public class PregenManager
}
else
{
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos()).thenAccept(result -> {
if (!result.success)
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos())
.whenComplete((DataSourceRetrievalResult result, Throwable throwable) ->
{
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
}
this.pendingGenerations.invalidate(result.pos);
});
if (throwable != null)
{
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
}
this.pendingGenerations.invalidate(result.pos);
});
}
fullDataSource.close();
@@ -1,13 +1,12 @@
package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.AbstractFullDataNetworkRequestQueue;
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.multiplayer.client.NetRequestResult;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
@@ -17,7 +16,6 @@ import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import java.util.ArrayList;
import java.util.concurrent.*;
public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
@@ -59,90 +57,23 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
long generationStartMsTime = System.currentTimeMillis();
CompletableFuture<DataSourceRetrievalResult> returnFuture = new CompletableFuture<>();
Executor worldGenExecutor = ThreadPoolUtil.getWorldGenExecutor();
if (worldGenExecutor == null)
CompletableFuture<DataSourceRetrievalResult> future = super.submitRequest(sectionPos, /* client timestamp */null);
future.thenAccept((DataSourceRetrievalResult result) ->
{
return CompletableFuture.completedFuture(DataSourceRetrievalResult.CreateFail());
}
CompletableFuture<NetRequestResult> netFuture = super.submitRequest(sectionPos, /* client timestamp */null);
netFuture.handle((NetRequestResult netResult, Throwable throwable) ->
{
try
if (result.state == ERetrievalResultState.SUCCESS)
{
if (throwable != null)
{
return DataSourceRetrievalResult.CreateFail();
}
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
int chunkWidth = DhSectionPos.getChunkWidth(sectionPos);
int chunkCount = chunkWidth * chunkWidth;
double timePerChunk = (double) totalGenTimeInMs / (double) chunkCount;
switch (netResult.state)
{
case SUCCESS:
// only add the time on successes
// it won't be a perfect estimate but fails will often come back faster, skewing the time faster
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
return DataSourceRetrievalResult.CreateSuccess(sectionPos, netResult.receivedDataSource);
case FAIL:
return DataSourceRetrievalResult.CreateFail();
case REQUIRES_SPLITTING:
ArrayList<CompletableFuture<DataSourceRetrievalResult>> childFutures = new ArrayList<>(4);
DhSectionPos.forEachChild(sectionPos, (long childPos) ->
{
boolean shouldGenerate;
try (FullDataSourceV2 fullDataSource = this.level.remoteDataSourceProvider.get(childPos))
{
if (fullDataSource != null)
{
shouldGenerate = !this.level.remoteDataSourceProvider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps);
}
else
{
shouldGenerate = true;
}
}
if (shouldGenerate)
{
childFutures.add(this.submitRetrievalTask(childPos, requiredDataDetail));
}
});
return DataSourceRetrievalResult.CreateSplit(childFutures);
}
LodUtil.assertNotReach("Unexpected and unhandled request response result: [" + netResult.state + "]");
return DataSourceRetrievalResult.CreateFail();
// only add the time on successes
// it won't be a perfect estimate but fails will often come back faster, skewing the time faster
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
}
catch (Exception e)
{
LOGGER.error("Unexpected issue in submitRetrievalTask returned future, error: ["+e.getMessage()+"]", e);
return DataSourceRetrievalResult.CreateFail();
}
})
// convert the net result
.handleAsync((DataSourceRetrievalResult retrievalResult, Throwable throwable) ->
{
if (throwable != null)
{
returnFuture.completeExceptionally(throwable);
}
else
{
returnFuture.complete(retrievalResult);
}
return null;
}, worldGenExecutor);
return returnFuture;
});
return future;
}
@Override
@@ -176,13 +107,13 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxGenerationRequestDistance() * 16;
}
@Override
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<NetRequestResult> future)
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future)
{
// split up large requests if N-sized gen isn't enabled
if (!Config.Server.Experimental.enableNSizedGeneration.get()
&& DhSectionPos.getDetailLevel(sectionPos) > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
{
future.complete(NetRequestResult.CreateSplit());
future.complete(DataSourceRetrievalResult.CreateSplit());
return false;
}
@@ -92,7 +92,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private int estimatedRemainingChunkCount = 0;
private final RollingAverage rollingAverageChunkGenTimeInMs = new RollingAverage(Runtime.getRuntime().availableProcessors() * 500);
public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
@Override public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
@@ -128,7 +128,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// the generator is shutting down, don't add new tasks
if (this.generatorClosingFuture != null)
{
return CompletableFuture.completedFuture(DataSourceRetrievalResult.CreateFail());
CompletableFuture<DataSourceRetrievalResult> f = new CompletableFuture<>();
f.completeExceptionally(new CancellationException());
return f;
}
// use the existing task if present
@@ -316,22 +318,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// detail level is too high (if the detail level was too low, the generator would've ignored the request),
// split up the task
// split up the task and add each to the queue
ArrayList<CompletableFuture<DataSourceRetrievalResult>> childFutures = new ArrayList<>(4);
DhSectionPos.forEachChild(closestTask.pos, (childDhSectionPos) ->
{
DataSourceRetrievalTask newGenTask = new DataSourceRetrievalTask(childDhSectionPos, DhSectionPos.getDetailLevel(childDhSectionPos));
childFutures.add(newGenTask.future);
this.waitingTasks.put(newGenTask.pos, newGenTask);
});
// send the child futures to the future recipient, to notify them of the new tasks
closestTask.future.complete(DataSourceRetrievalResult.CreateSplit(childFutures));
closestTask.future.complete(DataSourceRetrievalResult.CreateSplit());
}
// a task has been started or queued
// a task has been started or queued,
// queue another task
return true;
}
@@ -360,7 +351,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
});
generationFuture.handle((fullDataSourceV2, exception) ->
generationFuture.handle((FullDataSourceV2 fullDataSource, Throwable exception) ->
{
try
{
@@ -372,13 +363,16 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
}
worldGenTask.future.complete(DataSourceRetrievalResult.CreateFail());
LodUtil.assertTrue(fullDataSource == null);
worldGenTask.future.completeExceptionally(exception);
}
else
{
boolean taskRemoved = this.inProgressGenTasksByLodPos.remove(taskPos, worldGenTask);
LodUtil.assertTrue(taskRemoved, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]");
worldGenTask.future.complete(DataSourceRetrievalResult.CreateSuccess(taskPos, fullDataSource));
}
boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, worldGenTask);
LodUtil.assertTrue(worked, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]");
worldGenTask.future.complete(DataSourceRetrievalResult.CreateSuccess(taskPos, fullDataSourceV2));
}
catch (Exception e)
{
@@ -692,9 +686,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LOGGER.error("Error when terminating data generation for pos: ["+DhSectionPos.toString(genTask.pos)+"], error: ["+throwable.getMessage()+"].", throwable);
}
if (result.generatedDataSource != null)
if (result != null
&& result.dataSource != null)
{
result.generatedDataSource.close();
result.dataSource.close();
}
return null;
@@ -22,23 +22,16 @@ package com.seibel.distanthorizons.core.generation.tasks;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
/**
* @see DataSourceRetrievalTask
*/
public class DataSourceRetrievalResult
{
/** true if terrain was generated */
public final boolean success; // TODO reponse enum?
public final ERetrievalResultState state;
/** the position that was generated, will be null if nothing was generated */
public final long pos;
@Nullable
public final FullDataSourceV2 generatedDataSource;
/** if a position is too high detail for world generator to handle it, these futures are for its 4 children positions after being split up. */
public final ArrayList<CompletableFuture<DataSourceRetrievalResult>> childFutures = new ArrayList<>(4);
public final FullDataSourceV2 dataSource;
@@ -46,19 +39,13 @@ public class DataSourceRetrievalResult
// constructors //
//==============//
public static DataSourceRetrievalResult CreateSplit(ArrayList<CompletableFuture<DataSourceRetrievalResult>> siblingFutures) { return new DataSourceRetrievalResult(false, 0, null, siblingFutures); }
public static DataSourceRetrievalResult CreateFail() { return new DataSourceRetrievalResult(false, 0, null,null); }
public static DataSourceRetrievalResult CreateSuccess(long pos, FullDataSourceV2 generatedDataSource) { return new DataSourceRetrievalResult(true, pos, generatedDataSource, null); }
private DataSourceRetrievalResult(boolean success, long pos, @Nullable FullDataSourceV2 generatedDataSource, ArrayList<CompletableFuture<DataSourceRetrievalResult>> childFutures)
public static DataSourceRetrievalResult CreateSplit() { return new DataSourceRetrievalResult(ERetrievalResultState.REQUIRES_SPLITTING, 0, null); }
public static DataSourceRetrievalResult CreateSuccess(long pos, FullDataSourceV2 generatedDataSource) { return new DataSourceRetrievalResult(ERetrievalResultState.SUCCESS, pos, generatedDataSource); }
private DataSourceRetrievalResult(ERetrievalResultState state, long pos, @Nullable FullDataSourceV2 dataSource)
{
this.success = success;
this.state = state;
this.pos = pos;
this.generatedDataSource = generatedDataSource;
if (childFutures != null)
{
this.childFutures.addAll(childFutures);
}
this.dataSource = dataSource;
}
@@ -0,0 +1,13 @@
package com.seibel.distanthorizons.core.generation.tasks;
/**
* SUCCESS <br>
* REQUIRES_SPLITTING <br>
*
* @see DataSourceRetrievalResult
*/
public enum ERetrievalResultState
{
SUCCESS,
REQUIRES_SPLITTING,
}
@@ -104,7 +104,7 @@ public class JarUtils
*/
public static InputStream accessFile(String resource)
{
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
final ClassLoader loader = JarUtils.class.getClassLoader();
// this is the path within the jar file
InputStream input = loader.getResourceAsStream(resource);
if (input == null)
@@ -192,23 +192,30 @@ public abstract class AbstractDhLevel implements IDhLevel
return this.updateDataSourcesAsync(fullDataSource)
.thenRun(() ->
{
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
if (updatedChunkPosSet != null)
try
{
for (DhChunkPos chunkPos : updatedChunkPosSet)
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
if (updatedChunkPosSet != null)
{
// save after the data source has been updated to prevent saving the hash without the associated datasource
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
if (this.chunkHashRepo != null && chunkHash != null)
for (DhChunkPos chunkPos : updatedChunkPosSet)
{
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
}
ApiEventInjector.INSTANCE.fireAllEvents(
// save after the data source has been updated to prevent saving the hash without the associated datasource
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
if (this.chunkHashRepo != null && chunkHash != null)
{
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
}
ApiEventInjector.INSTANCE.fireAllEvents(
DhApiChunkModifiedEvent.class,
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ()));
}
}
}
catch (Exception e)
{
LOGGER.error("Unexpected issue after onDataSourceSaveAsync, error: ["+e.getMessage()+"].", e);
}
});
}
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
@@ -302,32 +303,32 @@ public class DhLogger implements IConfigListener
String prefix = "[" + ModInfo.READABLE_NAME + "] ";
if (logLevel == Level.ERROR)
{
prefix += "\u00A74";
prefix += MinecraftTextFormat.DARK_RED;
}
else if (logLevel == Level.WARN)
{
prefix += "\u00A76";
prefix += MinecraftTextFormat.ORANGE;
}
else if (logLevel == Level.INFO)
{
prefix += "\u00A7f";
prefix += MinecraftTextFormat.AQUA;
}
else if (logLevel == Level.DEBUG)
{
prefix += "\u00A77";
prefix += MinecraftTextFormat.GREEN;
}
else if (logLevel == Level.TRACE)
{
prefix += "\u00A78";
prefix += MinecraftTextFormat.DARK_GRAY;
}
else
{
prefix += "\u00A7f";
prefix += MinecraftTextFormat.WHITE;
}
prefix += "\u00A7l\u00A7u";
prefix += MinecraftTextFormat.BOLD + "" + MinecraftTextFormat.WHITE;
prefix += logLevel.name();
prefix += ":\u00A7r ";
prefix += MinecraftTextFormat.CLEAR_FORMATTING + " ";
mc_client.sendChatMessage(prefix + message);
}
@@ -6,6 +6,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -23,6 +24,7 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -71,16 +73,6 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
private final SupplierBasedRateLimiter<Void> rateLimiter = new SupplierBasedRateLimiter<>(this::getRequestRateLimit);
private final Set<Long> succeededPositions = Collections.newSetFromMap(CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.MINUTES)
.<Long, Boolean>build()
.asMap());
private final Set<Long> requiresSplittingPositions = Collections.newSetFromMap(CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.MINUTES)
.<Long, Boolean>build()
.asMap());
//=============//
@@ -106,7 +98,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
protected abstract int getRequestRateLimit();
protected abstract boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos);
protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<NetRequestResult> future);
protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future);
protected abstract String getQueueName();
@@ -116,19 +108,9 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
// request submitting //
//====================//
public CompletableFuture<NetRequestResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp)
public CompletableFuture<DataSourceRetrievalResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp)
{
if (this.succeededPositions.contains(sectionPos))
{
return CompletableFuture.completedFuture(NetRequestResult.CreateFail());
}
if (this.requiresSplittingPositions.contains(sectionPos))
{
return CompletableFuture.completedFuture(NetRequestResult.CreateSplit());
}
NetRequestTask requestEntry = this.waitingTasksBySectionPos.compute(sectionPos, (pos, existingNetTask) ->
NetRequestTask requestEntry = this.waitingTasksBySectionPos.compute(sectionPos, (Long pos, NetRequestTask existingNetTask) ->
{
// ignore already queued tasks
if (existingNetTask != null)
@@ -138,27 +120,25 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
NetRequestTask newRequestEntry = new NetRequestTask(pos, clientTimestamp);
newRequestEntry.future.whenComplete((requestResult, throwable) ->
newRequestEntry.future.whenComplete((DataSourceRetrievalResult requestResult, Throwable throwable) ->
{
this.waitingTasksBySectionPos.remove(pos);
if (throwable != null)
{
if (!(throwable instanceof CancellationException))
{
this.failedRequests.incrementAndGet();
}
return;
}
switch (requestResult.state)
{
case SUCCESS:
this.finishedRequests.incrementAndGet();
this.succeededPositions.add(pos);
break;
case REQUIRES_SPLITTING:
this.requiresSplittingPositions.add(pos);
break;
case FAIL:
this.failedRequests.incrementAndGet();
break;
default:
if (throwable != null && !(throwable instanceof CancellationException))
{
this.failedRequests.incrementAndGet();
}
break;
}
});
@@ -238,11 +218,18 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
FullDataSourceResponseMessage.class
);
requestTask.networkDataSourceFuture = dataSourceNetworkFuture;
dataSourceNetworkFuture.handle((FullDataSourceResponseMessage response, Throwable throwable) ->
Executor networkCompressionExecutor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (networkCompressionExecutor == null)
{
return;
}
dataSourceNetworkFuture.handleAsync((FullDataSourceResponseMessage response, Throwable throwable) ->
{
this.handleNetResponse(requestTask, response, throwable);
return null;
});
}, networkCompressionExecutor);
}
private void handleNetResponse(NetRequestTask requestTask, FullDataSourceResponseMessage response, Throwable throwable)
{
@@ -273,12 +260,12 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
this.level.updateBeaconBeamsForSectionPos(dataSourceDto.pos, response.payload.beaconBeams);
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper(), null);
requestTask.future.complete(NetRequestResult.CreateSuccess(fullDataSource));
requestTask.future.complete(DataSourceRetrievalResult.CreateSuccess(dataSourceDto.pos, fullDataSource));
}
}
catch (SectionRequiresSplittingException ignored)
{
requestTask.future.complete(NetRequestResult.CreateSplit());
requestTask.future.complete(DataSourceRetrievalResult.CreateSplit());
}
catch (SessionClosedException | CancellationException ignored)
{
@@ -287,7 +274,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
catch (RequestRejectedException e)
{
LOGGER.info("Request rejected by the server, message: [" + e.getMessage() + "].");
requestTask.future.complete(NetRequestResult.CreateFail());
requestTask.future.completeExceptionally(e);
}
catch (RateLimitedException e)
{
@@ -316,7 +303,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
}
else
{
requestTask.future.complete(NetRequestResult.CreateFail());
requestTask.future.completeExceptionally(e);
}
}
}
@@ -457,7 +444,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
public final long pos;
/** encapsulates the entire request, including client side queuing and the actual server request */
public final CompletableFuture<NetRequestResult> future = new CompletableFuture<>();
public final CompletableFuture<DataSourceRetrievalResult> future = new CompletableFuture<>();
/** will be null if we want to retrieve the LOD regardless of when it was last updated */
@Nullable
public final Long updateTimestamp;
@@ -1,8 +1,10 @@
package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
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.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
@@ -18,6 +20,7 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartial
import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
@@ -29,6 +32,10 @@ public class ClientNetworkState implements Closeable
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build();
protected static final DhLogger CONFIG_CHANGE_LOGGER = new DhLoggerBuilder()
.fileLevelConfig(Config.Common.Logging.logConnectionConfigChangesToFile)
.build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@@ -44,6 +51,7 @@ public class ClientNetworkState implements Closeable
*/
public NetworkSession getSession() { return this.networkSession; }
@NotNull
public SessionConfig sessionConfig = new SessionConfig();
private volatile boolean configReceived = false;
@@ -89,6 +97,15 @@ public class ClientNetworkState implements Closeable
|| Math.abs(event.protocolVersion - ModInfo.PROTOCOL_VERSION) < this.closestProtocolVersion)
{
this.closestProtocolVersion = event.protocolVersion;
if (ModInfo.PROTOCOL_VERSION < event.protocolVersion)
{
ClientApi.INSTANCE.showChatMessageNextFrame(MinecraftTextFormat.ORANGE + "Distant Horizons: Your mod is outdated. Update to receive LODs on this server.");
}
else
{
ClientApi.INSTANCE.showChatMessageNextFrame(MinecraftTextFormat.ORANGE + "Distant Horizons: The server's mod is outdated. Ask the server's owner to update.");
}
}
});
@@ -129,8 +146,9 @@ public class ClientNetworkState implements Closeable
{
this.serverSupportStatus = EServerSupportStatus.FULL;
// TODO only log changes
LOGGER.info("Connection config has been changed: [" + message.config + "].");
String configChanges = this.sessionConfig.getDifferencesAsString(message.config);
CONFIG_CHANGE_LOGGER.info("Connection config has been changed: [" + configChanges + "].");
this.sessionConfig = message.config;
this.configReceived = true;
});
@@ -1,15 +0,0 @@
package com.seibel.distanthorizons.core.multiplayer.client;
/**
* SUCCESS <br>
* REQUIRES_SPLITTING <br>
* FAIL <br>
*
* @see NetRequestResult
*/
public enum ENetRequestState
{
SUCCESS,
REQUIRES_SPLITTING,
FAIL,
}
@@ -1,47 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import org.jetbrains.annotations.Nullable;
public class NetRequestResult
{
public final ENetRequestState state;
@Nullable
public final FullDataSourceV2 receivedDataSource;
//==============//
// constructors //
//==============//
public static NetRequestResult CreateFail() { return new NetRequestResult(ENetRequestState.FAIL, null); }
public static NetRequestResult CreateSuccess(FullDataSourceV2 receivedDataSource) { return new NetRequestResult(ENetRequestState.SUCCESS, receivedDataSource); }
public static NetRequestResult CreateSplit() { return new NetRequestResult(ENetRequestState.REQUIRES_SPLITTING, null); }
private NetRequestResult(ENetRequestState state, @Nullable FullDataSourceV2 receivedDataSource)
{
this.state = state;
this.receivedDataSource = receivedDataSource;
}
}
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
@@ -40,7 +41,7 @@ public class SyncOnLoadRequestQueue extends AbstractFullDataNetworkRequestQueue
return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxSyncOnLoadDistance() * 16;
}
@Override
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<NetRequestResult> future) { return true; }
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future) { return true; }
@Override
protected String getQueueName() { return "Sync On Login Queue"; }
@@ -162,6 +162,34 @@ public class SessionConfig implements INetworkObject
//=========//
// logging //
//=========//
/**
* example: "common.playerBandwidthLimit:[497], " <br>
* Useful to see what was changed when receiving a new config from the server.
*/
public String getDifferencesAsString(SessionConfig that)
{
StringBuilder stringBuilder = new StringBuilder();
for (String key : this.values.keySet())
{
String thisFieldString = this.values.get(key) + "";
String thatFieldString = that.values.get(key) + "";
if (!thisFieldString.equals(thatFieldString))
{
stringBuilder.append(key+":["+thisFieldString+"], ");
}
}
return stringBuilder.toString();
}
//================//
// base overrides //
//================//
@@ -9,7 +9,7 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMe
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
@@ -36,7 +36,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
{
if (message.isFirst)
{
composite = UnpooledByteBufAllocator.DEFAULT.compositeBuffer();
composite = Unpooled.compositeBuffer();
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
}
else if (composite == null)
@@ -45,6 +45,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
return null;
}
message.buffer.readerIndex(0);
composite.addComponent(message.buffer);
composite.writerIndex(composite.writerIndex() + message.buffer.writerIndex());
LOGGER.debug("Updated full data buffer [" + message.bufferId + "]: [" + composite + "].");
@@ -174,12 +174,21 @@ public class FullDataSourceRequestHandler implements AutoCloseable
DataSourceRequestGroup requestGroup = this.requestGroupsByPos.computeIfAbsent(requestData.sectionPos(), pos ->
{
DataSourceRequestGroup newGroup = new DataSourceRequestGroup(pos);
newGroup.tryAddRequest(requestData);
createdNewGroup.set(true);
try
{
newGroup.tryAddRequest(requestData);
createdNewGroup.set(true);
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
LOGGER.debug("[" + this.getLevelIdentifier() + "] Created request group for pos [" + DhSectionPos.toString(pos) + "].");
return newGroup;
}
catch (Exception e)
{
LOGGER.error("Unable to queue request for pos: ["+DhSectionPos.toString(requestData.sectionPos())+"], error: ["+e.getMessage()+"].", e);
}
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
LOGGER.debug("[" + this.getLevelIdentifier() + "] Created request group for pos [" + DhSectionPos.toString(pos) + "].");
return newGroup;
});
@@ -229,10 +238,14 @@ public class FullDataSourceRequestHandler implements AutoCloseable
private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
{
this.fullDataSourceProvider().getAsync(pos).thenAccept(fullDataSource ->
final GeneratedFullDataSourceProvider provider = this.fullDataSourceProvider();
provider.getAsync(pos)
.thenAccept((FullDataSourceV2 fullDataSource) ->
{
if (this.fullDataSourceProvider().generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps))
if (provider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps))
{
//LOGGER.info("sending - complete [" + DhSectionPos.toString(pos) + "]");
requestGroup.fullDataSource = fullDataSource;
return;
}
@@ -247,11 +260,14 @@ public class FullDataSourceRequestHandler implements AutoCloseable
this.requestGroupsByPos.remove(pos);
if (!requestGroup.tryClose())
{
//LOGGER.info("closing [" + DhSectionPos.toString(pos) + "]");
return;
}
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
{
//LOGGER.info("sending [" + DhSectionPos.toString(pos) + "] - ["+DhSectionPos.toString(requestData.sectionPos())+"]");
this.requestGroupsByFutureId.remove(requestData.futureId());
requestData.rateLimiterSet.generationRequestRateLimiter.release();
requestData.message.sendResponse(new SectionRequiresSplittingException());
@@ -264,7 +280,7 @@ public class FullDataSourceRequestHandler implements AutoCloseable
}
else
{
//LOGGER.info("sending - queueing [" + DhSectionPos.toString(pos) + "]");
//LOGGER.info("queueing incomplete world gen [" + DhSectionPos.toString(pos) + "]");
this.fullDataSourceProvider().queuePositionForRetrieval(pos);
}
});
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Arrays;
/**
* This keeps track of all the poolable
@@ -33,7 +34,7 @@ public class PhantomArrayListCheckout implements AutoCloseable
/** Will be null if the parent pool doesn't want leak stack tracing */
@Nullable
public final String allocationStackTrace;
public String allocationStackTrace = null;
private final ArrayList<ByteArrayList> byteArrayLists = new ArrayList<>();
private final ArrayList<ShortArrayList> shortArrayLists = new ArrayList<>();
@@ -47,21 +48,20 @@ public class PhantomArrayListCheckout implements AutoCloseable
public PhantomArrayListCheckout(@NotNull PhantomArrayListPool owningPool)
{
if (owningPool.logGarbageCollectedStacks)
{
// TODO remove the top 4 or so lines since those will always be the same (relating to the phantom allocations)
// and aren't helpful when debugging
this.allocationStackTrace = StringUtil.join("\n", Thread.currentThread().getStackTrace());
}
else
{
this.allocationStackTrace = null;
}
this.owningPool = owningPool;
this.ownerSoftReference = new SoftReference<>(this);
}
public void onCheckout()
{
if (this.owningPool.logGarbageCollectedStacks)
{
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement[] trimmedElements = Arrays.copyOfRange(stackTraceElements, 4, stackTraceElements.length);
this.allocationStackTrace = StringUtil.join("\n", trimmedElements).intern();
}
}
//=========//
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.pooling;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
@@ -12,7 +13,6 @@ import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -159,6 +159,7 @@ public class PhantomArrayListPool
{
// pool is empty, create new checkout
checkout = new PhantomArrayListCheckout(this);
checkout.onCheckout();
}
else
{
@@ -166,6 +167,7 @@ public class PhantomArrayListPool
if (checkout != null)
{
// use pooled checkout
checkout.onCheckout();
}
else
{
@@ -176,8 +178,7 @@ public class PhantomArrayListPool
{
lowMemoryWarningLogged = true;
// orange text
String message = "\u00A76" + "Distant Horizons: Insufficient memory detected." + "\u00A7r \n" +
String message = MinecraftTextFormat.ORANGE + "Distant Horizons: Insufficient memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"This may cause stuttering or crashing. \n" +
"Potential causes: \n" +
"1. your allocated memory isn't high enough \n" +
@@ -23,7 +23,9 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
import java.util.Objects;
/** immutable */
@Deprecated // TODO why does this exist vs blockpos2d?
public class Pos2D
{
public static final Pos2D ZERO = new Pos2D(0, 0);
@@ -20,10 +20,12 @@
package com.seibel.distanthorizons.core.render;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -42,15 +44,16 @@ import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.jetbrains.annotations.Nullable;
import javax.annotation.WillNotClose;
import java.awt.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
@@ -58,7 +61,7 @@ import java.util.concurrent.locks.ReentrantLock;
* This quadTree structure is our core data structure and holds
* all rendering data.
*/
public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRenderable, AutoCloseable
public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRenderable, IConfigListener, AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */
@@ -75,7 +78,12 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
*/
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
private final IDhClientLevel level;
private final ReentrantLock treeReadWriteLock = new ReentrantLock();
/**
* Note: this doesn't lock all operations as some other threads/operations
* that may traverse the tree while it's being modified.
* IE {@link RenderBufferHandler} will walk through the tree each frame.
*/
private final ReentrantLock treeLock = new ReentrantLock();
private ArrayList<LodRenderSection> debugRenderSections = new ArrayList<>();
private ArrayList<LodRenderSection> altDebugRenderSections = new ArrayList<>();
@@ -88,7 +96,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
* as further sections are loaded before closer ones.
* Only queuing a few of the sections at a time solves this problem.
*/
public final AtomicInteger uploadTaskCountRef = new AtomicInteger(0);
private final AtomicInteger uploadTaskCountRef = new AtomicInteger(0);
private final AtomicBoolean requeueAllRetrievalTasksRef = new AtomicBoolean(false);
private final AtomicBoolean queueThreadRunningRef = new AtomicBoolean(false);
@Nullable
@@ -105,8 +115,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
private double detailDropOffLogBase;
/** the {@link DhSectionPos} that need to be retrieved/generated */
public final LongOpenHashSet missingGenerationPosSet = new LongOpenHashSet();
public final LongOpenHashSet queuedGenerationPosSet = new LongOpenHashSet();
private final Set<Long> missingGenerationPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); // concurrency is annoying but required due to needing to add/remove items in the world gen future
private final Set<Long> queuedGenerationPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
/** cached array to prevent having to re-allocate it each tick */
private final ArrayList<Long> sortedMissingPosList = new ArrayList<>();
@@ -131,6 +143,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
GenericObjectRenderer genericObjectRenderer = this.level.getGenericRenderer();
this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null;
Config.Common.WorldGenerator.enableDistantGeneration.addListener(this);
}
//endregion constructor
@@ -157,8 +171,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// don't traverse the tree if it is being modified
if (this.treeReadWriteLock.tryLock())
// don't tick the tree if a modification is still going
// TODO is this lock necessary for anything beyond this tick method?
if (this.treeLock.tryLock())
{
// this shouldn't be updated while the tree is being iterated through
this.updateDetailLevelVariables();
@@ -186,7 +201,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
finally
{
this.treeReadWriteLock.unlock();
this.treeLock.unlock();
}
}
}
@@ -231,18 +246,56 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
// queue full data retrieval (world gen) requests if needed
if (this.missingGenerationPosSet.size() != 0
&& this.fullDataSourceProvider.canQueueRetrievalNow())
// requeue everything if needed
if (this.requeueAllRetrievalTasksRef.get()
&& !this.queueThreadRunningRef.get())
{
try
this.queueThreadRunningRef.set(true);
this.requeueAllRetrievalTasksRef.set(false);
// running on a separate thread allows for faster loading
// of finished LODs
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() ->
{
this.queueFullDataRetrievalTasks(playerPos);
}
catch (Exception e)
try
{
this.checkAllNodesForRetrievalRequests();
}
catch (Exception e)
{
LOGGER.error("Unexpected error getting new queued retrieval tasks, error: [" + e.getMessage() + "].", e);
}
finally
{
this.queueThreadRunningRef.set(false);
}
});
}
// queue full data retrieval (world gen) requests if needed
if (this.missingGenerationPosSet.size() != 0 //
&& this.fullDataSourceProvider.canQueueRetrievalNow()
&& !this.queueThreadRunningRef.get())
{
this.queueThreadRunningRef.set(true);
// running on a separate thread allows for faster loading
// of finished LODs
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() ->
{
LOGGER.error("Unexpected error queuing retrieval tasks, error: [" + e.getMessage() + "].", e);
}
try
{
this.startQueuedRetrievalTasks(playerPos);
}
catch (Exception e)
{
LOGGER.error("Unexpected error starting queued retrieval tasks, error: [" + e.getMessage() + "].", e);
}
finally
{
this.queueThreadRunningRef.set(false);
}
});
}
@@ -438,19 +491,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// since this section wants to render
// check if it needs any generation to do so
LongArrayList missingPosList = this.fullDataSourceProvider.getPositionsToRetrieve(renderSection.pos);
if (missingPosList != null)
{
for (int i = 0; i < missingPosList.size(); i++)
{
long missingPos = missingPosList.getLong(i);
if (!this.queuedGenerationPosSet.contains(missingPos))
{
this.missingGenerationPosSet.add(missingPos);
}
}
}
this.tryQueuePosForRetrieval(renderSection.pos);
}
}
@@ -473,28 +514,23 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
continue;
}
try
// the section only needs to be updated if a buffer is currently present
LodRenderSection renderSection = this.tryGetValue(pos);
if (renderSection != null)
{
// the section only needs to be updated if a buffer is currently present
LodRenderSection renderSection = this.getValue(pos);
if (renderSection != null)
if (renderSection.canRender())
{
if (renderSection.canRender())
if (renderSection.gpuUploadInProgress()
|| !renderSection.uploadRenderDataToGpuAsync())
{
if (renderSection.gpuUploadInProgress()
|| !renderSection.uploadRenderDataToGpuAsync())
{
// if a section is already loading or failed to start upload
// we need to wait to trigger it again
// if we don't trigger it again the LOD will be out of date
// and may be invisible/missing
positionsToRequeue.add(pos);
}
// if a section is already loading or failed to start upload
// we need to wait to trigger it again
// if we don't trigger it again the LOD will be out of date
// and may be invisible/missing
positionsToRequeue.add(pos);
}
}
}
catch (IndexOutOfBoundsException e)
{ /* the section is now out of bounds, it doesn't need to be reloaded */ }
}
this.sectionsToReload.addAll(positionsToRequeue);
}
@@ -523,6 +559,172 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
//=================================//
// full data retrieval (world gen) //
//=================================//
//region world gen
private void startQueuedRetrievalTasks(DhBlockPos2D playerPos)
{
// sort the nodes from nearest to farthest
this.sortedMissingPosList.clear();
this.sortedMissingPosList.addAll(this.missingGenerationPosSet);
this.sortedMissingPosList.sort((posA, posB) ->
{
int aDist = DhSectionPos.getManhattanBlockDistance(posA, playerPos);
int bDist = DhSectionPos.getManhattanBlockDistance(posB, playerPos);
return Integer.compare(aDist, bDist);
});
//==================================//
// add retrieval tasks to the queue //
//==================================//
for (int i = 0; i < this.sortedMissingPosList.size(); i++)
{
if (!this.fullDataSourceProvider.canQueueRetrievalNow())
{
break;
}
long missingPos = this.sortedMissingPosList.get(i);
// is this position within acceptable generator range?
boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
missingPos,
Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(),
Config.Common.WorldGenerator.generationMaxChunkRadius.get()
);
if (!posInRange)
{
continue;
}
CompletableFuture<DataSourceRetrievalResult> genFuture = this.fullDataSourceProvider.queuePositionForRetrieval(missingPos);
boolean positionQueued = (genFuture != null && !genFuture.isCompletedExceptionally());
if (positionQueued)
{
this.queuedGenerationPosSet.add(missingPos);
this.missingGenerationPosSet.remove(missingPos);
genFuture.exceptionally((Throwable throwable) ->
{
// gen task failed,
// requeue so we can try again in the future
this.queuedGenerationPosSet.remove(missingPos);
this.missingGenerationPosSet.add(missingPos);
return null;
});
genFuture.thenAccept((DataSourceRetrievalResult result) ->
{
// task finished
this.queuedGenerationPosSet.remove(missingPos);
if (result.state == ERetrievalResultState.REQUIRES_SPLITTING)
{
DhSectionPos.forEachChild(missingPos, (long childPos) ->
{
this.tryQueuePosForRetrieval(childPos);
});
}
});
}
}
//==========================//
// calc task count estimate //
//==========================//
// calculate an estimate for the max number of chunks for the queue
int totalWorldGenChunkCount = 0;
int totalWorldGenTaskCount = 0;
for (int i = 0; i < this.sortedMissingPosList.size(); i++)
{
long missingPos = this.sortedMissingPosList.get(i);
// chunk count
int sectionWidthInChunks = DhSectionPos.getChunkWidth(missingPos);
totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks;
// task count
totalWorldGenTaskCount++;
}
this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount);
this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount);
}
@Override
public void onConfigValueSet()
{
boolean generatorEnabled = Config.Common.WorldGenerator.enableDistantGeneration.get();
if (generatorEnabled)
{
// world gen tasks will need to be re-queued
// since all the render sections will already have been loaded
this.requeueAllRetrievalTasksRef.set(true);
}
else
{
// generation is disabled, clear the queues
this.missingGenerationPosSet.clear();
this.queuedGenerationPosSet.clear();
this.requeueAllRetrievalTasksRef.set(false);
}
}
/**
* Needed to get all necessary retrieval requests
* after the quad tree has already been loaded.
*/
private void checkAllNodesForRetrievalRequests()
{
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
while (nodeIterator.hasNext())
{
QuadNode<LodRenderSection> node = nodeIterator.next();
if (node != null)
{
LodRenderSection renderSection = node.value;
if (renderSection != null
&& renderSection.getRenderingEnabled())
{
this.tryQueuePosForRetrieval(renderSection.pos);
}
}
}
}
/** Does nothing if the missing positions are already queued. */
private void tryQueuePosForRetrieval(long pos)
{
LongArrayList missingPosList = this.fullDataSourceProvider.getPositionsToRetrieve(pos);
if (missingPosList == null)
{
return;
}
for (int i = 0; i < missingPosList.size(); i++)
{
long missingPos = missingPosList.getLong(i);
if (!this.queuedGenerationPosSet.contains(missingPos))
{
this.missingGenerationPosSet.add(missingPos);
}
}
}
//endregion world gen
//====================//
// detail level logic //
//====================//
@@ -597,7 +799,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
{
try
{
this.treeReadWriteLock.lock();
this.treeLock.lock();
LOGGER.info("Disposing render data...");
// clear the tree
@@ -620,7 +822,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
finally
{
this.treeReadWriteLock.unlock();
this.treeLock.unlock();
}
}
@@ -652,107 +854,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
//=================================//
// full data retrieval (world gen) //
//=================================//
//region world gen
private void queueFullDataRetrievalTasks(DhBlockPos2D playerPos)
{
// sort the nodes from nearest to farthest
LongArrayList sortedMissingPosList = new LongArrayList(this.missingGenerationPosSet);
sortedMissingPosList.sort((posA, posB) ->
{
int aDist = DhSectionPos.getManhattanBlockDistance(posA, playerPos);
int bDist = DhSectionPos.getManhattanBlockDistance(posB, playerPos);
return Integer.compare(aDist, bDist);
});
//==================================//
// add retrieval tasks to the queue //
//==================================//
for (int i = 0; i < sortedMissingPosList.size(); i++)
{
if (!this.fullDataSourceProvider.canQueueRetrievalNow())
{
break;
}
long missingPos = sortedMissingPosList.getLong(i);
// is this position within acceptable generator range?
boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
missingPos,
Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(),
Config.Common.WorldGenerator.generationMaxChunkRadius.get()
);
if (!posInRange)
{
continue;
}
CompletableFuture<DataSourceRetrievalResult> genFuture = this.fullDataSourceProvider.queuePositionForRetrieval(missingPos);
boolean positionQueued = (genFuture != null && !genFuture.isCompletedExceptionally());
if (positionQueued)
{
this.queuedGenerationPosSet.add(missingPos);
this.missingGenerationPosSet.remove(missingPos);
genFuture.exceptionally((Throwable throwable) ->
{
// gen task failed,
// requeue so we can try again in the future
this.queuedGenerationPosSet.remove(missingPos);
this.missingGenerationPosSet.add(missingPos);
return null;
});
genFuture.thenAccept((DataSourceRetrievalResult result) ->
{
// task finished
this.queuedGenerationPosSet.remove(missingPos);
// if the task failed re-queue so we can try again
if (!result.success)
{
this.missingGenerationPosSet.add(missingPos);
}
});
}
}
//==========================//
// calc task count estimate //
//==========================//
// calculate an estimate for the max number of chunks for the queue
int totalWorldGenChunkCount = 0;
int totalWorldGenTaskCount = 0;
for (int i = 0; i < sortedMissingPosList.size(); i++)
{
long missingPos = sortedMissingPosList.getLong(i);
// chunk count
int sectionWidthInChunks = DhSectionPos.getChunkWidth(missingPos);
totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks;
// task count
totalWorldGenTaskCount++;
}
this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount);
this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount);
}
//endregion world gen
//===========//
// debugging //
//===========//
@@ -818,13 +919,15 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
LOGGER.info("Shutting down LodQuadTree...");
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
ThreadPoolExecutor mainCleanupExecutor = ThreadPoolUtil.getCleanupExecutor();
// closing every node may take a few moments
// so this is run on a separate thread to prevent lagging the render thread
mainCleanupExecutor.execute(() ->
{
this.treeReadWriteLock.lock();
this.treeLock.lock();
try
{
// walk through each node
@@ -842,7 +945,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
finally
{
this.treeReadWriteLock.unlock();
this.treeLock.unlock();
}
});
@@ -77,7 +77,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
* contains the list of beacons currently being rendered in this section
* if this list is modified the {@link LodRenderSection#beaconRenderHandler} should be updated to match.
*/
private final List<BeaconBeamDTO> activeBeaconList = new ArrayList<>();
private final ArrayList<BeaconBeamDTO> activeBeaconList = new ArrayList<>();
@Nullable
public final BeaconRenderHandler beaconRenderHandler;
@Nullable
@@ -241,24 +241,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// get the adjacent positions
// needs to be done async to prevent threads waiting on the same positions to be processed
final CompletableFuture<ColumnRenderSource>[] adjacentLoadFutures = new CompletableFuture[4];
if (Config.Client.Advanced.Graphics.Experimental.onlyLoadCenterLods.get())
{
// TODO temporary test, long term something else should be done to so we can get adjacent lighting data
// probably a change to the LOD data format
adjacentLoadFutures[0] = CompletableFuture.completedFuture(null);
adjacentLoadFutures[1] = CompletableFuture.completedFuture(null);
adjacentLoadFutures[2] = CompletableFuture.completedFuture(null);
adjacentLoadFutures[3] = CompletableFuture.completedFuture(null);
}
else
{
adjacentLoadFutures[0] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.NORTH);
adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.SOUTH);
adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.EAST);
adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.WEST);
}
adjacentLoadFutures[0] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.NORTH);
adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.SOUTH);
adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.EAST);
adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.WEST);
return CompletableFuture.allOf(adjacentLoadFutures).thenRun(() ->
{
try (ColumnRenderSource northRenderSource = adjacentLoadFutures[0].get();
@@ -460,20 +446,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// stop rendering current beacons
for (BeaconBeamDTO beam : this.activeBeaconList)
{
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
}
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
// swap old and new active beacon list
this.activeBeaconList.clear();
this.activeBeaconList.addAll(activeBeacons);
// start rendering new beacon list
for (BeaconBeamDTO beam : this.activeBeaconList)
{
this.beaconRenderHandler.startRenderingBeacon(beam);
}
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
}
}
@@ -488,10 +469,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
synchronized (this.activeBeaconList)
{
for (BeaconBeamDTO beam : this.activeBeaconList)
{
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
}
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
}
}
@@ -506,10 +484,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
synchronized (this.activeBeaconList)
{
for (BeaconBeamDTO beam : this.activeBeaconList)
{
this.beaconRenderHandler.startRenderingBeacon(beam);
}
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
}
}
@@ -0,0 +1,444 @@
package com.seibel.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL46;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.function.Function;
public class DepthCalculator
{
public static DepthCalculator INSTANCE = new DepthCalculator();
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
private float[] mcOpaqueDepthTextureValues = new float[1];
private float[] mcTransparentDepthTextureValues = new float[1];
private float[] dhDepthTextureValues = new float[1];
private float[] outDepthTextureValues = new float[1];
private float lastClosestDhDepth = 1.0f;
public float actualMcBlockDistance = 8 * 16; // needs to be non-zero to start DH rendering
@Deprecated // Replace with thread pool and an AtomicBool for running state
public Thread thread = null;
public boolean pause = false;
private boolean gotDhDepthThisFrame = false;
private boolean gotMcDepthThisFrame = false;
//=============//
// constructor //
//=============//
private DepthCalculator() { }
//======//
// test //
//======//
public void getMcOpaqueDepthTexture() { this.gotMcDepthThisFrame = this.trySetDepthTexture(MC_RENDER.getDepthTextureId(), this.mcOpaqueDepthTextureValues); }
public void getMcTransparentDepthTexture() { this.gotMcDepthThisFrame = this.trySetDepthTexture(MC_RENDER.getDepthTextureId(), this.mcTransparentDepthTextureValues); }
public void trySetDhDepthTexture() { this.gotDhDepthThisFrame = this.trySetDepthTexture(LodRenderer.INSTANCE.getActiveDepthTextureId(), this.dhDepthTextureValues); }
private boolean trySetDepthTexture(int id, float[] outputRef)
{
// don't change the texture if a process is already running
if (this.thread != null)
{
return false;
}
this.resizeTexturesIfNeeded();
if (id == -1)
{
return false;
}
// FIXME this is slow and causes frame stuttering
GL46.glGetTextureImage(id, 0, GL32.GL_DEPTH_COMPONENT, GL32.GL_FLOAT, outputRef);
return true;
}
private void resizeTexturesIfNeeded()
{
int width = MC_RENDER.getTargetFramebufferViewportWidth();
int height = MC_RENDER.getTargetFramebufferViewportHeight();
int elementCount = width * height;
if (this.dhDepthTextureValues.length != elementCount)
{
this.mcOpaqueDepthTextureValues = new float[width * height];
this.mcTransparentDepthTextureValues = new float[width * height];
this.dhDepthTextureValues = new float[width * height];
this.outDepthTextureValues = new float[width * height];
}
}
public void tryCalculateAsync()
{
if (this.thread != null)
{
return;
}
//if (!this.gotDhDepthThisFrame
// || !this.gotMcDepthThisFrame)
//{
// return;
//}
if (this.pause)
{
int k = 0;
}
this.thread = new Thread(() ->
{
try
{
this.calculateDepth();
}
catch (Exception e)
{
LOGGER.error("async test: " + e.getMessage(), e);
}
finally
{
this.thread = null;
this.pause = false;
}
});
this.thread.start();
}
public void calculateDepth()
{
int width = MC_RENDER.getTargetFramebufferViewportWidth();
int height = MC_RENDER.getTargetFramebufferViewportHeight();
int elementCount = width * height;
// used to calculate the DH render matrices
RenderParams renderParams =
new RenderParams(
EDhApiRenderPass.OPAQUE,
ClientApi.RENDER_STATE.frameTime,
ClientApi.RENDER_STATE.mcProjectionMatrix, ClientApi.RENDER_STATE.mcModelViewMatrix,
ClientApi.RENDER_STATE.clientLevelWrapper
);
Mat4f dhInvProj = new Mat4f(renderParams.dhProjectionMatrix);
dhInvProj.invert();
Matrix4f dhInvProjJoml = dhInvProj.createJomlMatrix();
Mat4f dhInvMvm = new Mat4f(renderParams.dhModelViewMatrix);
dhInvMvm.invert();
Matrix4f dhInvMvmJoml = dhInvMvm.createJomlMatrix(); // TODO can we use JOML for MC 1.16?
float[] sampledDistances = new float[9];
// find the closest depth value MC hasn't drawn to
float closestDhDepth = 1.0f; float closestDhDistance = Float.MAX_VALUE;
int closeUIndex = 0; int closeVIndex = 0;
for (int u = 0; u < width; u++) // x
{
for (int v = 0; v < height; v++) // y
{
int invertedV = height - 1 - v;
int i = (invertedV * width) + u;
this.outDepthTextureValues[i] = 0.0f;
float mcDepth = Config.Client.dynamicFadeUseOpaqueMcDepth.get()
? this.mcOpaqueDepthTextureValues[i]
: this.mcTransparentDepthTextureValues[i];
if (mcDepth < 1.0f) // ignore positions MC has drawn to
{
continue;
}
float dhDepth = this.dhDepthTextureValues[i];
if (dhDepth == 1.0f) // ignore positions DH has NOT drawn to
{
continue;
}
this.outDepthTextureValues[i] = dhDepth;
// calculate this point's distance from the camera
float ndcU = u / (float)width;
float ndcV = invertedV / (float)height;
float dist = convertDepthToBlockDistance(ndcU, ndcV, dhInvProjJoml, dhInvMvmJoml, dhDepth);
//// sample the 9 surrounding pixels to account for off-by-one errors between the MC and DH depth textures
//int sampleIndex = 0;
//for (int relU = -1; relU < 1; relU++)
//{
// for (int relV = -1; relV < 1; relV++)
// {
// invertedV = height - 1 - v + relV;
// i = (invertedV * width) + u + relU;
// dhDepth = this.dhDepthTextureValues[i];
//
// if (v + relV < 0 || v + relV > height
// || u + relU < 0 || u + relU > width)
// {
// sampledDistances[sampleIndex] = 0.0f;
// sampleIndex++;
// }
//
// // calculate this point's distance from the camera
// float ndcU = u / (float)width;
// float ndcV = invertedV / (float)height;
// float dist = convertDepthToBlockDistance(ndcU, ndcV, dhInvProjJoml, dhInvMvmJoml, dhDepth);
//
// sampledDistances[sampleIndex] = dist;
// sampleIndex++;
// }
//}
//
//
//Arrays.sort(sampledDistances);
//// return the median element
//float dist = sampledDistances[sampledDistances.length / 2];
//if (dist == 0.0f)
//{
// // the median was 0, return the smallest non-zero element
// for (sampleIndex = 0; sampleIndex < sampledDistances.length; sampleIndex++)
// {
// if (sampledDistances[sampleIndex] != 0.0f)
// {
// dist = sampledDistances[sampleIndex];
// break;
// }
// }
//}
if (dist < closestDhDistance)
{
closestDhDepth = dhDepth;
closestDhDistance = dist;
closeUIndex = u;
closeVIndex = invertedV;
}
}
}
if (this.lastClosestDhDepth != closestDhDepth)
{
NumberFormat numForm = NumberFormat.getNumberInstance();
ClientApi.INSTANCE.showChatMessageNextFrame(
"closest: ["+numForm.format(closestDhDepth)+"]-b["+numForm.format(closestDhDistance)+"]c["+numForm.format(closestDhDistance/16)+"] ("+(closeUIndex)+","+(closeVIndex)+")" +
"");
this.actualMcBlockDistance = closestDhDistance;
}
this.lastClosestDhDepth = closestDhDepth;
if (this.pause)
{
// find the range of depth values used by both textures for clearer exporting
float closestMcDepth = 1.0f; float furthestMcDepth = 0.0f;
float minDhDepth = 1.0f; float maxDhDepth = 0.0f;
for (int i = 0; i < elementCount; i++)
{
float mcDepth = this.mcOpaqueDepthTextureValues[i];
if (mcDepth != 0.0f && mcDepth != 1.0f)
{
if (mcDepth < closestMcDepth)
{
closestMcDepth = mcDepth;
}
if (mcDepth > furthestMcDepth)
{
furthestMcDepth = mcDepth;
}
}
float dhDepth = this.dhDepthTextureValues[i];
if (dhDepth != 0.0f && dhDepth != 1.0f)
{
if (dhDepth < minDhDepth)
{
minDhDepth = dhDepth;
}
if (dhDepth > maxDhDepth)
{
maxDhDepth = dhDepth;
}
}
}
String exportPath = Config.Client.dynamicFadeExportPath.get();
ClientApi.INSTANCE.showChatMessageNextFrame("exporting debug textures to: ["+exportPath+"]...");
// mc opaque
createImg(
this.mcOpaqueDepthTextureValues,
closeUIndex, closeVIndex,
(Float depth) -> { return null; },
width, height, closestMcDepth, furthestMcDepth,
exportPath+"mc-opaque_depth.png");
// mc transparent
createImg(
this.mcTransparentDepthTextureValues,
closeUIndex, closeVIndex,
(Float depth) -> { return null; },
width, height, closestMcDepth, furthestMcDepth,
exportPath+"mc-tran_depth.png");
// dh
Function<Float, Color> customColorFunc = (Float depth) ->
{
if (depth == this.lastClosestDhDepth)
{
return Color.RED;
}
//else if (depth <= (lastClosestDhDepth + 0.01f))
//{
// return Color.ORANGE;
//}
return null;
};
createImg(
this.dhDepthTextureValues,
closeUIndex, closeVIndex,
customColorFunc,
width, height, minDhDepth, maxDhDepth,
exportPath+"dh_depth.png");
// temp
createImg(
this.outDepthTextureValues,
closeUIndex, closeVIndex,
(Float depth) -> { return null; },
width, height, minDhDepth, maxDhDepth,
exportPath+"temp_depth.png");
int breakPoint = 0;
}
}
/** NDC (Normalized Device Coordinates) must be between 0.0 and 1.0 (inclusive) */
private static float convertDepthToBlockDistance(float ndcU, float ndcV, Matrix4f invProj, Matrix4f invMvm, float depth)
{
// This assumes depth is scaled to [0, 1]
// Transform depth to clip space Z value
float z = depth * 2.0f - 1.0f;
// Create a vector in clip space
Vector4f clipSpacePosition = new Vector4f(ndcU, ndcV, z, 1.0f);
// Transform to world space
Vector4f worldSpacePosition = clipSpacePosition.mul(invProj);
worldSpacePosition.div(worldSpacePosition.w); // Perform perspective divide
// Finally apply the inverse model-view matrix to get world space coordinates
worldSpacePosition = worldSpacePosition.mul(invMvm);
// calculate distance from the camera
float distance = worldSpacePosition.distance(0, 0, 0, 0);
return distance;
}
private static void createImg(
float[] tex,
int nearestU, int nearestV,
Function<Float, Color> customColorFunc,
int width, int height,
float minDepth, float maxDepth,
String filePath)
{
// Create a BufferedImage
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int u = 0; u < width; u++)
{
for (int v = 0; v < height; v++)
{
int invertedY = height - 1 - v;
// Normalize the depth value to a grayscale pixel
float depthValue = tex[(invertedY * width) + u];
Color color = customColorFunc.apply(depthValue);
if (color == null)
{
float normalizedDepth = (depthValue - minDepth) / (maxDepth - minDepth); // Normalize to 0.0 to 1.0
normalizedDepth = Math.max(0.0f, Math.min(1.0f, normalizedDepth)); // Clamp to valid range
int gray = (int) (normalizedDepth * 255); // Map to 0-255
gray = Math.max(0, Math.min(255, gray)); // Clamp to valid range
color = new Color(gray, gray, gray);
}
if (depthValue == 1.0f)
{
color = new Color(0,0,0,0);
}
if (u == nearestU)
{
color = Color.MAGENTA;
}
else if (invertedY == nearestV)
{
color = Color.ORANGE;
}
image.setRGB(u, v, color.getRGB());
}
}
// Write the image to a file
try
{
ImageIO.write(image, "png", new File(filePath));
}
catch (IOException e)
{
LOGGER.error("Unable to write texture to file, error: ["+e.getMessage()+"].", e);
}
}
}
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShader
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.shader.Shader;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
@@ -37,6 +38,7 @@ import com.seibel.distanthorizons.core.util.math.Vec3f;
/**
* Handles rendering the normal LOD terrain.
* @see LodQuadBuilder
*/
public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShaderProgram
{
@@ -46,16 +48,14 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
public int uCombinedMatrix = -1;
public int uModelOffset = -1;
public int uWorldYOffset = -1;
public int uDitherDhRendering = -1;
public int uMircoOffset = -1;
public int uEarthRadius = -1;
public int uLightMap = -1;
// Fog/Clip Uniforms
// fragment shader uniforms
public int uClipDistance = -1;
public int uDitherDhRendering = -1;
// Noise Uniforms
public int uNoiseEnabled = -1;
@@ -76,19 +76,16 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
public DhTerrainShaderProgram()
{
super(
() -> Shader.loadFile(Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get() != 0
? "shaders/curve.vert"
: "shaders/standard.vert",
false, new StringBuilder()).toString(),
() -> Shader.loadFile("shaders/flat_shaded.frag", false, new StringBuilder()).toString(),
"fragColor", new String[]{"vPosition", "color"});
() -> Shader.loadFile("shaders/standard.vert", false, new StringBuilder()).toString(),
() -> Shader.loadFile("shaders/flat_shaded.frag", false, new StringBuilder()).toString(),
"fragColor", new String[]{"vPosition", "color"});
this.uCombinedMatrix = this.getUniformLocation("uCombinedMatrix");
this.uModelOffset = this.getUniformLocation("uModelOffset");
this.uWorldYOffset = this.tryGetUniformLocation("uWorldYOffset");
this.uDitherDhRendering = this.tryGetUniformLocation("uDitherDhRendering");
this.uWorldYOffset = this.getUniformLocation("uWorldYOffset");
this.uDitherDhRendering = this.getUniformLocation("uDitherDhRendering");
this.uMircoOffset = this.getUniformLocation("uMircoOffset");
this.uEarthRadius = this.tryGetUniformLocation("uEarthRadius");
this.uEarthRadius = this.getUniformLocation("uEarthRadius");
this.uLightMap = this.getUniformLocation("uLightMap");
@@ -117,10 +114,13 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
}
this.vao.bind();
// TODO comment what each attribute represents
this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true)); // 2+2+2+2 // TODO probably color, blockpos
this.vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false)); // +4 // TODO ?
this.vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true)); // +4 // TODO probably normal index and Iris block ID
// short: x, y, z, meta
// meta: byte skylight, byte blocklight, byte microOffset
this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true));
// byte: r, g, b, a
this.vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false));
// byte: iris material ID, normal index, 2 spacers
this.vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true));
try
{
@@ -178,12 +178,21 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
// setUniform(skyLightUniform, skyLight);
this.setUniform(this.uLightMap, 0); // TODO this should probably be passed in
if (this.uWorldYOffset != -1) this.setUniform(this.uWorldYOffset, (float) renderParameters.worldYOffset);
this.setUniform(this.uWorldYOffset, (float) renderParameters.worldYOffset);
if (this.uDitherDhRendering != -1) this.setUniform(this.uDitherDhRendering, Config.Client.Advanced.Graphics.Quality.ditherDhFade.get());
this.setUniform(this.uDitherDhRendering, Config.Client.Advanced.Graphics.Quality.ditherDhFade.get());
if (this.uEarthRadius != -1) this.setUniform(this.uEarthRadius,
/*6371KM*/ 6371000.0f / Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get());
float curveRatio = Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get();
if (curveRatio < -1.0f || curveRatio > 1.0f)
{
curveRatio = /*6371KM*/ 6371000.0f / curveRatio;
}
else
{
// disable curvature if the config value is between -1 and 1
curveRatio = 0.0f;
}
this.setUniform(this.uEarthRadius, curveRatio);
// Noise Uniforms
this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get());
@@ -195,17 +204,17 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
this.setUniform(this.uIsWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
// Clip Uniform
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks);
if (!Config.Client.Advanced.Debugging.lodOnlyMode.get())
{
// this added value prevents the near clip plane and discard circle from touching, which looks bad
dhNearClipDistance += 16f;
}
float dhNearClipDistance = 0.1f;//RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks);
//if (!Config.Client.Advanced.Debugging.lodOnlyMode.get())
//{
// // this added value prevents the near clip plane and discard circle from touching, which looks bad
// dhNearClipDistance += 16f;
//}
// if the player is very high up and the near clip plane has been modified, disable the distance clipping
// we're high enough that nothing will render on top of the player and this can cause issues otherwise
if (RenderUtil.getHeightBasedNearClipOverride() != -1)
{
dhNearClipDistance = 1.0f;
dhNearClipDistance = 1.0f; // TODO does this actually disable anything?
}
this.setUniform(this.uClipDistance, dhNearClipDistance);
}
@@ -222,6 +222,8 @@ public class LodRenderer
profiler.popPush("LOD Opaque");
this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ true);
DepthCalculator.INSTANCE.trySetDhDepthTexture();
// custom objects with SSAO
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
{
@@ -434,7 +436,7 @@ public class LodRenderer
// resize the textures if needed
if (MC_RENDER.getTargetFramebufferViewportWidth() != this.textureWidth
|| MC_RENDER.getTargetFramebufferViewportHeight() != this.textureHeight)
|| MC_RENDER.getTargetFramebufferViewportHeight() != this.textureHeight)
{
// just resizing the textures doesn't work when Optifine is present,
// so recreate the textures with the new size instead
@@ -536,7 +538,7 @@ public class LodRenderer
return true;
}
@SuppressWarnings( "deprecation" )
@SuppressWarnings( "deprecation" ) // done to ignore DhApiColorDepthTextureCreatedEvent
private void createAndBindTextures()
{
int oldWidth = this.textureWidth;
@@ -30,6 +30,7 @@ public class RenderParams extends DhApiRenderParam
public IDhClientWorld dhClientWorld;
public IDhClientLevel dhClientLevel;
/** more specific override of the API value {@link DhApiRenderParam#clientLevelWrapper} */
public IClientLevelWrapper clientLevelWrapper;
public ILightMapWrapper lightmap;
public RenderBufferHandler renderBufferHandler;
@@ -56,7 +57,8 @@ public class RenderParams extends DhApiRenderParam
RenderUtil.getNearClipPlaneDistanceInBlocks(newPartialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(),
newMcProjectionMatrix, newMcModelViewMatrix,
RenderUtil.createLodProjectionMatrix(newMcProjectionMatrix, newPartialTicks), RenderUtil.createLodModelViewMatrix(newMcModelViewMatrix),
clientLevelWrapper.getMinHeight());
clientLevelWrapper.getMinHeight(),
clientLevelWrapper);
this.dhClientWorld = SharedApi.tryGetDhClientWorld();
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShad
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -39,8 +40,7 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.*;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock;
@@ -54,6 +54,8 @@ public class BeaconRenderHandler
/** how often should we check if a beacon should be culled? */
private static final int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
private static final Comparator<BeaconBeamDTO> NEGATIVE_BLOCKPOS_COMPARATOR = new NegativeInfiniteBlockPosComparator();
private final ReentrantLock updateLock = new ReentrantLock();
@@ -89,20 +91,74 @@ public class BeaconRenderHandler
// render handling //
//=================//
public void startRenderingBeacon(BeaconBeamDTO beacon)
public void startRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList, byte detailLevel)
{
try
{
this.updateLock.lock();
if (this.beaconBlockPosSet.add(beacon.blockPos))
// how wide should each beacon be?
int beaconBlockWidth = 1;
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
{
beaconBlockWidth = DhSectionPos.getBlockWidth(detailLevel);
}
ArrayList<BeaconBeamDTO> sortedBeaconList = new ArrayList<>(beaconList);
// merge distant beams if requested
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
{
// sort beacons from neg inf -> pos inf
// so we can consistently merge adjacent beacons
sortedBeaconList.sort(NEGATIVE_BLOCKPOS_COMPARATOR);
// go through each beacon...
for (int outerIndex = 0; outerIndex < sortedBeaconList.size(); outerIndex++)
{
BeaconBeamDTO outerBeacon = sortedBeaconList.get(outerIndex);
DhBlockPos outerBlockPos = outerBeacon.blockPos;
// ...and remove any beacons that are within the block width to prevent overlaps
for (int mergeIndex = outerIndex + 1; mergeIndex < sortedBeaconList.size(); mergeIndex++)
{
BeaconBeamDTO beaconToMerge = sortedBeaconList.get(mergeIndex);
DhBlockPos mergeBlockPos = beaconToMerge.blockPos;
int xDiff = mergeBlockPos.getX() - outerBlockPos.getX();
int zDiff = mergeBlockPos.getZ() - outerBlockPos.getZ();
// merge (remove) this beacon if
// it's close to the outer beacon
if (xDiff < beaconBlockWidth
&& zDiff < beaconBlockWidth)
{
sortedBeaconList.remove(mergeIndex);
mergeIndex--; // minus 1 so we don't go past the end of the array when incrementing in the for loop up top
}
}
}
}
// add each beacon to the renderer
for (int i = 0; i < sortedBeaconList.size(); i++)
{
BeaconBeamDTO beacon = sortedBeaconList.get(i);
if (!this.beaconBlockPosSet.add(beacon.blockPos))
{
continue;
}
int maxBeaconBeamHeight = Config.Client.Advanced.Graphics.GenericRendering.beaconRenderHeight.get();
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
new DhApiVec3d(beacon.blockPos.getX() + 1, maxBeaconBeamHeight, beacon.blockPos.getZ() + 1),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
new DhApiVec3d(beacon.blockPos.getX() + beaconBlockWidth, maxBeaconBeamHeight, beacon.blockPos.getZ() + beaconBlockWidth),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
);
this.beaconBoxGroup.add(beaconBox);
@@ -116,19 +172,26 @@ public class BeaconRenderHandler
}
}
public void stopRenderingBeaconAtPos(DhBlockPos beaconPos)
public void stopRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList)
{
try
{
this.updateLock.lock();
if (this.beaconBlockPosSet.remove(beaconPos))
for (int i = 0; i < beaconList.size(); i++)
{
BeaconBeamDTO beacon = beaconList.get(i);
DhBlockPos beaconPos = beacon.blockPos;
if (!this.beaconBlockPosSet.remove(beaconPos))
{
continue;
}
Predicate<DhApiRenderableBox> removePredicate = (DhApiRenderableBox box) ->
{
return box.minPos.x == beaconPos.getX()
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.getZ();
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.getZ();
};
this.beaconBoxGroup.removeIf(removePredicate);
this.fullBeaconBoxList.removeIf(removePredicate);
@@ -255,4 +318,28 @@ public class BeaconRenderHandler
}
//================//
// helper classes //
//================//
private static class NegativeInfiniteBlockPosComparator implements Comparator<BeaconBeamDTO>
{
@Override
public int compare(BeaconBeamDTO beacon1, BeaconBeamDTO beacon2)
{
DhBlockPos blockPos1 = beacon1.blockPos;
DhBlockPos blockPos2 = beacon2.blockPos;
// sort by X, then by Z
if (blockPos1.getX() != blockPos2.getX())
{
return Integer.compare(blockPos1.getX(), blockPos2.getX());
}
return Integer.compare(blockPos1.getZ(), blockPos2.getZ());
}
}
}
@@ -493,7 +493,7 @@ public class CloudRenderHandler
private static boolean[][] getCloudsFromTexture() throws FileNotFoundException, IOException
{
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
final ClassLoader loader = CloudRenderHandler.class.getClassLoader();
boolean[][] whitePixels = null;
try(InputStream imageInputStream = loader.getResourceAsStream(CLOUD_RESOURCE_TEXTURE_PATH))
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.render.renderer.shaders;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
@@ -114,7 +115,7 @@ public class VanillaFadeShader extends AbstractShaderRenderer
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(partialTicks);
// this added value prevents the near clip plane and discard circle from touching, which looks bad
dhNearClipDistance += 16f;
//dhNearClipDistance += 16f;
// measured in blocks
// these multipliers in James' tests should provide a fairly smooth transition
@@ -173,7 +173,7 @@ public class DatabaseUpdater
/** @throws NullPointerException if any of the script files failed to be read. */
private static ArrayList<SqlScript> getAutoUpdateScripts() throws NullPointerException, IOException
{
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
final ClassLoader loader = DatabaseUpdater.class.getClassLoader();
// get the script list
@@ -76,6 +76,9 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>, INetworkObject
public void close()
{ /* no closing needed */ }
@Override
public String toString() { return this.blockPos + " " + this.color; }
}
@@ -19,8 +19,10 @@
package com.seibel.distanthorizons.core.util;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.renderer.DepthCalculator;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -127,13 +129,16 @@ public class RenderUtil
}
public static float getNearClipPlaneInBlocksForFading(float partialTicks)
{
float overdraw = getAutoOverdrawPrevention();
return getNearClipPlaneDistanceInBlocks(partialTicks, overdraw);
//float overdraw = getAutoOverdrawPrevention();
//return getNearClipPlaneDistanceInBlocks(partialTicks, overdraw);
return DepthCalculator.INSTANCE.actualMcBlockDistance;
}
private static float getNearClipPlaneDistanceInBlocks(float partialTicks, float overdrawPreventionPercent)
{
int chunkRenderDistance = MC_RENDER.getRenderDistance();
int vanillaBlockRenderedDistance = chunkRenderDistance * LodUtil.CHUNK_WIDTH;
//float chunkRenderDistance = ClientApi.actualMcBlockDistance / 16.0f;
float vanillaBlockRenderedDistance = chunkRenderDistance * LodUtil.CHUNK_WIDTH;
float nearClipPlane;
if (Config.Client.Advanced.Debugging.lodOnlyMode.get())
@@ -31,12 +31,10 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
import com.seibel.distanthorizons.core.util.gridList.MovableGridRingList;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongIterator;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongConsumer;
/**
@@ -98,9 +96,27 @@ public class QuadTree<T>
// getters and setters //
//=====================//
/** @return the value at the given section position. Null will be returned if the value is missing or the position is out of bounds. */
@Nullable
public final T tryGetValue(long pos)
{
QuadNode<T> node = this.tryGetNode(pos);
if (node != null)
{
return node.value;
}
return null;
}
/** @return the node at the given section position, null if out of bounds */
@Nullable
public final QuadNode<T> tryGetNode(long pos) { return this.getOrSetNode(pos, false, null, false); }
/** @return the node at the given section position */
@Nullable
public final QuadNode<T> getNode(long pos) throws IndexOutOfBoundsException { return this.getOrSetNode(pos, false, null, true); }
/** @return the value at the given section position */
@Nullable
public final T getValue(long pos) throws IndexOutOfBoundsException
@@ -122,16 +138,24 @@ public class QuadTree<T>
return previousValue;
}
/** @param runBoundaryChecks should only ever be set to true internally for removing out of bound nodes */
/** @param throwIfOutOfBounds if false returns null */
@Nullable
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean runBoundaryChecks) throws IndexOutOfBoundsException
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean throwIfOutOfBounds) throws IndexOutOfBoundsException
{
if (runBoundaryChecks && !this.isSectionPosInBounds(pos))
if (!this.isSectionPosInBounds(pos))
{
int radius = this.diameterInBlocks() / 2;
DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
DhBlockPos2D maxPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeLeafDetailLevel + ", max detail level: " + this.treeRootDetailLevel + ". Given Position: [" + DhSectionPos.toString(pos) + "] = block pos: " + DhSectionPos.convertToDetailLevel(pos, LodUtil.BLOCK_DETAIL_LEVEL));
// how should out-of-bounds positions be handled?
if (throwIfOutOfBounds)
{
int radius = this.diameterInBlocks() / 2;
DhBlockPos2D minBlockPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
DhBlockPos2D maxBlockPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min block pos: [" + minBlockPos + "], max block pos: [" + maxBlockPos + "], leaf detail level: [" + this.treeLeafDetailLevel + "], root detail level: [" + this.treeRootDetailLevel + "]. Requested section pos: [" + DhSectionPos.toString(pos) + "].");
}
else
{
return null;
}
}
@@ -278,46 +302,6 @@ public class QuadTree<T>
removedItemConsumer.accept(quadNode.value);
}
});
// // remove out of bound nodes and clean up empty nodes
// // Note: this will iterate over a lot of unnecessary nodes, hopefully speed won't be an issue
// Iterator<DhSectionPos> rootNodePosIterator = this.rootNodePosIterator();
// while (rootNodePosIterator.hasNext())
// {
// // get the root node (regular nodeIterators won't return them if they are out of bounds)
// DhSectionPos rootPos = rootNodePosIterator.next();
// QuadNode<T> rootNode = this.getOrSetNode(rootPos, false, null, false);
// if (rootNode == null)
// {
// continue;
// }
//
// // remove any child nodes that are out of bounds
// Iterator<QuadNode<T>> nodeIterator = this.nodeIterator();
// while (nodeIterator.hasNext())
// {
// QuadNode<T> node = nodeIterator.next();
// if(!this.isSectionPosInBounds(node.sectionPos))
// {
// // node is out of bounds
//
// // FIXME(?) this appears to potentially return large nodes that are partially or entirely in bounds
//
// if (node.getNonNullChildCount() == 0)
// {
// // no child nodes, can be safely removed
// nodeIterator.remove();
// }
// else
// {
// // node can't be removed, but its value can be set to null
// node.value = null;
// }
// }
// }
// }
}
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
@@ -544,7 +528,9 @@ public class QuadTree<T>
&& this.rootNodeIterator.hasNext())
{
long sectionPos = this.rootNodeIterator.nextLong();
QuadNode<T> rootNode = QuadTree.this.getNode(sectionPos);
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
if (rootNode != null)
{
nodeIterator = this.onlyReturnLeaves ? rootNode.getLeafNodeIterator() : rootNode.getNodeIterator(this.stopIteratingFunc);
@@ -30,7 +30,7 @@ import java.util.function.Consumer;
public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
{
/** lowest numerical value, inclusive */
private final byte highestDetailLevel;
private final byte leafDetailLevel;
private final Queue<QuadNode<T>> validNodesForDetailLevel = new ArrayDeque<>();
@@ -48,8 +48,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
{
this.onlyReturnLeafValues = onlyReturnLeafValues;
this.stopIteratingFunc = stopIteratingFunc;
// TODO the naming conversion for these are flipped in a lot of places
this.highestDetailLevel = rootNode.parentTreeLeafDetailLevel;
this.leafDetailLevel = rootNode.parentTreeLeafDetailLevel;
this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos);
@@ -110,9 +109,9 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
@Override
public QuadNode<T> next()
{
if (this.iteratorDetailLevel < this.highestDetailLevel)
if (this.iteratorDetailLevel < this.leafDetailLevel)
{
throw new NoSuchElementException("Highest detail level reached [" + this.highestDetailLevel + "].");
throw new NoSuchElementException("Leaf detail level reached [" + this.leafDetailLevel + "].");
}
if (this.iteratorNodeQueue.size() == 0)
{
@@ -133,7 +132,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
this.iteratorDetailLevel--;
// only continue if we can go down farther
if (this.iteratorDetailLevel >= this.highestDetailLevel)
if (this.iteratorDetailLevel >= this.leafDetailLevel)
{
Queue<QuadNode<T>> parentNodes = new LinkedList<>(this.validNodesForDetailLevel);
this.validNodesForDetailLevel.clear();
@@ -69,10 +69,14 @@ public class ThreadPoolUtil
@Nullable
public static ThreadPoolExecutor getBeaconCullingExecutor() { return beaconCullingThreadPool; }
// The main distinction between these thread pools is that one for compression has multiple threads and client handler is single-threaded
private static PriorityTaskPicker.Executor networkCompressionThreadPool;
@Nullable
public static PriorityTaskPicker.Executor getNetworkCompressionExecutor() { return networkCompressionThreadPool; }
private static ThreadPoolExecutor networkClientHandlerThreadPool;
@Nullable
public static ThreadPoolExecutor networkClientHandlerExecutor() { return networkClientHandlerThreadPool; }
public static final String FULL_DATA_MIGRATION_THREAD_NAME = "Full Data Migration";
@@ -103,7 +107,8 @@ public class ThreadPoolUtil
}
taskPicker = new PriorityTaskPicker();
networkCompressionThreadPool = taskPicker.createExecutor("Network");
networkCompressionThreadPool = taskPicker.createExecutor("Network Compression");
networkClientHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Network Client Handler");
fileHandlerThreadPool = taskPicker.createExecutor("IO");
renderSectionLoadThreadPool = taskPicker.createExecutor("Render Loader");
chunkToLodBuilderThreadPool = taskPicker.createExecutor("LOD Builder");
@@ -133,6 +138,7 @@ public class ThreadPoolUtil
public static void shutdownThreadPools()
{
// standalone threads
networkClientHandlerThreadPool.shutdownNow();
taskPicker.shutdownNow();
beaconCullingThreadPool.shutdown();
fullDataMigrationThreadPool.shutdown();
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -82,7 +83,7 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
LOGGER.fatal("Failed to load client-server level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
"\u00A7c" + "Distant Horizons: ClientServer level loading failed." + "\u00A7r \n" +
MinecraftTextFormat.RED + "Distant Horizons: ClientServer level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Unable to load level ["+levelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
@@ -94,9 +95,9 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
"\u00A7c" + "Distant Horizons: Client level loading failed." + "\u00A7r \n" +
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.level.DhServerLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
@@ -62,9 +63,9 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
{
LOGGER.fatal("Failed to load server level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
"\u00A7c" + "Distant Horizons: Server level loading failed." + "\u00A7r \n" +
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.RED + "Distant Horizons: Server level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
}
@@ -63,6 +63,8 @@ public interface IChunkWrapper extends IBindable
*/
int getMaxNonEmptyHeight();
void createDhHeightMaps();
/** @return The highest y position of a solid block at the given relative chunk position. */
int getSolidHeightMapValue(int xRel, int zRel);
/**
@@ -404,5 +406,8 @@ public interface IChunkWrapper extends IBindable
return beaconTintBlockFound ? new Color(red, green, blue) : Color.WHITE;
}
IChunkWrapper copy();
}
@@ -63,7 +63,9 @@ public interface IMinecraftRenderWrapper extends IBindable
/** @return -1 if no valid framebuffer is available yet */
int getTargetFramebuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances)
// Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
/** @return -1 if there was an issue or no texture exists */
int getDepthTextureId();
/** @return -1 if there was an issue or no texture exists */
int getColorTextureId();
int getTargetFramebufferViewportWidth();
int getTargetFramebufferViewportHeight();
@@ -85,8 +85,6 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
@Override
int getMinHeight();
default IChunkWrapper tryGetChunk(DhChunkPos pos) { return null; }
/** Fired when the level is being unloaded. Doesn't unload the level. */
void onUnload();
@@ -84,7 +84,13 @@
"Show The Options Button",
"distanthorizons.config.client.optionsButton.@tooltip":
"Show the config button to the left of the fov button",
"distanthorizons.config.client.dynamicFadeUseOpaqueMcDepth":
"Dynamic Fade Use MC Opaque Depth",
"distanthorizons.config.client.dynamicFadeExportPath":
"Dynamic Fade Export Path",
"distanthorizons.config.client.dynamicFadeExportPath.@tooltip":
"Press 'p' to export the depth textures for troubleshooting",
"distanthorizons.config.client.advanced":
@@ -227,6 +233,10 @@
"Beacon render height",
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip":
"Sets the maximum height at which beacons will render. \nThis will only affect new beacons coming into LOD render distance. \nBeacons currently visible in LOD chunks will not be affected.",
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons":
"Expand Distant Beacons",
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons.@tooltip":
"If true LOD beacon beams will be rendered wider at extreme distances, \nmaking them easier to see. \nIf false all LOD beacon beams will only ever be 1 block wide.",
"distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering.@tooltip":
"If true LOD beacon beams will be rendered.",
"distanthorizons.config.client.advanced.graphics.genericRendering.enableCloudRendering":
@@ -400,14 +410,13 @@
"Experimental",
"distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio":
"Earth Curve Ratio §6(EXPERIMENTAL)§r",
"Earth Curve Ratio",
"distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio.@tooltip":
"A value of 1 is equivalent to the curvature of Earth in real life. \nThe minimum accepted value is 50 and the maximum value is 5000. \nEverything between 1 and 49 will be rounded up to 50.",
"distanthorizons.config.client.advanced.graphics.experimental.onlyLoadCenterLods":
"Only load center LODs",
"distanthorizons.config.client.advanced.graphics.experimental.onlyLoadCenterLods.@tooltip":
"Skips loading adjacent LODs to significantly reduce load times (~5x)\nbut causes lighting on LOD borders to appear as full-bright\nand other graphical bugs.\n",
"distanthorizons.config.client.advanced.graphics.experimental.ignoredDimensionCsv":
"Ignored Dimension CSV",
"distanthorizons.config.client.advanced.graphics.experimental.ignoredDimensionCsv.@tooltip":
"A comma separated list of dimension resource locations where DH won't render. Example: \"minecraft:the_nether,minecraft:the_end\" \n\nNote: \nSome DH settings will be disabled and/or changed to improve \nvisuals when DH rendering is disabled.",
@@ -718,6 +727,8 @@
"OpenGL Events - Chat",
"distanthorizons.config.common.logging.logNetworkEventToFile":
"Network Events - File",
"distanthorizons.config.common.logging.logConnectionConfigChangesToFile":
"Network Connection Config Changes - File",
"distanthorizons.config.common.logging.warning":
"Warnings",
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

@@ -1,79 +0,0 @@
#version 150 core
in uvec4 vPosition;
out vec4 vPos;
in vec4 color;
out vec4 vertexColor;
out vec3 vertexWorldPos;
out float vertexYPos;
uniform bool uWhiteWorld;
uniform mat4 uCombinedMatrix;
uniform vec3 uModelOffset;
uniform float uWorldYOffset;
uniform int uWorldSkyLight;
uniform sampler2D uLightMap;
uniform float uMircoOffset;
uniform float uEarthRadius;
/**
* TODO in the future this and standard.vert should be merged together to prevent inconsistencies between the two
*
* Vertex Shader
*
* author: James Seibel
* author: TomTheFurry
* author: stduhpf
* updated: coolGi
* version: 24-1-2023
*/
void main()
{
vPos = vPosition; // This is so it can be passed to the fragment shader
vertexWorldPos = vPosition.xyz + uModelOffset;
vertexYPos = vPosition.y + uWorldYOffset;
uint meta = vPosition.a;
uint mirco = (meta & 0xFF00u) >> 8u; // mirco offset which is a xyz 2bit value
// 0b00 = no offset
// 0b01 = positive offset
// 0b11 = negative offset
// format is: 0b00zzyyxx
float mx = (mirco & 1u) != 0u ? uMircoOffset : 0.0;
mx = (mirco & 2u) != 0u ? -mx : mx;
float my = (mirco & 4u) != 0u ? uMircoOffset : 0.0;
my = (mirco & 8u) != 0u ? -my : my;
float mz = (mirco & 16u) != 0u ? uMircoOffset : 0.0;
mz = (mirco & 32u) != 0u ? -mz : mz;
vertexWorldPos.x += mx;
vertexWorldPos.y += my;
vertexWorldPos.z += mz;
// vertex transformation logic - stduhpf
float localRadius = uEarthRadius + vertexYPos;
float phi = length(vertexWorldPos.xz) / localRadius;
vertexWorldPos.y += (cos(phi) - 1.0) * localRadius;
vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi;
uint lights = meta & 0xFFu;
float light2 = (mod(float(lights), 16.0) + 0.5) / 16.0;
float light = (float(lights / 16u) + 0.5) / 16.0;
vertexColor = vec4(texture(uLightMap, vec2(light, light2)).xyz, 1.0);
if (!uWhiteWorld)
{
vertexColor *= color;
}
gl_Position = uCombinedMatrix * vec4(vertexWorldPos, 1.0);
}
+31 -17
View File
@@ -17,27 +17,28 @@ uniform float uWorldYOffset;
uniform sampler2D uLightMap;
uniform float uMircoOffset;
uniform float uEarthRadius;
/**
* TODO in the future this and curve.vert should be merged together to prevent inconsistencies between the two
*
* Vertex Shader
*
* author: James Seibel
* updated: TomTheFurry
* author: TomTheFurry
* author: stduhpf
* updated: coolGi
* version: 2023-6-25
*
* version: 2025-12-22
*/
void main()
{
vPos = vPosition; // This is so it can be passed to the fragment shader
vertexWorldPos = vPosition.xyz + uModelOffset;
vertexYPos = vPosition.y + uWorldYOffset;
uint meta = vPosition.a;
uint mirco = (meta & 0xFF00u) >> 8u; // mirco offset which is a xyz 2bit value
// 0b00 = no offset
// 0b01 = positive offset
@@ -45,21 +46,34 @@ void main()
// format is: 0b00zzyyxx
float mx = (mirco & 1u)!=0u ? uMircoOffset : 0.0;
mx = (mirco & 2u)!=0u ? -mx : mx;
float my = (mirco & 4u)!=0u ? uMircoOffset : 0.0;
my = (mirco & 8u)!=0u ? -my : my;
//float my = (mirco & 4u)!=0u ? uMircoOffset : 0.0;
//my = (mirco & 8u)!=0u ? -my : my;
float mz = (mirco & 16u)!=0u ? uMircoOffset : 0.0;
mz = (mirco & 32u)!=0u ? -mz : mz;
vertexWorldPos.x += mx;
//vertexWorldPos.y += my;
vertexWorldPos.z += mz;
// apply the earth curvature if needed
if (uEarthRadius < -1.0f || uEarthRadius > 1.0f)
{
// vertex transformation logic - stduhpf
float localRadius = uEarthRadius + vertexYPos;
float phi = length(vertexWorldPos.xz) / localRadius;
vertexWorldPos.y += (cos(phi) - 1.0) * localRadius;
vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi;
}
uint lights = meta & 0xFFu;
float light2 = (mod(float(lights), 16.0)+0.5) / 16.0;
float light = (float(lights/16u)+0.5) / 16.0;
vertexColor = vec4(texture(uLightMap, vec2(light, light2)).xyz, 1.0);
float skyLight = (float(lights/16u)+0.5) / 16.0;
float blockLight = (mod(float(lights), 16.0)+0.5) / 16.0;
vertexColor = vec4(texture(uLightMap, vec2(skyLight, blockLight)).xyz, 1.0);
if (!uIsWhiteWorld)
{
vertexColor *= color;
}
gl_Position = uCombinedMatrix * vec4(vertexWorldPos + vec3(mx, 0, mz), 1.0);
gl_Position = uCombinedMatrix * vec4(vertexWorldPos, 1.0);
}