Compare commits

..

39 Commits

Author SHA1 Message Date
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
52 changed files with 993 additions and 839 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.5-b";
/** 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); }
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.enums.EMinecraftColor;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
@@ -45,7 +46,6 @@ 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;
@@ -56,8 +56,6 @@ import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.io.File;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
@@ -95,7 +93,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 +153,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(EMinecraftColor.ORANGE + "Distant Horizons: Replay detected." + EMinecraftColor.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(EMinecraftColor.GRAY +".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+EMinecraftColor.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
MC_CLIENT.sendChatMessage("");
}
@@ -245,7 +242,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())
@@ -511,10 +508,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(EMinecraftColor.DARK_RED + "" + EMinecraftColor.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + EMinecraftColor.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(EMinecraftColor.DARK_RED + "Renderer disabled to try preventing GL state corruption." + EMinecraftColor.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(EMinecraftColor.DARK_RED + "Toggle DH rendering via the config UI to re-activate DH rendering." + EMinecraftColor.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(EMinecraftColor.DARK_RED + "Error: " + EMinecraftColor.CLEAR_FORMATTING + e);
}
@@ -659,8 +656,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" +
EMinecraftColor.DARK_GREEN + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." +EMinecraftColor.CLEAR_FORMATTING + "\n" +
"Issues may occur with this version.\n" +
"Here be dragons!\n";
MC_CLIENT.sendChatMessage(message);
@@ -684,7 +680,7 @@ public class ClientApi
{
String message =
// orange text
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" +
EMinecraftColor.ORANGE + "Distant Horizons: Low memory detected." + EMinecraftColor.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 +702,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";
EMinecraftColor.YELLOW + "Distant Horizons: High vanilla render distance detected." + EMinecraftColor.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" +
"Lowing your vanilla render distance will give you better FPS\n" +
"and reduce stuttering at a similar visual quality.\n" +
EMinecraftColor.GRAY + "A vanilla render distance of 8 is recommended." + EMinecraftColor.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,24 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.google.common.cache.Cache;
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.EMinecraftColor;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
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 +29,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 +82,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 +111,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 +120,7 @@ public class ChunkUpdateQueueManager
{
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
String message = "\u00A76" + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + "\u00A7r" +
String message = EMinecraftColor.ORANGE + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + EMinecraftColor.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 +141,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();
}
//=========//
@@ -505,11 +505,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 +566,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 +833,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 +843,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 +1411,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 +1592,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 +1862,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
@@ -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 +1,47 @@
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
*
* <br><br>
* source: https://minecraft.wiki/w/Formatting_codes
*/
public class EMinecraftColor
public enum EMinecraftColor // TODO EMinecraftTextFormat
{
BLACK("\u00A70"),
DARK_BLUE("\u00A71"),
DARK_GREEN("\u00A72"),
DARK_AQUA("\u00A73"),
DARK_RED("\u00A74"),
DARK_PURPLE("\u00A75"),
ORANGE("\u00A76"),
GRAY("\u00A77"),
DARK_GRAY("\u00A78"),
BLUE("\u00A79"),
GREEN("\u00A7a"),
AQUA("\u00A7b"),
RED("\u00A7c"),
LIGHT_PURPLE("\u00A7d"),
YELLOW("\u00A7e"),
WHITE("\u00A7f"),
OBFUSCATED("\u00A7k"),
BOLD("\u00A7l"),
STRIKETHROUGH("\u00A7m"),
UNDERLINE("\u00A7n"),
ITALIC("\u00A7o"),
CLEAR_FORMATTING("\u00A7r");
public final String colorCode;
EMinecraftColor(String colorCode)
{
this.colorCode = colorCode;
}
@Override
public String toString() { return this.colorCode; }
}
@@ -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;
@@ -117,35 +118,45 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
private void onWorldGenTaskComplete(DataSourceRetrievalResult genTaskResult, 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);
return;
}
if (genTaskResult.state == ERetrievalResultState.FAIL)
{
LodUtil.assertTrue(genTaskResult.dataSource == null, "Errored retrieval object should not have a datasource.");
// don't log shutdown exceptions
if (!ExceptionUtil.isInterruptOrReject(exception))
{
LOGGER.error("Uncaught Gen Task Exception at [" + genTaskResult.pos + "], error: [" + exception.getMessage() + "].", exception);
}
}
else 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 +279,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
@@ -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())
.thenAccept((DataSourceRetrievalResult result) ->
{
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
}
this.pendingGenerations.invalidate(result.pos);
});
if (result.state == ERetrievalResultState.FAIL)
{
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;
}
@@ -316,22 +316,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 +349,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
});
generationFuture.handle((fullDataSourceV2, exception) ->
generationFuture.handle((FullDataSourceV2 fullDataSource, Throwable exception) ->
{
try
{
@@ -372,13 +361,16 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
}
LodUtil.assertTrue(fullDataSource == null);
worldGenTask.future.complete(DataSourceRetrievalResult.CreateFail());
}
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));
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));
}
}
catch (Exception e)
{
@@ -692,9 +684,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,14 @@ 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 CreateFail() { return new DataSourceRetrievalResult(ERetrievalResultState.FAIL, 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,15 @@
package com.seibel.distanthorizons.core.generation.tasks;
/**
* SUCCESS <br>
* REQUIRES_SPLITTING <br>
* FAIL <br>
*
* @see DataSourceRetrievalResult
*/
public enum ERetrievalResultState
{
SUCCESS,
REQUIRES_SPLITTING,
FAIL,
}
@@ -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.EMinecraftColor;
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 += EMinecraftColor.DARK_RED;
}
else if (logLevel == Level.WARN)
{
prefix += "\u00A76";
prefix += EMinecraftColor.ORANGE;
}
else if (logLevel == Level.INFO)
{
prefix += "\u00A7f";
prefix += EMinecraftColor.AQUA;
}
else if (logLevel == Level.DEBUG)
{
prefix += "\u00A77";
prefix += EMinecraftColor.GREEN;
}
else if (logLevel == Level.TRACE)
{
prefix += "\u00A78";
prefix += EMinecraftColor.DARK_GRAY;
}
else
{
prefix += "\u00A7f";
prefix += EMinecraftColor.WHITE;
}
prefix += "\u00A7l\u00A7u";
prefix += EMinecraftColor.BOLD + "" + EMinecraftColor.WHITE;
prefix += logLevel.name();
prefix += ":\u00A7r ";
prefix += EMinecraftColor.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;
@@ -71,16 +72,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 +97,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 +107,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,28 +119,29 @@ 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;
}
});
@@ -273,12 +255,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 +269,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.complete(DataSourceRetrievalResult.CreateFail());
}
catch (RateLimitedException e)
{
@@ -316,7 +298,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
}
else
{
requestTask.future.complete(NetRequestResult.CreateFail());
requestTask.future.complete(DataSourceRetrievalResult.CreateFail());
}
}
}
@@ -457,7 +439,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;
@@ -18,6 +18,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 +30,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 +49,7 @@ public class ClientNetworkState implements Closeable
*/
public NetworkSession getSession() { return this.networkSession; }
@NotNull
public SessionConfig sessionConfig = new SessionConfig();
private volatile boolean configReceived = false;
@@ -129,8 +135,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)
@@ -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.EMinecraftColor;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
@@ -159,6 +160,7 @@ public class PhantomArrayListPool
{
// pool is empty, create new checkout
checkout = new PhantomArrayListCheckout(this);
checkout.onCheckout();
}
else
{
@@ -166,6 +168,7 @@ public class PhantomArrayListPool
if (checkout != null)
{
// use pooled checkout
checkout.onCheckout();
}
else
{
@@ -176,8 +179,7 @@ public class PhantomArrayListPool
{
lowMemoryWarningLogged = true;
// orange text
String message = "\u00A76" + "Distant Horizons: Insufficient memory detected." + "\u00A7r \n" +
String message = EMinecraftColor.ORANGE + "Distant Horizons: Insufficient memory detected." + EMinecraftColor.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,7 @@ 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();
private final ReentrantLock treeLock = new ReentrantLock();
private ArrayList<LodRenderSection> debugRenderSections = new ArrayList<>();
private ArrayList<LodRenderSection> altDebugRenderSections = new ArrayList<>();
@@ -88,7 +91,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 +110,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 +138,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
@@ -158,7 +167,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// don't traverse the tree if it is being modified
if (this.treeReadWriteLock.tryLock())
if (this.treeLock.tryLock())
{
// this shouldn't be updated while the tree is being iterated through
this.updateDetailLevelVariables();
@@ -186,7 +195,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
finally
{
this.treeReadWriteLock.unlock();
this.treeLock.unlock();
}
}
}
@@ -231,18 +240,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 +485,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);
}
}
@@ -523,6 +558,177 @@ 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 the task failed re-queue so we can try again
if (result.state == ERetrievalResultState.FAIL)
{
this.missingGenerationPosSet.add(missingPos);
}
else 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 +803,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 +826,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
finally
{
this.treeReadWriteLock.unlock();
this.treeLock.unlock();
}
}
@@ -652,107 +858,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 +923,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 +949,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
finally
{
this.treeReadWriteLock.unlock();
this.treeLock.unlock();
}
});
@@ -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();
@@ -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());
@@ -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();
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.EMinecraftColor;
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" +
EMinecraftColor.RED + "Distant Horizons: ClientServer level loading failed." + EMinecraftColor.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.EMinecraftColor;
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(
EMinecraftColor.RED + "Distant Horizons: Client level loading failed." + EMinecraftColor.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.EMinecraftColor;
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(
EMinecraftColor.RED + "Distant Horizons: Server level loading failed." + EMinecraftColor.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();
}
@@ -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();
@@ -400,14 +400,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 +717,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",
@@ -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);
}