Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a4bfef7a6 | |||
| 7be65a2258 | |||
| 1a540cf2bc | |||
| 20fc2efb46 | |||
| d8beba2498 | |||
| 9f0cb5a394 | |||
| df63401d11 | |||
| db95951ade | |||
| 1e020f93a6 | |||
| 7aee6dfb44 | |||
| 546a51a295 | |||
| ec7e791e9f | |||
| d60dec3d82 | |||
| 89a80103f0 | |||
| 8e14a7223c | |||
| 7cf1e901f5 | |||
| ba923fa829 | |||
| 505dbe2f62 | |||
| 48c5828e8f | |||
| eb2317934f | |||
| 60537cda1b | |||
| 508ff2b776 | |||
| 7c4ac2bd7e | |||
| 8c13c2cf47 | |||
| 802019ff72 | |||
| 141890556c | |||
| 353838db41 | |||
| f1547477c9 | |||
| 535a645a84 | |||
| 2dc7f02b32 | |||
| 50bdb73a52 | |||
| 53e6c95432 | |||
| 36f0029e45 | |||
| 5067e970a2 | |||
| 167ca94e69 | |||
| 8d94b86bfd | |||
| a29567430e | |||
| fb2dae48e2 | |||
| 948b4bfd9c | |||
| ca44256ca9 | |||
| a29b6a5aab | |||
| 868254ccc8 | |||
| 195fde8d73 | |||
| ce7b9b94b6 | |||
| 1f0c2e286a | |||
| f79fd5e06f | |||
| 47c1d3955f | |||
| 2c5f5a3d4c | |||
| 81c533051e | |||
| 5cbe5ecfd8 | |||
| d4b4d28c9f | |||
| b8e653b5f7 | |||
| 80fea09598 | |||
| 1d4f914a9f | |||
| bf92dea2eb | |||
| 1c30213aca | |||
| e9a044308f | |||
| 1aabc0c792 |
+20
-8
@@ -20,6 +20,8 @@
|
||||
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
|
||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldLoadEvent;
|
||||
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
||||
|
||||
@@ -27,7 +29,7 @@ import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
||||
* Contains information relevant to Distant Horizons and Minecraft rendering.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2024-1-31
|
||||
* @version 2025-12-23
|
||||
* @since API 1.0.0
|
||||
*/
|
||||
public class DhApiRenderParam implements IDhApiEventParam
|
||||
@@ -61,6 +63,13 @@ public class DhApiRenderParam implements IDhApiEventParam
|
||||
|
||||
public final int worldYOffset;
|
||||
|
||||
/**
|
||||
* The level currently being rendered.
|
||||
*
|
||||
* @since API 5.1.0
|
||||
*/
|
||||
public final IDhApiLevelWrapper clientLevelWrapper;
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
@@ -70,12 +79,13 @@ public class DhApiRenderParam implements IDhApiEventParam
|
||||
public DhApiRenderParam(DhApiRenderParam parent)
|
||||
{
|
||||
this(
|
||||
parent.renderPass,
|
||||
parent.partialTicks,
|
||||
parent.nearClipPlane, parent.farClipPlane,
|
||||
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
|
||||
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
|
||||
parent.worldYOffset
|
||||
parent.renderPass,
|
||||
parent.partialTicks,
|
||||
parent.nearClipPlane, parent.farClipPlane,
|
||||
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
|
||||
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
|
||||
parent.worldYOffset,
|
||||
parent.clientLevelWrapper
|
||||
);
|
||||
}
|
||||
public DhApiRenderParam(
|
||||
@@ -84,7 +94,8 @@ public class DhApiRenderParam implements IDhApiEventParam
|
||||
float nearClipPlane, float farClipPlane,
|
||||
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
|
||||
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
|
||||
int worldYOffset
|
||||
int worldYOffset,
|
||||
IDhApiLevelWrapper clientLevelWrapper
|
||||
)
|
||||
{
|
||||
this.renderPass = renderPass;
|
||||
@@ -101,6 +112,7 @@ public class DhApiRenderParam implements IDhApiEventParam
|
||||
this.dhModelViewMatrix = newDhModelViewMatrix;
|
||||
|
||||
this.worldYOffset = worldYOffset;
|
||||
this.clientLevelWrapper = clientLevelWrapper;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -38,14 +38,14 @@ public final class ModInfo
|
||||
public static final String NAME = "DistantHorizons";
|
||||
/** Human-readable version of NAME */
|
||||
public static final String READABLE_NAME = "Distant Horizons";
|
||||
public static final String VERSION = "2.4.3-b-dev";
|
||||
public static final String VERSION = "2.4.6-b-dev";
|
||||
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
||||
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
||||
|
||||
/** This version should only be updated when breaking changes are introduced to the DH API */
|
||||
public static final int API_MAJOR_VERSION = 5;
|
||||
/** This version should be updated whenever new methods are added to the DH API */
|
||||
public static final int API_MINOR_VERSION = 0;
|
||||
public static final int API_MINOR_VERSION = 1;
|
||||
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
||||
public static final int API_PATCH_VERSION = 0;
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
package com.seibel.distanthorizons.core;
|
||||
|
||||
import com.github.luben.zstd.ZstdOutputStream;
|
||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
|
||||
@@ -171,6 +173,8 @@ public class Initializer
|
||||
}
|
||||
}
|
||||
|
||||
DhApi.events.bind(DhApiBeforeRenderEvent.class, IgnoredDimensionCsvHandler.INSTANCE);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -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.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
|
||||
@@ -32,6 +33,8 @@ import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
||||
import com.seibel.distanthorizons.core.render.renderer.*;
|
||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
@@ -45,7 +48,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,10 +58,10 @@ 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;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* This holds the methods that should be called
|
||||
@@ -95,7 +97,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 +157,10 @@ public class ClientApi
|
||||
|
||||
if (Config.Common.Logging.Warning.showReplayWarningOnStartup.get())
|
||||
{
|
||||
MC_CLIENT.sendChatMessage("\u00A76" + "Distant Horizons: Replay detected." + "\u00A7r"); // gold color
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.ORANGE + "Distant Horizons: Replay detected." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage("DH may behave strangely or have missing functionality.");
|
||||
MC_CLIENT.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:");
|
||||
MC_CLIENT.sendChatMessage("\u00A77"+".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+"\u00A7r"); // light gray color
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.GRAY +".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+ MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
|
||||
MC_CLIENT.sendChatMessage("");
|
||||
}
|
||||
@@ -245,7 +246,7 @@ public class ClientApi
|
||||
}
|
||||
}
|
||||
|
||||
public void clientLevelLoadEvent(IClientLevelWrapper levelWrapper)
|
||||
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
|
||||
{
|
||||
// wait a moment before loading the level to give the server a chance to handle the client's login request
|
||||
if (MC_CLIENT.clientConnectedToDedicatedServer())
|
||||
@@ -332,10 +333,27 @@ public class ClientApi
|
||||
*/
|
||||
public void pluginMessageReceived(@NotNull AbstractNetworkMessage message)
|
||||
{
|
||||
NetworkSession networkSession = this.pluginChannelApi.networkSession;
|
||||
if (networkSession != null)
|
||||
@Nullable ThreadPoolExecutor executor = ThreadPoolUtil.networkClientHandlerExecutor();
|
||||
if (executor == null)
|
||||
{
|
||||
networkSession.tryHandleMessage(message);
|
||||
LOGGER.warn("warn");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
executor.execute(() ->
|
||||
{
|
||||
NetworkSession networkSession = this.pluginChannelApi.networkSession;
|
||||
if (networkSession != null)
|
||||
{
|
||||
networkSession.tryHandleMessage(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (RejectedExecutionException e)
|
||||
{
|
||||
LOGGER.warn("Plugin message executor rejected");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,10 +529,10 @@ public class ClientApi
|
||||
this.rendererDisabledBecauseOfExceptions = true;
|
||||
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
|
||||
|
||||
MC_CLIENT.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
|
||||
MC_CLIENT.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption.");
|
||||
MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
|
||||
MC_CLIENT.sendChatMessage("\u00A74Error: " + e);
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "" + MinecraftTextFormat.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Renderer disabled to try preventing GL state corruption." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Toggle DH rendering via the config UI to re-activate DH rendering." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
|
||||
}
|
||||
|
||||
|
||||
@@ -659,8 +677,7 @@ public class ClientApi
|
||||
|
||||
// remind the user that this is a development build
|
||||
String message =
|
||||
// green text
|
||||
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" +
|
||||
MinecraftTextFormat.DARK_GREEN + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Issues may occur with this version.\n" +
|
||||
"Here be dragons!\n";
|
||||
MC_CLIENT.sendChatMessage(message);
|
||||
@@ -684,7 +701,7 @@ public class ClientApi
|
||||
{
|
||||
String message =
|
||||
// orange text
|
||||
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" +
|
||||
MinecraftTextFormat.ORANGE + "Distant Horizons: Low memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Stuttering or low FPS may occur. \n" +
|
||||
"Please increase Minecraft's available memory to 4 GB or more. \n" +
|
||||
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
|
||||
@@ -706,15 +723,13 @@ public class ClientApi
|
||||
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
|
||||
|
||||
String message =
|
||||
// yellow text
|
||||
"\u00A7e" + "Distant Horizons: High vanilla render distance detected." + "\u00A7r \n" +
|
||||
"Using a high vanilla render distance uses a lot of CPU power \n" +
|
||||
"and doesn't improve graphics much after about 12.\n" +
|
||||
"Lowing your vanilla render distance will give you better FPS\n" +
|
||||
"and reduce stuttering at a similar visual quality.\n" +
|
||||
// gray text
|
||||
"\u00A77" + "A vanilla render distance of 8 is recommended." + "\u00A7r \n" +
|
||||
"This message can be disabled in DH's config under Advanced -> Logging.\n";
|
||||
MinecraftTextFormat.YELLOW + "Distant Horizons: High vanilla render distance detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Using a high vanilla render distance uses a lot of CPU power \n" +
|
||||
"and doesn't improve graphics much after about 12.\n" +
|
||||
"Lowering your vanilla render distance will give you better FPS\n" +
|
||||
"and reduce stuttering at a similar visual quality.\n" +
|
||||
MinecraftTextFormat.GRAY + "A vanilla render distance of 8 is recommended." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"This message can be disabled in DH's config under Advanced -> Logging.\n";
|
||||
MC_CLIENT.sendChatMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,15 +82,15 @@ public class ServerApi
|
||||
// level events //
|
||||
//==============//
|
||||
|
||||
public void serverLevelLoadEvent(IServerLevelWrapper level)
|
||||
public void serverLevelLoadEvent(IServerLevelWrapper levelWrapper)
|
||||
{
|
||||
LOGGER.debug("Server Level " + level + " loading");
|
||||
LOGGER.debug("Server Level " + levelWrapper + " loading");
|
||||
|
||||
AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld();
|
||||
if (serverWorld != null)
|
||||
{
|
||||
serverWorld.getOrLoadLevel(level);
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
|
||||
serverWorld.getOrLoadLevel(levelWrapper);
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
|
||||
}
|
||||
}
|
||||
public void serverLevelUnloadEvent(IServerLevelWrapper level)
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.Initializer;
|
||||
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData;
|
||||
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||
@@ -184,12 +185,11 @@ public class SharedApi
|
||||
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, false); }
|
||||
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, true); }
|
||||
|
||||
//public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks) { this.applyChunkUpdate(chunkWrapper, level, canGetNeighboringChunks, false); }
|
||||
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded)
|
||||
{
|
||||
//========================//
|
||||
// world and level checks //
|
||||
//========================//
|
||||
//===================//
|
||||
// validation checks //
|
||||
//===================//
|
||||
|
||||
if (chunkWrapper == null)
|
||||
{
|
||||
@@ -217,7 +217,6 @@ public class SharedApi
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// only continue if the level is loaded
|
||||
IDhLevel dhLevel = dhWorld.getLevel(level);
|
||||
if (dhLevel == null)
|
||||
@@ -232,6 +231,7 @@ public class SharedApi
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore chunk updates if the network should handle them
|
||||
if (dhLevel instanceof DhClientLevel)
|
||||
{
|
||||
if (!((DhClientLevel) dhLevel).shouldProcessChunkUpdate(chunkWrapper.getChunkPos()))
|
||||
@@ -240,7 +240,14 @@ public class SharedApi
|
||||
}
|
||||
}
|
||||
|
||||
// shoudln't normally happen, but just in case
|
||||
// ignore chunk updates for non-rendered levels
|
||||
String dimName = dhLevel.getLevelWrapper().getDimensionName();
|
||||
if (IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// shouldn't normally happen, but just in case
|
||||
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
|
||||
{
|
||||
// TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded
|
||||
@@ -248,94 +255,20 @@ public class SharedApi
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===============================//
|
||||
// update the necessary chunk(s) //
|
||||
//===============================//
|
||||
|
||||
if (!canGetNeighboringChunks)
|
||||
{
|
||||
// only update the center chunk
|
||||
queueChunkUpdate(chunkWrapper, null, dhLevel, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ArrayList<IChunkWrapper> neighboringChunkList = getNeighborChunkListForChunk(chunkWrapper, dhLevel);
|
||||
|
||||
if (newlyLoaded)
|
||||
{
|
||||
// this means this chunkWrapper is a newly loaded chunk
|
||||
// which may be missing some neighboring chunk data
|
||||
// because it is bordering the render distance
|
||||
// thus, only the chunks neighboring this chunkWrapper will get updated
|
||||
// because those are more likely to have their full neighboring chunk data
|
||||
//TODO this does not prevent those neighboring chunks from updating
|
||||
// this newly loaded chunk that were just skipped
|
||||
// leading to occasional lighting issues
|
||||
for (IChunkWrapper neighboringChunk : neighboringChunkList)
|
||||
{
|
||||
if (neighboringChunk == chunkWrapper)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.applyChunkUpdate(neighboringChunk, level, true, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if not all neighboring chunk data is available, do not try to update
|
||||
if (neighboringChunkList.size() < 9)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// update the center with any existing neighbour chunks.
|
||||
// this is done so lighting changes are propagated correctly
|
||||
queueChunkUpdate(chunkWrapper, neighboringChunkList, dhLevel, true);
|
||||
}
|
||||
}
|
||||
private static ArrayList<IChunkWrapper> getNeighborChunkListForChunk(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
|
||||
{
|
||||
// get the neighboring chunk list
|
||||
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
|
||||
for (int xOffset = -1; xOffset <= 1; xOffset++)
|
||||
{
|
||||
for (int zOffset = -1; zOffset <= 1; zOffset++)
|
||||
{
|
||||
if (xOffset == 0 && zOffset == 0)
|
||||
{
|
||||
// center chunk
|
||||
neighborChunkList.add(chunkWrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
// neighboring chunk
|
||||
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
|
||||
IChunkWrapper neighborChunk = dhLevel.getLevelWrapper().tryGetChunk(neighborPos);
|
||||
if (neighborChunk != null)
|
||||
{
|
||||
neighborChunkList.add(neighborChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return neighborChunkList;
|
||||
queueChunkUpdate(chunkWrapper, dhLevel);
|
||||
}
|
||||
|
||||
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighboringChunks)
|
||||
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
|
||||
{
|
||||
|
||||
// return if the chunk is already queued
|
||||
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// add chunk update data to preUpdate queue
|
||||
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, neighborChunkList, dhLevel, canGetNeighboringChunks);
|
||||
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel);
|
||||
CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData);
|
||||
|
||||
|
||||
@@ -343,7 +276,8 @@ public class SharedApi
|
||||
// (this prevents doing extra work queuing tasks that may not be necessary)
|
||||
// and makes sure the chunks closest to the player are updated first
|
||||
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
|
||||
if (executor != null && executor.getQueueSize() < executor.getPoolSize())
|
||||
if (executor != null
|
||||
&& executor.getQueueSize() < executor.getPoolSize())
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -383,10 +317,7 @@ public class SharedApi
|
||||
// update the necessary chunk(s) //
|
||||
//===============================//
|
||||
|
||||
// process preUpdate queue
|
||||
processQueuedChunkPreUpdate();
|
||||
|
||||
// process update queue
|
||||
processQueuedChunkUpdate();
|
||||
|
||||
// queue the next position if there are still positions to process
|
||||
@@ -415,8 +346,7 @@ public class SharedApi
|
||||
|
||||
IDhLevel dhLevel = preUpdateData.dhLevel;
|
||||
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
|
||||
boolean canGetNeighboringChunks = preUpdateData.canGetNeighboringChunks;
|
||||
ArrayList<IChunkWrapper> neighborChunkList = preUpdateData.neighborChunkList;
|
||||
chunkWrapper.createDhHeightMaps();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -433,34 +363,6 @@ public class SharedApi
|
||||
// do not update the chunk if the hash is the same
|
||||
return;
|
||||
}
|
||||
|
||||
// if this chunk will update and can get neighbors
|
||||
// then queue neighboring chunks to update as well
|
||||
// neighboring chunk will get added directly to the update queue
|
||||
// so they won't queue further chunk updates
|
||||
if (neighborChunkList != null
|
||||
&& !neighborChunkList.isEmpty())
|
||||
{
|
||||
for (IChunkWrapper adjacentChunk : neighborChunkList)
|
||||
{
|
||||
// pulling a new chunkWrapper is necessary to prevent concurrent modification on the existing chunkWrappers
|
||||
IChunkWrapper newCenterChunk = dhLevel.getLevelWrapper().tryGetChunk(adjacentChunk.getChunkPos());
|
||||
if (newCenterChunk != null)
|
||||
{
|
||||
ChunkUpdateData newUpdateData;
|
||||
if (canGetNeighboringChunks)
|
||||
{
|
||||
newUpdateData = new ChunkUpdateData(newCenterChunk, getNeighborChunkListForChunk(newCenterChunk, dhLevel), dhLevel, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
newUpdateData = new ChunkUpdateData(newCenterChunk, null, dhLevel, false);
|
||||
}
|
||||
|
||||
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(newCenterChunk.getChunkPos(), newUpdateData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
|
||||
@@ -473,8 +375,6 @@ public class SharedApi
|
||||
|
||||
private static void processQueuedChunkUpdate()
|
||||
{
|
||||
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
|
||||
|
||||
ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
|
||||
if (updateData == null)
|
||||
{
|
||||
@@ -484,15 +384,11 @@ public class SharedApi
|
||||
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
|
||||
IDhLevel dhLevel = updateData.dhLevel;
|
||||
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
|
||||
// having a list of the nearby chunks is needed for lighting and beacon generation
|
||||
@Nullable ArrayList<IChunkWrapper> nearbyChunkList = updateData.neighborChunkList;
|
||||
|
||||
// a non-null list is needed for the lighting engine
|
||||
if (nearbyChunkList == null)
|
||||
{
|
||||
nearbyChunkList = new ArrayList<IChunkWrapper>();
|
||||
nearbyChunkList.add(chunkWrapper);
|
||||
}
|
||||
// having a list of the nearby chunks is needed for lighting and beacon generation
|
||||
ArrayList<IChunkWrapper> nearbyChunkList = tryGetNeighborChunkListForChunk(chunkWrapper);
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
@@ -508,6 +404,35 @@ public class SharedApi
|
||||
{
|
||||
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
|
||||
}
|
||||
|
||||
CHUNK_UPDATE_QUEUE_MANAGER.queuedChunkWrapperByChunkPos.remove(updateData.chunkWrapper.getChunkPos());
|
||||
}
|
||||
private static ArrayList<IChunkWrapper> tryGetNeighborChunkListForChunk(IChunkWrapper chunkWrapper)
|
||||
{
|
||||
// get the neighboring chunk list
|
||||
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
|
||||
for (int xOffset = -1; xOffset <= 1; xOffset++)
|
||||
{
|
||||
for (int zOffset = -1; zOffset <= 1; zOffset++)
|
||||
{
|
||||
if (xOffset == 0 && zOffset == 0)
|
||||
{
|
||||
// center chunk
|
||||
neighborChunkList.add(chunkWrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
// neighboring chunk
|
||||
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
|
||||
IChunkWrapper neighborChunk = CHUNK_UPDATE_QUEUE_MANAGER.tryGetChunk(neighborPos);
|
||||
if (neighborChunk != null)
|
||||
{
|
||||
neighborChunkList.add(neighborChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return neighborChunkList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
@@ -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())
|
||||
|
||||
+1
-6
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+45
-26
@@ -1,16 +1,22 @@
|
||||
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChunkUpdateQueueManager
|
||||
{
|
||||
@@ -21,6 +27,12 @@ public class ChunkUpdateQueueManager
|
||||
public final ChunkPosQueue preUpdateQueue;
|
||||
public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
public final ConcurrentMap<DhChunkPos, IChunkWrapper> queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(20, TimeUnit.SECONDS)
|
||||
.<DhChunkPos, IChunkWrapper>build()
|
||||
.asMap();
|
||||
|
||||
/** dynamically changes based on the number of threads currently available */
|
||||
public int maxSize = 500;
|
||||
|
||||
private static long lastOverloadedLogMessageMsTime = 0;
|
||||
@@ -68,22 +80,27 @@ public class ChunkUpdateQueueManager
|
||||
* If there are no more slots, replaces the item furthest from the center in the update queue.
|
||||
*/
|
||||
public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
|
||||
{ this.addItemToQueue(pos, updateData, this.preUpdateQueue); }
|
||||
|
||||
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
|
||||
{ this.addItemToQueue(pos, updateData, this.updateQueue); }
|
||||
|
||||
private void addItemToQueue(DhChunkPos pos, ChunkUpdateData updateData, ChunkPosQueue queue)
|
||||
{
|
||||
int remainingSlots = this.maxSize - this.getQueuedCount();
|
||||
|
||||
// If no slots are left, get one by removing the item furthest from the center
|
||||
if (remainingSlots <= 0)
|
||||
{
|
||||
if (!this.updateQueue.isEmpty())
|
||||
ChunkUpdateData removedData = queue.popFurthest();
|
||||
if (removedData != null)
|
||||
{
|
||||
this.updateQueue.popFurthest();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.preUpdateQueue.popFurthest();
|
||||
this.queuedChunkWrapperByChunkPos.remove(removedData.chunkWrapper.getChunkPos());
|
||||
}
|
||||
}
|
||||
this.preUpdateQueue.addItem(pos, updateData);
|
||||
|
||||
queue.addItem(pos,updateData);
|
||||
this.queuedChunkWrapperByChunkPos.putIfAbsent(pos, updateData.chunkWrapper);
|
||||
|
||||
remainingSlots = this.maxSize - this.getQueuedCount();
|
||||
if (remainingSlots <= 0)
|
||||
@@ -92,24 +109,6 @@ public class ChunkUpdateQueueManager
|
||||
}
|
||||
}
|
||||
|
||||
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
|
||||
{
|
||||
int remainingSlots = this.maxSize - this.getQueuedCount();
|
||||
|
||||
// If no slots are left, get one by removing the item furthest from the center
|
||||
if (remainingSlots <= 0)
|
||||
{
|
||||
this.updateQueue.popFurthest();
|
||||
}
|
||||
|
||||
this.updateQueue.addItem(pos,updateData);
|
||||
|
||||
remainingSlots = this.maxSize - this.getQueuedCount();
|
||||
if (remainingSlots <= 0)
|
||||
{
|
||||
this.sendOverloadMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendOverloadMessage()
|
||||
{
|
||||
@@ -119,7 +118,7 @@ public class ChunkUpdateQueueManager
|
||||
{
|
||||
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
|
||||
|
||||
String message = "\u00A76" + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + "\u00A7r" +
|
||||
String message = MinecraftTextFormat.ORANGE + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + MinecraftTextFormat.CLEAR_FORMATTING +
|
||||
"\nThis may result in holes in your LODs. " +
|
||||
"\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " +
|
||||
"\nMax queue count [" + SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.maxSize + "] ([" + SharedApi.MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players).";
|
||||
@@ -140,6 +139,26 @@ public class ChunkUpdateQueueManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to return a cloned chunk wrapper from memory.
|
||||
* Returns null if no chunk is available.
|
||||
* <br><br>
|
||||
* This is done instead of accessing the MC level since
|
||||
* accessing the level often requires running on the render or server
|
||||
* thread, which causes stuttering.
|
||||
*/
|
||||
@Nullable
|
||||
public IChunkWrapper tryGetChunk(DhChunkPos pos)
|
||||
{
|
||||
IChunkWrapper existingWrapper = this.queuedChunkWrapperByChunkPos.get(pos);
|
||||
if (existingWrapper == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return existingWrapper.copy();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
|
||||
@@ -101,6 +101,7 @@ public class Config
|
||||
.build();
|
||||
|
||||
public static ConfigUiLinkedEntry quickEnableWorldGenerator = new ConfigUiLinkedEntry(Common.WorldGenerator.enableDistantGeneration);
|
||||
public static ConfigUiLinkedEntry quickEnableServerGeneration = new ConfigUiLinkedEntry(Server.enableServerGeneration);
|
||||
|
||||
public static ConfigUiLinkedEntry quickShowWorldGenProgress = new ConfigUiLinkedEntry(Common.WorldGenerator.showGenerationProgress);
|
||||
|
||||
@@ -457,6 +458,15 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Boolean> expandDistantBeacons = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
+ "If true LOD beacon beams will be rendered wider at extreme distances, \n"
|
||||
+ "making them easier to see. \n"
|
||||
+ "If false all LOD beacon beams will only ever be 1 block wide. \n"
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Boolean> enableCloudRendering = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
@@ -505,11 +515,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 +576,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 +843,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 +853,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 +1421,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 +1602,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();
|
||||
|
||||
|
||||
@@ -1728,6 +1735,15 @@ public class Config
|
||||
|
||||
|
||||
// Generation
|
||||
public static ConfigEntry<Boolean> enableServerGeneration = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
+ "When enabled, Distant Horizons will attempt to download missing LODs from the server.\n"
|
||||
+ "\n"
|
||||
+ "Note: the server must have Distant Generation enabled for it to work."
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Integer> generationRequestRateLimit = new ConfigEntry.Builder<Integer>()
|
||||
.setChatCommandName("generation.requestRateLimit")
|
||||
.setMinDefaultMax(1, 20, 100)
|
||||
@@ -1865,6 +1881,8 @@ public class Config
|
||||
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
||||
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
||||
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
||||
|
||||
IgnoredDimensionCsvHandler.INSTANCE.onConfigValueSet();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
+43
@@ -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; }
|
||||
|
||||
}
|
||||
+125
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+6
-1
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+10
-13
@@ -295,24 +295,21 @@ public class LodBufferContainer implements AutoCloseable
|
||||
{
|
||||
this.buffersUploaded = false;
|
||||
|
||||
GLProxy.queueRunningOnRenderThread(() ->
|
||||
for (GLVertexBuffer buffer : this.vbos)
|
||||
{
|
||||
for (GLVertexBuffer buffer : this.vbos)
|
||||
if (buffer != null)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
buffer.destroyAsync();
|
||||
}
|
||||
buffer.destroyAsync();
|
||||
}
|
||||
|
||||
for (GLVertexBuffer buffer : this.vbosTransparent)
|
||||
}
|
||||
|
||||
for (GLVertexBuffer buffer : this.vbosTransparent)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
buffer.destroyAsync();
|
||||
}
|
||||
buffer.destroyAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -372,10 +372,10 @@ public class LodQuadBuilder
|
||||
if (quad.direction.axis.isHorizontal() || quad.direction == EDhDirection.DOWN)
|
||||
{
|
||||
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
|
||||
// if we want the color to fade, only apply the dirt color to the bottom vertices
|
||||
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
|
||||
// always render the bottom as dirt
|
||||
|| quad.direction == EDhDirection.DOWN)
|
||||
// if we want the color to fade, only apply the dirt color to the bottom vertices
|
||||
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
|
||||
// always render the bottom as dirt
|
||||
|| quad.direction == EDhDirection.DOWN)
|
||||
{
|
||||
// for horizontal and bottom faces of grass blocks, use the dirt color to
|
||||
// prevent green cliff walls
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.seibel.distanthorizons.core.enums;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* might be deprecated in the future? in that case we'll probably want a wrapper
|
||||
* function to handle colors for new MC versions
|
||||
*
|
||||
* source: https://minecraft.wiki/w/Formatting_codes
|
||||
*/
|
||||
public class EMinecraftColor
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.seibel.distanthorizons.core.enums;
|
||||
|
||||
/**
|
||||
* might be deprecated in the future? in that case we'll probably want a wrapper
|
||||
* function to handle colors for new MC versions
|
||||
* <br><br>
|
||||
* source: https://minecraft.wiki/w/Formatting_codes
|
||||
*/
|
||||
public class MinecraftTextFormat
|
||||
{
|
||||
public static final String BLACK = "\u00A70";
|
||||
public static final String DARK_BLUE = "\u00A71";
|
||||
public static final String DARK_GREEN = "\u00A72";
|
||||
public static final String DARK_AQUA = "\u00A73";
|
||||
public static final String DARK_RED = "\u00A74";
|
||||
public static final String DARK_PURPLE = "\u00A75";
|
||||
public static final String ORANGE = "\u00A76";
|
||||
public static final String GRAY = "\u00A77";
|
||||
public static final String DARK_GRAY = "\u00A78";
|
||||
public static final String BLUE = "\u00A79";
|
||||
public static final String GREEN = "\u00A7a";
|
||||
public static final String AQUA = "\u00A7b";
|
||||
public static final String RED = "\u00A7c";
|
||||
public static final String LIGHT_PURPLE = "\u00A7d";
|
||||
public static final String YELLOW = "\u00A7e";
|
||||
public static final String WHITE = "\u00A7f";
|
||||
|
||||
public static final String OBFUSCATED = "\u00A7k";
|
||||
public static final String BOLD = "\u00A7l";
|
||||
public static final String STRIKETHROUGH = "\u00A7m";
|
||||
public static final String UNDERLINE = "\u00A7n";
|
||||
public static final String ITALIC = "\u00A7o";
|
||||
public static final String CLEAR_FORMATTING = "\u00A7r";
|
||||
|
||||
}
|
||||
+6
-1
@@ -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
|
||||
{
|
||||
|
||||
+40
-29
@@ -29,6 +29,7 @@ import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
||||
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
||||
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
@@ -43,6 +44,7 @@ import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
@@ -115,37 +117,43 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
// events //
|
||||
//========//
|
||||
|
||||
private void onWorldGenTaskComplete(DataSourceRetrievalResult genTaskResult, Throwable exception)
|
||||
private void onWorldGenTaskComplete(@NotNull Long genPos, @Nullable DataSourceRetrievalResult genTaskResult, @Nullable Throwable exception)
|
||||
{
|
||||
if (exception != null)
|
||||
try
|
||||
{
|
||||
// don't log shutdown exceptions
|
||||
if (!ExceptionUtil.isInterruptOrReject(exception))
|
||||
if (exception != null)
|
||||
{
|
||||
LOGGER.error("Uncaught Gen Task Exception at ["+genTaskResult.pos+"], error: ["+exception.getMessage()+"].", exception);
|
||||
// don't log shutdown exceptions
|
||||
if (!ExceptionUtil.isInterruptOrReject(exception))
|
||||
{
|
||||
LOGGER.error("Uncaught Gen Task Exception at [" + genPos + "], error: [" + exception.getMessage() + "].", exception);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(genTaskResult);
|
||||
if (genTaskResult.state == ERetrievalResultState.SUCCESS)
|
||||
{
|
||||
LodUtil.assertTrue(genTaskResult.dataSource != null, "Successful retrieval object should have a datasource.");
|
||||
|
||||
this.dataUpdater.updateDataSource(genTaskResult.dataSource);
|
||||
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
|
||||
genTaskResult.dataSource.close();
|
||||
}
|
||||
else if (genTaskResult.state == ERetrievalResultState.REQUIRES_SPLITTING)
|
||||
{
|
||||
// task was split
|
||||
LodUtil.assertTrue(genTaskResult.dataSource == null, "Split retrieval object should not have a datasource.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// shouldn't happen, but just in case
|
||||
LOGGER.warn("Unexpected gen Task state at: [" + DhSectionPos.toString(genTaskResult.pos) + "], state: [" + genTaskResult.state + "], datasource: NULL, exception: NULL.");
|
||||
}
|
||||
}
|
||||
else if (genTaskResult.generatedDataSource != null)
|
||||
catch (Exception e)
|
||||
{
|
||||
this.dataUpdater.updateDataSource(genTaskResult.generatedDataSource);
|
||||
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
|
||||
}
|
||||
else if (exception == null && !genTaskResult.success) // TODO use enum to check type
|
||||
{
|
||||
// task was split
|
||||
}
|
||||
else
|
||||
{
|
||||
// shouldn't happen, but just in case
|
||||
// TODO is definitely happening
|
||||
LOGGER.warn("Unexpected gen Task state at: [" + DhSectionPos.toString(genTaskResult.pos) + "], success: ["+genTaskResult.success+"], datasource: NULL, exception: NULL.");
|
||||
}
|
||||
|
||||
|
||||
// if the generation task was split up into smaller positions, add the on-complete event to them
|
||||
for (CompletableFuture<DataSourceRetrievalResult> siblingFuture : genTaskResult.childFutures)
|
||||
{
|
||||
siblingFuture.whenComplete((siblingGenTaskResult, siblingEx) -> this.onWorldGenTaskComplete(siblingGenTaskResult, siblingEx));
|
||||
LOGGER.error("Unexpected issue during onWorldGenTaskComplete, error: ["+e.getMessage()+"].", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,12 +276,15 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
|
||||
|
||||
int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount();
|
||||
if (availableTaskSlots <= 0)
|
||||
if (availableTaskSlots == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (availableTaskSlots < 0)
|
||||
{
|
||||
//if (false)
|
||||
if (pruneWaitingTasksAboveLimit)
|
||||
{
|
||||
AtomicInteger tasksToCancel = new AtomicInteger(-availableTaskSlots + 1);
|
||||
AtomicInteger tasksToCancel = new AtomicInteger(availableTaskSlots * -1);
|
||||
worldGenQueue.removeRetrievalRequestIf(taskPos -> tasksToCancel.getAndDecrement() > 0);
|
||||
}
|
||||
else
|
||||
@@ -296,7 +307,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
}
|
||||
|
||||
CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
|
||||
worldGenFuture.whenComplete(this::onWorldGenTaskComplete);
|
||||
worldGenFuture.whenComplete((r, e) -> this.onWorldGenTaskComplete(genPos, r, e));
|
||||
|
||||
return worldGenFuture;
|
||||
}
|
||||
|
||||
+11
-14
@@ -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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+16
-1
@@ -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);
|
||||
|
||||
+1
-1
@@ -185,7 +185,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
|
||||
parentLocked = true;
|
||||
this.dataUpdater.lockedPosSet.add(parentUpdatePos);
|
||||
|
||||
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos)) // TODO can we cache anything in memory to speed up the propagation process? Compression/Disk IO is by far the slowest part of this process
|
||||
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos))
|
||||
{
|
||||
// will return null if the file handler is shutting down
|
||||
if (parentDataSource != null)
|
||||
|
||||
@@ -7,7 +7,9 @@ import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.util.FormatUtil;
|
||||
@@ -154,14 +156,16 @@ public class PregenManager
|
||||
}
|
||||
else
|
||||
{
|
||||
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos()).thenAccept(result -> {
|
||||
if (!result.success)
|
||||
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos())
|
||||
.whenComplete((DataSourceRetrievalResult result, Throwable throwable) ->
|
||||
{
|
||||
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
|
||||
}
|
||||
|
||||
this.pendingGenerations.invalidate(result.pos);
|
||||
});
|
||||
if (throwable != null)
|
||||
{
|
||||
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
|
||||
}
|
||||
|
||||
this.pendingGenerations.invalidate(result.pos);
|
||||
});
|
||||
}
|
||||
|
||||
fullDataSource.close();
|
||||
|
||||
+13
-82
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+19
-24
@@ -92,7 +92,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
private int estimatedRemainingChunkCount = 0;
|
||||
|
||||
private final RollingAverage rollingAverageChunkGenTimeInMs = new RollingAverage(Runtime.getRuntime().availableProcessors() * 500);
|
||||
public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
|
||||
@Override public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
|
||||
|
||||
|
||||
|
||||
@@ -128,7 +128,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
// the generator is shutting down, don't add new tasks
|
||||
if (this.generatorClosingFuture != null)
|
||||
{
|
||||
return CompletableFuture.completedFuture(DataSourceRetrievalResult.CreateFail());
|
||||
CompletableFuture<DataSourceRetrievalResult> f = new CompletableFuture<>();
|
||||
f.completeExceptionally(new CancellationException());
|
||||
return f;
|
||||
}
|
||||
|
||||
// use the existing task if present
|
||||
@@ -316,22 +318,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
// detail level is too high (if the detail level was too low, the generator would've ignored the request),
|
||||
// split up the task
|
||||
|
||||
|
||||
// split up the task and add each to the queue
|
||||
ArrayList<CompletableFuture<DataSourceRetrievalResult>> childFutures = new ArrayList<>(4);
|
||||
DhSectionPos.forEachChild(closestTask.pos, (childDhSectionPos) ->
|
||||
{
|
||||
DataSourceRetrievalTask newGenTask = new DataSourceRetrievalTask(childDhSectionPos, DhSectionPos.getDetailLevel(childDhSectionPos));
|
||||
childFutures.add(newGenTask.future);
|
||||
this.waitingTasks.put(newGenTask.pos, newGenTask);
|
||||
});
|
||||
|
||||
// send the child futures to the future recipient, to notify them of the new tasks
|
||||
closestTask.future.complete(DataSourceRetrievalResult.CreateSplit(childFutures));
|
||||
closestTask.future.complete(DataSourceRetrievalResult.CreateSplit());
|
||||
}
|
||||
|
||||
|
||||
// a task has been started or queued
|
||||
// a task has been started or queued,
|
||||
// queue another task
|
||||
return true;
|
||||
}
|
||||
@@ -360,7 +351,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
|
||||
});
|
||||
|
||||
generationFuture.handle((fullDataSourceV2, exception) ->
|
||||
generationFuture.handle((FullDataSourceV2 fullDataSource, Throwable exception) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -372,13 +363,16 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
|
||||
}
|
||||
|
||||
worldGenTask.future.complete(DataSourceRetrievalResult.CreateFail());
|
||||
LodUtil.assertTrue(fullDataSource == null);
|
||||
worldGenTask.future.completeExceptionally(exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
boolean taskRemoved = this.inProgressGenTasksByLodPos.remove(taskPos, worldGenTask);
|
||||
LodUtil.assertTrue(taskRemoved, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]");
|
||||
|
||||
worldGenTask.future.complete(DataSourceRetrievalResult.CreateSuccess(taskPos, fullDataSource));
|
||||
}
|
||||
|
||||
boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, worldGenTask);
|
||||
LodUtil.assertTrue(worked, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]");
|
||||
|
||||
worldGenTask.future.complete(DataSourceRetrievalResult.CreateSuccess(taskPos, fullDataSourceV2));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -692,9 +686,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
LOGGER.error("Error when terminating data generation for pos: ["+DhSectionPos.toString(genTask.pos)+"], error: ["+throwable.getMessage()+"].", throwable);
|
||||
}
|
||||
|
||||
if (result.generatedDataSource != null)
|
||||
if (result != null
|
||||
&& result.dataSource != null)
|
||||
{
|
||||
result.generatedDataSource.close();
|
||||
result.dataSource.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
+7
-20
@@ -22,23 +22,16 @@ package com.seibel.distanthorizons.core.generation.tasks;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* @see DataSourceRetrievalTask
|
||||
*/
|
||||
public class DataSourceRetrievalResult
|
||||
{
|
||||
/** true if terrain was generated */
|
||||
public final boolean success; // TODO reponse enum?
|
||||
public final ERetrievalResultState state;
|
||||
/** the position that was generated, will be null if nothing was generated */
|
||||
public final long pos;
|
||||
@Nullable
|
||||
public final FullDataSourceV2 generatedDataSource;
|
||||
|
||||
/** if a position is too high detail for world generator to handle it, these futures are for its 4 children positions after being split up. */
|
||||
public final ArrayList<CompletableFuture<DataSourceRetrievalResult>> childFutures = new ArrayList<>(4);
|
||||
public final FullDataSourceV2 dataSource;
|
||||
|
||||
|
||||
|
||||
@@ -46,19 +39,13 @@ public class DataSourceRetrievalResult
|
||||
// constructors //
|
||||
//==============//
|
||||
|
||||
public static DataSourceRetrievalResult CreateSplit(ArrayList<CompletableFuture<DataSourceRetrievalResult>> siblingFutures) { return new DataSourceRetrievalResult(false, 0, null, siblingFutures); }
|
||||
public static DataSourceRetrievalResult CreateFail() { return new DataSourceRetrievalResult(false, 0, null,null); }
|
||||
public static DataSourceRetrievalResult CreateSuccess(long pos, FullDataSourceV2 generatedDataSource) { return new DataSourceRetrievalResult(true, pos, generatedDataSource, null); }
|
||||
private DataSourceRetrievalResult(boolean success, long pos, @Nullable FullDataSourceV2 generatedDataSource, ArrayList<CompletableFuture<DataSourceRetrievalResult>> childFutures)
|
||||
public static DataSourceRetrievalResult CreateSplit() { return new DataSourceRetrievalResult(ERetrievalResultState.REQUIRES_SPLITTING, 0, null); }
|
||||
public static DataSourceRetrievalResult CreateSuccess(long pos, FullDataSourceV2 generatedDataSource) { return new DataSourceRetrievalResult(ERetrievalResultState.SUCCESS, pos, generatedDataSource); }
|
||||
private DataSourceRetrievalResult(ERetrievalResultState state, long pos, @Nullable FullDataSourceV2 dataSource)
|
||||
{
|
||||
this.success = success;
|
||||
this.state = state;
|
||||
this.pos = pos;
|
||||
this.generatedDataSource = generatedDataSource;
|
||||
|
||||
if (childFutures != null)
|
||||
{
|
||||
this.childFutures.addAll(childFutures);
|
||||
}
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.seibel.distanthorizons.core.generation.tasks;
|
||||
|
||||
/**
|
||||
* SUCCESS <br>
|
||||
* REQUIRES_SPLITTING <br>
|
||||
*
|
||||
* @see DataSourceRetrievalResult
|
||||
*/
|
||||
public enum ERetrievalResultState
|
||||
{
|
||||
SUCCESS,
|
||||
REQUIRES_SPLITTING,
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public class JarUtils
|
||||
*/
|
||||
public static InputStream accessFile(String resource)
|
||||
{
|
||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
final ClassLoader loader = JarUtils.class.getClassLoader();
|
||||
// this is the path within the jar file
|
||||
InputStream input = loader.getResourceAsStream(resource);
|
||||
if (input == null)
|
||||
|
||||
@@ -192,23 +192,30 @@ public abstract class AbstractDhLevel implements IDhLevel
|
||||
return this.updateDataSourcesAsync(fullDataSource)
|
||||
.thenRun(() ->
|
||||
{
|
||||
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
|
||||
if (updatedChunkPosSet != null)
|
||||
try
|
||||
{
|
||||
for (DhChunkPos chunkPos : updatedChunkPosSet)
|
||||
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
|
||||
if (updatedChunkPosSet != null)
|
||||
{
|
||||
// save after the data source has been updated to prevent saving the hash without the associated datasource
|
||||
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
|
||||
if (this.chunkHashRepo != null && chunkHash != null)
|
||||
for (DhChunkPos chunkPos : updatedChunkPosSet)
|
||||
{
|
||||
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
|
||||
}
|
||||
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(
|
||||
// save after the data source has been updated to prevent saving the hash without the associated datasource
|
||||
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
|
||||
if (this.chunkHashRepo != null && chunkHash != null)
|
||||
{
|
||||
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
|
||||
}
|
||||
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(
|
||||
DhApiChunkModifiedEvent.class,
|
||||
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected issue after onDataSourceSaveAsync, error: ["+e.getMessage()+"].", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
@@ -302,32 +303,32 @@ public class DhLogger implements IConfigListener
|
||||
String prefix = "[" + ModInfo.READABLE_NAME + "] ";
|
||||
if (logLevel == Level.ERROR)
|
||||
{
|
||||
prefix += "\u00A74";
|
||||
prefix += MinecraftTextFormat.DARK_RED;
|
||||
}
|
||||
else if (logLevel == Level.WARN)
|
||||
{
|
||||
prefix += "\u00A76";
|
||||
prefix += MinecraftTextFormat.ORANGE;
|
||||
}
|
||||
else if (logLevel == Level.INFO)
|
||||
{
|
||||
prefix += "\u00A7f";
|
||||
prefix += MinecraftTextFormat.AQUA;
|
||||
}
|
||||
else if (logLevel == Level.DEBUG)
|
||||
{
|
||||
prefix += "\u00A77";
|
||||
prefix += MinecraftTextFormat.GREEN;
|
||||
}
|
||||
else if (logLevel == Level.TRACE)
|
||||
{
|
||||
prefix += "\u00A78";
|
||||
prefix += MinecraftTextFormat.DARK_GRAY;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix += "\u00A7f";
|
||||
prefix += MinecraftTextFormat.WHITE;
|
||||
}
|
||||
|
||||
prefix += "\u00A7l\u00A7u";
|
||||
prefix += MinecraftTextFormat.BOLD + "" + MinecraftTextFormat.WHITE;
|
||||
prefix += logLevel.name();
|
||||
prefix += ":\u00A7r ";
|
||||
prefix += MinecraftTextFormat.CLEAR_FORMATTING + " ";
|
||||
|
||||
mc_client.sendChatMessage(prefix + message);
|
||||
}
|
||||
|
||||
+29
-42
@@ -6,6 +6,7 @@ import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
@@ -23,6 +24,7 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
|
||||
@@ -71,16 +73,6 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
|
||||
private final SupplierBasedRateLimiter<Void> rateLimiter = new SupplierBasedRateLimiter<>(this::getRequestRateLimit);
|
||||
|
||||
private final Set<Long> succeededPositions = Collections.newSetFromMap(CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(20, TimeUnit.MINUTES)
|
||||
.<Long, Boolean>build()
|
||||
.asMap());
|
||||
|
||||
private final Set<Long> requiresSplittingPositions = Collections.newSetFromMap(CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(20, TimeUnit.MINUTES)
|
||||
.<Long, Boolean>build()
|
||||
.asMap());
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
@@ -106,7 +98,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
|
||||
protected abstract int getRequestRateLimit();
|
||||
protected abstract boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos);
|
||||
protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<NetRequestResult> future);
|
||||
protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future);
|
||||
|
||||
protected abstract String getQueueName();
|
||||
|
||||
@@ -116,19 +108,9 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
// request submitting //
|
||||
//====================//
|
||||
|
||||
public CompletableFuture<NetRequestResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp)
|
||||
public CompletableFuture<DataSourceRetrievalResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp)
|
||||
{
|
||||
if (this.succeededPositions.contains(sectionPos))
|
||||
{
|
||||
return CompletableFuture.completedFuture(NetRequestResult.CreateFail());
|
||||
}
|
||||
|
||||
if (this.requiresSplittingPositions.contains(sectionPos))
|
||||
{
|
||||
return CompletableFuture.completedFuture(NetRequestResult.CreateSplit());
|
||||
}
|
||||
|
||||
NetRequestTask requestEntry = this.waitingTasksBySectionPos.compute(sectionPos, (pos, existingNetTask) ->
|
||||
NetRequestTask requestEntry = this.waitingTasksBySectionPos.compute(sectionPos, (Long pos, NetRequestTask existingNetTask) ->
|
||||
{
|
||||
// ignore already queued tasks
|
||||
if (existingNetTask != null)
|
||||
@@ -138,27 +120,25 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
|
||||
|
||||
NetRequestTask newRequestEntry = new NetRequestTask(pos, clientTimestamp);
|
||||
newRequestEntry.future.whenComplete((requestResult, throwable) ->
|
||||
newRequestEntry.future.whenComplete((DataSourceRetrievalResult requestResult, Throwable throwable) ->
|
||||
{
|
||||
this.waitingTasksBySectionPos.remove(pos);
|
||||
|
||||
if (throwable != null)
|
||||
{
|
||||
if (!(throwable instanceof CancellationException))
|
||||
{
|
||||
this.failedRequests.incrementAndGet();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestResult.state)
|
||||
{
|
||||
case SUCCESS:
|
||||
this.finishedRequests.incrementAndGet();
|
||||
this.succeededPositions.add(pos);
|
||||
break;
|
||||
case REQUIRES_SPLITTING:
|
||||
this.requiresSplittingPositions.add(pos);
|
||||
break;
|
||||
case FAIL:
|
||||
this.failedRequests.incrementAndGet();
|
||||
break;
|
||||
default:
|
||||
if (throwable != null && !(throwable instanceof CancellationException))
|
||||
{
|
||||
this.failedRequests.incrementAndGet();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -238,11 +218,18 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
FullDataSourceResponseMessage.class
|
||||
);
|
||||
requestTask.networkDataSourceFuture = dataSourceNetworkFuture;
|
||||
dataSourceNetworkFuture.handle((FullDataSourceResponseMessage response, Throwable throwable) ->
|
||||
|
||||
Executor networkCompressionExecutor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
||||
if (networkCompressionExecutor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dataSourceNetworkFuture.handleAsync((FullDataSourceResponseMessage response, Throwable throwable) ->
|
||||
{
|
||||
this.handleNetResponse(requestTask, response, throwable);
|
||||
return null;
|
||||
});
|
||||
}, networkCompressionExecutor);
|
||||
}
|
||||
private void handleNetResponse(NetRequestTask requestTask, FullDataSourceResponseMessage response, Throwable throwable)
|
||||
{
|
||||
@@ -273,12 +260,12 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
this.level.updateBeaconBeamsForSectionPos(dataSourceDto.pos, response.payload.beaconBeams);
|
||||
|
||||
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper(), null);
|
||||
requestTask.future.complete(NetRequestResult.CreateSuccess(fullDataSource));
|
||||
requestTask.future.complete(DataSourceRetrievalResult.CreateSuccess(dataSourceDto.pos, fullDataSource));
|
||||
}
|
||||
}
|
||||
catch (SectionRequiresSplittingException ignored)
|
||||
{
|
||||
requestTask.future.complete(NetRequestResult.CreateSplit());
|
||||
requestTask.future.complete(DataSourceRetrievalResult.CreateSplit());
|
||||
}
|
||||
catch (SessionClosedException | CancellationException ignored)
|
||||
{
|
||||
@@ -287,7 +274,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
catch (RequestRejectedException e)
|
||||
{
|
||||
LOGGER.info("Request rejected by the server, message: [" + e.getMessage() + "].");
|
||||
requestTask.future.complete(NetRequestResult.CreateFail());
|
||||
requestTask.future.completeExceptionally(e);
|
||||
}
|
||||
catch (RateLimitedException e)
|
||||
{
|
||||
@@ -316,7 +303,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
}
|
||||
else
|
||||
{
|
||||
requestTask.future.complete(NetRequestResult.CreateFail());
|
||||
requestTask.future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -457,7 +444,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
public final long pos;
|
||||
|
||||
/** encapsulates the entire request, including client side queuing and the actual server request */
|
||||
public final CompletableFuture<NetRequestResult> future = new CompletableFuture<>();
|
||||
public final CompletableFuture<DataSourceRetrievalResult> future = new CompletableFuture<>();
|
||||
/** will be null if we want to retrieve the LOD regardless of when it was last updated */
|
||||
@Nullable
|
||||
public final Long updateTimestamp;
|
||||
|
||||
+20
-2
@@ -1,8 +1,10 @@
|
||||
package com.seibel.distanthorizons.core.multiplayer.client;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
|
||||
@@ -18,6 +20,7 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartial
|
||||
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
@@ -29,6 +32,10 @@ public class ClientNetworkState implements Closeable
|
||||
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
|
||||
.build();
|
||||
|
||||
protected static final DhLogger CONFIG_CHANGE_LOGGER = new DhLoggerBuilder()
|
||||
.fileLevelConfig(Config.Common.Logging.logConnectionConfigChangesToFile)
|
||||
.build();
|
||||
|
||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||
|
||||
|
||||
@@ -44,6 +51,7 @@ public class ClientNetworkState implements Closeable
|
||||
*/
|
||||
public NetworkSession getSession() { return this.networkSession; }
|
||||
|
||||
@NotNull
|
||||
public SessionConfig sessionConfig = new SessionConfig();
|
||||
|
||||
private volatile boolean configReceived = false;
|
||||
@@ -89,6 +97,15 @@ public class ClientNetworkState implements Closeable
|
||||
|| Math.abs(event.protocolVersion - ModInfo.PROTOCOL_VERSION) < this.closestProtocolVersion)
|
||||
{
|
||||
this.closestProtocolVersion = event.protocolVersion;
|
||||
|
||||
if (ModInfo.PROTOCOL_VERSION < event.protocolVersion)
|
||||
{
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(MinecraftTextFormat.ORANGE + "Distant Horizons: Your mod is outdated. Update to receive LODs on this server.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(MinecraftTextFormat.ORANGE + "Distant Horizons: The server's mod is outdated. Ask the server's owner to update.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -129,8 +146,9 @@ public class ClientNetworkState implements Closeable
|
||||
{
|
||||
this.serverSupportStatus = EServerSupportStatus.FULL;
|
||||
|
||||
// TODO only log changes
|
||||
LOGGER.info("Connection config has been changed: [" + message.config + "].");
|
||||
String configChanges = this.sessionConfig.getDifferencesAsString(message.config);
|
||||
CONFIG_CHANGE_LOGGER.info("Connection config has been changed: [" + configChanges + "].");
|
||||
|
||||
this.sessionConfig = message.config;
|
||||
this.configReceived = true;
|
||||
});
|
||||
|
||||
-15
@@ -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,
|
||||
}
|
||||
-47
@@ -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
-1
@@ -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"; }
|
||||
|
||||
+80
-17
@@ -11,6 +11,7 @@ import java.io.Closeable;
|
||||
import java.util.*;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SessionConfig implements INetworkObject
|
||||
@@ -31,7 +32,23 @@ public class SessionConfig implements INetworkObject
|
||||
{
|
||||
// Note: config values are transmitted in the insertion order
|
||||
|
||||
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd);
|
||||
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration.getChatCommandName(), new Entry(
|
||||
Config.Server.enableServerGeneration::get,
|
||||
runnable -> new Closeable()
|
||||
{
|
||||
private final ConfigChangeListener<Boolean> distantGenerationChanges = new ConfigChangeListener<>(Config.Common.WorldGenerator.enableDistantGeneration, ignored -> runnable.run());
|
||||
private final ConfigChangeListener<Boolean> serverGenerationChanges = new ConfigChangeListener<>(Config.Server.enableServerGeneration, ignored -> runnable.run());
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
this.serverGenerationChanges.close();
|
||||
this.distantGenerationChanges.close();
|
||||
}
|
||||
},
|
||||
(Boolean client, Boolean server) -> client && Config.Common.WorldGenerator.enableDistantGeneration.get()
|
||||
));
|
||||
|
||||
registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min);
|
||||
registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkX, (x, y) -> y);
|
||||
registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkZ, (x, y) -> y);
|
||||
@@ -90,14 +107,24 @@ public class SessionConfig implements INetworkObject
|
||||
|
||||
private static <T> void registerConfigEntry(ConfigEntry<T> configEntry, BinaryOperator<T> valueConstrainer)
|
||||
{
|
||||
CONFIG_ENTRIES.compute(Objects.requireNonNull(configEntry.getChatCommandName()), (key, existingEntry) -> {
|
||||
if (existingEntry != null)
|
||||
{
|
||||
throw new IllegalArgumentException("Attempted to register config entry with duplicate chatCommandName: " + key);
|
||||
}
|
||||
|
||||
return new Entry(configEntry, valueConstrainer);
|
||||
});
|
||||
registerConfigEntry(
|
||||
Objects.requireNonNull(configEntry.getChatCommandName()),
|
||||
new Entry(
|
||||
configEntry::get,
|
||||
runnable -> new ConfigChangeListener<>(configEntry, ignored -> runnable.run()),
|
||||
valueConstrainer
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static void registerConfigEntry(String key, Entry entry)
|
||||
{
|
||||
if (CONFIG_ENTRIES.containsKey(key))
|
||||
{
|
||||
throw new IllegalArgumentException("Attempted to register config entry with duplicate key: " + key);
|
||||
}
|
||||
|
||||
CONFIG_ENTRIES.put(key, entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +142,7 @@ public class SessionConfig implements INetworkObject
|
||||
T value = (T) this.values.get(name);
|
||||
if (value == null)
|
||||
{
|
||||
value = (T) entry.supplier.get();
|
||||
value = (T) entry.valueSupplier.get();
|
||||
}
|
||||
|
||||
return (this.constrainingConfig != null
|
||||
@@ -162,6 +189,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 //
|
||||
//================//
|
||||
@@ -182,13 +237,15 @@ public class SessionConfig implements INetworkObject
|
||||
|
||||
private static class Entry
|
||||
{
|
||||
public final ConfigEntry<Object> supplier;
|
||||
public final Supplier<Object> valueSupplier;
|
||||
public final Function<Runnable, Closeable> changeListenerFactory;
|
||||
public final BinaryOperator<Object> valueConstrainer;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Entry(ConfigEntry<T> supplier, BinaryOperator<T> valueConstrainer)
|
||||
private <T> Entry(Supplier<Object> valueSupplier, Function<Runnable, Closeable> changeListenerFactory, BinaryOperator<T> valueConstrainer)
|
||||
{
|
||||
this.supplier = (ConfigEntry<Object>) supplier;
|
||||
this.valueSupplier = valueSupplier;
|
||||
this.changeListenerFactory = changeListenerFactory;
|
||||
this.valueConstrainer = (BinaryOperator<Object>) valueConstrainer;
|
||||
}
|
||||
|
||||
@@ -197,23 +254,29 @@ public class SessionConfig implements INetworkObject
|
||||
/** fires if any config value was changed */
|
||||
public static class AnyChangeListener implements Closeable
|
||||
{
|
||||
private final ArrayList<ConfigChangeListener<?>> changeListeners;
|
||||
private final ArrayList<Closeable> changeListeners;
|
||||
|
||||
public AnyChangeListener(Runnable runnable)
|
||||
{
|
||||
this.changeListeners = new ArrayList<>(CONFIG_ENTRIES.size());
|
||||
for (Entry entry : CONFIG_ENTRIES.values())
|
||||
{
|
||||
this.changeListeners.add(new ConfigChangeListener<>(entry.supplier, ignored -> runnable.run()));
|
||||
this.changeListeners.add(entry.changeListenerFactory.apply(runnable));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
for (ConfigChangeListener<?> changeListener : this.changeListeners)
|
||||
for (Closeable changeListener : this.changeListeners)
|
||||
{
|
||||
changeListener.close();
|
||||
try
|
||||
{
|
||||
changeListener.close();
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
this.changeListeners.clear();
|
||||
}
|
||||
|
||||
+3
-2
@@ -9,7 +9,7 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMe
|
||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -36,7 +36,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
||||
{
|
||||
if (message.isFirst)
|
||||
{
|
||||
composite = UnpooledByteBufAllocator.DEFAULT.compositeBuffer();
|
||||
composite = Unpooled.compositeBuffer();
|
||||
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
|
||||
}
|
||||
else if (composite == null)
|
||||
@@ -45,6 +45,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
||||
return null;
|
||||
}
|
||||
|
||||
message.buffer.readerIndex(0);
|
||||
composite.addComponent(message.buffer);
|
||||
composite.writerIndex(composite.writerIndex() + message.buffer.writerIndex());
|
||||
LOGGER.debug("Updated full data buffer [" + message.bufferId + "]: [" + composite + "].");
|
||||
|
||||
+24
-8
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
+12
-12
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.pooling;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
@@ -12,7 +13,6 @@ import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -159,6 +159,7 @@ public class PhantomArrayListPool
|
||||
{
|
||||
// pool is empty, create new checkout
|
||||
checkout = new PhantomArrayListCheckout(this);
|
||||
checkout.onCheckout();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -166,6 +167,7 @@ public class PhantomArrayListPool
|
||||
if (checkout != null)
|
||||
{
|
||||
// use pooled checkout
|
||||
checkout.onCheckout();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -176,8 +178,7 @@ public class PhantomArrayListPool
|
||||
{
|
||||
lowMemoryWarningLogged = true;
|
||||
|
||||
// orange text
|
||||
String message = "\u00A76" + "Distant Horizons: Insufficient memory detected." + "\u00A7r \n" +
|
||||
String message = MinecraftTextFormat.ORANGE + "Distant Horizons: Insufficient memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"This may cause stuttering or crashing. \n" +
|
||||
"Potential causes: \n" +
|
||||
"1. your allocated memory isn't high enough \n" +
|
||||
|
||||
@@ -23,7 +23,9 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/** immutable */
|
||||
@Deprecated // TODO why does this exist vs blockpos2d?
|
||||
public class Pos2D
|
||||
{
|
||||
public static final Pos2D ZERO = new Pos2D(0, 0);
|
||||
|
||||
@@ -20,10 +20,13 @@
|
||||
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.DhClientServerLevel;
|
||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
@@ -42,15 +45,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 +62,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 +79,12 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
*/
|
||||
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
|
||||
private final IDhClientLevel level;
|
||||
private final ReentrantLock treeReadWriteLock = new ReentrantLock();
|
||||
/**
|
||||
* Note: this doesn't lock all operations as some other threads/operations
|
||||
* that may traverse the tree while it's being modified.
|
||||
* IE {@link RenderBufferHandler} will walk through the tree each frame.
|
||||
*/
|
||||
private final ReentrantLock treeLock = new ReentrantLock();
|
||||
|
||||
private ArrayList<LodRenderSection> debugRenderSections = new ArrayList<>();
|
||||
private ArrayList<LodRenderSection> altDebugRenderSections = new ArrayList<>();
|
||||
@@ -88,7 +97,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 +116,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 +144,9 @@ 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);
|
||||
Config.Server.enableServerGeneration.addListener(this);
|
||||
|
||||
}
|
||||
|
||||
//endregion constructor
|
||||
@@ -157,8 +173,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
|
||||
|
||||
|
||||
// don't traverse the tree if it is being modified
|
||||
if (this.treeReadWriteLock.tryLock())
|
||||
// don't tick the tree if a modification is still going
|
||||
// TODO is this lock necessary for anything beyond this tick method?
|
||||
if (this.treeLock.tryLock())
|
||||
{
|
||||
// this shouldn't be updated while the tree is being iterated through
|
||||
this.updateDetailLevelVariables();
|
||||
@@ -186,7 +203,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.treeReadWriteLock.unlock();
|
||||
this.treeLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,18 +248,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 +493,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
|
||||
// since this section wants to render
|
||||
// check if it needs any generation to do so
|
||||
LongArrayList missingPosList = this.fullDataSourceProvider.getPositionsToRetrieve(renderSection.pos);
|
||||
if (missingPosList != null)
|
||||
{
|
||||
for (int i = 0; i < missingPosList.size(); i++)
|
||||
{
|
||||
long missingPos = missingPosList.getLong(i);
|
||||
if (!this.queuedGenerationPosSet.contains(missingPos))
|
||||
{
|
||||
this.missingGenerationPosSet.add(missingPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.tryQueuePosForRetrieval(renderSection.pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,28 +516,23 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
// the section only needs to be updated if a buffer is currently present
|
||||
LodRenderSection renderSection = this.tryGetValue(pos);
|
||||
if (renderSection != null)
|
||||
{
|
||||
// the section only needs to be updated if a buffer is currently present
|
||||
LodRenderSection renderSection = this.getValue(pos);
|
||||
if (renderSection != null)
|
||||
if (renderSection.canRender())
|
||||
{
|
||||
if (renderSection.canRender())
|
||||
if (renderSection.gpuUploadInProgress()
|
||||
|| !renderSection.uploadRenderDataToGpuAsync())
|
||||
{
|
||||
if (renderSection.gpuUploadInProgress()
|
||||
|| !renderSection.uploadRenderDataToGpuAsync())
|
||||
{
|
||||
// if a section is already loading or failed to start upload
|
||||
// we need to wait to trigger it again
|
||||
// if we don't trigger it again the LOD will be out of date
|
||||
// and may be invisible/missing
|
||||
positionsToRequeue.add(pos);
|
||||
}
|
||||
// if a section is already loading or failed to start upload
|
||||
// we need to wait to trigger it again
|
||||
// if we don't trigger it again the LOD will be out of date
|
||||
// and may be invisible/missing
|
||||
positionsToRequeue.add(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfBoundsException e)
|
||||
{ /* the section is now out of bounds, it doesn't need to be reloaded */ }
|
||||
}
|
||||
this.sectionsToReload.addAll(positionsToRequeue);
|
||||
}
|
||||
@@ -523,6 +561,174 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
|
||||
|
||||
|
||||
//=================================//
|
||||
// full data retrieval (world gen) //
|
||||
//=================================//
|
||||
//region world gen
|
||||
|
||||
private void startQueuedRetrievalTasks(DhBlockPos2D playerPos)
|
||||
{
|
||||
// sort the nodes from nearest to farthest
|
||||
this.sortedMissingPosList.clear();
|
||||
this.sortedMissingPosList.addAll(this.missingGenerationPosSet);
|
||||
this.sortedMissingPosList.sort((posA, posB) ->
|
||||
{
|
||||
int aDist = DhSectionPos.getManhattanBlockDistance(posA, playerPos);
|
||||
int bDist = DhSectionPos.getManhattanBlockDistance(posB, playerPos);
|
||||
return Integer.compare(aDist, bDist);
|
||||
});
|
||||
|
||||
|
||||
|
||||
//==================================//
|
||||
// add retrieval tasks to the queue //
|
||||
//==================================//
|
||||
|
||||
for (int i = 0; i < this.sortedMissingPosList.size(); i++)
|
||||
{
|
||||
if (!this.fullDataSourceProvider.canQueueRetrievalNow())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
long missingPos = this.sortedMissingPosList.get(i);
|
||||
|
||||
// is this position within acceptable generator range?
|
||||
boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
|
||||
missingPos,
|
||||
Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(),
|
||||
Config.Common.WorldGenerator.generationMaxChunkRadius.get()
|
||||
);
|
||||
if (!posInRange)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CompletableFuture<DataSourceRetrievalResult> genFuture = this.fullDataSourceProvider.queuePositionForRetrieval(missingPos);
|
||||
boolean positionQueued = (genFuture != null && !genFuture.isCompletedExceptionally());
|
||||
if (positionQueued)
|
||||
{
|
||||
this.queuedGenerationPosSet.add(missingPos);
|
||||
this.missingGenerationPosSet.remove(missingPos);
|
||||
|
||||
genFuture.exceptionally((Throwable throwable) ->
|
||||
{
|
||||
// gen task failed,
|
||||
// requeue so we can try again in the future
|
||||
|
||||
this.queuedGenerationPosSet.remove(missingPos);
|
||||
this.missingGenerationPosSet.add(missingPos);
|
||||
return null;
|
||||
});
|
||||
genFuture.thenAccept((DataSourceRetrievalResult result) ->
|
||||
{
|
||||
// task finished
|
||||
this.queuedGenerationPosSet.remove(missingPos);
|
||||
|
||||
if (result.state == ERetrievalResultState.REQUIRES_SPLITTING)
|
||||
{
|
||||
DhSectionPos.forEachChild(missingPos, (long childPos) ->
|
||||
{
|
||||
this.tryQueuePosForRetrieval(childPos);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==========================//
|
||||
// calc task count estimate //
|
||||
//==========================//
|
||||
|
||||
// calculate an estimate for the max number of chunks for the queue
|
||||
int totalWorldGenChunkCount = 0;
|
||||
int totalWorldGenTaskCount = 0;
|
||||
for (int i = 0; i < this.sortedMissingPosList.size(); i++)
|
||||
{
|
||||
long missingPos = this.sortedMissingPosList.get(i);
|
||||
|
||||
// chunk count
|
||||
int sectionWidthInChunks = DhSectionPos.getChunkWidth(missingPos);
|
||||
totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks;
|
||||
|
||||
// task count
|
||||
totalWorldGenTaskCount++;
|
||||
}
|
||||
|
||||
this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount);
|
||||
this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigValueSet()
|
||||
{
|
||||
boolean generatorEnabled = this.level instanceof DhClientServerLevel
|
||||
? Config.Common.WorldGenerator.enableDistantGeneration.get()
|
||||
: Config.Server.enableServerGeneration.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,16 @@ 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);
|
||||
Config.Server.enableServerGeneration.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 +950,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.treeReadWriteLock.unlock();
|
||||
this.treeLock.unlock();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
* contains the list of beacons currently being rendered in this section
|
||||
* if this list is modified the {@link LodRenderSection#beaconRenderHandler} should be updated to match.
|
||||
*/
|
||||
private final List<BeaconBeamDTO> activeBeaconList = new ArrayList<>();
|
||||
private final ArrayList<BeaconBeamDTO> activeBeaconList = new ArrayList<>();
|
||||
@Nullable
|
||||
public final BeaconRenderHandler beaconRenderHandler;
|
||||
@Nullable
|
||||
@@ -241,24 +241,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
// get the adjacent positions
|
||||
// needs to be done async to prevent threads waiting on the same positions to be processed
|
||||
final CompletableFuture<ColumnRenderSource>[] adjacentLoadFutures = new CompletableFuture[4];
|
||||
|
||||
if (Config.Client.Advanced.Graphics.Experimental.onlyLoadCenterLods.get())
|
||||
{
|
||||
// TODO temporary test, long term something else should be done to so we can get adjacent lighting data
|
||||
// probably a change to the LOD data format
|
||||
adjacentLoadFutures[0] = CompletableFuture.completedFuture(null);
|
||||
adjacentLoadFutures[1] = CompletableFuture.completedFuture(null);
|
||||
adjacentLoadFutures[2] = CompletableFuture.completedFuture(null);
|
||||
adjacentLoadFutures[3] = CompletableFuture.completedFuture(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
adjacentLoadFutures[0] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.NORTH);
|
||||
adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.SOUTH);
|
||||
adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.EAST);
|
||||
adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.WEST);
|
||||
}
|
||||
|
||||
adjacentLoadFutures[0] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.NORTH);
|
||||
adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.SOUTH);
|
||||
adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.EAST);
|
||||
adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.WEST);
|
||||
return CompletableFuture.allOf(adjacentLoadFutures).thenRun(() ->
|
||||
{
|
||||
try (ColumnRenderSource northRenderSource = adjacentLoadFutures[0].get();
|
||||
@@ -460,20 +446,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
|
||||
// stop rendering current beacons
|
||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
||||
{
|
||||
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
|
||||
}
|
||||
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
|
||||
|
||||
// swap old and new active beacon list
|
||||
this.activeBeaconList.clear();
|
||||
this.activeBeaconList.addAll(activeBeacons);
|
||||
|
||||
// start rendering new beacon list
|
||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
||||
{
|
||||
this.beaconRenderHandler.startRenderingBeacon(beam);
|
||||
}
|
||||
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,10 +469,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
synchronized (this.activeBeaconList)
|
||||
{
|
||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
||||
{
|
||||
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
|
||||
}
|
||||
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,10 +484,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
synchronized (this.activeBeaconList)
|
||||
{
|
||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
||||
{
|
||||
this.beaconRenderHandler.startRenderingBeacon(beam);
|
||||
}
|
||||
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+30
-21
@@ -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());
|
||||
|
||||
@@ -434,7 +434,7 @@ public class LodRenderer
|
||||
|
||||
// resize the textures if needed
|
||||
if (MC_RENDER.getTargetFramebufferViewportWidth() != this.textureWidth
|
||||
|| MC_RENDER.getTargetFramebufferViewportHeight() != this.textureHeight)
|
||||
|| MC_RENDER.getTargetFramebufferViewportHeight() != this.textureHeight)
|
||||
{
|
||||
// just resizing the textures doesn't work when Optifine is present,
|
||||
// so recreate the textures with the new size instead
|
||||
@@ -536,7 +536,7 @@ public class LodRenderer
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "deprecation" )
|
||||
@SuppressWarnings( "deprecation" ) // done to ignore DhApiColorDepthTextureCreatedEvent
|
||||
private void createAndBindTextures()
|
||||
{
|
||||
int oldWidth = this.textureWidth;
|
||||
|
||||
+3
-1
@@ -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();
|
||||
|
||||
+99
-12
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShad
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
@@ -39,8 +40,7 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -54,6 +54,8 @@ public class BeaconRenderHandler
|
||||
/** how often should we check if a beacon should be culled? */
|
||||
private static final int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
|
||||
|
||||
private static final Comparator<BeaconBeamDTO> NEGATIVE_BLOCKPOS_COMPARATOR = new NegativeInfiniteBlockPosComparator();
|
||||
|
||||
|
||||
|
||||
private final ReentrantLock updateLock = new ReentrantLock();
|
||||
@@ -89,20 +91,74 @@ public class BeaconRenderHandler
|
||||
// render handling //
|
||||
//=================//
|
||||
|
||||
public void startRenderingBeacon(BeaconBeamDTO beacon)
|
||||
public void startRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList, byte detailLevel)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.updateLock.lock();
|
||||
|
||||
if (this.beaconBlockPosSet.add(beacon.blockPos))
|
||||
|
||||
// how wide should each beacon be?
|
||||
int beaconBlockWidth = 1;
|
||||
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
||||
{
|
||||
beaconBlockWidth = DhSectionPos.getBlockWidth(detailLevel);
|
||||
}
|
||||
|
||||
|
||||
ArrayList<BeaconBeamDTO> sortedBeaconList = new ArrayList<>(beaconList);
|
||||
|
||||
// merge distant beams if requested
|
||||
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
||||
{
|
||||
// sort beacons from neg inf -> pos inf
|
||||
// so we can consistently merge adjacent beacons
|
||||
sortedBeaconList.sort(NEGATIVE_BLOCKPOS_COMPARATOR);
|
||||
|
||||
// go through each beacon...
|
||||
for (int outerIndex = 0; outerIndex < sortedBeaconList.size(); outerIndex++)
|
||||
{
|
||||
BeaconBeamDTO outerBeacon = sortedBeaconList.get(outerIndex);
|
||||
DhBlockPos outerBlockPos = outerBeacon.blockPos;
|
||||
|
||||
// ...and remove any beacons that are within the block width to prevent overlaps
|
||||
for (int mergeIndex = outerIndex + 1; mergeIndex < sortedBeaconList.size(); mergeIndex++)
|
||||
{
|
||||
BeaconBeamDTO beaconToMerge = sortedBeaconList.get(mergeIndex);
|
||||
DhBlockPos mergeBlockPos = beaconToMerge.blockPos;
|
||||
|
||||
int xDiff = mergeBlockPos.getX() - outerBlockPos.getX();
|
||||
int zDiff = mergeBlockPos.getZ() - outerBlockPos.getZ();
|
||||
|
||||
// merge (remove) this beacon if
|
||||
// it's close to the outer beacon
|
||||
if (xDiff < beaconBlockWidth
|
||||
&& zDiff < beaconBlockWidth)
|
||||
{
|
||||
sortedBeaconList.remove(mergeIndex);
|
||||
mergeIndex--; // minus 1 so we don't go past the end of the array when incrementing in the for loop up top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// add each beacon to the renderer
|
||||
for (int i = 0; i < sortedBeaconList.size(); i++)
|
||||
{
|
||||
BeaconBeamDTO beacon = sortedBeaconList.get(i);
|
||||
if (!this.beaconBlockPosSet.add(beacon.blockPos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int maxBeaconBeamHeight = Config.Client.Advanced.Graphics.GenericRendering.beaconRenderHeight.get();
|
||||
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
|
||||
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
|
||||
new DhApiVec3d(beacon.blockPos.getX() + 1, maxBeaconBeamHeight, beacon.blockPos.getZ() + 1),
|
||||
beacon.color,
|
||||
EDhApiBlockMaterial.ILLUMINATED
|
||||
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
|
||||
new DhApiVec3d(beacon.blockPos.getX() + beaconBlockWidth, maxBeaconBeamHeight, beacon.blockPos.getZ() + beaconBlockWidth),
|
||||
beacon.color,
|
||||
EDhApiBlockMaterial.ILLUMINATED
|
||||
);
|
||||
|
||||
this.beaconBoxGroup.add(beaconBox);
|
||||
@@ -116,19 +172,26 @@ public class BeaconRenderHandler
|
||||
}
|
||||
}
|
||||
|
||||
public void stopRenderingBeaconAtPos(DhBlockPos beaconPos)
|
||||
public void stopRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.updateLock.lock();
|
||||
|
||||
if (this.beaconBlockPosSet.remove(beaconPos))
|
||||
for (int i = 0; i < beaconList.size(); i++)
|
||||
{
|
||||
BeaconBeamDTO beacon = beaconList.get(i);
|
||||
DhBlockPos beaconPos = beacon.blockPos;
|
||||
if (!this.beaconBlockPosSet.remove(beaconPos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Predicate<DhApiRenderableBox> removePredicate = (DhApiRenderableBox box) ->
|
||||
{
|
||||
return box.minPos.x == beaconPos.getX()
|
||||
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
|
||||
&& box.minPos.z == beaconPos.getZ();
|
||||
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
|
||||
&& box.minPos.z == beaconPos.getZ();
|
||||
};
|
||||
this.beaconBoxGroup.removeIf(removePredicate);
|
||||
this.fullBeaconBoxList.removeIf(removePredicate);
|
||||
@@ -255,4 +318,28 @@ public class BeaconRenderHandler
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
private static class NegativeInfiniteBlockPosComparator implements Comparator<BeaconBeamDTO>
|
||||
{
|
||||
@Override
|
||||
public int compare(BeaconBeamDTO beacon1, BeaconBeamDTO beacon2)
|
||||
{
|
||||
DhBlockPos blockPos1 = beacon1.blockPos;
|
||||
DhBlockPos blockPos2 = beacon2.blockPos;
|
||||
|
||||
// sort by X, then by Z
|
||||
if (blockPos1.getX() != blockPos2.getX())
|
||||
{
|
||||
return Integer.compare(blockPos1.getX(), blockPos2.getX());
|
||||
}
|
||||
return Integer.compare(blockPos1.getZ(), blockPos2.getZ());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -493,7 +493,7 @@ public class CloudRenderHandler
|
||||
|
||||
private static boolean[][] getCloudsFromTexture() throws FileNotFoundException, IOException
|
||||
{
|
||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
final ClassLoader loader = CloudRenderHandler.class.getClassLoader();
|
||||
|
||||
boolean[][] whitePixels = null;
|
||||
try(InputStream imageInputStream = loader.getResourceAsStream(CLOUD_RESOURCE_TEXTURE_PATH))
|
||||
|
||||
@@ -173,7 +173,7 @@ public class DatabaseUpdater
|
||||
/** @throws NullPointerException if any of the script files failed to be read. */
|
||||
private static ArrayList<SqlScript> getAutoUpdateScripts() throws NullPointerException, IOException
|
||||
{
|
||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
final ClassLoader loader = DatabaseUpdater.class.getClassLoader();
|
||||
|
||||
|
||||
// get the script list
|
||||
|
||||
@@ -76,6 +76,9 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>, INetworkObject
|
||||
public void close()
|
||||
{ /* no closing needed */ }
|
||||
|
||||
@Override
|
||||
public String toString() { return this.blockPos + " " + this.color; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+36
-50
@@ -31,12 +31,10 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||
import com.seibel.distanthorizons.core.util.gridList.MovableGridRingList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
/**
|
||||
@@ -98,9 +96,27 @@ public class QuadTree<T>
|
||||
// getters and setters //
|
||||
//=====================//
|
||||
|
||||
/** @return the value at the given section position. Null will be returned if the value is missing or the position is out of bounds. */
|
||||
@Nullable
|
||||
public final T tryGetValue(long pos)
|
||||
{
|
||||
QuadNode<T> node = this.tryGetNode(pos);
|
||||
if (node != null)
|
||||
{
|
||||
return node.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return the node at the given section position, null if out of bounds */
|
||||
@Nullable
|
||||
public final QuadNode<T> tryGetNode(long pos) { return this.getOrSetNode(pos, false, null, false); }
|
||||
|
||||
|
||||
/** @return the node at the given section position */
|
||||
@Nullable
|
||||
public final QuadNode<T> getNode(long pos) throws IndexOutOfBoundsException { return this.getOrSetNode(pos, false, null, true); }
|
||||
|
||||
/** @return the value at the given section position */
|
||||
@Nullable
|
||||
public final T getValue(long pos) throws IndexOutOfBoundsException
|
||||
@@ -122,16 +138,24 @@ public class QuadTree<T>
|
||||
return previousValue;
|
||||
}
|
||||
|
||||
/** @param runBoundaryChecks should only ever be set to true internally for removing out of bound nodes */
|
||||
/** @param throwIfOutOfBounds if false returns null */
|
||||
@Nullable
|
||||
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean runBoundaryChecks) throws IndexOutOfBoundsException
|
||||
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean throwIfOutOfBounds) throws IndexOutOfBoundsException
|
||||
{
|
||||
if (runBoundaryChecks && !this.isSectionPosInBounds(pos))
|
||||
if (!this.isSectionPosInBounds(pos))
|
||||
{
|
||||
int radius = this.diameterInBlocks() / 2;
|
||||
DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
|
||||
DhBlockPos2D maxPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
|
||||
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeLeafDetailLevel + ", max detail level: " + this.treeRootDetailLevel + ". Given Position: [" + DhSectionPos.toString(pos) + "] = block pos: " + DhSectionPos.convertToDetailLevel(pos, LodUtil.BLOCK_DETAIL_LEVEL));
|
||||
// how should out-of-bounds positions be handled?
|
||||
if (throwIfOutOfBounds)
|
||||
{
|
||||
int radius = this.diameterInBlocks() / 2;
|
||||
DhBlockPos2D minBlockPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
|
||||
DhBlockPos2D maxBlockPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
|
||||
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min block pos: [" + minBlockPos + "], max block pos: [" + maxBlockPos + "], leaf detail level: [" + this.treeLeafDetailLevel + "], root detail level: [" + this.treeRootDetailLevel + "]. Requested section pos: [" + DhSectionPos.toString(pos) + "].");
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -278,46 +302,6 @@ public class QuadTree<T>
|
||||
removedItemConsumer.accept(quadNode.value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// // remove out of bound nodes and clean up empty nodes
|
||||
// // Note: this will iterate over a lot of unnecessary nodes, hopefully speed won't be an issue
|
||||
// Iterator<DhSectionPos> rootNodePosIterator = this.rootNodePosIterator();
|
||||
// while (rootNodePosIterator.hasNext())
|
||||
// {
|
||||
// // get the root node (regular nodeIterators won't return them if they are out of bounds)
|
||||
// DhSectionPos rootPos = rootNodePosIterator.next();
|
||||
// QuadNode<T> rootNode = this.getOrSetNode(rootPos, false, null, false);
|
||||
// if (rootNode == null)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// // remove any child nodes that are out of bounds
|
||||
// Iterator<QuadNode<T>> nodeIterator = this.nodeIterator();
|
||||
// while (nodeIterator.hasNext())
|
||||
// {
|
||||
// QuadNode<T> node = nodeIterator.next();
|
||||
// if(!this.isSectionPosInBounds(node.sectionPos))
|
||||
// {
|
||||
// // node is out of bounds
|
||||
//
|
||||
// // FIXME(?) this appears to potentially return large nodes that are partially or entirely in bounds
|
||||
//
|
||||
// if (node.getNonNullChildCount() == 0)
|
||||
// {
|
||||
// // no child nodes, can be safely removed
|
||||
// nodeIterator.remove();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // node can't be removed, but its value can be set to null
|
||||
// node.value = null;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
|
||||
@@ -544,7 +528,9 @@ public class QuadTree<T>
|
||||
&& this.rootNodeIterator.hasNext())
|
||||
{
|
||||
long sectionPos = this.rootNodeIterator.nextLong();
|
||||
QuadNode<T> rootNode = QuadTree.this.getNode(sectionPos);
|
||||
|
||||
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
|
||||
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
|
||||
if (rootNode != null)
|
||||
{
|
||||
nodeIterator = this.onlyReturnLeaves ? rootNode.getLeafNodeIterator() : rootNode.getNodeIterator(this.stopIteratingFunc);
|
||||
|
||||
+5
-6
@@ -30,7 +30,7 @@ import java.util.function.Consumer;
|
||||
public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
{
|
||||
/** lowest numerical value, inclusive */
|
||||
private final byte highestDetailLevel;
|
||||
private final byte leafDetailLevel;
|
||||
|
||||
|
||||
private final Queue<QuadNode<T>> validNodesForDetailLevel = new ArrayDeque<>();
|
||||
@@ -48,8 +48,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
{
|
||||
this.onlyReturnLeafValues = onlyReturnLeafValues;
|
||||
this.stopIteratingFunc = stopIteratingFunc;
|
||||
// TODO the naming conversion for these are flipped in a lot of places
|
||||
this.highestDetailLevel = rootNode.parentTreeLeafDetailLevel;
|
||||
this.leafDetailLevel = rootNode.parentTreeLeafDetailLevel;
|
||||
this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos);
|
||||
|
||||
|
||||
@@ -110,9 +109,9 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
@Override
|
||||
public QuadNode<T> next()
|
||||
{
|
||||
if (this.iteratorDetailLevel < this.highestDetailLevel)
|
||||
if (this.iteratorDetailLevel < this.leafDetailLevel)
|
||||
{
|
||||
throw new NoSuchElementException("Highest detail level reached [" + this.highestDetailLevel + "].");
|
||||
throw new NoSuchElementException("Leaf detail level reached [" + this.leafDetailLevel + "].");
|
||||
}
|
||||
if (this.iteratorNodeQueue.size() == 0)
|
||||
{
|
||||
@@ -133,7 +132,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
|
||||
this.iteratorDetailLevel--;
|
||||
// only continue if we can go down farther
|
||||
if (this.iteratorDetailLevel >= this.highestDetailLevel)
|
||||
if (this.iteratorDetailLevel >= this.leafDetailLevel)
|
||||
{
|
||||
Queue<QuadNode<T>> parentNodes = new LinkedList<>(this.validNodesForDetailLevel);
|
||||
this.validNodesForDetailLevel.clear();
|
||||
|
||||
+7
-1
@@ -69,10 +69,14 @@ public class ThreadPoolUtil
|
||||
@Nullable
|
||||
public static ThreadPoolExecutor getBeaconCullingExecutor() { return beaconCullingThreadPool; }
|
||||
|
||||
// The main distinction between these thread pools is that one for compression has multiple threads and client handler is single-threaded
|
||||
private static PriorityTaskPicker.Executor networkCompressionThreadPool;
|
||||
@Nullable
|
||||
public static PriorityTaskPicker.Executor getNetworkCompressionExecutor() { return networkCompressionThreadPool; }
|
||||
|
||||
private static ThreadPoolExecutor networkClientHandlerThreadPool;
|
||||
@Nullable
|
||||
public static ThreadPoolExecutor networkClientHandlerExecutor() { return networkClientHandlerThreadPool; }
|
||||
|
||||
|
||||
public static final String FULL_DATA_MIGRATION_THREAD_NAME = "Full Data Migration";
|
||||
@@ -103,7 +107,8 @@ public class ThreadPoolUtil
|
||||
}
|
||||
taskPicker = new PriorityTaskPicker();
|
||||
|
||||
networkCompressionThreadPool = taskPicker.createExecutor("Network");
|
||||
networkCompressionThreadPool = taskPicker.createExecutor("Network Compression");
|
||||
networkClientHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Network Client Handler");
|
||||
fileHandlerThreadPool = taskPicker.createExecutor("IO");
|
||||
renderSectionLoadThreadPool = taskPicker.createExecutor("Render Loader");
|
||||
chunkToLodBuilderThreadPool = taskPicker.createExecutor("LOD Builder");
|
||||
@@ -133,6 +138,7 @@ public class ThreadPoolUtil
|
||||
public static void shutdownThreadPools()
|
||||
{
|
||||
// standalone threads
|
||||
networkClientHandlerThreadPool.shutdownNow();
|
||||
taskPicker.shutdownNow();
|
||||
beaconCullingThreadPool.shutdown();
|
||||
fullDataMigrationThreadPool.shutdown();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
|
||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
@@ -82,7 +83,7 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
||||
LOGGER.fatal("Failed to load client-server level, error: ["+e.getMessage()+"].", e);
|
||||
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
|
||||
"\u00A7c" + "Distant Horizons: ClientServer level loading failed." + "\u00A7r \n" +
|
||||
MinecraftTextFormat.RED + "Distant Horizons: ClientServer level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Unable to load level ["+levelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
|
||||
return null;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
@@ -94,9 +95,9 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
{
|
||||
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
|
||||
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
|
||||
"\u00A7c" + "Distant Horizons: Client level loading failed." + "\u00A7r \n" +
|
||||
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(
|
||||
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.level.DhServerLevel;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
@@ -62,9 +63,9 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
||||
{
|
||||
LOGGER.fatal("Failed to load server level, error: ["+e.getMessage()+"].", e);
|
||||
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
|
||||
"\u00A7c" + "Distant Horizons: Server level loading failed." + "\u00A7r \n" +
|
||||
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(
|
||||
MinecraftTextFormat.RED + "Distant Horizons: Server level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
+5
@@ -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();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
-2
@@ -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();
|
||||
|
||||
|
||||
@@ -227,6 +227,10 @@
|
||||
"Beacon render height",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip":
|
||||
"Sets the maximum height at which beacons will render. \nThis will only affect new beacons coming into LOD render distance. \nBeacons currently visible in LOD chunks will not be affected.",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons":
|
||||
"Expand Distant Beacons",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons.@tooltip":
|
||||
"If true LOD beacon beams will be rendered wider at extreme distances, \nmaking them easier to see. \nIf false all LOD beacon beams will only ever be 1 block wide.",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering.@tooltip":
|
||||
"If true LOD beacon beams will be rendered.",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableCloudRendering":
|
||||
@@ -400,14 +404,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 +721,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",
|
||||
@@ -758,6 +763,11 @@
|
||||
"distanthorizons.config.server.levelKeyPrefix.@tooltip":
|
||||
"Prefix of the level keys sent to the clients.\nIf the mod is running behind a proxy, each backend should use a unique value.\nIf this value is empty, level key will be based on the server's seed hash.",
|
||||
|
||||
"distanthorizons.config.server.enableServerGeneration":
|
||||
"Enable Server Generation",
|
||||
"distanthorizons.config.server.enableServerGeneration.@tooltip":
|
||||
"When enabled, Distant Horizons will attempt to download missing LODs from the server.\n\nNote: the server must have Distant Generation enabled for it to work.",
|
||||
|
||||
"distanthorizons.config.server.generationRequestRateLimit":
|
||||
"Rate Limit for Generation Requests",
|
||||
"distanthorizons.config.server.generationRequestRateLimit.@tooltip":
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
@@ -1,79 +0,0 @@
|
||||
#version 150 core
|
||||
|
||||
in uvec4 vPosition;
|
||||
out vec4 vPos;
|
||||
in vec4 color;
|
||||
|
||||
out vec4 vertexColor;
|
||||
out vec3 vertexWorldPos;
|
||||
out float vertexYPos;
|
||||
|
||||
uniform bool uWhiteWorld;
|
||||
|
||||
uniform mat4 uCombinedMatrix;
|
||||
uniform vec3 uModelOffset;
|
||||
uniform float uWorldYOffset;
|
||||
|
||||
uniform int uWorldSkyLight;
|
||||
uniform sampler2D uLightMap;
|
||||
uniform float uMircoOffset;
|
||||
|
||||
uniform float uEarthRadius;
|
||||
|
||||
/**
|
||||
* TODO in the future this and standard.vert should be merged together to prevent inconsistencies between the two
|
||||
*
|
||||
* Vertex Shader
|
||||
*
|
||||
* author: James Seibel
|
||||
* author: TomTheFurry
|
||||
* author: stduhpf
|
||||
* updated: coolGi
|
||||
* version: 24-1-2023
|
||||
*/
|
||||
void main()
|
||||
{
|
||||
vPos = vPosition; // This is so it can be passed to the fragment shader
|
||||
|
||||
vertexWorldPos = vPosition.xyz + uModelOffset;
|
||||
|
||||
vertexYPos = vPosition.y + uWorldYOffset;
|
||||
|
||||
uint meta = vPosition.a;
|
||||
|
||||
uint mirco = (meta & 0xFF00u) >> 8u; // mirco offset which is a xyz 2bit value
|
||||
// 0b00 = no offset
|
||||
// 0b01 = positive offset
|
||||
// 0b11 = negative offset
|
||||
// format is: 0b00zzyyxx
|
||||
float mx = (mirco & 1u) != 0u ? uMircoOffset : 0.0;
|
||||
mx = (mirco & 2u) != 0u ? -mx : mx;
|
||||
float my = (mirco & 4u) != 0u ? uMircoOffset : 0.0;
|
||||
my = (mirco & 8u) != 0u ? -my : my;
|
||||
float mz = (mirco & 16u) != 0u ? uMircoOffset : 0.0;
|
||||
mz = (mirco & 32u) != 0u ? -mz : mz;
|
||||
vertexWorldPos.x += mx;
|
||||
vertexWorldPos.y += my;
|
||||
vertexWorldPos.z += mz;
|
||||
|
||||
|
||||
// vertex transformation logic - stduhpf
|
||||
float localRadius = uEarthRadius + vertexYPos;
|
||||
float phi = length(vertexWorldPos.xz) / localRadius;
|
||||
vertexWorldPos.y += (cos(phi) - 1.0) * localRadius;
|
||||
vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi;
|
||||
|
||||
|
||||
uint lights = meta & 0xFFu;
|
||||
|
||||
float light2 = (mod(float(lights), 16.0) + 0.5) / 16.0;
|
||||
float light = (float(lights / 16u) + 0.5) / 16.0;
|
||||
vertexColor = vec4(texture(uLightMap, vec2(light, light2)).xyz, 1.0);
|
||||
|
||||
if (!uWhiteWorld)
|
||||
{
|
||||
vertexColor *= color;
|
||||
}
|
||||
|
||||
gl_Position = uCombinedMatrix * vec4(vertexWorldPos, 1.0);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user