Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a3c24f39e | |||
| 7b6fd03d78 | |||
| 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 | |||
| 2dd675b8da | |||
| ff3145336d | |||
| 280181c91e | |||
| 60232e713b | |||
| 55d9030954 | |||
| 452bd75f5d | |||
| 72be1e2602 | |||
| 1c30213aca | |||
| e9a044308f | |||
| 1aabc0c792 | |||
| 4a1513ed65 | |||
| 6d98c9cb84 | |||
| b1b0642fbe | |||
| eecb28d11f | |||
| 90564f2537 | |||
| ded0b979cf | |||
| ed9cc5485c | |||
| cbd5974657 | |||
| 0e5fba58ab | |||
| 2943e63382 | |||
| 30564aade7 |
+1
-1
@@ -169,7 +169,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
|||||||
*
|
*
|
||||||
* After the {@link IDhApiWorldGenerator} has been generated, it should be passed into the
|
* After the {@link IDhApiWorldGenerator} has been generated, it should be passed into the
|
||||||
* resultConsumer's {@link Consumer#accept(Object)} method.
|
* resultConsumer's {@link Consumer#accept(Object)} method.
|
||||||
* Note: if air blocks aren't included in the with the {@link DhApiChunk} with proper lighting, lower detail levels will appear as black/unlit.
|
* Note: if air blocks aren't included in the {@link IDhApiFullDataSource} with proper lighting, lower detail levels will appear as black/unlit.
|
||||||
*
|
*
|
||||||
* @implNote the default implementation of this method throws an {@link UnsupportedOperationException},
|
* @implNote the default implementation of this method throws an {@link UnsupportedOperationException},
|
||||||
* and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
|
* and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
|
||||||
|
|||||||
+15
-3
@@ -20,6 +20,8 @@
|
|||||||
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
|
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
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.methods.events.interfaces.IDhApiEventParam;
|
||||||
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
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.
|
* Contains information relevant to Distant Horizons and Minecraft rendering.
|
||||||
*
|
*
|
||||||
* @author James Seibel
|
* @author James Seibel
|
||||||
* @version 2024-1-31
|
* @version 2025-12-23
|
||||||
* @since API 1.0.0
|
* @since API 1.0.0
|
||||||
*/
|
*/
|
||||||
public class DhApiRenderParam implements IDhApiEventParam
|
public class DhApiRenderParam implements IDhApiEventParam
|
||||||
@@ -61,6 +63,13 @@ public class DhApiRenderParam implements IDhApiEventParam
|
|||||||
|
|
||||||
public final int worldYOffset;
|
public final int worldYOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The level currently being rendered.
|
||||||
|
*
|
||||||
|
* @since API 5.1.0
|
||||||
|
*/
|
||||||
|
public final IDhApiLevelWrapper clientLevelWrapper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
@@ -75,7 +84,8 @@ public class DhApiRenderParam implements IDhApiEventParam
|
|||||||
parent.nearClipPlane, parent.farClipPlane,
|
parent.nearClipPlane, parent.farClipPlane,
|
||||||
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
|
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
|
||||||
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
|
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
|
||||||
parent.worldYOffset
|
parent.worldYOffset,
|
||||||
|
parent.clientLevelWrapper
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public DhApiRenderParam(
|
public DhApiRenderParam(
|
||||||
@@ -84,7 +94,8 @@ public class DhApiRenderParam implements IDhApiEventParam
|
|||||||
float nearClipPlane, float farClipPlane,
|
float nearClipPlane, float farClipPlane,
|
||||||
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
|
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
|
||||||
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
|
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
|
||||||
int worldYOffset
|
int worldYOffset,
|
||||||
|
IDhApiLevelWrapper clientLevelWrapper
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.renderPass = renderPass;
|
this.renderPass = renderPass;
|
||||||
@@ -101,6 +112,7 @@ public class DhApiRenderParam implements IDhApiEventParam
|
|||||||
this.dhModelViewMatrix = newDhModelViewMatrix;
|
this.dhModelViewMatrix = newDhModelViewMatrix;
|
||||||
|
|
||||||
this.worldYOffset = worldYOffset;
|
this.worldYOffset = worldYOffset;
|
||||||
|
this.clientLevelWrapper = clientLevelWrapper;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ public final class ModInfo
|
|||||||
public static final String NAME = "DistantHorizons";
|
public static final String NAME = "DistantHorizons";
|
||||||
/** Human-readable version of NAME */
|
/** Human-readable version of NAME */
|
||||||
public static final String READABLE_NAME = "Distant Horizons";
|
public static final String READABLE_NAME = "Distant Horizons";
|
||||||
public static final String VERSION = "2.4.2-b";
|
public static final String VERSION = "2.4.6-b-dev";
|
||||||
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
||||||
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
||||||
|
|
||||||
/** This version should only be updated when breaking changes are introduced to the DH API */
|
/** This version should only be updated when breaking changes are introduced to the DH API */
|
||||||
public static final int API_MAJOR_VERSION = 5;
|
public static final int API_MAJOR_VERSION = 5;
|
||||||
/** This version should be updated whenever new methods are added to the DH API */
|
/** This version should be updated whenever new methods are added to the DH API */
|
||||||
public static final int API_MINOR_VERSION = 0;
|
public static final int API_MINOR_VERSION = 1;
|
||||||
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
||||||
public static final int API_PATCH_VERSION = 0;
|
public static final int API_PATCH_VERSION = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,9 @@
|
|||||||
package com.seibel.distanthorizons.core;
|
package com.seibel.distanthorizons.core;
|
||||||
|
|
||||||
import com.github.luben.zstd.ZstdOutputStream;
|
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.Config;
|
||||||
|
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
|
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.api.DhApiConfigValue;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.config.api.converters.ApiFogDrawModeConverter;
|
import com.seibel.distanthorizons.core.config.api.converters.ApiFogDrawModeConverter;
|
||||||
|
import com.seibel.distanthorizons.core.config.api.converters.InvertedBoolConverter;
|
||||||
|
|
||||||
public class DhApiFogConfig implements IDhApiFogConfig
|
public class DhApiFogConfig implements IDhApiFogConfig
|
||||||
{
|
{
|
||||||
@@ -67,7 +68,7 @@ public class DhApiFogConfig implements IDhApiFogConfig
|
|||||||
@Override
|
@Override
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public IDhApiConfigValue<Boolean> disableVanillaFog()
|
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
|
@Override
|
||||||
public IDhApiConfigValue<Boolean> enableVanillaFog()
|
public IDhApiConfigValue<Boolean> enableVanillaFog()
|
||||||
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog); }
|
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog); }
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ import com.seibel.distanthorizons.api.DhApi;
|
|||||||
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
|
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
|
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
|
||||||
|
import com.seibel.distanthorizons.api.objects.DhApiResult;
|
||||||
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
||||||
|
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
|
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
|
||||||
@@ -31,21 +33,21 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
|||||||
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.*;
|
import com.seibel.distanthorizons.core.render.renderer.*;
|
||||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
||||||
import com.seibel.distanthorizons.core.util.objects.Pair;
|
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||||
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||||
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
|
|
||||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
||||||
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
||||||
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
||||||
import com.seibel.distanthorizons.core.world.DhClientWorld;
|
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.chunk.IChunkWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||||
@@ -53,13 +55,23 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
|
|||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Vector4f;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.lwjgl.opengl.GL32;
|
||||||
|
import org.lwjgl.opengl.GL46;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.management.GarbageCollectorMXBean;
|
import java.io.IOException;
|
||||||
import java.lang.management.ManagementFactory;
|
import java.text.NumberFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This holds the methods that should be called
|
* This holds the methods that should be called
|
||||||
@@ -95,7 +107,6 @@ public class ClientApi
|
|||||||
private boolean isDevBuildMessagePrinted = false;
|
private boolean isDevBuildMessagePrinted = false;
|
||||||
private boolean lowMemoryWarningPrinted = false;
|
private boolean lowMemoryWarningPrinted = false;
|
||||||
private boolean highVanillaRenderDistanceWarningPrinted = false;
|
private boolean highVanillaRenderDistanceWarningPrinted = false;
|
||||||
private boolean g1GarbageCollectorWarningPrinted = false;
|
|
||||||
|
|
||||||
private long lastStaticWarningMessageSentMsTime = 0L;
|
private long lastStaticWarningMessageSentMsTime = 0L;
|
||||||
|
|
||||||
@@ -156,10 +167,10 @@ public class ClientApi
|
|||||||
|
|
||||||
if (Config.Common.Logging.Warning.showReplayWarningOnStartup.get())
|
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("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("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("This can be disabled in DH's config under Advanced -> Logging.");
|
||||||
MC_CLIENT.sendChatMessage("");
|
MC_CLIENT.sendChatMessage("");
|
||||||
}
|
}
|
||||||
@@ -245,7 +256,7 @@ public class ClientApi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clientLevelLoadEvent(IClientLevelWrapper levelWrapper)
|
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
|
||||||
{
|
{
|
||||||
// wait a moment before loading the level to give the server a chance to handle the client's login request
|
// wait a moment before loading the level to give the server a chance to handle the client's login request
|
||||||
if (MC_CLIENT.clientConnectedToDedicatedServer())
|
if (MC_CLIENT.clientConnectedToDedicatedServer())
|
||||||
@@ -331,12 +342,29 @@ public class ClientApi
|
|||||||
* @see MessageRegistry
|
* @see MessageRegistry
|
||||||
*/
|
*/
|
||||||
public void pluginMessageReceived(@NotNull AbstractNetworkMessage message)
|
public void pluginMessageReceived(@NotNull AbstractNetworkMessage message)
|
||||||
|
{
|
||||||
|
@Nullable ThreadPoolExecutor executor = ThreadPoolUtil.networkClientHandlerExecutor();
|
||||||
|
if (executor == null)
|
||||||
|
{
|
||||||
|
LOGGER.warn("warn");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
executor.execute(() ->
|
||||||
{
|
{
|
||||||
NetworkSession networkSession = this.pluginChannelApi.networkSession;
|
NetworkSession networkSession = this.pluginChannelApi.networkSession;
|
||||||
if (networkSession != null)
|
if (networkSession != null)
|
||||||
{
|
{
|
||||||
networkSession.tryHandleMessage(message);
|
networkSession.tryHandleMessage(message);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (RejectedExecutionException e)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Plugin message executor rejected");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -379,8 +407,11 @@ public class ClientApi
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// make sure the GLProxy is created for future use
|
||||||
|
GLProxy.getInstance();
|
||||||
|
|
||||||
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
|
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
|
||||||
GLProxy.getInstance().runRenderThreadTasks();
|
GLProxy.runRenderThreadTasks();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -508,10 +539,10 @@ public class ClientApi
|
|||||||
this.rendererDisabledBecauseOfExceptions = true;
|
this.rendererDisabledBecauseOfExceptions = true;
|
||||||
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
|
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(MinecraftTextFormat.DARK_RED + "" + MinecraftTextFormat.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||||
MC_CLIENT.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption.");
|
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Renderer disabled to try preventing GL state corruption." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||||
MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
|
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Toggle DH rendering via the config UI to re-activate DH rendering." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||||
MC_CLIENT.sendChatMessage("\u00A74Error: " + e);
|
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -530,8 +561,11 @@ public class ClientApi
|
|||||||
* The first fade pass.
|
* The first fade pass.
|
||||||
* Called after MC finishes rendering the opaque passes.
|
* Called after MC finishes rendering the opaque passes.
|
||||||
*/
|
*/
|
||||||
public void renderFadeOpaque()
|
public void renderFadeOpaque() // TODO this is actually the transparent pass
|
||||||
{
|
{
|
||||||
|
DepthCalculator.INSTANCE.getMcTransparentDepthTexture();
|
||||||
|
DepthCalculator.INSTANCE.tryCalculateAsync();
|
||||||
|
|
||||||
// only fade when DH is rendering
|
// only fade when DH is rendering
|
||||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
|
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
|
||||||
&&
|
&&
|
||||||
@@ -552,8 +586,10 @@ public class ClientApi
|
|||||||
* Called after MC finishes rendering both opaque
|
* Called after MC finishes rendering both opaque
|
||||||
* and transparent passes.
|
* and transparent passes.
|
||||||
*/
|
*/
|
||||||
public void renderFadeTransparent()
|
public void renderFadeTransparent() // TODO this is actually the opaque pass
|
||||||
{
|
{
|
||||||
|
DepthCalculator.INSTANCE.getMcOpaqueDepthTexture();
|
||||||
|
|
||||||
// only fade when DH is rendering
|
// only fade when DH is rendering
|
||||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
|
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
|
||||||
{
|
{
|
||||||
@@ -582,27 +618,30 @@ public class ClientApi
|
|||||||
/** Trigger once on key press, with CLIENT PLAYER. */
|
/** Trigger once on key press, with CLIENT PLAYER. */
|
||||||
public void keyPressedEvent(int glfwKey)
|
public void keyPressedEvent(int glfwKey)
|
||||||
{
|
{
|
||||||
if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get())
|
//if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get())
|
||||||
{
|
//{
|
||||||
// keybindings are disabled
|
// // keybindings are disabled
|
||||||
return;
|
// return;
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
|
||||||
if (glfwKey == GLFW.GLFW_KEY_F8)
|
if (glfwKey == GLFW.GLFW_KEY_F8)
|
||||||
{
|
{
|
||||||
Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
|
DepthCalculator.INSTANCE.pause = true;
|
||||||
MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
|
//Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
|
||||||
|
//MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
|
||||||
}
|
}
|
||||||
else if (glfwKey == GLFW.GLFW_KEY_F6)
|
else if (glfwKey == GLFW.GLFW_KEY_F6)
|
||||||
{
|
{
|
||||||
Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
|
DepthCalculator.INSTANCE.pause = true;
|
||||||
MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
|
//Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
|
||||||
|
//MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
|
||||||
}
|
}
|
||||||
else if (glfwKey == GLFW.GLFW_KEY_P)
|
else if (glfwKey == GLFW.GLFW_KEY_P)
|
||||||
{
|
{
|
||||||
prefLoggerEnabled = !prefLoggerEnabled;
|
DepthCalculator.INSTANCE.pause = true;
|
||||||
MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
|
//prefLoggerEnabled = !prefLoggerEnabled;
|
||||||
|
//MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,8 +695,7 @@ public class ClientApi
|
|||||||
|
|
||||||
// remind the user that this is a development build
|
// remind the user that this is a development build
|
||||||
String message =
|
String message =
|
||||||
// green text
|
MinecraftTextFormat.DARK_GREEN + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||||
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" +
|
|
||||||
"Issues may occur with this version.\n" +
|
"Issues may occur with this version.\n" +
|
||||||
"Here be dragons!\n";
|
"Here be dragons!\n";
|
||||||
MC_CLIENT.sendChatMessage(message);
|
MC_CLIENT.sendChatMessage(message);
|
||||||
@@ -681,7 +719,7 @@ public class ClientApi
|
|||||||
{
|
{
|
||||||
String message =
|
String message =
|
||||||
// orange text
|
// 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" +
|
"Stuttering or low FPS may occur. \n" +
|
||||||
"Please increase Minecraft's available memory to 4 GB or more. \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";
|
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
|
||||||
@@ -703,14 +741,12 @@ public class ClientApi
|
|||||||
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
|
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
|
||||||
|
|
||||||
String message =
|
String message =
|
||||||
// yellow text
|
MinecraftTextFormat.YELLOW + "Distant Horizons: High vanilla render distance detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||||
"\u00A7e" + "Distant Horizons: High vanilla render distance detected." + "\u00A7r \n" +
|
|
||||||
"Using a high vanilla render distance uses a lot of CPU power \n" +
|
"Using a high vanilla render distance uses a lot of CPU power \n" +
|
||||||
"and doesn't improve graphics much after about 12.\n" +
|
"and doesn't improve graphics much after about 12.\n" +
|
||||||
"Lowing your vanilla render distance will give you better FPS\n" +
|
"Lowering your vanilla render distance will give you better FPS\n" +
|
||||||
"and reduce stuttering at a similar visual quality.\n" +
|
"and reduce stuttering at a similar visual quality.\n" +
|
||||||
// gray text
|
MinecraftTextFormat.GRAY + "A vanilla render distance of 8 is recommended." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||||
"\u00A77" + "A vanilla render distance of 8 is recommended." + "\u00A7r \n" +
|
|
||||||
"This message can be disabled in DH's config under Advanced -> Logging.\n";
|
"This message can be disabled in DH's config under Advanced -> Logging.\n";
|
||||||
MC_CLIENT.sendChatMessage(message);
|
MC_CLIENT.sendChatMessage(message);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -90,7 +90,7 @@ public class ClientPluginChannelApi
|
|||||||
|
|
||||||
LOGGER.info("Server level key received: [" + msg.levelKey + "].");
|
LOGGER.info("Server level key received: [" + msg.levelKey + "].");
|
||||||
|
|
||||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
GLProxy.queueRunningOnRenderThread(() ->
|
||||||
{
|
{
|
||||||
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
|
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
|
||||||
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
||||||
|
|||||||
@@ -82,15 +82,15 @@ public class ServerApi
|
|||||||
// level events //
|
// 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();
|
AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld();
|
||||||
if (serverWorld != null)
|
if (serverWorld != null)
|
||||||
{
|
{
|
||||||
serverWorld.getOrLoadLevel(level);
|
serverWorld.getOrLoadLevel(levelWrapper);
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
|
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void serverLevelUnloadEvent(IServerLevelWrapper level)
|
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.ChunkUpdateData;
|
||||||
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager;
|
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
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.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
||||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
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 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 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)
|
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded)
|
||||||
{
|
{
|
||||||
//========================//
|
//===================//
|
||||||
// world and level checks //
|
// validation checks //
|
||||||
//========================//
|
//===================//
|
||||||
|
|
||||||
if (chunkWrapper == null)
|
if (chunkWrapper == null)
|
||||||
{
|
{
|
||||||
@@ -217,7 +217,6 @@ public class SharedApi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// only continue if the level is loaded
|
// only continue if the level is loaded
|
||||||
IDhLevel dhLevel = dhWorld.getLevel(level);
|
IDhLevel dhLevel = dhWorld.getLevel(level);
|
||||||
if (dhLevel == null)
|
if (dhLevel == null)
|
||||||
@@ -232,6 +231,7 @@ public class SharedApi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore chunk updates if the network should handle them
|
||||||
if (dhLevel instanceof DhClientLevel)
|
if (dhLevel instanceof DhClientLevel)
|
||||||
{
|
{
|
||||||
if (!((DhClientLevel) dhLevel).shouldProcessChunkUpdate(chunkWrapper.getChunkPos()))
|
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()))
|
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
|
||||||
{
|
{
|
||||||
// TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded
|
// TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded
|
||||||
@@ -248,85 +255,11 @@ public class SharedApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
queueChunkUpdate(chunkWrapper, dhLevel);
|
||||||
//===============================//
|
|
||||||
// update the necessary chunk(s) //
|
|
||||||
//===============================//
|
|
||||||
|
|
||||||
if (!canGetNeighboringChunks)
|
|
||||||
{
|
|
||||||
// only update the center chunk
|
|
||||||
queueChunkUpdate(chunkWrapper, null, dhLevel, false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighboringChunks)
|
|
||||||
{
|
|
||||||
|
|
||||||
// return if the chunk is already queued
|
// return if the chunk is already queued
|
||||||
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
|
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
|
||||||
{
|
{
|
||||||
@@ -335,7 +268,7 @@ public class SharedApi
|
|||||||
|
|
||||||
|
|
||||||
// add chunk update data to preUpdate queue
|
// 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);
|
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)
|
// (this prevents doing extra work queuing tasks that may not be necessary)
|
||||||
// and makes sure the chunks closest to the player are updated first
|
// and makes sure the chunks closest to the player are updated first
|
||||||
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
|
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
|
||||||
if (executor != null && executor.getQueueSize() < executor.getPoolSize())
|
if (executor != null
|
||||||
|
&& executor.getQueueSize() < executor.getPoolSize())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -383,10 +317,7 @@ public class SharedApi
|
|||||||
// update the necessary chunk(s) //
|
// update the necessary chunk(s) //
|
||||||
//===============================//
|
//===============================//
|
||||||
|
|
||||||
// process preUpdate queue
|
|
||||||
processQueuedChunkPreUpdate();
|
processQueuedChunkPreUpdate();
|
||||||
|
|
||||||
// process update queue
|
|
||||||
processQueuedChunkUpdate();
|
processQueuedChunkUpdate();
|
||||||
|
|
||||||
// queue the next position if there are still positions to process
|
// queue the next position if there are still positions to process
|
||||||
@@ -415,8 +346,7 @@ public class SharedApi
|
|||||||
|
|
||||||
IDhLevel dhLevel = preUpdateData.dhLevel;
|
IDhLevel dhLevel = preUpdateData.dhLevel;
|
||||||
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
|
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
|
||||||
boolean canGetNeighboringChunks = preUpdateData.canGetNeighboringChunks;
|
chunkWrapper.createDhHeightMaps();
|
||||||
ArrayList<IChunkWrapper> neighborChunkList = preUpdateData.neighborChunkList;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -433,34 +363,6 @@ public class SharedApi
|
|||||||
// do not update the chunk if the hash is the same
|
// do not update the chunk if the hash is the same
|
||||||
return;
|
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);
|
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
|
||||||
@@ -473,8 +375,6 @@ public class SharedApi
|
|||||||
|
|
||||||
private static void processQueuedChunkUpdate()
|
private static void processQueuedChunkUpdate()
|
||||||
{
|
{
|
||||||
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
|
|
||||||
|
|
||||||
ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
|
ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
|
||||||
if (updateData == null)
|
if (updateData == null)
|
||||||
{
|
{
|
||||||
@@ -484,15 +384,11 @@ public class SharedApi
|
|||||||
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
|
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
|
||||||
IDhLevel dhLevel = updateData.dhLevel;
|
IDhLevel dhLevel = updateData.dhLevel;
|
||||||
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
|
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
|
// having a list of the nearby chunks is needed for lighting and beacon generation
|
||||||
if (nearbyChunkList == null)
|
ArrayList<IChunkWrapper> nearbyChunkList = tryGetNeighborChunkListForChunk(chunkWrapper);
|
||||||
{
|
|
||||||
nearbyChunkList = new ArrayList<IChunkWrapper>();
|
|
||||||
nearbyChunkList.add(chunkWrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -508,6 +404,35 @@ public class SharedApi
|
|||||||
{
|
{
|
||||||
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
|
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.api.internal.SharedApi;
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -105,6 +106,7 @@ public class ChunkPosQueue
|
|||||||
this.furthestQueue.remove(closest);
|
this.furthestQueue.remove(closest);
|
||||||
return this.updateDataByChunkPos.remove(closest);
|
return this.updateDataByChunkPos.remove(closest);
|
||||||
}
|
}
|
||||||
|
@Nullable
|
||||||
public ChunkUpdateData popFurthest()
|
public ChunkUpdateData popFurthest()
|
||||||
{
|
{
|
||||||
if (this.furthestQueue.isEmpty())
|
if (this.furthestQueue.isEmpty())
|
||||||
|
|||||||
+1
-6
@@ -9,18 +9,13 @@ import java.util.ArrayList;
|
|||||||
public class ChunkUpdateData
|
public class ChunkUpdateData
|
||||||
{
|
{
|
||||||
public IChunkWrapper chunkWrapper;
|
public IChunkWrapper chunkWrapper;
|
||||||
@Nullable
|
|
||||||
public ArrayList<IChunkWrapper> neighborChunkList;
|
|
||||||
public IDhLevel dhLevel;
|
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.chunkWrapper = chunkWrapper;
|
||||||
this.neighborChunkList = neighborChunkList;
|
|
||||||
this.dhLevel = dhLevel;
|
this.dhLevel = dhLevel;
|
||||||
this.canGetNeighboringChunks = canGetNeighborChunks;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-26
@@ -1,16 +1,22 @@
|
|||||||
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
|
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.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
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.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||||
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
|
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
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.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class ChunkUpdateQueueManager
|
public class ChunkUpdateQueueManager
|
||||||
{
|
{
|
||||||
@@ -21,6 +27,12 @@ public class ChunkUpdateQueueManager
|
|||||||
public final ChunkPosQueue preUpdateQueue;
|
public final ChunkPosQueue preUpdateQueue;
|
||||||
public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
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;
|
public int maxSize = 500;
|
||||||
|
|
||||||
private static long lastOverloadedLogMessageMsTime = 0;
|
private static long lastOverloadedLogMessageMsTime = 0;
|
||||||
@@ -68,41 +80,27 @@ public class ChunkUpdateQueueManager
|
|||||||
* If there are no more slots, replaces the item furthest from the center in the update queue.
|
* If there are no more slots, replaces the item furthest from the center in the update queue.
|
||||||
*/
|
*/
|
||||||
public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
|
public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
|
||||||
{
|
{ this.addItemToQueue(pos, updateData, this.preUpdateQueue); }
|
||||||
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())
|
|
||||||
{
|
|
||||||
this.updateQueue.popFurthest();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.preUpdateQueue.popFurthest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.preUpdateQueue.addItem(pos, updateData);
|
|
||||||
|
|
||||||
remainingSlots = this.maxSize - this.getQueuedCount();
|
|
||||||
if (remainingSlots <= 0)
|
|
||||||
{
|
|
||||||
this.sendOverloadMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
|
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();
|
int remainingSlots = this.maxSize - this.getQueuedCount();
|
||||||
|
|
||||||
// If no slots are left, get one by removing the item furthest from the center
|
// If no slots are left, get one by removing the item furthest from the center
|
||||||
if (remainingSlots <= 0)
|
if (remainingSlots <= 0)
|
||||||
{
|
{
|
||||||
this.updateQueue.popFurthest();
|
ChunkUpdateData removedData = queue.popFurthest();
|
||||||
|
if (removedData != null)
|
||||||
|
{
|
||||||
|
this.queuedChunkWrapperByChunkPos.remove(removedData.chunkWrapper.getChunkPos());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateQueue.addItem(pos,updateData);
|
queue.addItem(pos,updateData);
|
||||||
|
this.queuedChunkWrapperByChunkPos.putIfAbsent(pos, updateData.chunkWrapper);
|
||||||
|
|
||||||
remainingSlots = this.maxSize - this.getQueuedCount();
|
remainingSlots = this.maxSize - this.getQueuedCount();
|
||||||
if (remainingSlots <= 0)
|
if (remainingSlots <= 0)
|
||||||
@@ -111,6 +109,7 @@ public class ChunkUpdateQueueManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void sendOverloadMessage()
|
private void sendOverloadMessage()
|
||||||
{
|
{
|
||||||
// limit how often an overloaded message can be sent
|
// limit how often an overloaded message can be sent
|
||||||
@@ -119,7 +118,7 @@ public class ChunkUpdateQueueManager
|
|||||||
{
|
{
|
||||||
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
|
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. " +
|
"\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. " +
|
"\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).";
|
"\nMax queue count [" + SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.maxSize + "] ([" + SharedApi.MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players).";
|
||||||
@@ -140,6 +139,26 @@ public class ChunkUpdateQueueManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to return a cloned chunk wrapper from memory.
|
||||||
|
* Returns null if no chunk is available.
|
||||||
|
* <br><br>
|
||||||
|
* This is done instead of accessing the MC level since
|
||||||
|
* accessing the level often requires running on the render or server
|
||||||
|
* thread, which causes stuttering.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public IChunkWrapper tryGetChunk(DhChunkPos pos)
|
||||||
|
{
|
||||||
|
IChunkWrapper existingWrapper = this.queuedChunkWrapperByChunkPos.get(pos);
|
||||||
|
if (existingWrapper == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingWrapper.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=========//
|
//=========//
|
||||||
|
|||||||
@@ -118,6 +118,20 @@ public class Config
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static ConfigEntry<Boolean> dynamicFadeUseOpaqueMcDepth = new ConfigEntry.Builder<Boolean>()
|
||||||
|
.set(true)
|
||||||
|
.comment(""
|
||||||
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<String> dynamicFadeExportPath = new ConfigEntry.Builder<String>()
|
||||||
|
.set("C:/Users/James_Seibel/Desktop/")
|
||||||
|
.comment(""
|
||||||
|
+ "")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class Advanced
|
public static class Advanced
|
||||||
{
|
{
|
||||||
// common config links need to have their destination
|
// common config links need to have their destination
|
||||||
@@ -419,6 +433,14 @@ public class Config
|
|||||||
"")
|
"")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static ConfigEntry<Integer> fadeDistanceInBlocks = new ConfigEntry.Builder<Integer>()
|
||||||
|
.setMinDefaultMax(0, 1_600, 30_000_000)
|
||||||
|
.comment("" +
|
||||||
|
"The distance in blocks from the camera where the SSAO will fade out to. \n"+
|
||||||
|
"This is done to prevent banding and noise at extreme distances. \n"+
|
||||||
|
"")
|
||||||
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GenericRendering
|
public static class GenericRendering
|
||||||
@@ -449,6 +471,15 @@ public class Config
|
|||||||
+ "")
|
+ "")
|
||||||
.build();
|
.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>()
|
public static ConfigEntry<Boolean> enableCloudRendering = new ConfigEntry.Builder<Boolean>()
|
||||||
.set(true)
|
.set(true)
|
||||||
.comment(""
|
.comment(""
|
||||||
@@ -497,11 +528,6 @@ public class Config
|
|||||||
+ "Note: Other mods may conflict with this setting. \n"
|
+ "Note: Other mods may conflict with this setting. \n"
|
||||||
+ "")
|
+ "")
|
||||||
.build();
|
.build();
|
||||||
@Deprecated
|
|
||||||
public static ConfigEntry<Boolean> disableVanillaFog = new ConfigEntry.Builder<Boolean>()
|
|
||||||
.set(!enableVanillaFog.get())
|
|
||||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -563,14 +589,6 @@ public class Config
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
static
|
|
||||||
{
|
|
||||||
disableVanillaFog.addListener(
|
|
||||||
new ConfigChangeListener<Boolean>(disableVanillaFog,
|
|
||||||
(disableVanillaFog) -> enableVanillaFog.setApiValue(disableVanillaFog))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class HeightFog
|
public static class HeightFog
|
||||||
{
|
{
|
||||||
public static ConfigUIComment heightFogHeader = new ConfigUIComment.Builder().setParentConfigClass(HeightFog.class).build();
|
public static ConfigUIComment heightFogHeader = new ConfigUIComment.Builder().setParentConfigClass(HeightFog.class).build();
|
||||||
@@ -838,7 +856,7 @@ public class Config
|
|||||||
public static ConfigUIComment experimentalHeader = new ConfigUIComment.Builder().setParentConfigClass(Experimental.class).build();
|
public static ConfigUIComment experimentalHeader = new ConfigUIComment.Builder().setParentConfigClass(Experimental.class).build();
|
||||||
|
|
||||||
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
|
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
|
||||||
.setMinDefaultMax(0, 0, 5000)
|
.setMinDefaultMax(-5000, 0, 5000)
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "This is the earth size ratio when applying the curvature shader effect. \n"
|
+ "This is the earth size ratio when applying the curvature shader effect. \n"
|
||||||
+ "Note: Enabling this feature may cause rendering bugs. \n"
|
+ "Note: Enabling this feature may cause rendering bugs. \n"
|
||||||
@@ -848,24 +866,26 @@ public class Config
|
|||||||
+ "100 = 1 to 100 (63,710 blocks) \n"
|
+ "100 = 1 to 100 (63,710 blocks) \n"
|
||||||
+ "10000 = 1 to 10000 (637.1 blocks) \n"
|
+ "10000 = 1 to 10000 (637.1 blocks) \n"
|
||||||
+ "\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"
|
+ "and the max value is 5000. Any values outside this range \n"
|
||||||
+ "will be set to 0 (disabled).")
|
+ "will be set to 0 (disabled).")
|
||||||
.addListener(WorldCurvatureConfigEventHandler.INSTANCE)
|
.addListener(WorldCurvatureConfigEventHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// TODO should be replaced with a better long-term solution
|
public static ConfigEntry<String> ignoredDimensionCsv = new ConfigEntry.Builder<String>()
|
||||||
@Deprecated
|
.set("")
|
||||||
public static ConfigEntry<Boolean> onlyLoadCenterLods = new ConfigEntry.Builder<Boolean>()
|
|
||||||
.set(false)
|
|
||||||
.comment(""
|
.comment(""
|
||||||
+ "For internal testing:\n"
|
+ "A comma separated list of dimension resource locations where DH won't render. \n"
|
||||||
+ "Skips loading adjacent LODs to significantly reduce load times (~5x)\n"
|
+ "\n"
|
||||||
+ "but causes lighting on LOD borders to appear as full-bright\n"
|
+ "Example: \"minecraft:the_nether,minecraft:the_end\"\n"
|
||||||
+ "and other graphical bugs.\n"
|
+ "\n"
|
||||||
|
+ "Note:\n"
|
||||||
|
+ "Some DH settings will be disabled and/or changed to improve \n"
|
||||||
|
+ "visuals when DH rendering is disabled. \n"
|
||||||
+ "")
|
+ "")
|
||||||
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
|
.addListener(IgnoredDimensionCsvHandler.INSTANCE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1414,7 +1434,7 @@ public class Config
|
|||||||
.set(false)
|
.set(false)
|
||||||
// enabling this can be quite detrimental to performance,
|
// enabling this can be quite detrimental to performance,
|
||||||
// so hiding it in the config file should reduce people accidentally enabling it
|
// 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(""
|
.comment(""
|
||||||
+ "Enabling this will drastically increase chunk processing time\n"
|
+ "Enabling this will drastically increase chunk processing time\n"
|
||||||
+ "and you may need to increase your CPU load to handle it.\n"
|
+ "and you may need to increase your CPU load to handle it.\n"
|
||||||
@@ -1595,6 +1615,14 @@ public class Config
|
|||||||
+ "This can be useful for debugging.")
|
+ "This can be useful for debugging.")
|
||||||
.build();
|
.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();
|
public static ConfigCategory warning = new ConfigCategory.Builder().set(Warning.class).build();
|
||||||
|
|
||||||
|
|
||||||
@@ -1857,6 +1885,8 @@ public class Config
|
|||||||
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
||||||
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
||||||
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
||||||
|
|
||||||
|
IgnoredDimensionCsvHandler.INSTANCE.onConfigValueSet();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
+15
-11
@@ -17,23 +17,27 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.seibel.distanthorizons.core.generation.tasks;
|
package com.seibel.distanthorizons.core.config.api.converters;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
||||||
|
import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Leetom
|
* Used to support deprecated config options that may be identical
|
||||||
* @version 2022-11-25
|
* in implementation but with the On/Off values flipped.
|
||||||
|
*
|
||||||
|
* @author James Seibel
|
||||||
|
* @version 2025-12-22
|
||||||
*/
|
*/
|
||||||
public final class InProgressWorldGenTaskGroup
|
public class InvertedBoolConverter implements IConverter<Boolean, Boolean>
|
||||||
{
|
{
|
||||||
public final WorldGenTaskGroup group;
|
|
||||||
public CompletableFuture<Void> genFuture = null;
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean convertToCoreType(Boolean core)
|
||||||
|
{ return !core; }
|
||||||
|
|
||||||
public InProgressWorldGenTaskGroup(WorldGenTaskGroup group)
|
@Override
|
||||||
{
|
public Boolean convertToApiType(Boolean api)
|
||||||
this.group = group;
|
{ 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();
|
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 */
|
/** 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
|
// shouldn't update the UI, otherwise we may end up fighting the user
|
||||||
Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(MIN_VALID_CURVE_VALUE);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -121,7 +121,7 @@ public class FullDataSourceV2
|
|||||||
public Boolean applyToChildren = null;
|
public Boolean applyToChildren = null;
|
||||||
|
|
||||||
/** should only be used by methods exposed via the DH API */
|
/** should only be used by methods exposed via the DH API */
|
||||||
private boolean runApiChunkValidation = false;
|
private boolean runApiSetterValidation = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -202,8 +202,9 @@ public class FullDataSourceV2
|
|||||||
|
|
||||||
public static FullDataSourceV2 createEmpty(long pos)
|
public static FullDataSourceV2 createEmpty(long pos)
|
||||||
{
|
{
|
||||||
|
FullDataPointIdMap map = new FullDataPointIdMap(pos);
|
||||||
return new FullDataSourceV2(
|
return new FullDataSourceV2(
|
||||||
pos, new FullDataPointIdMap(pos),
|
pos, map,
|
||||||
// data points, genSteps, and columnCompression are all null since
|
// data points, genSteps, and columnCompression are all null since
|
||||||
// nothing has been generated yet.
|
// nothing has been generated yet.
|
||||||
// Using the default value of all 0's is adequate
|
// Using the default value of all 0's is adequate
|
||||||
@@ -1296,7 +1297,7 @@ public class FullDataSourceV2
|
|||||||
// API methods //
|
// API methods //
|
||||||
//=============//
|
//=============//
|
||||||
|
|
||||||
public void setRunApiChunkValidation(boolean runValidation) { this.runApiChunkValidation = runValidation; }
|
public void setRunApiSetterValidation(boolean runValidation) { this.runApiSetterValidation = runValidation; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWidthInDataColumns() { return WIDTH; }
|
public int getWidthInDataColumns() { return WIDTH; }
|
||||||
@@ -1308,7 +1309,7 @@ public class FullDataSourceV2
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
LodDataBuilder.putListInTopDownOrder(columnDataPoints);
|
LodDataBuilder.putListInTopDownOrder(columnDataPoints);
|
||||||
if (this.runApiChunkValidation)
|
if (this.runApiSetterValidation)
|
||||||
{
|
{
|
||||||
LodDataBuilder.validateOrThrowApiDataColumn(columnDataPoints);
|
LodDataBuilder.validateOrThrowApiDataColumn(columnDataPoints);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -107,7 +107,7 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
|
|
||||||
|
|
||||||
// upload on MC's render thread
|
// upload on MC's render thread
|
||||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
GLProxy.queueRunningOnRenderThread(() ->
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -295,8 +295,6 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
{
|
{
|
||||||
this.buffersUploaded = false;
|
this.buffersUploaded = false;
|
||||||
|
|
||||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
|
||||||
{
|
|
||||||
for (GLVertexBuffer buffer : this.vbos)
|
for (GLVertexBuffer buffer : this.vbos)
|
||||||
{
|
{
|
||||||
if (buffer != null)
|
if (buffer != null)
|
||||||
@@ -312,7 +310,6 @@ public class LodBufferContainer implements AutoCloseable
|
|||||||
buffer.destroyAsync();
|
buffer.destroyAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-11
@@ -399,31 +399,33 @@ public class LodQuadBuilder
|
|||||||
}
|
}
|
||||||
private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz)
|
private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz)
|
||||||
{
|
{
|
||||||
skylight %= 16;
|
|
||||||
blocklight %= 16;
|
|
||||||
|
|
||||||
bb.putShort(x);
|
bb.putShort(x);
|
||||||
bb.putShort(y);
|
bb.putShort(y);
|
||||||
bb.putShort(z);
|
bb.putShort(z);
|
||||||
|
|
||||||
short meta = 0;
|
short meta = 0;
|
||||||
meta |= (skylight | (blocklight << 4));
|
{
|
||||||
byte mirco = 0;
|
skylight %= 16;
|
||||||
|
blocklight %= 16;
|
||||||
|
meta |= (short) (skylight | (blocklight << 4));
|
||||||
|
|
||||||
|
byte mircoOffset = 0;
|
||||||
// mirco offset which is a xyz 2bit value
|
// mirco offset which is a xyz 2bit value
|
||||||
// 0b00 = no offset
|
// 0b00 = no offset
|
||||||
// 0b01 = positive offset
|
// 0b01 = positive offset
|
||||||
// 0b11 = negative offset
|
// 0b11 = negative offset
|
||||||
// format is: 0b00zzyyxx
|
// format is: 0b00zzyyxx
|
||||||
if (mx != 0) mirco |= mx > 0 ? 0b01 : 0b11;
|
if (mx != 0) { mircoOffset |= (byte) (mx > 0 ? 0b01 : 0b11); }
|
||||||
if (my != 0) mirco |= my > 0 ? 0b0100 : 0b1100;
|
if (my != 0) { mircoOffset |= (byte) (my > 0 ? 0b0100 : 0b1100); }
|
||||||
if (mz != 0) mirco |= mz > 0 ? 0b010000 : 0b110000;
|
if (mz != 0) { mircoOffset |= (byte) (mz > 0 ? 0b010000 : 0b110000); }
|
||||||
meta |= mirco << 8;
|
meta |= (short) (mircoOffset << 8);
|
||||||
|
}
|
||||||
bb.putShort(meta);
|
bb.putShort(meta);
|
||||||
|
|
||||||
byte r = (byte) ColorUtil.getRed(color);
|
byte r = (byte) ColorUtil.getRed(color);
|
||||||
byte g = (byte) ColorUtil.getGreen(color);
|
byte g = (byte) ColorUtil.getGreen(color);
|
||||||
byte b = (byte) ColorUtil.getBlue(color);
|
byte b = (byte) ColorUtil.getBlue(color);
|
||||||
byte a = this.doTransparency ? (byte) ColorUtil.getAlpha(color) : (byte) 255; // TODO should this be called here or happen somewhere else?
|
byte a = this.doTransparency ? (byte) ColorUtil.getAlpha(color) : (byte) 255;
|
||||||
bb.put(r);
|
bb.put(r);
|
||||||
bb.put(g);
|
bb.put(g);
|
||||||
bb.put(b);
|
bb.put(b);
|
||||||
|
|||||||
@@ -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
|
// no data currently in the memory cache for this position
|
||||||
memoryDataSource = FullDataSourceV2.createEmpty(inputPos);
|
memoryDataSource = FullDataSourceV2.createEmpty(inputPos);
|
||||||
pair = new DataSourceSavedTimePair(memoryDataSource);
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
+51
-85
@@ -28,8 +28,8 @@ import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataUpdatePropag
|
|||||||
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
||||||
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
||||||
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
|
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
@@ -38,6 +38,7 @@ import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
|
|||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||||
|
import com.seibel.distanthorizons.core.util.ExceptionUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
@@ -70,6 +71,8 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
*/
|
*/
|
||||||
public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20;
|
public static final int MAX_WORLD_GEN_REQUESTS_PER_THREAD = 20;
|
||||||
|
|
||||||
|
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Generated Provider");
|
||||||
|
|
||||||
|
|
||||||
private final AtomicReference<IFullDataSourceRetrievalQueue> worldGenQueueRef = new AtomicReference<>(null);
|
private final AtomicReference<IFullDataSourceRetrievalQueue> worldGenQueueRef = new AtomicReference<>(null);
|
||||||
private final ArrayList<IOnWorldGenCompleteListener> onWorldGenTaskCompleteListeners = new ArrayList<>();
|
private final ArrayList<IOnWorldGenCompleteListener> onWorldGenTaskCompleteListeners = new ArrayList<>();
|
||||||
@@ -85,15 +88,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException
|
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException
|
||||||
{ this(level, saveStructure, null); }
|
{ this(level, saveStructure, null); }
|
||||||
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException
|
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException
|
||||||
{
|
{ super(level, saveStructure, saveDirOverride); }
|
||||||
super(level, saveStructure, saveDirOverride);
|
|
||||||
|
|
||||||
this.addDataSourceUpdateListener((@NotNull FullDataSourceV2 updatedData) ->
|
|
||||||
{
|
|
||||||
this.onWorldGenTaskComplete(WorldGenResult.CreateSuccess(updatedData.getPos()), null);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -122,32 +117,43 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
// events //
|
// events //
|
||||||
//========//
|
//========//
|
||||||
|
|
||||||
private void onWorldGenTaskComplete(WorldGenResult genTaskResult, Throwable exception)
|
private void onWorldGenTaskComplete(@NotNull Long genPos, @Nullable DataSourceRetrievalResult genTaskResult, @Nullable Throwable exception)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (exception != null)
|
if (exception != null)
|
||||||
{
|
{
|
||||||
// don't log shutdown exceptions
|
// don't log shutdown exceptions
|
||||||
if (!(exception instanceof CancellationException || exception.getCause() instanceof CancellationException))
|
if (!ExceptionUtil.isInterruptOrReject(exception))
|
||||||
{
|
{
|
||||||
LOGGER.error("Uncaught Gen Task Exception at ["+genTaskResult.pos+"], error: ["+exception.getMessage()+"].", exception);
|
LOGGER.error("Uncaught Gen Task Exception at [" + genPos + "], error: [" + exception.getMessage() + "].", exception);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (genTaskResult.success)
|
|
||||||
{
|
|
||||||
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
|
|
||||||
return;
|
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
|
else
|
||||||
{
|
{
|
||||||
// generation didn't complete
|
// shouldn't happen, but just in case
|
||||||
LOGGER.debug("Gen Task Failed at " + genTaskResult.pos);
|
LOGGER.warn("Unexpected gen Task state at: [" + DhSectionPos.toString(genTaskResult.pos) + "], state: [" + genTaskResult.state + "], datasource: NULL, exception: NULL.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
// if the generation task was split up into smaller positions, add the on-complete event to them
|
|
||||||
for (CompletableFuture<WorldGenResult> siblingFuture : genTaskResult.childFutures)
|
|
||||||
{
|
{
|
||||||
siblingFuture.whenComplete((siblingGenTaskResult, siblingEx) -> this.onWorldGenTaskComplete(siblingGenTaskResult, siblingEx));
|
LOGGER.error("Unexpected issue during onWorldGenTaskComplete, error: ["+e.getMessage()+"].", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,10 +213,10 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canQueueRetrieval() { return this.canQueueRetrieval(false); }
|
public boolean canQueueRetrievalNow() { return this.canQueueRetrievalNow(false); }
|
||||||
public boolean canQueueRetrieval(boolean pruneWaitingTasksAboveLimit)
|
public boolean canQueueRetrievalNow(boolean pruneWaitingTasksAboveLimit)
|
||||||
{
|
{
|
||||||
if (!super.canQueueRetrieval())
|
if (!super.canQueueRetrievalNow())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -270,12 +276,16 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
|
|
||||||
|
|
||||||
int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount();
|
int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount();
|
||||||
if (availableTaskSlots <= 0)
|
if (availableTaskSlots == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (availableTaskSlots < 0)
|
||||||
{
|
{
|
||||||
if (pruneWaitingTasksAboveLimit)
|
if (pruneWaitingTasksAboveLimit)
|
||||||
{
|
{
|
||||||
AtomicInteger tasksToCancel = new AtomicInteger(-availableTaskSlots + 1);
|
AtomicInteger tasksToCancel = new AtomicInteger(availableTaskSlots * -1);
|
||||||
worldGenQueue.removeRetrievalRequestIf(x -> tasksToCancel.getAndDecrement() > 0);
|
worldGenQueue.removeRetrievalRequestIf(taskPos -> tasksToCancel.getAndDecrement() > 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -288,7 +298,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<WorldGenResult> queuePositionForRetrieval(Long genPos)
|
public CompletableFuture<DataSourceRetrievalResult> queuePositionForRetrieval(Long genPos)
|
||||||
{
|
{
|
||||||
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
|
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
|
||||||
if (worldGenQueue == null)
|
if (worldGenQueue == null)
|
||||||
@@ -296,13 +306,8 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldGenTaskTracker genTaskTracker = new WorldGenTaskTracker(genPos);
|
CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
|
||||||
CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTaskTracker);
|
worldGenFuture.whenComplete((r, e) -> this.onWorldGenTaskComplete(genPos, r, e));
|
||||||
worldGenFuture.whenComplete((genTaskResult, ex) ->
|
|
||||||
{
|
|
||||||
//LOGGER.info("gen task complete ["+DhSectionPos.toString(genPos)+"]");
|
|
||||||
//this.onWorldGenTaskComplete(genTaskResult, ex);
|
|
||||||
});
|
|
||||||
|
|
||||||
return worldGenFuture;
|
return worldGenFuture;
|
||||||
}
|
}
|
||||||
@@ -321,22 +326,20 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
public void clearRetrievalQueue() { this.worldGenQueueRef.set(null); }
|
public void clearRetrievalQueue() { this.worldGenQueueRef.set(null); }
|
||||||
|
|
||||||
|
|
||||||
public boolean isFullyGenerated(ByteArrayList columnGenerationSteps)
|
public boolean generationStepsAreFullyGenerated(ByteArrayList columnGenerationSteps)
|
||||||
{
|
{
|
||||||
return IntStream.range(0, columnGenerationSteps.size())
|
return IntStream.range(0, columnGenerationSteps.size())
|
||||||
.noneMatch(i ->
|
.noneMatch((int intValue) ->
|
||||||
{
|
{
|
||||||
byte value = columnGenerationSteps.getByte(i);
|
byte value = columnGenerationSteps.getByte(intValue);
|
||||||
return value == EDhApiWorldGenerationStep.EMPTY.value
|
return value == EDhApiWorldGenerationStep.EMPTY.value
|
||||||
|| value == EDhApiWorldGenerationStep.DOWN_SAMPLED.value;
|
|| value == EDhApiWorldGenerationStep.DOWN_SAMPLED.value;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Generated Provider");
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LongArrayList getPositionsToRetrieve(Long pos)
|
public LongArrayList getPositionsToRetrieve(long pos)
|
||||||
{
|
{
|
||||||
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
|
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
|
||||||
if (worldGenQueue == null)
|
if (worldGenQueue == null)
|
||||||
@@ -352,7 +355,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
{
|
{
|
||||||
ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH);
|
ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH);
|
||||||
this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray);
|
this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray);
|
||||||
if (!columnGenStepArray.isEmpty())
|
if (columnGenStepArray.size() != 0)
|
||||||
{
|
{
|
||||||
boolean positionFullyGenerated = true;
|
boolean positionFullyGenerated = true;
|
||||||
|
|
||||||
@@ -378,7 +381,6 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
|
|
||||||
|
|
||||||
// this section is missing one or more columns, queue the missing ones for generation.
|
// this section is missing one or more columns, queue the missing ones for generation.
|
||||||
// TODO speed up this logic by only checking ungenerated columns
|
|
||||||
LongArrayList generationList = new LongArrayList();
|
LongArrayList generationList = new LongArrayList();
|
||||||
|
|
||||||
byte lowestGeneratorDetailLevel = (byte) Math.min(
|
byte lowestGeneratorDetailLevel = (byte) Math.min(
|
||||||
@@ -471,48 +473,13 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
// helper classes //
|
// helper classes //
|
||||||
//================//
|
//================//
|
||||||
|
|
||||||
// TODO may not be needed
|
|
||||||
private class WorldGenTaskTracker implements IWorldGenTaskTracker
|
|
||||||
{
|
|
||||||
/** just used when debugging/troubleshooting */
|
|
||||||
private final long pos;
|
|
||||||
|
|
||||||
public WorldGenTaskTracker(long pos) { this.pos = pos; }
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Consumer<FullDataSourceV2> getDataSourceConsumer()
|
|
||||||
{
|
|
||||||
return (dataSource) ->
|
|
||||||
{
|
|
||||||
GeneratedFullDataSourceProvider.this.delayedFullDataSourceSaveCache.writeDataSourceToMemoryAndQueueSave(dataSource);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<Boolean> shouldGenerateSplitChild(long pos)
|
|
||||||
{
|
|
||||||
return GeneratedFullDataSourceProvider.this.getAsync(pos).thenApply(fullDataSource ->
|
|
||||||
{
|
|
||||||
//noinspection TryFinallyCanBeTryWithResources
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return !GeneratedFullDataSourceProvider.this.isFullyGenerated(fullDataSource.columnGenerationSteps);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
fullDataSource.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
private CompletableFuture<Void> onDataSourceSaveAsync(FullDataSourceV2 fullDataSource)
|
private CompletableFuture<Void> onDataSourceSaveAsync(FullDataSourceV2 fullDataSource)
|
||||||
{
|
{
|
||||||
// block lights should have been populated at the chunkWrapper stage
|
// block lights should have been populated at the chunkWrapper stage
|
||||||
// waiting to populate the data source's skylight at this stage prevents re-lighting and
|
// waiting to populate the data source's skylight at this stage prevents re-lighting and
|
||||||
// allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once
|
// allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once
|
||||||
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, LodUtil.MAX_MC_LIGHT);
|
int skyLight = this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT;
|
||||||
|
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, skyLight);
|
||||||
|
|
||||||
return this.updateDataSourceAsync(fullDataSource);
|
return this.updateDataSourceAsync(fullDataSource);
|
||||||
}
|
}
|
||||||
@@ -524,7 +491,6 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
|||||||
{
|
{
|
||||||
boolean shouldDoWorldGen();
|
boolean shouldDoWorldGen();
|
||||||
|
|
||||||
@Nullable
|
|
||||||
DhBlockPos2D getTargetPosForGeneration();
|
DhBlockPos2D getTargetPosForGeneration();
|
||||||
|
|
||||||
/** Fired whenever a section has completed generating */
|
/** Fired whenever a section has completed generating */
|
||||||
|
|||||||
+15
-3
@@ -23,9 +23,11 @@ import com.google.common.cache.CacheBuilder;
|
|||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
||||||
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
|
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.IDhLevel;
|
||||||
import com.seibel.distanthorizons.core.level.LodRequestModule;
|
import com.seibel.distanthorizons.core.level.LodRequestModule;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
|
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -74,7 +76,7 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide
|
|||||||
//==================//
|
//==================//
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canQueueRetrieval() { return this.canQueueRetrieval(true); }
|
public boolean canQueueRetrievalNow() { return this.canQueueRetrievalNow(true); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -102,9 +104,19 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide
|
|||||||
Long timestamp = this.getTimestampForPos(pos);
|
Long timestamp = this.getTimestampForPos(pos);
|
||||||
if (timestamp != null)
|
if (timestamp != null)
|
||||||
{
|
{
|
||||||
this.syncOnLoadRequestQueue.submitRequest(pos, timestamp, fullDataSource ->
|
this.syncOnLoadRequestQueue.submitRequest(pos, timestamp)
|
||||||
|
.thenAccept((DataSourceRetrievalResult result) ->
|
||||||
{
|
{
|
||||||
this.updateDataSourceAsync(fullDataSource).whenComplete((result, throwable) -> fullDataSource.close());
|
if (result.state == ERetrievalResultState.SUCCESS
|
||||||
|
&& result.dataSource != null)
|
||||||
|
{
|
||||||
|
this.updateDataSourceAsync(result.dataSource)
|
||||||
|
.handle((voidObj, throwable) ->
|
||||||
|
{
|
||||||
|
result.dataSource.close();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-11
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
|
|||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
|
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
|
||||||
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
@@ -48,7 +48,6 @@ import java.sql.SQLException;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles reading/writing {@link FullDataSourceV2}
|
* Handles reading/writing {@link FullDataSourceV2}
|
||||||
@@ -86,9 +85,9 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
|||||||
protected final String levelId;
|
protected final String levelId;
|
||||||
|
|
||||||
|
|
||||||
private final FullDataUpdaterV2 dataUpdater;
|
protected final FullDataUpdaterV2 dataUpdater;
|
||||||
private final FullDataUpdatePropagatorV2 updatePropagator;
|
protected final FullDataUpdatePropagatorV2 updatePropagator;
|
||||||
private final DataMigratorV1 dataMigratorV1;
|
protected final DataMigratorV1 dataMigratorV1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -201,9 +200,10 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
|||||||
return FullDataSourceV2.createEmpty(pos);
|
return FullDataSourceV2.createEmpty(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FullDataSourceV2 dataSource = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
FullDataSourceV2 dataSource = this.createDataSourceFromDto(dto);
|
dataSource = this.createDataSourceFromDto(dto);
|
||||||
|
|
||||||
// automatically create and save adjacent data if missing
|
// automatically create and save adjacent data if missing
|
||||||
if (dto.dataFormatVersion == FullDataSourceV2DTO.DATA_FORMAT.V1_NO_ADJACENT_DATA)
|
if (dto.dataFormatVersion == FullDataSourceV2DTO.DATA_FORMAT.V1_NO_ADJACENT_DATA)
|
||||||
@@ -222,6 +222,15 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
|||||||
this.tryLogCorruptedDataError(DhSectionPos.toString(pos), e);
|
this.tryLogCorruptedDataError(DhSectionPos.toString(pos), e);
|
||||||
this.repo.deleteWithKey(pos);
|
this.repo.deleteWithKey(pos);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (dataSource != null)
|
||||||
|
{
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (InterruptedException ignore) { }
|
catch (InterruptedException ignore) { }
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
@@ -243,6 +252,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
String message = e.getMessage();
|
String message = e.getMessage();
|
||||||
|
if (message == null)
|
||||||
|
{
|
||||||
|
message = "NULL";
|
||||||
|
}
|
||||||
|
|
||||||
if (CORRUPT_DATA_ERRORS_LOGGED.add(message))
|
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);
|
LOGGER.warn("Unexpected error getting: [" + DhSectionPos.toString(pos) + "], this error message will only be logged once, error: [" + message + "].", e);
|
||||||
@@ -360,7 +374,7 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
|||||||
* to the beginning of your override.
|
* to the beginning of your override.
|
||||||
* Otherwise, parent retrieval limits will be ignored.
|
* Otherwise, parent retrieval limits will be ignored.
|
||||||
*/
|
*/
|
||||||
public boolean canQueueRetrieval()
|
public boolean canQueueRetrievalNow()
|
||||||
{
|
{
|
||||||
// Retrieval shouldn't happen while an unknown number of
|
// Retrieval shouldn't happen while an unknown number of
|
||||||
// legacy data sources are present.
|
// legacy data sources are present.
|
||||||
@@ -369,15 +383,15 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return null if this provider can't generate any positions and
|
* @return null if this provider can't generate any positions or
|
||||||
* an empty array if all positions were generated
|
* an empty array if all positions were generated
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public LongArrayList getPositionsToRetrieve(Long pos) { return null; }
|
public LongArrayList getPositionsToRetrieve(long pos) { return null; }
|
||||||
|
|
||||||
/** @return true if the position was queued, false if not */
|
/** @return null if the position couldn't be queued */
|
||||||
@Nullable
|
@Nullable
|
||||||
public CompletableFuture<WorldGenResult> queuePositionForRetrieval(Long genPos) { return null; }
|
public CompletableFuture<DataSourceRetrievalResult> queuePositionForRetrieval(Long genPos) { return null; }
|
||||||
|
|
||||||
/** does nothing if the given position isn't present in the queue */
|
/** does nothing if the given position isn't present in the queue */
|
||||||
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { }
|
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { }
|
||||||
|
|||||||
+1
-1
@@ -185,7 +185,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
|
|||||||
parentLocked = true;
|
parentLocked = true;
|
||||||
this.dataUpdater.lockedPosSet.add(parentUpdatePos);
|
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
|
// will return null if the file handler is shutting down
|
||||||
if (parentDataSource != null)
|
if (parentDataSource != null)
|
||||||
|
|||||||
+91
-40
@@ -118,14 +118,18 @@ public class DhLightingEngine
|
|||||||
* @param centerChunk the chunk we want to apply lighting to
|
* @param centerChunk the chunk we want to apply lighting to
|
||||||
* @param nearbyChunkList should also contain centerChunk
|
* @param nearbyChunkList should also contain centerChunk
|
||||||
* @param maxSkyLight should be a value between 0 and 15
|
* @param maxSkyLight should be a value between 0 and 15
|
||||||
|
*
|
||||||
|
* @return the number of light positions iterated over, can be used for profiling.
|
||||||
*/
|
*/
|
||||||
private void lightChunk(
|
private int lightChunk(
|
||||||
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
|
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
|
||||||
int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight)
|
int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight)
|
||||||
{
|
{
|
||||||
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
|
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
|
||||||
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
|
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
|
||||||
|
|
||||||
|
// how many positions we've walked over, can be used for profiling/debugging
|
||||||
|
int posIterations = 0;
|
||||||
|
|
||||||
// try-finally to handle the stableArray resources
|
// try-finally to handle the stableArray resources
|
||||||
StableLightPosStack blockLightWorldPosQueue = null;
|
StableLightPosStack blockLightWorldPosQueue = null;
|
||||||
@@ -245,13 +249,15 @@ public class DhLightingEngine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// block light
|
// block light
|
||||||
if (updateBlockLight)
|
if (updateBlockLight)
|
||||||
{
|
{
|
||||||
// done to prevent a rare issue where the light values are incorrectly set to -1
|
// done to prevent a rare issue where the light values are incorrectly set to -1
|
||||||
centerChunk.clearDhBlockLighting();
|
centerChunk.clearDhBlockLighting();
|
||||||
|
|
||||||
this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
|
posIterations += this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
|
||||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
|
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
|
||||||
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
|
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
|
||||||
true);
|
true);
|
||||||
@@ -262,7 +268,7 @@ public class DhLightingEngine
|
|||||||
{
|
{
|
||||||
centerChunk.clearDhSkyLighting();
|
centerChunk.clearDhSkyLighting();
|
||||||
|
|
||||||
this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
|
posIterations += this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
|
||||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
|
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
|
||||||
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
|
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
|
||||||
false);
|
false);
|
||||||
@@ -287,10 +293,12 @@ public class DhLightingEngine
|
|||||||
{
|
{
|
||||||
centerChunk.setIsDhSkyLightCorrect(true);
|
centerChunk.setIsDhSkyLightCorrect(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return posIterations;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
|
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
|
||||||
private void propagateChunkLightPosList(
|
private int propagateChunkLightPosList(
|
||||||
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
|
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
|
||||||
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc,
|
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc,
|
||||||
boolean propagatingBlockLights)
|
boolean propagatingBlockLights)
|
||||||
@@ -320,12 +328,25 @@ public class DhLightingEngine
|
|||||||
IBlockStateWrapper previousBlockState = null;
|
IBlockStateWrapper previousBlockState = null;
|
||||||
|
|
||||||
|
|
||||||
|
int iterations = 0;
|
||||||
|
|
||||||
|
// update each light level
|
||||||
|
for (int currentLightLevel = LodUtil.MAX_MC_LIGHT; currentLightLevel >= LodUtil.MIN_MC_LIGHT; currentLightLevel--)
|
||||||
|
{
|
||||||
|
// Walking down from the top light level to the bottom can reduce iterating over
|
||||||
|
// the same positions multiple times.
|
||||||
|
// At best this seems to behave at roughly 2x the speed of just blindly putting light pos
|
||||||
|
// in a queue and at worse slightly faster than the blind queue.
|
||||||
|
|
||||||
|
lightPos.lightValue = currentLightLevel;
|
||||||
|
|
||||||
// update each light position
|
// update each light position
|
||||||
while (!lightPosQueue.isEmpty())
|
while (!lightPosQueue.isLightLevelEmpty(currentLightLevel))
|
||||||
{
|
{
|
||||||
// since we don't care about the order the positions are processed,
|
// since we don't care about the order the positions are processed,
|
||||||
// we can grab the last position instead of the first for a slight performance increase (this way the array doesn't need to be shifted over every loop)
|
// we can grab the last position instead of the first for a slight performance increase (this way the array doesn't need to be shifted over every loop)
|
||||||
lightPosQueue.popMutate(lightPos);
|
lightPosQueue.popMutate(lightPos, currentLightLevel);
|
||||||
|
iterations++;
|
||||||
|
|
||||||
int lightValue = lightPos.lightValue;
|
int lightValue = lightPos.lightValue;
|
||||||
|
|
||||||
@@ -367,18 +388,28 @@ public class DhLightingEngine
|
|||||||
previousBlockState = neighbourBlockState;
|
previousBlockState = neighbourBlockState;
|
||||||
|
|
||||||
// Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles.
|
// Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles.
|
||||||
int targetLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
|
int targetLightLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
|
||||||
if (targetLevel > currentBlockLight)
|
if (targetLightLevel > currentBlockLight)
|
||||||
{
|
{
|
||||||
// this position is darker than the new light value, update/set it
|
// this position is darker than the new light value, update/set it
|
||||||
setLightFunc.setLight(neighbourChunk, relNeighbourBlockPos, targetLevel);
|
setLightFunc.setLight(neighbourChunk, relNeighbourBlockPos, targetLightLevel);
|
||||||
|
|
||||||
// now that light has been propagated to this blockPos
|
// now that light has been propagated to this blockPos
|
||||||
// we need to queue it up so its neighbours can be propagated as well
|
// we need to queue it up so its neighbours can be propagated as well
|
||||||
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLevel);
|
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLightLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int currentLightLevel = LodUtil.MAX_MC_LIGHT; currentLightLevel >= LodUtil.MIN_MC_LIGHT; currentLightLevel--)
|
||||||
|
{
|
||||||
|
if (!lightPosQueue.isLightLevelEmpty(currentLightLevel))
|
||||||
|
{
|
||||||
|
LodUtil.assertNotReach("Non empty light pos queue for light level ["+currentLightLevel+"] after light engine running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// can be enabled if troubleshooting lighting issues
|
// can be enabled if troubleshooting lighting issues
|
||||||
@@ -395,6 +426,7 @@ public class DhLightingEngine
|
|||||||
|
|
||||||
|
|
||||||
// propagation complete
|
// propagation complete
|
||||||
|
return iterations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -748,16 +780,24 @@ public class DhLightingEngine
|
|||||||
private static final Queue<StableLightPosStack> lightArrayCache = new ArrayDeque<>();
|
private static final Queue<StableLightPosStack> lightArrayCache = new ArrayDeque<>();
|
||||||
|
|
||||||
/** the index of the last item in the array, -1 if empty */
|
/** the index of the last item in the array, -1 if empty */
|
||||||
private int index = -1;
|
private int[] indexByLightLevel = new int[LodUtil.MAX_MC_LIGHT + 1];
|
||||||
|
|
||||||
/** x, y, z, and lightValue. */
|
/** x, y, z */
|
||||||
public static final int INTS_PER_LIGHT_POS = 4;
|
public static final int INTS_PER_LIGHT_POS = 3;
|
||||||
|
|
||||||
/**
|
private final IntArrayList[] lightPositionsByLightLevel = new IntArrayList[LodUtil.MAX_MC_LIGHT + 1];
|
||||||
* When tested with a normal 1.20 world James saw a maximum of 36,709 block and 2,355 sky lights,
|
|
||||||
* so 40,000 should be a good starting point that can contain most lighting tasks.
|
|
||||||
*/
|
public StableLightPosStack()
|
||||||
private final IntArrayList lightPositions = new IntArrayList(40_000 * INTS_PER_LIGHT_POS);
|
{
|
||||||
|
for (int i = 0; i < this.lightPositionsByLightLevel.length; i++)
|
||||||
|
{
|
||||||
|
// When tested with a normal 1.20 world James saw a maximum of 36,709 block and 2,355 sky lights,
|
||||||
|
// so 40,000 should be a good starting point that can contain most lighting tasks.
|
||||||
|
this.lightPositionsByLightLevel[i] = new IntArrayList(40_000 * INTS_PER_LIGHT_POS);
|
||||||
|
this.indexByLightLevel[i] = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -804,45 +844,56 @@ public class DhLightingEngine
|
|||||||
// stack methods //
|
// stack methods //
|
||||||
//===============//
|
//===============//
|
||||||
|
|
||||||
public boolean isEmpty() { return this.index == -1; }
|
public boolean isLightLevelEmpty(int lightLevel) { return this.indexByLightLevel[lightLevel] == -1; }
|
||||||
public int size() { return this.index+1; }
|
//public int size() { return this.index+1; }
|
||||||
|
|
||||||
public void push(int blockX, int blockY, int blockZ, int lightValue)
|
public void push(int blockX, int blockY, int blockZ, int lightLevel)
|
||||||
{
|
{
|
||||||
this.index++;
|
IntArrayList lightPositions = this.lightPositionsByLightLevel[lightLevel];
|
||||||
int subIndex = this.index * INTS_PER_LIGHT_POS;
|
|
||||||
if (subIndex < this.lightPositions.size())
|
this.indexByLightLevel[lightLevel]++;
|
||||||
|
int subIndex = this.indexByLightLevel[lightLevel] * INTS_PER_LIGHT_POS;
|
||||||
|
if (subIndex < lightPositions.size())
|
||||||
{
|
{
|
||||||
this.lightPositions.set(subIndex, blockX);
|
lightPositions.set(subIndex, blockX);
|
||||||
this.lightPositions.set(subIndex + 1, blockY);
|
lightPositions.set(subIndex + 1, blockY);
|
||||||
this.lightPositions.set(subIndex + 2, blockZ);
|
lightPositions.set(subIndex + 2, blockZ);
|
||||||
this.lightPositions.set(subIndex + 3, lightValue);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// add a new pos
|
// add a new pos
|
||||||
this.lightPositions.add(blockX);
|
lightPositions.add(blockX);
|
||||||
this.lightPositions.add(blockY);
|
lightPositions.add(blockY);
|
||||||
this.lightPositions.add(blockZ);
|
lightPositions.add(blockZ);
|
||||||
this.lightPositions.add(lightValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** mutates the given {@link LightPos} to match the next {@link LightPos} in the queue. */
|
/** mutates the given {@link LightPos} to match the next {@link LightPos} in the queue. */
|
||||||
public void popMutate(LightPos pos)
|
public void popMutate(LightPos pos, int lightLevel)
|
||||||
{
|
{
|
||||||
int subIndex = this.index * INTS_PER_LIGHT_POS;
|
int subIndex = this.indexByLightLevel[lightLevel] * INTS_PER_LIGHT_POS;
|
||||||
|
IntArrayList lightPositions = this.lightPositionsByLightLevel[lightLevel];
|
||||||
|
|
||||||
pos.setX(this.lightPositions.getInt(subIndex));
|
pos.setX(lightPositions.getInt(subIndex));
|
||||||
pos.setY(this.lightPositions.getInt(subIndex + 1));
|
pos.setY(lightPositions.getInt(subIndex + 1));
|
||||||
pos.setZ(this.lightPositions.getInt(subIndex + 2));
|
pos.setZ(lightPositions.getInt(subIndex + 2));
|
||||||
pos.lightValue = this.lightPositions.getInt(subIndex + 3);
|
|
||||||
|
|
||||||
this.index--;
|
this.indexByLightLevel[lightLevel]--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() { return this.index + "/" + (this.lightPositions.size() / INTS_PER_LIGHT_POS); }
|
public String toString()
|
||||||
|
{
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < this.indexByLightLevel.length; i++)
|
||||||
|
{
|
||||||
|
builder.append("light: ").append(i)
|
||||||
|
.append(" size: ").append(this.indexByLightLevel[i]).append("/").append(this.lightPositionsByLightLevel[i].size() / INTS_PER_LIGHT_POS).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -19,13 +19,11 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.generation;
|
package com.seibel.distanthorizons.core.generation;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.LodQuadTree;
|
import com.seibel.distanthorizons.core.render.LodQuadTree;
|
||||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -92,7 +90,7 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
|
|||||||
*/
|
*/
|
||||||
void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf);
|
void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf);
|
||||||
|
|
||||||
CompletableFuture<WorldGenResult> submitRetrievalTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker);
|
CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long pos, byte requiredDataDetail);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
|||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
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.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.util.FormatUtil;
|
import com.seibel.distanthorizons.core.util.FormatUtil;
|
||||||
@@ -148,14 +150,16 @@ public class PregenManager
|
|||||||
this.fullDataSourceProvider.getAsync(nextSectionPos)
|
this.fullDataSourceProvider.getAsync(nextSectionPos)
|
||||||
.thenAccept(fullDataSource ->
|
.thenAccept(fullDataSource ->
|
||||||
{
|
{
|
||||||
if (this.fullDataSourceProvider.isFullyGenerated(fullDataSource.columnGenerationSteps))
|
if (this.fullDataSourceProvider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps))
|
||||||
{
|
{
|
||||||
this.pendingGenerations.invalidate(fullDataSource.getPos());
|
this.pendingGenerations.invalidate(fullDataSource.getPos());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos()).thenAccept(result -> {
|
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos())
|
||||||
if (!result.success)
|
.whenComplete((DataSourceRetrievalResult result, Throwable throwable) ->
|
||||||
|
{
|
||||||
|
if (throwable != null)
|
||||||
{
|
{
|
||||||
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
|
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-37
@@ -1,8 +1,8 @@
|
|||||||
package com.seibel.distanthorizons.core.generation;
|
package com.seibel.distanthorizons.core.generation;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.client.AbstractFullDataNetworkRequestQueue;
|
import com.seibel.distanthorizons.core.multiplayer.client.AbstractFullDataNetworkRequestQueue;
|
||||||
@@ -14,10 +14,8 @@ import com.seibel.distanthorizons.core.util.LodUtil;
|
|||||||
import com.seibel.distanthorizons.core.util.WorldGenUtil;
|
import com.seibel.distanthorizons.core.util.WorldGenUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
|
public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
|
||||||
@@ -54,46 +52,28 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
|
|||||||
public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; }
|
public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<WorldGenResult> submitRetrievalTask(long sectionPos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
|
public CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long sectionPos, byte requiredDataDetail)
|
||||||
{
|
{
|
||||||
long generationStartMsTime = System.currentTimeMillis();
|
long generationStartMsTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
|
||||||
return super.submitRequest(sectionPos, fullDataSource -> {
|
CompletableFuture<DataSourceRetrievalResult> future = super.submitRequest(sectionPos, /* client timestamp */null);
|
||||||
Objects.requireNonNull(tracker.getDataSourceConsumer()).accept(fullDataSource);
|
future.thenAccept((DataSourceRetrievalResult result) ->
|
||||||
fullDataSource.close();
|
{
|
||||||
})
|
if (result.state == ERetrievalResultState.SUCCESS)
|
||||||
.thenApply(requestResult ->
|
|
||||||
{
|
{
|
||||||
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
|
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
|
||||||
|
|
||||||
int chunkWidth = DhSectionPos.getChunkWidth(sectionPos);
|
int chunkWidth = DhSectionPos.getChunkWidth(sectionPos);
|
||||||
int chunkCount = chunkWidth * chunkWidth;
|
int chunkCount = chunkWidth * chunkWidth;
|
||||||
double timePerChunk = (double) totalGenTimeInMs / (double) chunkCount;
|
double timePerChunk = (double) totalGenTimeInMs / (double) chunkCount;
|
||||||
|
|
||||||
|
// 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);
|
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
|
||||||
|
|
||||||
switch (requestResult)
|
|
||||||
{
|
|
||||||
case SUCCEEDED:
|
|
||||||
return WorldGenResult.CreateSuccess(sectionPos);
|
|
||||||
case FAILED:
|
|
||||||
return WorldGenResult.CreateFail();
|
|
||||||
case REQUIRES_SPLITTING:
|
|
||||||
List<CompletableFuture<WorldGenResult>> childFutures = new ArrayList<>(4);
|
|
||||||
DhSectionPos.forEachChild(sectionPos, childPos -> {
|
|
||||||
tracker.shouldGenerateSplitChild(childPos).thenAccept(shouldGenerate -> {
|
|
||||||
if (shouldGenerate)
|
|
||||||
{
|
|
||||||
childFutures.add(this.submitRetrievalTask(childPos, requiredDataDetail, tracker));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
return future;
|
||||||
return WorldGenResult.CreateSplit(childFutures);
|
|
||||||
}
|
|
||||||
|
|
||||||
LodUtil.assertNotReach("Unexpected and unhandled request response result: ["+requestResult+"]");
|
|
||||||
return WorldGenResult.CreateFail();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -109,7 +89,7 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
|
|||||||
@Override
|
@Override
|
||||||
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getGenerationRequestRateLimit(); }
|
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getGenerationRequestRateLimit(); }
|
||||||
@Override
|
@Override
|
||||||
protected boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos)
|
protected boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos)
|
||||||
{
|
{
|
||||||
if (this.networkState.sessionConfig.getGenerationMaxChunkRadius() > 0)
|
if (this.networkState.sessionConfig.getGenerationMaxChunkRadius() > 0)
|
||||||
{
|
{
|
||||||
@@ -127,12 +107,13 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
|
|||||||
return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxGenerationRequestDistance() * 16;
|
return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxGenerationRequestDistance() * 16;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<ERequestResult> future)
|
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future)
|
||||||
{
|
{
|
||||||
if (DhSectionPos.getDetailLevel(sectionPos) > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL
|
// split up large requests if N-sized gen isn't enabled
|
||||||
&& !Config.Server.Experimental.enableNSizedGeneration.get())
|
if (!Config.Server.Experimental.enableNSizedGeneration.get()
|
||||||
|
&& DhSectionPos.getDetailLevel(sectionPos) > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||||
{
|
{
|
||||||
future.complete(ERequestResult.REQUIRES_SPLITTING);
|
future.complete(DataSourceRetrievalResult.CreateSplit());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+299
-237
@@ -26,11 +26,8 @@ import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
|
|||||||
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
|
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.InProgressWorldGenTaskGroup;
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalTask;
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTask;
|
|
||||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTaskGroup;
|
|
||||||
import com.seibel.distanthorizons.core.level.IDhServerLevel;
|
import com.seibel.distanthorizons.core.level.IDhServerLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
@@ -52,14 +49,12 @@ import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
|||||||
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
|
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
|
public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
|
||||||
{
|
{
|
||||||
@@ -71,9 +66,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
private final IDhServerLevel level;
|
private final IDhServerLevel level;
|
||||||
|
|
||||||
/** contains the positions that need to be generated */
|
/** contains the positions that need to be generated */
|
||||||
private final ConcurrentHashMap<Long, WorldGenTask> waitingTasks = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<Long, DataSourceRetrievalTask> waitingTasks = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<Long, DataSourceRetrievalTask> inProgressGenTasksByLodPos = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<Long, InProgressWorldGenTaskGroup> inProgressGenTasksByLodPos = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
/** largest numerical detail level allowed */
|
/** largest numerical detail level allowed */
|
||||||
public final byte lowestDataDetail;
|
public final byte lowestDataDetail;
|
||||||
@@ -98,13 +92,14 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
private int estimatedRemainingChunkCount = 0;
|
private int estimatedRemainingChunkCount = 0;
|
||||||
|
|
||||||
private final RollingAverage rollingAverageChunkGenTimeInMs = new RollingAverage(Runtime.getRuntime().availableProcessors() * 500);
|
private final RollingAverage rollingAverageChunkGenTimeInMs = new RollingAverage(Runtime.getRuntime().availableProcessors() * 500);
|
||||||
public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
|
@Override public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//=============//
|
||||||
// constructors //
|
// constructor //
|
||||||
//==============//
|
//=============//
|
||||||
|
///region constructor
|
||||||
|
|
||||||
public WorldGenerationQueue(IDhApiWorldGenerator generator, IDhServerLevel level)
|
public WorldGenerationQueue(IDhApiWorldGenerator generator, IDhServerLevel level)
|
||||||
{
|
{
|
||||||
@@ -118,20 +113,31 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
LOGGER.info("Created world gen queue");
|
LOGGER.info("Created world gen queue");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///endregion constructor
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
|
||||||
// world generator //
|
//===============//
|
||||||
// task handling //
|
// task handling //
|
||||||
//=================//
|
//===============//
|
||||||
|
///region task handling
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<WorldGenResult> submitRetrievalTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
|
public CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long pos, byte requiredDataDetail)
|
||||||
{
|
{
|
||||||
// the generator is shutting down, don't add new tasks
|
// the generator is shutting down, don't add new tasks
|
||||||
if (this.generatorClosingFuture != null)
|
if (this.generatorClosingFuture != null)
|
||||||
{
|
{
|
||||||
return CompletableFuture.completedFuture(WorldGenResult.CreateFail());
|
CompletableFuture<DataSourceRetrievalResult> f = new CompletableFuture<>();
|
||||||
|
f.completeExceptionally(new CancellationException());
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the existing task if present
|
||||||
|
DataSourceRetrievalTask existingGenTask = this.waitingTasks.get(pos);
|
||||||
|
if (existingGenTask != null)
|
||||||
|
{
|
||||||
|
return existingGenTask.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -145,13 +151,12 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
requiredDataDetail = this.lowestDataDetail;
|
requiredDataDetail = this.lowestDataDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert that the data at least can fill in 1 single ChunkSizedFullDataAccessor
|
// the request should be at least chunk-sized
|
||||||
LodUtil.assertTrue(DhSectionPos.getDetailLevel(pos) > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL);
|
LodUtil.assertTrue(DhSectionPos.getDetailLevel(pos) > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL);
|
||||||
|
|
||||||
|
DataSourceRetrievalTask genTask = new DataSourceRetrievalTask(pos, requiredDataDetail);
|
||||||
CompletableFuture<WorldGenResult> future = new CompletableFuture<>();
|
this.waitingTasks.put(pos, genTask);
|
||||||
this.waitingTasks.put(pos, new WorldGenTask(pos, requiredDataDetail, tracker, future));
|
return genTask.future;
|
||||||
return future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -161,11 +166,17 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
{
|
{
|
||||||
if (removeIf.accept(genPos))
|
if (removeIf.accept(genPos))
|
||||||
{
|
{
|
||||||
this.waitingTasks.remove(genPos);
|
DataSourceRetrievalTask removedTask = this.waitingTasks.remove(genPos);
|
||||||
|
if (removedTask != null)
|
||||||
|
{
|
||||||
|
// cancel tasks so any waiting future steps can be triggered
|
||||||
|
removedTask.future.cancel(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///endregion task handling
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -248,18 +259,27 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// find the closest task
|
||||||
TaskDistancePair closestTaskPair = this.waitingTasks.reduceEntries(1024,
|
TaskDistancePair closestTaskPair = this.waitingTasks.reduceEntries(1024,
|
||||||
entry -> new TaskDistancePair(entry.getValue(), DhSectionPos.getSectionBBoxPos(entry.getValue().pos).getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())),
|
// get the target distance for each task
|
||||||
(TaskDistancePair aTaskPair, TaskDistancePair bTaskPair) -> (aTaskPair.dist < bTaskPair.dist) ? aTaskPair : bTaskPair);
|
(Map.Entry<Long, DataSourceRetrievalTask> entry) ->
|
||||||
|
{
|
||||||
|
DataSourceRetrievalTask task = entry.getValue();
|
||||||
|
int distance = DhSectionPos.getCenterBlockPos(task.pos).chebyshevDist(targetPos);
|
||||||
|
return new TaskDistancePair(entry.getValue(), distance);
|
||||||
|
},
|
||||||
|
// find the closest task
|
||||||
|
(TaskDistancePair aTaskPair, TaskDistancePair bTaskPair) ->
|
||||||
|
{
|
||||||
|
return (aTaskPair.dist < bTaskPair.dist) ? aTaskPair : bTaskPair;
|
||||||
|
});
|
||||||
|
|
||||||
if (closestTaskPair == null)
|
if (closestTaskPair == null)
|
||||||
{
|
{
|
||||||
// FIXME concurrency issue
|
// the waitingTasks was modified while this check was running
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
DataSourceRetrievalTask closestTask = closestTaskPair.task;
|
||||||
WorldGenTask closestTask = closestTaskPair.task;
|
|
||||||
|
|
||||||
// remove the task we found, we are going to start it and don't want to run it multiple times
|
// remove the task we found, we are going to start it and don't want to run it multiple times
|
||||||
this.waitingTasks.remove(closestTask.pos, closestTask);
|
this.waitingTasks.remove(closestTask.pos, closestTask);
|
||||||
@@ -269,76 +289,69 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
{
|
{
|
||||||
// detail level is correct for generation, start generation
|
// detail level is correct for generation, start generation
|
||||||
|
|
||||||
WorldGenTaskGroup closestTaskGroup = new WorldGenTaskGroup(closestTask.pos, (byte)(closestTask.pos - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
|
DataSourceRetrievalTask existingTask = this.inProgressGenTasksByLodPos.get(closestTask.pos);
|
||||||
closestTaskGroup.worldGenTasks.add(closestTask);
|
if (existingTask == null)
|
||||||
|
|
||||||
if (!this.inProgressGenTasksByLodPos.containsKey(closestTask.pos))
|
|
||||||
{
|
{
|
||||||
// no task exists for this position, start one
|
// no task exists for this position, start one
|
||||||
InProgressWorldGenTaskGroup newTaskGroup = new InProgressWorldGenTaskGroup(closestTaskGroup);
|
this.startWorldGenTaskGroup(closestTask);
|
||||||
this.startWorldGenTaskGroup(newTaskGroup);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// TODO replace the previous inProgress task if one exists
|
// shouldn't normally happen, but if
|
||||||
// Note: Due to concurrency reasons, even if the currently running task is compatible with
|
// we somehow queued the same task twice:
|
||||||
// the newly selected task, we cannot use it,
|
// merge the two futures so they both complete
|
||||||
// as some chunks may have already been written into.
|
|
||||||
|
|
||||||
//LOGGER.warn("A task already exists for this position, todo: "+DhSectionPos.toString(closestTask.pos));
|
existingTask.future.thenApply((DataSourceRetrievalResult result)->
|
||||||
|
{
|
||||||
|
closestTask.future.complete(result);
|
||||||
|
return closestTask.future; // return value ignored
|
||||||
|
});
|
||||||
|
existingTask.future.exceptionally((Throwable throwable)->
|
||||||
|
{
|
||||||
|
closestTask.future.completeExceptionally(throwable);
|
||||||
|
return null; // return value ignored
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// a task has been started
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// detail level is too high (if the detail level was too low, the generator would've ignored the request),
|
// 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
|
||||||
|
|
||||||
|
closestTask.future.complete(DataSourceRetrievalResult.CreateSplit());
|
||||||
|
}
|
||||||
|
|
||||||
// split up the task and add each one to the tree
|
|
||||||
LinkedList<CompletableFuture<WorldGenResult>> childFutures = new LinkedList<>();
|
|
||||||
long sectionPos = closestTask.pos;
|
|
||||||
WorldGenTask finalClosestTask = closestTask;
|
|
||||||
DhSectionPos.forEachChild(sectionPos, (childDhSectionPos) ->
|
|
||||||
{
|
|
||||||
CompletableFuture<WorldGenResult> newFuture = new CompletableFuture<>();
|
|
||||||
childFutures.add(newFuture);
|
|
||||||
|
|
||||||
WorldGenTask newGenTask = new WorldGenTask(childDhSectionPos, DhSectionPos.getDetailLevel(childDhSectionPos), finalClosestTask.taskTracker, newFuture);
|
// a task has been started or queued,
|
||||||
this.waitingTasks.put(newGenTask.pos, newGenTask);
|
// queue another task
|
||||||
});
|
|
||||||
|
|
||||||
// send the child futures to the future recipient, to notify them of the new tasks
|
|
||||||
closestTask.future.complete(WorldGenResult.CreateSplit(childFutures));
|
|
||||||
|
|
||||||
// return true so we attempt to generate again
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
private boolean canGenerateDetailLevel(byte taskDetailLevel)
|
||||||
private void startWorldGenTaskGroup(InProgressWorldGenTaskGroup newTaskGroup)
|
|
||||||
{
|
{
|
||||||
byte taskDetailLevel = newTaskGroup.group.dataDetail;
|
byte requestedDetailLevel = (byte) (taskDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||||
long taskPos = newTaskGroup.group.pos;
|
return (this.highestDataDetail <= requestedDetailLevel && requestedDetailLevel <= this.lowestDataDetail);
|
||||||
LodUtil.assertTrue(taskDetailLevel >= this.highestDataDetail && taskDetailLevel <= this.lowestDataDetail);
|
}
|
||||||
|
private void startWorldGenTaskGroup(DataSourceRetrievalTask worldGenTask)
|
||||||
int generationRequestChunkWidthCount = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(taskPos) - taskDetailLevel - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale
|
{
|
||||||
|
long taskPos = worldGenTask.pos;
|
||||||
|
LodUtil.assertTrue(
|
||||||
|
worldGenTask.requestDetailLevel >= this.highestDataDetail
|
||||||
|
&& worldGenTask.requestDetailLevel <= this.lowestDataDetail,
|
||||||
|
"World gen task started that isn't within the range that the generator can create.");
|
||||||
|
|
||||||
long generationStartMsTime = System.currentTimeMillis();
|
long generationStartMsTime = System.currentTimeMillis();
|
||||||
CompletableFuture<Void> generationFuture = this.startGenerationEvent(taskPos, taskDetailLevel, generationRequestChunkWidthCount, newTaskGroup.group::consumeDataSource);
|
CompletableFuture<FullDataSourceV2> generationFuture = this.startGenerationEvent(worldGenTask);
|
||||||
|
|
||||||
|
// calculate generation speed
|
||||||
generationFuture.thenRun(() ->
|
generationFuture.thenRun(() ->
|
||||||
{
|
{
|
||||||
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
|
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
|
||||||
int chunkCount = generationRequestChunkWidthCount * generationRequestChunkWidthCount;
|
int chunkCount = worldGenTask.widthInChunks * worldGenTask.widthInChunks;
|
||||||
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
|
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
|
||||||
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
|
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
|
||||||
});
|
});
|
||||||
|
|
||||||
newTaskGroup.genFuture = generationFuture;
|
generationFuture.handle((FullDataSourceV2 fullDataSource, Throwable exception) ->
|
||||||
LodUtil.assertTrue(newTaskGroup.genFuture != null);
|
|
||||||
|
|
||||||
newTaskGroup.genFuture.whenComplete((voidObj, exception) ->
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -350,35 +363,35 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
|
LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateFail()));
|
LodUtil.assertTrue(fullDataSource == null);
|
||||||
|
worldGenTask.future.completeExceptionally(exception);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(taskPos)));
|
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, newTaskGroup);
|
|
||||||
LodUtil.assertTrue(worked, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]");
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e);
|
LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e);
|
||||||
|
worldGenTask.future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
this.tryQueueNewWorldGenRequestsAsync();
|
this.tryQueueNewWorldGenRequestsAsync();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
this.inProgressGenTasksByLodPos.put(taskPos, newTaskGroup);
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
private CompletableFuture<Void> startGenerationEvent(
|
private CompletableFuture<FullDataSourceV2> startGenerationEvent(DataSourceRetrievalTask task)
|
||||||
long requestPos,
|
|
||||||
byte targetDataDetail,
|
|
||||||
int generationRequestChunkWidthCount,
|
|
||||||
Consumer<FullDataSourceV2> dataSourceConsumer
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
DhChunkPos chunkPosMin = new DhChunkPos(DhSectionPos.getSectionBBoxPos(requestPos).getCornerBlockPos());
|
this.inProgressGenTasksByLodPos.put(task.pos, task);
|
||||||
|
|
||||||
|
DhChunkPos chunkPosMin = new DhChunkPos(new DhBlockPos2D(DhSectionPos.getMinCornerBlockX(task.pos), DhSectionPos.getMinCornerBlockZ(task.pos)));
|
||||||
|
|
||||||
EDhApiDistantGeneratorMode generatorMode = Config.Common.WorldGenerator.distantGeneratorMode.get();
|
EDhApiDistantGeneratorMode generatorMode = Config.Common.WorldGenerator.distantGeneratorMode.get();
|
||||||
EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType();
|
EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType();
|
||||||
@@ -386,10 +399,34 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
{
|
{
|
||||||
case VANILLA_CHUNKS:
|
case VANILLA_CHUNKS:
|
||||||
{
|
{
|
||||||
return this.generator.generateChunks(
|
return this.startVanillaChunkGenerationEvent(task, chunkPosMin, generatorMode);
|
||||||
|
}
|
||||||
|
case API_CHUNKS:
|
||||||
|
{
|
||||||
|
return this.startApiChunkGenerationEvent(task, chunkPosMin, generatorMode);
|
||||||
|
}
|
||||||
|
case API_DATA_SOURCES:
|
||||||
|
{
|
||||||
|
return this.startApiDataSourceGenerationEvent(task, chunkPosMin, generatorMode);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
||||||
|
throw new AssertFailureException("Unknown return type: " + returnType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private CompletableFuture<FullDataSourceV2> startVanillaChunkGenerationEvent(
|
||||||
|
DataSourceRetrievalTask task, DhChunkPos chunkPosMin, EDhApiDistantGeneratorMode generatorMode)
|
||||||
|
{
|
||||||
|
final CompletableFuture<FullDataSourceV2> returnFuture = new CompletableFuture<>();
|
||||||
|
|
||||||
|
ArrayList<IChunkWrapper> generatedChunks = new ArrayList<>(task.widthInChunks * task.widthInChunks);
|
||||||
|
|
||||||
|
CompletableFuture<Void> chunkGenFuture = this.generator.generateChunks(
|
||||||
chunkPosMin.getX(), chunkPosMin.getZ(),
|
chunkPosMin.getX(), chunkPosMin.getZ(),
|
||||||
generationRequestChunkWidthCount,
|
task.widthInChunks,
|
||||||
targetDataDetail,
|
task.requestDetailLevel,
|
||||||
generatorMode,
|
generatorMode,
|
||||||
ThreadPoolUtil.getWorldGenExecutor(),
|
ThreadPoolUtil.getWorldGenExecutor(),
|
||||||
(Object[] generatedObjectArray) ->
|
(Object[] generatedObjectArray) ->
|
||||||
@@ -397,23 +434,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
|
IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
|
||||||
|
generatedChunks.add(chunkWrapper);
|
||||||
// only light the chunk here if necessary,
|
|
||||||
// lighting before this point is preferred but for potenial legacy API uses this
|
|
||||||
// check should be done
|
|
||||||
if (!chunkWrapper.isDhBlockLightingCorrect())
|
|
||||||
{
|
|
||||||
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<>();
|
|
||||||
nearbyChunkList.add(chunkWrapper);
|
|
||||||
byte maxSkyLight = this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT;
|
|
||||||
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, maxSkyLight);
|
|
||||||
}
|
|
||||||
|
|
||||||
try (FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper))
|
|
||||||
{
|
|
||||||
LodUtil.assertTrue(dataSource != null);
|
|
||||||
dataSourceConsumer.accept(dataSource);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (ClassCastException e)
|
catch (ClassCastException e)
|
||||||
{
|
{
|
||||||
@@ -427,87 +448,129 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
case API_CHUNKS:
|
chunkGenFuture.exceptionally((throwable) ->
|
||||||
{
|
{
|
||||||
return this.generator.generateApiChunks(
|
returnFuture.completeExceptionally(throwable);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
chunkGenFuture.thenRun(() ->
|
||||||
|
{
|
||||||
|
FullDataSourceV2 requestedDataSource = FullDataSourceV2.createEmpty(task.pos);
|
||||||
|
|
||||||
|
// process chunks //
|
||||||
|
for (int i = 0; i < generatedChunks.size(); i++)
|
||||||
|
{
|
||||||
|
IChunkWrapper chunkWrapper = generatedChunks.get(i);
|
||||||
|
|
||||||
|
// only light the chunk here if necessary,
|
||||||
|
// lighting before this point is preferred but for legacy API use this
|
||||||
|
// check should be done
|
||||||
|
if (!chunkWrapper.isDhBlockLightingCorrect())
|
||||||
|
{
|
||||||
|
ArrayList<IChunkWrapper> nearbyChunkList = new ArrayList<>();
|
||||||
|
nearbyChunkList.add(chunkWrapper);
|
||||||
|
byte maxSkyLight = this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT;
|
||||||
|
DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, maxSkyLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FullDataSourceV2 generatedDataSource = LodDataBuilder.createFromChunk(this.level.getLevelWrapper(), chunkWrapper))
|
||||||
|
{
|
||||||
|
LodUtil.assertTrue(generatedDataSource != null);
|
||||||
|
requestedDataSource.updateFromDataSource(generatedDataSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(requestedDataSource, LodUtil.MAX_MC_LIGHT);
|
||||||
|
returnFuture.complete(requestedDataSource);
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnFuture;
|
||||||
|
}
|
||||||
|
private CompletableFuture<FullDataSourceV2> startApiChunkGenerationEvent(
|
||||||
|
DataSourceRetrievalTask task, DhChunkPos chunkPosMin, EDhApiDistantGeneratorMode generatorMode)
|
||||||
|
{
|
||||||
|
final CompletableFuture<FullDataSourceV2> returnFuture = new CompletableFuture<>();
|
||||||
|
|
||||||
|
ArrayList<DhApiChunk> generatedChunks = new ArrayList<>(task.widthInChunks * task.widthInChunks);
|
||||||
|
|
||||||
|
CompletableFuture<Void> chunkGenFuture = this.generator.generateApiChunks(
|
||||||
chunkPosMin.getX(), chunkPosMin.getZ(),
|
chunkPosMin.getX(), chunkPosMin.getZ(),
|
||||||
generationRequestChunkWidthCount,
|
task.widthInChunks,
|
||||||
targetDataDetail,
|
task.requestDetailLevel,
|
||||||
generatorMode,
|
generatorMode,
|
||||||
ThreadPoolUtil.getWorldGenExecutor(),
|
ThreadPoolUtil.getWorldGenExecutor(),
|
||||||
(DhApiChunk dataPoints) ->
|
(DhApiChunk apiChunk) -> { generatedChunks.add(apiChunk); }
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
chunkGenFuture.exceptionally((throwable) ->
|
||||||
{
|
{
|
||||||
try(FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints, this.generator.runApiValidation()))
|
returnFuture.completeExceptionally(throwable);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
chunkGenFuture.thenRun(() ->
|
||||||
{
|
{
|
||||||
dataSourceConsumer.accept(dataSource);
|
FullDataSourceV2 requestedDataSource = FullDataSourceV2.createEmpty(task.pos);
|
||||||
|
|
||||||
|
for (int i = 0; i < generatedChunks.size(); i++)
|
||||||
|
{
|
||||||
|
DhApiChunk apiChunk = generatedChunks.get(i);
|
||||||
|
|
||||||
|
try(FullDataSourceV2 generatedDataSource = LodDataBuilder.createFromApiChunkData(apiChunk, this.generator.runApiValidation()))
|
||||||
|
{
|
||||||
|
requestedDataSource.updateFromDataSource(generatedDataSource);
|
||||||
}
|
}
|
||||||
catch (DataCorruptedException | IllegalArgumentException e)
|
catch (DataCorruptedException | IllegalArgumentException e)
|
||||||
{
|
{
|
||||||
LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
LOGGER.error("World generator returned a corrupt API chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
||||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
|
||||||
}
|
|
||||||
catch (ClassCastException e)
|
|
||||||
{
|
|
||||||
LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
|
||||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
returnFuture.complete(requestedDataSource);
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnFuture;
|
||||||
}
|
}
|
||||||
case API_DATA_SOURCES:
|
private CompletableFuture<FullDataSourceV2> startApiDataSourceGenerationEvent(
|
||||||
|
DataSourceRetrievalTask task, DhChunkPos chunkPosMin, EDhApiDistantGeneratorMode generatorMode)
|
||||||
{
|
{
|
||||||
|
final CompletableFuture<FullDataSourceV2> returnFuture = new CompletableFuture<>();
|
||||||
|
|
||||||
|
|
||||||
// done to reduce GC overhead
|
// done to reduce GC overhead
|
||||||
FullDataSourceV2 pooledDataSource = FullDataSourceV2.createEmpty(requestPos);
|
FullDataSourceV2 pooledDataSource = FullDataSourceV2.createEmpty(task.pos);
|
||||||
// set here so the API user doesn't have to pass in this value anywhere themselves
|
// set here so the API user doesn't have to pass in this value anywhere themselves
|
||||||
pooledDataSource.setRunApiChunkValidation(this.generator.runApiValidation());
|
pooledDataSource.setRunApiSetterValidation(this.generator.runApiValidation());
|
||||||
|
|
||||||
// only apply to children if we aren't at the bottom of the tree
|
// only apply to children if we aren't at the bottom of the tree
|
||||||
|
|
||||||
pooledDataSource.applyToChildren = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
|
pooledDataSource.applyToChildren = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
|
||||||
pooledDataSource.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12;
|
pooledDataSource.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12; // TODO what does this 12 reference?
|
||||||
|
|
||||||
|
CompletableFuture<Void> lodGenFuture = this.generator.generateLod(
|
||||||
return this.generator.generateLod(
|
|
||||||
chunkPosMin.getX(), chunkPosMin.getZ(),
|
chunkPosMin.getX(), chunkPosMin.getZ(),
|
||||||
DhSectionPos.getX(requestPos), DhSectionPos.getZ(requestPos),
|
DhSectionPos.getX(task.pos), DhSectionPos.getZ(task.pos),
|
||||||
(byte) (DhSectionPos.getDetailLevel(requestPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL),
|
(byte) (DhSectionPos.getDetailLevel(task.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL),
|
||||||
pooledDataSource,
|
pooledDataSource,
|
||||||
generatorMode,
|
generatorMode,
|
||||||
ThreadPoolUtil.getWorldGenExecutor(),
|
ThreadPoolUtil.getWorldGenExecutor(),
|
||||||
(IDhApiFullDataSource apiDataSource) ->
|
(IDhApiFullDataSource apiDataSource) -> { }
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
FullDataSourceV2 fullDataSource = (FullDataSourceV2) apiDataSource;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
dataSourceConsumer.accept(fullDataSource);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
fullDataSource.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException e)
|
|
||||||
{
|
|
||||||
LOGGER.error("World generator returned a corrupt data source. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
|
||||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
|
||||||
}
|
|
||||||
catch (ClassCastException e)
|
|
||||||
{
|
|
||||||
LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
|
||||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
default:
|
|
||||||
|
lodGenFuture.exceptionally((throwable) ->
|
||||||
{
|
{
|
||||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
returnFuture.completeExceptionally(throwable);
|
||||||
throw new AssertFailureException("Unknown return type: " + returnType);
|
pooledDataSource.close();
|
||||||
}
|
return null;
|
||||||
}
|
});
|
||||||
|
lodGenFuture.thenRun(() ->
|
||||||
|
{
|
||||||
|
returnFuture.complete(pooledDataSource);
|
||||||
|
});
|
||||||
|
|
||||||
|
return returnFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -515,24 +578,19 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
//===================//
|
//===================//
|
||||||
// getters / setters //
|
// getters / setters //
|
||||||
//===================//
|
//===================//
|
||||||
|
///region getters/setters
|
||||||
|
|
||||||
@Override public int getWaitingTaskCount() { return this.waitingTasks.size(); }
|
@Override public int getWaitingTaskCount() { return this.waitingTasks.size(); }
|
||||||
@Override public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); }
|
@Override public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); }
|
||||||
|
|
||||||
@Override
|
@Override public byte lowestDataDetail() { return this.lowestDataDetail; }
|
||||||
public byte lowestDataDetail() { return this.lowestDataDetail; }
|
@Override public byte highestDataDetail() { return this.highestDataDetail; }
|
||||||
@Override
|
|
||||||
public byte highestDataDetail() { return this.highestDataDetail; }
|
|
||||||
|
|
||||||
@Override
|
@Override public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; }
|
||||||
public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; }
|
@Override public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; }
|
||||||
@Override
|
|
||||||
public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; }
|
|
||||||
|
|
||||||
@Override
|
@Override public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; }
|
||||||
public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; }
|
@Override public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; }
|
||||||
@Override
|
|
||||||
public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addDebugMenuStringsToList(List<String> messageList) { }
|
public void addDebugMenuStringsToList(List<String> messageList) { }
|
||||||
@@ -550,13 +608,55 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
return chunkCount;
|
return chunkCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///endregion getters/setters
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=======//
|
||||||
|
// debug //
|
||||||
|
//=======//
|
||||||
|
///region debug
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void debugRender(DebugRenderer renderer)
|
||||||
|
{
|
||||||
|
int levelMinY = this.level.getLevelWrapper().getMinHeight();
|
||||||
|
int levelMaxY = this.level.getLevelWrapper().getMaxHeight();
|
||||||
|
|
||||||
|
// show the wireframe a bit lower than world max height,
|
||||||
|
// since most worlds don't render all the way up to the max height
|
||||||
|
int levelHeightRange = (levelMaxY - levelMinY);
|
||||||
|
int maxY = levelMaxY - (levelHeightRange / 2);
|
||||||
|
|
||||||
|
|
||||||
|
// blue - queued
|
||||||
|
this.waitingTasks.keySet().forEach((Long pos) ->
|
||||||
|
{
|
||||||
|
renderer.renderBox(
|
||||||
|
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// red - in progress
|
||||||
|
this.inProgressGenTasksByLodPos.forEach((Long pos, DataSourceRetrievalTask task) ->
|
||||||
|
{
|
||||||
|
renderer.renderBox(
|
||||||
|
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
///endregion debug
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==========//
|
//==========//
|
||||||
// shutdown //
|
// shutdown //
|
||||||
//==========//
|
//==========//
|
||||||
|
///region shutdown
|
||||||
|
|
||||||
@Override public CompletableFuture<Void> startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
|
@Override
|
||||||
|
public CompletableFuture<Void> startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
|
||||||
{
|
{
|
||||||
LOGGER.info("Closing world gen queue");
|
LOGGER.info("Closing world gen queue");
|
||||||
this.queueingThread.shutdownNow();
|
this.queueingThread.shutdownNow();
|
||||||
@@ -564,33 +664,32 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
|
|
||||||
// stop and remove any in progress tasks
|
// stop and remove any in progress tasks
|
||||||
ArrayList<CompletableFuture<Void>> inProgressTasksCancelingFutures = new ArrayList<>(this.inProgressGenTasksByLodPos.size());
|
ArrayList<CompletableFuture<Void>> inProgressTasksCancelingFutures = new ArrayList<>(this.inProgressGenTasksByLodPos.size());
|
||||||
this.inProgressGenTasksByLodPos.values().forEach(runningTaskGroup ->
|
this.inProgressGenTasksByLodPos.values().forEach((DataSourceRetrievalTask genTask) ->
|
||||||
{
|
{
|
||||||
CompletableFuture<Void> genFuture = runningTaskGroup.genFuture; // Do this to prevent it getting swapped out
|
CompletableFuture<DataSourceRetrievalResult> genFuture = genTask.future;
|
||||||
if (genFuture == null)
|
|
||||||
{
|
|
||||||
// genFuture's shouldn't be null, but sometimes they are...
|
|
||||||
LOGGER.info("Null gen future: "+runningTaskGroup.group.pos);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (cancelCurrentGeneration)
|
if (cancelCurrentGeneration)
|
||||||
{
|
{
|
||||||
genFuture.cancel(alsoInterruptRunning);
|
genFuture.cancel(alsoInterruptRunning);
|
||||||
}
|
}
|
||||||
|
|
||||||
inProgressTasksCancelingFutures.add(genFuture.handle((voidObj, exception) ->
|
inProgressTasksCancelingFutures.add(genFuture.handle((DataSourceRetrievalResult result, Throwable throwable) ->
|
||||||
{
|
{
|
||||||
if (exception instanceof CompletionException)
|
if (throwable instanceof CompletionException)
|
||||||
{
|
{
|
||||||
exception = exception.getCause();
|
throwable = throwable.getCause();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!UncheckedInterruptedException.isInterrupt(exception)
|
if (!UncheckedInterruptedException.isInterrupt(throwable)
|
||||||
&& !(exception instanceof CancellationException))
|
&& !(throwable instanceof CancellationException))
|
||||||
{
|
{
|
||||||
LOGGER.error("Error when terminating data generation for pos: ["+DhSectionPos.toString(runningTaskGroup.group.pos)+"], error: ["+exception.getMessage()+"].", exception);
|
LOGGER.error("Error when terminating data generation for pos: ["+DhSectionPos.toString(genTask.pos)+"], error: ["+throwable.getMessage()+"].", throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != null
|
||||||
|
&& result.dataSource != null)
|
||||||
|
{
|
||||||
|
result.dataSource.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -623,7 +722,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
LOGGER.info("World generator thread pool shutdown with [" + queueSize + "] incomplete tasks.");
|
LOGGER.info("World generator thread pool shutdown with [" + queueSize + "] incomplete tasks.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.inProgressGenTasksByLodPos.values().forEach((inProgressWorldGenTaskGroup) -> inProgressWorldGenTaskGroup.genFuture.cancel(true));
|
this.inProgressGenTasksByLodPos.values().forEach((inProgressWorldGenTaskGroup) -> inProgressWorldGenTaskGroup.future.cancel(true));
|
||||||
this.waitingTasks.values().forEach((worldGenTask) -> worldGenTask.future.cancel(true));
|
this.waitingTasks.values().forEach((worldGenTask) -> worldGenTask.future.cancel(true));
|
||||||
|
|
||||||
|
|
||||||
@@ -644,63 +743,22 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
LOGGER.info("Finished closing " + WorldGenerationQueue.class.getSimpleName());
|
LOGGER.info("Finished closing " + WorldGenerationQueue.class.getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///endregion shutdown
|
||||||
|
|
||||||
//=======//
|
|
||||||
// debug //
|
|
||||||
//=======//
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void debugRender(DebugRenderer renderer)
|
|
||||||
{
|
|
||||||
int levelMinY = this.level.getLevelWrapper().getMinHeight();
|
|
||||||
int levelMaxY = this.level.getLevelWrapper().getMaxHeight();
|
|
||||||
|
|
||||||
// show the wireframe a bit lower than world max height,
|
|
||||||
// since most worlds don't render all the way up to the max height
|
|
||||||
int levelHeightRange = (levelMaxY - levelMinY);
|
|
||||||
int maxY = levelMaxY - (levelHeightRange / 2);
|
|
||||||
|
|
||||||
|
|
||||||
// blue - queued
|
|
||||||
this.waitingTasks.keySet().forEach((pos) ->
|
|
||||||
{
|
|
||||||
renderer.renderBox(
|
|
||||||
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue));
|
|
||||||
});
|
|
||||||
|
|
||||||
// red - in progress
|
|
||||||
this.inProgressGenTasksByLodPos.forEach((pos, t) ->
|
|
||||||
{
|
|
||||||
renderer.renderBox(
|
|
||||||
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
|
||||||
// helper methods //
|
|
||||||
//================//
|
|
||||||
|
|
||||||
private boolean canGenerateDetailLevel(byte taskDetailLevel)
|
|
||||||
{
|
|
||||||
byte requestedDetailLevel = (byte) (taskDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
|
||||||
return (this.highestDataDetail <= requestedDetailLevel && requestedDetailLevel <= this.lowestDataDetail);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
// helper classes //
|
// helper classes //
|
||||||
//================//
|
//================//
|
||||||
|
///region helper classes
|
||||||
|
|
||||||
|
/** Used during task starting to determine the closest task */
|
||||||
private static class TaskDistancePair
|
private static class TaskDistancePair
|
||||||
{
|
{
|
||||||
public final WorldGenTask task;
|
public final DataSourceRetrievalTask task;
|
||||||
public final int dist;
|
public final int dist;
|
||||||
|
|
||||||
public TaskDistancePair(WorldGenTask task, int dist)
|
public TaskDistancePair(DataSourceRetrievalTask task, int dist)
|
||||||
{
|
{
|
||||||
this.task = task;
|
this.task = task;
|
||||||
this.dist = dist;
|
this.dist = dist;
|
||||||
@@ -708,4 +766,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///endregion helper classes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-9
@@ -20,20 +20,33 @@
|
|||||||
package com.seibel.distanthorizons.core.generation.tasks;
|
package com.seibel.distanthorizons.core.generation.tasks;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Leetom
|
* @see DataSourceRetrievalTask
|
||||||
* @version 2022-11-25
|
|
||||||
*/
|
*/
|
||||||
public interface IWorldGenTaskTracker
|
public class DataSourceRetrievalResult
|
||||||
{
|
{
|
||||||
|
public final ERetrievalResultState state;
|
||||||
|
/** the position that was generated, will be null if nothing was generated */
|
||||||
|
public final long pos;
|
||||||
@Nullable
|
@Nullable
|
||||||
Consumer<FullDataSourceV2> getDataSourceConsumer();
|
public final FullDataSourceV2 dataSource;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==============//
|
||||||
|
// constructors //
|
||||||
|
//==============//
|
||||||
|
|
||||||
|
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.state = state;
|
||||||
|
this.pos = pos;
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
CompletableFuture<Boolean> shouldGenerateSplitChild(long pos);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+19
-11
@@ -19,29 +19,37 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.generation.tasks;
|
package com.seibel.distanthorizons.core.generation.tasks;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Leetom
|
* @see DataSourceRetrievalResult
|
||||||
* @version 2022-11-25
|
|
||||||
*/
|
*/
|
||||||
public final class WorldGenTask
|
public final class DataSourceRetrievalTask
|
||||||
{
|
{
|
||||||
public final long pos;
|
public final long pos;
|
||||||
public final byte dataDetailLevel;
|
/**
|
||||||
public final IWorldGenTaskTracker taskTracker;
|
* Usually the same as {@link DataSourceRetrievalTask#pos}, but
|
||||||
public final CompletableFuture<WorldGenResult> future;
|
* can differ if the task needs something different.
|
||||||
|
*/
|
||||||
|
public final byte requestDetailLevel;
|
||||||
|
public final int widthInChunks;
|
||||||
|
|
||||||
|
public final CompletableFuture<DataSourceRetrievalResult> future = new CompletableFuture<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public WorldGenTask(long pos, byte dataDetail, IWorldGenTaskTracker taskTracker, CompletableFuture<WorldGenResult> future)
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
public DataSourceRetrievalTask(long pos, byte dataDetail)
|
||||||
{
|
{
|
||||||
this.dataDetailLevel = dataDetail;
|
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
this.taskTracker = taskTracker;
|
this.requestDetailLevel = dataDetail;
|
||||||
this.future = future;
|
this.widthInChunks = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(this.pos) - this.requestDetailLevel - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+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,
|
||||||
|
}
|
||||||
-51
@@ -1,51 +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.generation.tasks;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public class WorldGenResult
|
|
||||||
{
|
|
||||||
/** true if terrain was generated */
|
|
||||||
public final boolean success;
|
|
||||||
/** the position that was generated, will be null if nothing was generated */
|
|
||||||
public final long pos;
|
|
||||||
/** 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 LinkedList<CompletableFuture<WorldGenResult>> childFutures = new LinkedList<>();
|
|
||||||
|
|
||||||
|
|
||||||
public static WorldGenResult CreateSplit(Collection<CompletableFuture<WorldGenResult>> siblingFutures) { return new WorldGenResult(false, 0, siblingFutures); }
|
|
||||||
public static WorldGenResult CreateFail() { return new WorldGenResult(false, 0, null); }
|
|
||||||
public static WorldGenResult CreateSuccess(long pos) { return new WorldGenResult(true, pos, null); }
|
|
||||||
private WorldGenResult(boolean success, long pos, Collection<CompletableFuture<WorldGenResult>> childFutures)
|
|
||||||
{
|
|
||||||
this.success = success;
|
|
||||||
this.pos = pos;
|
|
||||||
|
|
||||||
if (childFutures != null)
|
|
||||||
{
|
|
||||||
this.childFutures.addAll(childFutures);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
-67
@@ -1,67 +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.generation.tasks;
|
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Leetom
|
|
||||||
* @version 2022-11-25
|
|
||||||
*/
|
|
||||||
@Deprecated // TODO look into how these are used and if they should continue to be used
|
|
||||||
public final class WorldGenTaskGroup
|
|
||||||
{
|
|
||||||
public final long pos;
|
|
||||||
public byte dataDetail;
|
|
||||||
/** Only accessed by the generator polling thread */
|
|
||||||
public final LinkedList<WorldGenTask> worldGenTasks = new LinkedList<>();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public WorldGenTaskGroup(long pos, byte dataDetail)
|
|
||||||
{
|
|
||||||
this.pos = pos;
|
|
||||||
this.dataDetail = dataDetail;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void consumeDataSource(FullDataSourceV2 dataSource)
|
|
||||||
{
|
|
||||||
Iterator<WorldGenTask> tasks = this.worldGenTasks.iterator();
|
|
||||||
while (tasks.hasNext())
|
|
||||||
{
|
|
||||||
WorldGenTask task = tasks.next();
|
|
||||||
Consumer<FullDataSourceV2> dataSourceConsumer = task.taskTracker.getDataSourceConsumer();
|
|
||||||
if (dataSourceConsumer == null)
|
|
||||||
{
|
|
||||||
tasks.remove();
|
|
||||||
task.future.complete(WorldGenResult.CreateFail());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dataSourceConsumer.accept(dataSource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -104,7 +104,7 @@ public class JarUtils
|
|||||||
*/
|
*/
|
||||||
public static InputStream accessFile(String resource)
|
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
|
// this is the path within the jar file
|
||||||
InputStream input = loader.getResourceAsStream(resource);
|
InputStream input = loader.getResourceAsStream(resource);
|
||||||
if (input == null)
|
if (input == null)
|
||||||
|
|||||||
@@ -191,6 +191,8 @@ public abstract class AbstractDhLevel implements IDhLevel
|
|||||||
|
|
||||||
return this.updateDataSourcesAsync(fullDataSource)
|
return this.updateDataSourcesAsync(fullDataSource)
|
||||||
.thenRun(() ->
|
.thenRun(() ->
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
|
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
|
||||||
if (updatedChunkPosSet != null)
|
if (updatedChunkPosSet != null)
|
||||||
@@ -209,6 +211,11 @@ public abstract class AbstractDhLevel implements IDhLevel
|
|||||||
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ()));
|
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected issue after onDataSourceSaveAsync, error: ["+e.getMessage()+"].", e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,13 +102,12 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
|
|||||||
{ return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); }
|
{ return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
|
||||||
public DhBlockPos2D getTargetPosForGeneration()
|
public DhBlockPos2D getTargetPosForGeneration()
|
||||||
{
|
{
|
||||||
IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek();
|
IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek();
|
||||||
if (firstPlayer == null)
|
if (firstPlayer == null)
|
||||||
{
|
{
|
||||||
return null;
|
return DhBlockPos2D.ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put first player in back before removing from front, so it can be removed by other thread without blocking
|
// Put first player in back before removing from front, so it can be removed by other thread without blocking
|
||||||
|
|||||||
@@ -247,7 +247,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
|
||||||
public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); }
|
public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); }
|
||||||
|
|
||||||
|
|
||||||
@@ -259,13 +258,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
|||||||
@Override
|
@Override
|
||||||
public void onWorldGenTaskComplete(long pos)
|
public void onWorldGenTaskComplete(long pos)
|
||||||
{
|
{
|
||||||
DebugRenderer.makeParticle(
|
|
||||||
new DebugRenderer.BoxParticle(
|
|
||||||
new DebugRenderer.Box(pos, 128f, 156f, 0.09f, Color.red.darker()),
|
|
||||||
0.2, 32f
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.clientside.reloadPos(pos);
|
this.clientside.reloadPos(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,9 +371,9 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
|||||||
|
|
||||||
private static class LodRequestState extends LodRequestModule.AbstractLodRequestState
|
private static class LodRequestState extends LodRequestModule.AbstractLodRequestState
|
||||||
{
|
{
|
||||||
LodRequestState(DhClientLevel level, ClientNetworkState networkState)
|
LodRequestState(DhClientLevel clientLevel, ClientNetworkState networkState)
|
||||||
{
|
{
|
||||||
this.retrievalQueue = new RemoteWorldRetrievalQueue(networkState, level);
|
this.retrievalQueue = new RemoteWorldRetrievalQueue(networkState, clientLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,14 +132,6 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
|
|||||||
public void onWorldGenTaskComplete(long pos)
|
public void onWorldGenTaskComplete(long pos)
|
||||||
{
|
{
|
||||||
super.onWorldGenTaskComplete(pos);
|
super.onWorldGenTaskComplete(pos);
|
||||||
|
|
||||||
DebugRenderer.makeParticle(
|
|
||||||
new DebugRenderer.BoxParticle(
|
|
||||||
new DebugRenderer.Box(pos, 128f, 156f, 0.09f, Color.red.darker()),
|
|
||||||
0.2, 32f
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.clientside.reloadPos(pos);
|
this.clientside.reloadPos(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class DhServerLevel extends AbstractDhServerLevel
|
|||||||
return true; //todo;
|
return true; //todo;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public @Nullable DhBlockPos2D getTargetPosForGeneration()
|
public DhBlockPos2D getTargetPosForGeneration()
|
||||||
{
|
{
|
||||||
DhBlockPos2D targetPos = super.getTargetPosForGeneration();
|
DhBlockPos2D targetPos = super.getTargetPosForGeneration();
|
||||||
if (targetPos == null)
|
if (targetPos == null)
|
||||||
|
|||||||
@@ -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.listeners.IConfigListener;
|
||||||
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
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.util.ThreadUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
@@ -302,32 +303,32 @@ public class DhLogger implements IConfigListener
|
|||||||
String prefix = "[" + ModInfo.READABLE_NAME + "] ";
|
String prefix = "[" + ModInfo.READABLE_NAME + "] ";
|
||||||
if (logLevel == Level.ERROR)
|
if (logLevel == Level.ERROR)
|
||||||
{
|
{
|
||||||
prefix += "\u00A74";
|
prefix += MinecraftTextFormat.DARK_RED;
|
||||||
}
|
}
|
||||||
else if (logLevel == Level.WARN)
|
else if (logLevel == Level.WARN)
|
||||||
{
|
{
|
||||||
prefix += "\u00A76";
|
prefix += MinecraftTextFormat.ORANGE;
|
||||||
}
|
}
|
||||||
else if (logLevel == Level.INFO)
|
else if (logLevel == Level.INFO)
|
||||||
{
|
{
|
||||||
prefix += "\u00A7f";
|
prefix += MinecraftTextFormat.AQUA;
|
||||||
}
|
}
|
||||||
else if (logLevel == Level.DEBUG)
|
else if (logLevel == Level.DEBUG)
|
||||||
{
|
{
|
||||||
prefix += "\u00A77";
|
prefix += MinecraftTextFormat.GREEN;
|
||||||
}
|
}
|
||||||
else if (logLevel == Level.TRACE)
|
else if (logLevel == Level.TRACE)
|
||||||
{
|
{
|
||||||
prefix += "\u00A78";
|
prefix += MinecraftTextFormat.DARK_GRAY;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
prefix += "\u00A7f";
|
prefix += MinecraftTextFormat.WHITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix += "\u00A7l\u00A7u";
|
prefix += MinecraftTextFormat.BOLD + "" + MinecraftTextFormat.WHITE;
|
||||||
prefix += logLevel.name();
|
prefix += logLevel.name();
|
||||||
prefix += ":\u00A7r ";
|
prefix += MinecraftTextFormat.CLEAR_FORMATTING + " ";
|
||||||
|
|
||||||
mc_client.sendChatMessage(prefix + message);
|
mc_client.sendChatMessage(prefix + message);
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-149
@@ -6,6 +6,7 @@ import com.seibel.distanthorizons.core.config.Config;
|
|||||||
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
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.level.DhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
@@ -33,9 +34,7 @@ import java.awt.*;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRenderable, AutoCloseable
|
public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRenderable, AutoCloseable
|
||||||
{
|
{
|
||||||
@@ -58,7 +57,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
|
|
||||||
private volatile CompletableFuture<Void> closingFuture = null;
|
private volatile CompletableFuture<Void> closingFuture = null;
|
||||||
|
|
||||||
protected final ConcurrentMap<Long, RequestQueueEntry> waitingTasksBySectionPos = new ConcurrentHashMap<>();
|
protected final ConcurrentMap<Long, NetRequestTask> waitingTasksBySectionPos = new ConcurrentHashMap<>();
|
||||||
/**
|
/**
|
||||||
* This semaphore prevents a given thread from accidentally locking on the same group
|
* This semaphore prevents a given thread from accidentally locking on the same group
|
||||||
* multiple times, as the semaphore is tied to the given thread. <br>
|
* multiple times, as the semaphore is tied to the given thread. <br>
|
||||||
@@ -74,16 +73,6 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
|
|
||||||
private final SupplierBasedRateLimiter<Void> rateLimiter = new SupplierBasedRateLimiter<>(this::getRequestRateLimit);
|
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());
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
@@ -108,8 +97,8 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
//==================//
|
//==================//
|
||||||
|
|
||||||
protected abstract int getRequestRateLimit();
|
protected abstract int getRequestRateLimit();
|
||||||
protected abstract boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos);
|
protected abstract boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos);
|
||||||
protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<ERequestResult> future);
|
protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future);
|
||||||
|
|
||||||
protected abstract String getQueueName();
|
protected abstract String getQueueName();
|
||||||
|
|
||||||
@@ -119,74 +108,57 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
// request submitting //
|
// request submitting //
|
||||||
//====================//
|
//====================//
|
||||||
|
|
||||||
public CompletableFuture<ERequestResult> submitRequest(long sectionPos, Consumer<FullDataSourceV2> dataSourceConsumer)
|
public CompletableFuture<DataSourceRetrievalResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp)
|
||||||
{ return this.submitRequest(sectionPos, null, dataSourceConsumer); }
|
|
||||||
public CompletableFuture<ERequestResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp, Consumer<FullDataSourceV2> dataSourceConsumer)
|
|
||||||
{
|
{
|
||||||
if (this.succeededPositions.contains(sectionPos))
|
NetRequestTask requestEntry = this.waitingTasksBySectionPos.compute(sectionPos, (Long pos, NetRequestTask existingNetTask) ->
|
||||||
{
|
{
|
||||||
return CompletableFuture.completedFuture(ERequestResult.FAILED);
|
// ignore already queued tasks
|
||||||
|
if (existingNetTask != null)
|
||||||
|
{
|
||||||
|
return existingNetTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.requiresSplittingPositions.contains(sectionPos))
|
|
||||||
|
NetRequestTask newRequestEntry = new NetRequestTask(pos, clientTimestamp);
|
||||||
|
newRequestEntry.future.whenComplete((DataSourceRetrievalResult requestResult, Throwable throwable) ->
|
||||||
{
|
{
|
||||||
return CompletableFuture.completedFuture(ERequestResult.REQUIRES_SPLITTING);
|
this.waitingTasksBySectionPos.remove(pos);
|
||||||
|
|
||||||
|
if (throwable != null)
|
||||||
|
{
|
||||||
|
if (!(throwable instanceof CancellationException))
|
||||||
|
{
|
||||||
|
this.failedRequests.incrementAndGet();
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AtomicBoolean added = new AtomicBoolean(false);
|
switch (requestResult.state)
|
||||||
RequestQueueEntry entry = this.waitingTasksBySectionPos.compute(sectionPos, (pos, existingQueueEntry) ->
|
|
||||||
{
|
{
|
||||||
if (existingQueueEntry != null)
|
case SUCCESS:
|
||||||
{
|
|
||||||
return existingQueueEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestQueueEntry newEntry = new RequestQueueEntry(dataSourceConsumer, clientTimestamp);
|
|
||||||
newEntry.future.whenComplete((requestResult, throwable) ->
|
|
||||||
{
|
|
||||||
this.waitingTasksBySectionPos.remove(sectionPos);
|
|
||||||
|
|
||||||
switch (requestResult)
|
|
||||||
{
|
|
||||||
case SUCCEEDED:
|
|
||||||
this.finishedRequests.incrementAndGet();
|
this.finishedRequests.incrementAndGet();
|
||||||
this.succeededPositions.add(pos);
|
break;
|
||||||
return;
|
|
||||||
case REQUIRES_SPLITTING:
|
case REQUIRES_SPLITTING:
|
||||||
this.requiresSplittingPositions.add(sectionPos);
|
|
||||||
return;
|
|
||||||
case FAILED:
|
|
||||||
this.failedRequests.incrementAndGet();
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
if (throwable != null && !(throwable instanceof CancellationException))
|
|
||||||
{
|
|
||||||
this.failedRequests.incrementAndGet();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
added.set(true);
|
return newRequestEntry;
|
||||||
return newEntry;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!added.get())
|
return requestEntry.future;
|
||||||
{
|
|
||||||
return CompletableFuture.completedFuture(ERequestResult.FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry.future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean tick(DhBlockPos2D targetPos)
|
public synchronized boolean tick(DhBlockPos2D targetPos)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.worldLoaded()
|
||||||
|
&& DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.closingFuture != null || !this.networkState.isReady())
|
if (this.closingFuture != null
|
||||||
|
|| !this.networkState.isReady())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -209,43 +181,57 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
}
|
}
|
||||||
private void sendNextRequest(DhBlockPos2D targetPos)
|
private void sendNextRequest(DhBlockPos2D targetPos)
|
||||||
{
|
{
|
||||||
Map.Entry<Long, RequestQueueEntry> mapEntry = this.waitingTasksBySectionPos.entrySet().stream()
|
Map.Entry<Long, NetRequestTask> nearestMapEntry = this.waitingTasksBySectionPos
|
||||||
|
.entrySet().stream()
|
||||||
.filter(task -> task.getValue().networkDataSourceFuture == null)
|
.filter(task -> task.getValue().networkDataSourceFuture == null)
|
||||||
.min(Comparator.comparingInt(x -> DhSectionPos.getChebyshevSignedBlockDistance(x.getKey(), targetPos)))
|
.min(Comparator.comparingInt(mapEntry -> DhSectionPos.getChebyshevSignedBlockDistance(mapEntry.getKey(), targetPos)))
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
if (mapEntry == null)
|
if (nearestMapEntry == null)
|
||||||
{
|
{
|
||||||
this.pendingTasksSemaphore.release();
|
this.pendingTasksSemaphore.release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long sectionPos = mapEntry.getKey();
|
long requestPos = nearestMapEntry.getKey();
|
||||||
RequestQueueEntry entry = mapEntry.getValue();
|
NetRequestTask requestTask = nearestMapEntry.getValue();
|
||||||
|
|
||||||
if (!this.isSectionAllowedToGenerate(sectionPos, targetPos))
|
if (!this.sectionInAllowedGenerationRadius(requestPos, targetPos))
|
||||||
{
|
{
|
||||||
entry.future.cancel(false);
|
requestTask.future.cancel(false);
|
||||||
this.pendingTasksSemaphore.release();
|
this.pendingTasksSemaphore.release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.onBeforeRequest(sectionPos, entry.future))
|
if (!this.onBeforeRequest(requestPos, requestTask.future))
|
||||||
{
|
{
|
||||||
this.pendingTasksSemaphore.release();
|
this.pendingTasksSemaphore.release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Long offsetEntryTimestamp = entry.updateTimestamp != null
|
Long offsetEntryTimestamp = requestTask.updateTimestamp != null
|
||||||
? entry.updateTimestamp + this.networkState.getServerTimeOffset()
|
? requestTask.updateTimestamp + this.networkState.getServerTimeOffset()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
CompletableFuture<FullDataSourceResponseMessage> dataSourceFuture = this.networkState.getSession().sendRequest(
|
CompletableFuture<FullDataSourceResponseMessage> dataSourceNetworkFuture = this.networkState.getSession().sendRequest(
|
||||||
new FullDataSourceRequestMessage(this.level.getLevelWrapper(), sectionPos, offsetEntryTimestamp),
|
new FullDataSourceRequestMessage(this.level.getLevelWrapper(), requestPos, offsetEntryTimestamp),
|
||||||
FullDataSourceResponseMessage.class
|
FullDataSourceResponseMessage.class
|
||||||
);
|
);
|
||||||
entry.networkDataSourceFuture = dataSourceFuture;
|
requestTask.networkDataSourceFuture = dataSourceNetworkFuture;
|
||||||
dataSourceFuture.handle((response, 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)
|
||||||
{
|
{
|
||||||
this.pendingTasksSemaphore.release();
|
this.pendingTasksSemaphore.release();
|
||||||
|
|
||||||
@@ -256,123 +242,104 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
throw throwable;
|
throw throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.payload != null)
|
if (response.payload == null)
|
||||||
{
|
{
|
||||||
FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(response.payload);
|
LodUtil.assertTrue(this.changedOnly, "Received empty data source response for not changes-only request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try(FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(response.payload))
|
||||||
|
{
|
||||||
// set application flags based on the received detail level,
|
// set application flags based on the received detail level,
|
||||||
// this is needed so the data sources propagate correctly
|
// this is needed so the data sources propagate correctly
|
||||||
dataSourceDto.applyToChildren = DhSectionPos.getDetailLevel(dataSourceDto.pos) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
|
dataSourceDto.applyToChildren = DhSectionPos.getDetailLevel(dataSourceDto.pos) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
|
||||||
dataSourceDto.applyToParent = DhSectionPos.getDetailLevel(dataSourceDto.pos) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12;
|
dataSourceDto.applyToParent = DhSectionPos.getDetailLevel(dataSourceDto.pos) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12;
|
||||||
|
|
||||||
AbstractExecutorService executor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
|
||||||
if (executor == null)
|
|
||||||
{
|
|
||||||
LOGGER.warn("Unable to handle FullDataPayload - getNetworkCompressionExecutor() is null");
|
|
||||||
dataSourceDto.close();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompletableFuture.runAsync(() ->
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.level.updateBeaconBeamsForSectionPos(dataSourceDto.pos, response.payload.beaconBeams);
|
this.level.updateBeaconBeamsForSectionPos(dataSourceDto.pos, response.payload.beaconBeams);
|
||||||
|
|
||||||
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper(), null);
|
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper(), null);
|
||||||
entry.dataSourceConsumer.accept(fullDataSource);
|
requestTask.future.complete(DataSourceRetrievalResult.CreateSuccess(dataSourceDto.pos, fullDataSource));
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
dataSourceDto.close();
|
|
||||||
}
|
|
||||||
}, executor);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LodUtil.assertTrue(this.changedOnly, "Received empty data source response for not changes-only request");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (SectionRequiresSplittingException ignored)
|
catch (SectionRequiresSplittingException ignored)
|
||||||
{
|
{
|
||||||
return entry.future.complete(ERequestResult.REQUIRES_SPLITTING);
|
requestTask.future.complete(DataSourceRetrievalResult.CreateSplit());
|
||||||
}
|
}
|
||||||
catch (SessionClosedException | CancellationException ignored)
|
catch (SessionClosedException | CancellationException ignored)
|
||||||
{
|
{
|
||||||
return entry.future.cancel(false);
|
requestTask.future.cancel(false);
|
||||||
}
|
}
|
||||||
catch (RequestRejectedException e)
|
catch (RequestRejectedException e)
|
||||||
{
|
{
|
||||||
LOGGER.info("Request rejected by the server: " + e.getMessage());
|
LOGGER.info("Request rejected by the server, message: [" + e.getMessage() + "].");
|
||||||
return entry.future.complete(ERequestResult.FAILED);
|
requestTask.future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
catch (RateLimitedException e)
|
catch (RateLimitedException e)
|
||||||
{
|
{
|
||||||
LOGGER.info("Rate limited by server, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage());
|
LOGGER.info("Rate limited by server, re-queueing task [" + DhSectionPos.toString(requestTask.pos) + "], message: [" + e.getMessage() + "].");
|
||||||
|
|
||||||
// Skip all requests for 1 second
|
// Skip all requests for 1 second
|
||||||
this.rateLimiter.acquireAll();
|
this.rateLimiter.acquireAll();
|
||||||
|
|
||||||
entry.networkDataSourceFuture = null;
|
requestTask.networkDataSourceFuture = null;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
catch (RequestOutOfRangeException e)
|
catch (RequestOutOfRangeException e)
|
||||||
{
|
{
|
||||||
LOGGER.debug("Out of range, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage());
|
LOGGER.debug("Out of range, re-queueing task [" + DhSectionPos.toString(requestTask.pos) + "], message: [" + e.getMessage() + "].");
|
||||||
|
|
||||||
entry.networkDataSourceFuture = null;
|
requestTask.networkDataSourceFuture = null;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
{
|
{
|
||||||
entry.retryAttempts--;
|
requestTask.retryAttempts--;
|
||||||
LOGGER.error("Error while fetching full data source, attempts left: {} / {}", entry.retryAttempts, MAX_RETRY_ATTEMPTS, e);
|
LOGGER.error("Unexpected error: ["+e.getMessage()+"] while fetching full data source, attempts left: ["+requestTask.retryAttempts+"] / ["+MAX_RETRY_ATTEMPTS+"]", e);
|
||||||
|
|
||||||
// Retry logic
|
// Retry logic
|
||||||
if (entry.retryAttempts > 0)
|
if (requestTask.retryAttempts > 0)
|
||||||
{
|
{
|
||||||
entry.networkDataSourceFuture = null;
|
requestTask.networkDataSourceFuture = null;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return entry.future.complete(ERequestResult.FAILED);
|
requestTask.future.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry.future.complete(ERequestResult.SUCCEEDED);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=========================================//
|
//=========================================//
|
||||||
// IFullDataSourceRetrievalQueue overrides //
|
// IFullDataSourceRetrievalQueue overrides //
|
||||||
//=========================================//
|
//=========================================//
|
||||||
|
|
||||||
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf)
|
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf)
|
||||||
{
|
{
|
||||||
for (Map.Entry<Long, RequestQueueEntry> mapEntry : (Iterable<? extends Map.Entry<Long, RequestQueueEntry>>) this.waitingTasksBySectionPos.entrySet().stream()
|
// remove tasks furthest
|
||||||
.sorted(Comparator.comparingInt((Map.Entry<Long, RequestQueueEntry> entry) -> DhSectionPos.getChebyshevSignedBlockDistance(entry.getKey(), Objects.requireNonNull(this.level.getTargetPosForGeneration()))).reversed())
|
Iterator<Map.Entry<Long, NetRequestTask>> farestTaskIterator = this.waitingTasksBySectionPos
|
||||||
::iterator)
|
.entrySet().stream()
|
||||||
|
.sorted(Comparator.comparingInt((Map.Entry<Long, NetRequestTask> entry) ->
|
||||||
{
|
{
|
||||||
|
Long pos = entry.getKey();
|
||||||
|
DhBlockPos2D targetPos = this.level.getTargetPosForGeneration();
|
||||||
|
return DhSectionPos.getChebyshevSignedBlockDistance(pos, targetPos);
|
||||||
|
}).reversed())
|
||||||
|
.iterator();
|
||||||
|
|
||||||
|
while (farestTaskIterator.hasNext())
|
||||||
|
{
|
||||||
|
Map.Entry<Long, NetRequestTask> mapEntry = farestTaskIterator.next();
|
||||||
long pos = mapEntry.getKey();
|
long pos = mapEntry.getKey();
|
||||||
RequestQueueEntry entry = mapEntry.getValue();
|
NetRequestTask entry = mapEntry.getValue();
|
||||||
|
|
||||||
if (removeIf.accept(pos))
|
if (removeIf.accept(pos))
|
||||||
{
|
{
|
||||||
LOGGER.debug("Removing request [" + mapEntry.getKey() + "]...");
|
|
||||||
|
|
||||||
entry.future.cancel(false);
|
|
||||||
if (entry.networkDataSourceFuture != null)
|
if (entry.networkDataSourceFuture != null)
|
||||||
{
|
{
|
||||||
entry.networkDataSourceFuture.cancel(false);
|
entry.networkDataSourceFuture.cancel(false);
|
||||||
}
|
}
|
||||||
|
entry.future.cancel(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,7 +367,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
for (RequestQueueEntry entry : this.waitingTasksBySectionPos.values())
|
for (NetRequestTask entry : this.waitingTasksBySectionPos.values())
|
||||||
{
|
{
|
||||||
entry.future.cancel(alsoInterruptRunning);
|
entry.future.cancel(alsoInterruptRunning);
|
||||||
if (entry.networkDataSourceFuture != null && entry.networkDataSourceFuture.cancel(alsoInterruptRunning))
|
if (entry.networkDataSourceFuture != null && entry.networkDataSourceFuture.cancel(alsoInterruptRunning))
|
||||||
@@ -438,13 +405,31 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<Long, RequestQueueEntry> mapEntry : this.waitingTasksBySectionPos.entrySet())
|
DhBlockPos2D targetPos = this.level.getTargetPosForGeneration();
|
||||||
|
for (Map.Entry<Long, NetRequestTask> mapEntry : this.waitingTasksBySectionPos.entrySet())
|
||||||
{
|
{
|
||||||
renderer.renderBox(new DebugRenderer.Box(mapEntry.getKey(), -32f, 64f, 0.05f,
|
long pos = mapEntry.getKey();
|
||||||
mapEntry.getValue().networkDataSourceFuture != null ? Color.red
|
NetRequestTask task = mapEntry.getValue();
|
||||||
: this.isSectionAllowedToGenerate(mapEntry.getKey(), Objects.requireNonNull(this.level.getTargetPosForGeneration())) ? Color.gray
|
|
||||||
: Color.darkGray
|
Color color;
|
||||||
));
|
if (task.networkDataSourceFuture != null)
|
||||||
|
{
|
||||||
|
color = Color.RED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boolean taskInAllowedGenRadius = this.sectionInAllowedGenerationRadius(pos, targetPos);
|
||||||
|
if (taskInAllowedGenRadius)
|
||||||
|
{
|
||||||
|
color = Color.GRAY;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
color = Color.DARK_GRAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,11 +439,12 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
// helper classes //
|
// helper classes //
|
||||||
//================//
|
//================//
|
||||||
|
|
||||||
protected static class RequestQueueEntry
|
protected static class NetRequestTask
|
||||||
{
|
{
|
||||||
|
public final long pos;
|
||||||
|
|
||||||
/** encapsulates the entire request, including client side queuing and the actual server request */
|
/** encapsulates the entire request, including client side queuing and the actual server request */
|
||||||
public final CompletableFuture<ERequestResult> future = new CompletableFuture<>();
|
public final CompletableFuture<DataSourceRetrievalResult> future = new CompletableFuture<>();
|
||||||
public final Consumer<FullDataSourceV2> dataSourceConsumer;
|
|
||||||
/** will be null if we want to retrieve the LOD regardless of when it was last updated */
|
/** will be null if we want to retrieve the LOD regardless of when it was last updated */
|
||||||
@Nullable
|
@Nullable
|
||||||
public final Long updateTimestamp;
|
public final Long updateTimestamp;
|
||||||
@@ -477,23 +463,14 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
|
||||||
public RequestQueueEntry(
|
public NetRequestTask(long pos, @Nullable Long updateTimestamp)
|
||||||
Consumer<FullDataSourceV2> dataSourceConsumer,
|
|
||||||
@Nullable Long updateTimestamp)
|
|
||||||
{
|
{
|
||||||
this.dataSourceConsumer = dataSourceConsumer;
|
this.pos = pos;
|
||||||
this.updateTimestamp = updateTimestamp;
|
this.updateTimestamp = updateTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ERequestResult
|
|
||||||
{
|
|
||||||
SUCCEEDED,
|
|
||||||
REQUIRES_SPLITTING,
|
|
||||||
FAILED,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+20
-1
@@ -1,8 +1,10 @@
|
|||||||
package com.seibel.distanthorizons.core.multiplayer.client;
|
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.Config;
|
||||||
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
|
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.network.session.NetworkSession;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
@@ -29,6 +32,10 @@ public class ClientNetworkState implements Closeable
|
|||||||
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
|
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
|
||||||
.build();
|
.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);
|
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; }
|
public NetworkSession getSession() { return this.networkSession; }
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public SessionConfig sessionConfig = new SessionConfig();
|
public SessionConfig sessionConfig = new SessionConfig();
|
||||||
|
|
||||||
private volatile boolean configReceived = false;
|
private volatile boolean configReceived = false;
|
||||||
@@ -89,6 +97,15 @@ public class ClientNetworkState implements Closeable
|
|||||||
|| Math.abs(event.protocolVersion - ModInfo.PROTOCOL_VERSION) < this.closestProtocolVersion)
|
|| Math.abs(event.protocolVersion - ModInfo.PROTOCOL_VERSION) < this.closestProtocolVersion)
|
||||||
{
|
{
|
||||||
this.closestProtocolVersion = event.protocolVersion;
|
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,7 +146,9 @@ public class ClientNetworkState implements Closeable
|
|||||||
{
|
{
|
||||||
this.serverSupportStatus = EServerSupportStatus.FULL;
|
this.serverSupportStatus = EServerSupportStatus.FULL;
|
||||||
|
|
||||||
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.sessionConfig = message.config;
|
||||||
this.configReceived = true;
|
this.configReceived = true;
|
||||||
});
|
});
|
||||||
|
|||||||
+3
-2
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.client;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
|
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.level.DhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
@@ -35,12 +36,12 @@ public class SyncOnLoadRequestQueue extends AbstractFullDataNetworkRequestQueue
|
|||||||
@Override
|
@Override
|
||||||
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getSyncOnLoginRateLimit(); }
|
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getSyncOnLoginRateLimit(); }
|
||||||
@Override
|
@Override
|
||||||
protected boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos)
|
protected boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos)
|
||||||
{
|
{
|
||||||
return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxSyncOnLoadDistance() * 16;
|
return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxSyncOnLoadDistance() * 16;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<ERequestResult> future) { return true; }
|
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future) { return true; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getQueueName() { return "Sync On Login Queue"; }
|
protected String getQueueName() { return "Sync On Login Queue"; }
|
||||||
|
|||||||
+28
@@ -162,6 +162,34 @@ public class SessionConfig implements INetworkObject
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=========//
|
||||||
|
// logging //
|
||||||
|
//=========//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* example: "common.playerBandwidthLimit:[497], " <br>
|
||||||
|
* Useful to see what was changed when receiving a new config from the server.
|
||||||
|
*/
|
||||||
|
public String getDifferencesAsString(SessionConfig that)
|
||||||
|
{
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
for (String key : this.values.keySet())
|
||||||
|
{
|
||||||
|
String thisFieldString = this.values.get(key) + "";
|
||||||
|
String thatFieldString = that.values.get(key) + "";
|
||||||
|
|
||||||
|
if (!thisFieldString.equals(thatFieldString))
|
||||||
|
{
|
||||||
|
stringBuilder.append(key+":["+thisFieldString+"], ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
// base overrides //
|
// base overrides //
|
||||||
//================//
|
//================//
|
||||||
|
|||||||
+4
-4
@@ -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.sql.dto.FullDataSourceV2DTO;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import io.netty.buffer.CompositeByteBuf;
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -36,7 +36,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
{
|
{
|
||||||
if (message.isFirst)
|
if (message.isFirst)
|
||||||
{
|
{
|
||||||
composite = UnpooledByteBufAllocator.DEFAULT.compositeBuffer();
|
composite = Unpooled.compositeBuffer();
|
||||||
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
|
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
|
||||||
}
|
}
|
||||||
else if (composite == null)
|
else if (composite == null)
|
||||||
@@ -45,6 +45,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message.buffer.readerIndex(0);
|
||||||
composite.addComponent(message.buffer);
|
composite.addComponent(message.buffer);
|
||||||
composite.writerIndex(composite.writerIndex() + message.buffer.writerIndex());
|
composite.writerIndex(composite.writerIndex() + message.buffer.writerIndex());
|
||||||
LOGGER.debug("Updated full data buffer [" + message.bufferId + "]: [" + composite + "].");
|
LOGGER.debug("Updated full data buffer [" + message.bufferId + "]: [" + composite + "].");
|
||||||
@@ -55,12 +56,11 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
||||||
{
|
{
|
||||||
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
||||||
LodUtil.assertTrue(compositeByteBuffer != null);
|
LodUtil.assertTrue(compositeByteBuffer != null, "decoded data source missing byte buffer");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
FullDataSourceV2DTO dataSourceDto = INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer);
|
FullDataSourceV2DTO dataSourceDto = INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer);
|
||||||
LOGGER.debug("Buffer {} DTO: {}", payload.dtoBufferId, dataSourceDto);
|
|
||||||
return dataSourceDto;
|
return dataSourceDto;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
+19
-3
@@ -174,12 +174,21 @@ public class FullDataSourceRequestHandler implements AutoCloseable
|
|||||||
DataSourceRequestGroup requestGroup = this.requestGroupsByPos.computeIfAbsent(requestData.sectionPos(), pos ->
|
DataSourceRequestGroup requestGroup = this.requestGroupsByPos.computeIfAbsent(requestData.sectionPos(), pos ->
|
||||||
{
|
{
|
||||||
DataSourceRequestGroup newGroup = new DataSourceRequestGroup(pos);
|
DataSourceRequestGroup newGroup = new DataSourceRequestGroup(pos);
|
||||||
|
try
|
||||||
|
{
|
||||||
newGroup.tryAddRequest(requestData);
|
newGroup.tryAddRequest(requestData);
|
||||||
createdNewGroup.set(true);
|
createdNewGroup.set(true);
|
||||||
|
|
||||||
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
|
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
|
||||||
|
|
||||||
LOGGER.debug("[" + this.getLevelIdentifier() + "] Created request group for pos [" + DhSectionPos.toString(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);
|
||||||
|
}
|
||||||
|
|
||||||
return newGroup;
|
return newGroup;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -229,10 +238,14 @@ public class FullDataSourceRequestHandler implements AutoCloseable
|
|||||||
|
|
||||||
private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
|
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().isFullyGenerated(fullDataSource.columnGenerationSteps))
|
if (provider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps))
|
||||||
{
|
{
|
||||||
|
//LOGGER.info("sending - complete [" + DhSectionPos.toString(pos) + "]");
|
||||||
requestGroup.fullDataSource = fullDataSource;
|
requestGroup.fullDataSource = fullDataSource;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -247,11 +260,14 @@ public class FullDataSourceRequestHandler implements AutoCloseable
|
|||||||
this.requestGroupsByPos.remove(pos);
|
this.requestGroupsByPos.remove(pos);
|
||||||
if (!requestGroup.tryClose())
|
if (!requestGroup.tryClose())
|
||||||
{
|
{
|
||||||
|
//LOGGER.info("closing [" + DhSectionPos.toString(pos) + "]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
|
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
|
||||||
{
|
{
|
||||||
|
//LOGGER.info("sending [" + DhSectionPos.toString(pos) + "] - ["+DhSectionPos.toString(requestData.sectionPos())+"]");
|
||||||
|
|
||||||
this.requestGroupsByFutureId.remove(requestData.futureId());
|
this.requestGroupsByFutureId.remove(requestData.futureId());
|
||||||
requestData.rateLimiterSet.generationRequestRateLimiter.release();
|
requestData.rateLimiterSet.generationRequestRateLimiter.release();
|
||||||
requestData.message.sendResponse(new SectionRequiresSplittingException());
|
requestData.message.sendResponse(new SectionRequiresSplittingException());
|
||||||
@@ -264,7 +280,7 @@ public class FullDataSourceRequestHandler implements AutoCloseable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//LOGGER.info("sending - queueing [" + DhSectionPos.toString(pos) + "]");
|
//LOGGER.info("queueing incomplete world gen [" + DhSectionPos.toString(pos) + "]");
|
||||||
this.fullDataSourceProvider().queuePositionForRetrieval(pos);
|
this.fullDataSourceProvider().queuePositionForRetrieval(pos);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-1
@@ -92,7 +92,8 @@ public class NetworkSession extends AbstractNetworkEventSource
|
|||||||
{
|
{
|
||||||
LOGGER.error("Failed to handle the message. New messages will be ignored.", e);
|
LOGGER.error("Failed to handle the message. New messages will be ignored.", e);
|
||||||
LOGGER.error("Message: ["+message+"]");
|
LOGGER.error("Message: ["+message+"]");
|
||||||
this.close();
|
|
||||||
|
this.close(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -40,7 +40,9 @@ public abstract class AbstractPhantomArrayList implements AutoCloseable
|
|||||||
/** The Array counts can be 0 or greater. */
|
/** The Array counts can be 0 or greater. */
|
||||||
public AbstractPhantomArrayList(PhantomArrayListPool phantomArrayListPool, int byteArrayCount, int shortArrayCount, int longArrayCount)
|
public AbstractPhantomArrayList(PhantomArrayListPool phantomArrayListPool, int byteArrayCount, int shortArrayCount, int longArrayCount)
|
||||||
{
|
{
|
||||||
if (byteArrayCount < 0 || shortArrayCount < 0 || longArrayCount < 0)
|
if (byteArrayCount < 0
|
||||||
|
|| shortArrayCount < 0
|
||||||
|
|| longArrayCount < 0)
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("Can't get a negative number of pooled arrays.");
|
throw new IllegalArgumentException("Can't get a negative number of pooled arrays.");
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This keeps track of all the poolable
|
* 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 */
|
/** Will be null if the parent pool doesn't want leak stack tracing */
|
||||||
@Nullable
|
@Nullable
|
||||||
public final String allocationStackTrace;
|
public String allocationStackTrace = null;
|
||||||
|
|
||||||
private final ArrayList<ByteArrayList> byteArrayLists = new ArrayList<>();
|
private final ArrayList<ByteArrayList> byteArrayLists = new ArrayList<>();
|
||||||
private final ArrayList<ShortArrayList> shortArrayLists = new ArrayList<>();
|
private final ArrayList<ShortArrayList> shortArrayLists = new ArrayList<>();
|
||||||
@@ -47,21 +48,20 @@ public class PhantomArrayListCheckout implements AutoCloseable
|
|||||||
|
|
||||||
public PhantomArrayListCheckout(@NotNull PhantomArrayListPool owningPool)
|
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.owningPool = owningPool;
|
||||||
this.ownerSoftReference = new SoftReference<>(this);
|
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.api.internal.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
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.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
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.bytes.ByteArrayList;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||||
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -159,6 +159,7 @@ public class PhantomArrayListPool
|
|||||||
{
|
{
|
||||||
// pool is empty, create new checkout
|
// pool is empty, create new checkout
|
||||||
checkout = new PhantomArrayListCheckout(this);
|
checkout = new PhantomArrayListCheckout(this);
|
||||||
|
checkout.onCheckout();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -166,6 +167,7 @@ public class PhantomArrayListPool
|
|||||||
if (checkout != null)
|
if (checkout != null)
|
||||||
{
|
{
|
||||||
// use pooled checkout
|
// use pooled checkout
|
||||||
|
checkout.onCheckout();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -176,8 +178,7 @@ public class PhantomArrayListPool
|
|||||||
{
|
{
|
||||||
lowMemoryWarningLogged = true;
|
lowMemoryWarningLogged = true;
|
||||||
|
|
||||||
// orange text
|
String message = MinecraftTextFormat.ORANGE + "Distant Horizons: Insufficient memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||||
String message = "\u00A76" + "Distant Horizons: Insufficient memory detected." + "\u00A7r \n" +
|
|
||||||
"This may cause stuttering or crashing. \n" +
|
"This may cause stuttering or crashing. \n" +
|
||||||
"Potential causes: \n" +
|
"Potential causes: \n" +
|
||||||
"1. your allocated memory isn't high enough \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;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
/** immutable */
|
/** immutable */
|
||||||
|
@Deprecated // TODO why does this exist vs blockpos2d?
|
||||||
public class Pos2D
|
public class Pos2D
|
||||||
{
|
{
|
||||||
public static final Pos2D ZERO = new Pos2D(0, 0);
|
public static final Pos2D ZERO = new Pos2D(0, 0);
|
||||||
|
|||||||
@@ -75,6 +75,30 @@ public class DhBlockPos2D
|
|||||||
public long distSquared(DhBlockPos2D other) { return this.distSquared(other.x, other.z); }
|
public long distSquared(DhBlockPos2D other) { return this.distSquared(other.x, other.z); }
|
||||||
public long distSquared(int x, int z) { return MathUtil.pow2((long) this.x - x) + MathUtil.pow2((long) this.z - z); }
|
public long distSquared(int x, int z) { return MathUtil.pow2((long) this.x - x) + MathUtil.pow2((long) this.z - z); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum distance along either the X or Z axis <br><br>
|
||||||
|
*
|
||||||
|
* Example chebyshev distance between X and every point around it: <br>
|
||||||
|
* <code>
|
||||||
|
* 2 2 2 2 2 <br>
|
||||||
|
* 2 1 1 1 2 <br>
|
||||||
|
* 2 1 X 1 2 <br>
|
||||||
|
* 2 1 1 1 2 <br>
|
||||||
|
* 2 2 2 2 2 <br>
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
public int chebyshevDist(DhBlockPos2D other) { return Math.max(Math.abs(this.x - other.x), Math.abs(this.z - other.z)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used to quickly determine the rough distance between two points<Br>
|
||||||
|
* or determine the taxi cab (manhattan) distance between two points. <Br><Br>
|
||||||
|
*
|
||||||
|
* Manhattan distance is equivalent to determining the distance between two street intersections,
|
||||||
|
* where you can only drive along each street, instead of directly to the other point.
|
||||||
|
*/
|
||||||
|
public int manhattanDist(DhBlockPos2D other) { return Math.abs(this.x - other.x) + Math.abs(this.z - other.z); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===========//
|
//===========//
|
||||||
|
|||||||
@@ -20,10 +20,12 @@
|
|||||||
package com.seibel.distanthorizons.core.render;
|
package com.seibel.distanthorizons.core.render;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
|
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
|
||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||||
|
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||||
|
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
@@ -34,12 +36,13 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
|||||||
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
|
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.PerfRecorder;
|
|
||||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||||
|
import com.seibel.distanthorizons.core.util.WorldGenUtil;
|
||||||
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
|
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
|
||||||
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
|
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
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.LongIterator;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -47,6 +50,7 @@ import javax.annotation.WillNotClose;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@@ -57,11 +61,11 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
* This quadTree structure is our core data structure and holds
|
* This quadTree structure is our core data structure and holds
|
||||||
* all rendering data.
|
* 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();
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
/** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */
|
/** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */
|
||||||
private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleThreadPool("QuadTree Full Data Retrieval Queue Populator");
|
private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleThreadPool("LodQuadTree Data Retrieval Queue");
|
||||||
|
|
||||||
|
|
||||||
public final int blockRenderDistanceDiameter;
|
public final int blockRenderDistanceDiameter;
|
||||||
@@ -73,9 +77,13 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
* This is a {@link ConcurrentLinkedQueue} because new sections can be added to this list via the world generator threads.
|
* This is a {@link ConcurrentLinkedQueue} because new sections can be added to this list via the world generator threads.
|
||||||
*/
|
*/
|
||||||
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
|
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
|
||||||
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
|
private final IDhClientLevel level;
|
||||||
private final ReentrantLock treeReadWriteLock = new ReentrantLock();
|
/**
|
||||||
private final AtomicBoolean fullDataRetrievalQueueRunning = new AtomicBoolean(false);
|
* 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> debugRenderSections = new ArrayList<>();
|
||||||
private ArrayList<LodRenderSection> altDebugRenderSections = new ArrayList<>();
|
private ArrayList<LodRenderSection> altDebugRenderSections = new ArrayList<>();
|
||||||
@@ -88,7 +96,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
* as further sections are loaded before closer ones.
|
* as further sections are loaded before closer ones.
|
||||||
* Only queuing a few of the sections at a time solves this problem.
|
* 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
|
@Nullable
|
||||||
@@ -104,11 +114,18 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
/** used to calculate when a detail drop will occur */
|
/** used to calculate when a detail drop will occur */
|
||||||
private double detailDropOffLogBase;
|
private double detailDropOffLogBase;
|
||||||
|
|
||||||
|
/** the {@link DhSectionPos} that need to be retrieved/generated */
|
||||||
|
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<>();
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
|
||||||
// constructors //
|
//=============//
|
||||||
//==============//
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
//region constructor
|
||||||
|
|
||||||
public LodQuadTree(
|
public LodQuadTree(
|
||||||
IDhClientLevel level, int viewDiameterInBlocks,
|
IDhClientLevel level, int viewDiameterInBlocks,
|
||||||
@@ -126,13 +143,18 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
GenericObjectRenderer genericObjectRenderer = this.level.getGenericRenderer();
|
GenericObjectRenderer genericObjectRenderer = this.level.getGenericRenderer();
|
||||||
this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null;
|
this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null;
|
||||||
|
|
||||||
|
Config.Common.WorldGenerator.enableDistantGeneration.addListener(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion constructor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
// tick update //
|
// tick update //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region tick update
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function updates the quadTree based on the playerPos and the current game configs (static and global)
|
* This function updates the quadTree based on the playerPos and the current game configs (static and global)
|
||||||
@@ -143,23 +165,33 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
{
|
{
|
||||||
if (this.level == null)
|
if (this.level == null)
|
||||||
{
|
{
|
||||||
// the level hasn't finished loading yet
|
// the quad tree was created before a level reference was created
|
||||||
// TODO sometimes null pointers still happen, when logging back into a world (maybe the old level isn't null but isn't valid either?)
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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 shouldn't be updated while the tree is being iterated through
|
||||||
this.updateDetailLevelVariables();
|
this.updateDetailLevelVariables();
|
||||||
|
|
||||||
// don't traverse the tree if it is being modified
|
|
||||||
if (this.treeReadWriteLock.tryLock())
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// recenter if necessary, removing out of bounds sections
|
// recenter if necessary...
|
||||||
this.setCenterBlockPos(playerPos, LodRenderSection::close);
|
this.setCenterBlockPos(playerPos, (renderSection) ->
|
||||||
|
{
|
||||||
|
//...removing out of bounds sections
|
||||||
|
if (renderSection != null)
|
||||||
|
{
|
||||||
|
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
|
this.missingGenerationPosSet.remove(renderSection.pos);
|
||||||
|
this.queuedGenerationPosSet.remove(renderSection.pos);
|
||||||
|
renderSection.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.updateAllRenderSections(playerPos);
|
this.updateAllRenderSections(playerPos);
|
||||||
}
|
}
|
||||||
@@ -169,7 +201,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
this.treeReadWriteLock.unlock();
|
this.treeLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +229,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
|
|
||||||
// walk through each root node
|
// walk through each root node
|
||||||
HashSet<LodRenderSection> nodesNeedingRetrieval = new HashSet<>();
|
|
||||||
HashSet<LodRenderSection> nodesNeedingLoading = new HashSet<>();
|
HashSet<LodRenderSection> nodesNeedingLoading = new HashSet<>();
|
||||||
LongIterator rootPosIterator = this.rootNodePosIterator();
|
LongIterator rootPosIterator = this.rootNodePosIterator();
|
||||||
while (rootPosIterator.hasNext())
|
while (rootPosIterator.hasNext())
|
||||||
@@ -210,17 +241,61 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
}
|
}
|
||||||
|
|
||||||
QuadNode<LodRenderSection> rootNode = this.getNode(rootPos);
|
QuadNode<LodRenderSection> rootNode = this.getNode(rootPos);
|
||||||
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, rootNode, rootNode.sectionPos, false, nodesNeedingRetrieval, nodesNeedingLoading);
|
LodUtil.assertTrue(rootNode != null, "All root nodes should have been created by this point.");
|
||||||
|
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, rootNode, rootNode.sectionPos, false, nodesNeedingLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// queue full data retrieval (world gen) requests if needed
|
// requeue everything if needed
|
||||||
if (nodesNeedingRetrieval.size() != 0
|
if (this.requeueAllRetrievalTasksRef.get()
|
||||||
&& !this.fullDataRetrievalQueueRunning.get()
|
&& !this.queueThreadRunningRef.get())
|
||||||
&& this.fullDataSourceProvider.canQueueRetrieval())
|
|
||||||
{
|
{
|
||||||
this.fullDataRetrievalQueueRunning.set(true);
|
this.queueThreadRunningRef.set(true);
|
||||||
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() -> this.queueFullDataRetrievalTasks(playerPos, nodesNeedingRetrieval));
|
this.requeueAllRetrievalTasksRef.set(false);
|
||||||
|
|
||||||
|
// running on a separate thread allows for faster loading
|
||||||
|
// of finished LODs
|
||||||
|
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() ->
|
||||||
|
{
|
||||||
|
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(() ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.startQueuedRetrievalTasks(playerPos);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected error starting queued retrieval tasks, error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.queueThreadRunningRef.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -236,7 +311,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
DhBlockPos2D playerPos,
|
DhBlockPos2D playerPos,
|
||||||
QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> quadNode, long sectionPos,
|
QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> quadNode, long sectionPos,
|
||||||
boolean parentSectionIsRendering,
|
boolean parentSectionIsRendering,
|
||||||
HashSet<LodRenderSection> nodesNeedingRetrieval,
|
|
||||||
HashSet<LodRenderSection> nodesNeedingLoading)
|
HashSet<LodRenderSection> nodesNeedingLoading)
|
||||||
{
|
{
|
||||||
//=====================//
|
//=====================//
|
||||||
@@ -245,7 +319,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//=====================//
|
//=====================//
|
||||||
|
|
||||||
// create the node
|
// create the node
|
||||||
if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance
|
if (quadNode == null
|
||||||
|
&& this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance
|
||||||
{
|
{
|
||||||
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider, this.uploadTaskCountRef));
|
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider, this.uploadTaskCountRef));
|
||||||
quadNode = rootNode.getNode(sectionPos);
|
quadNode = rootNode.getNode(sectionPos);
|
||||||
@@ -288,7 +363,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||||
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), thisPosIsRendering || parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
|
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), thisPosIsRendering || parentSectionIsRendering, nodesNeedingLoading);
|
||||||
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
|
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +422,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||||
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading);
|
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
// disabling rendering must be done after the children are enabled
|
// disabling rendering must be done after the children are enabled
|
||||||
@@ -368,23 +443,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
// prepare this section for rendering
|
// prepare this section for rendering
|
||||||
if (!renderSection.gpuUploadInProgress()
|
if (!renderSection.gpuUploadInProgress()
|
||||||
&& renderSection.bufferContainer == null
|
&& renderSection.bufferContainer == null)
|
||||||
// TODO this is commented out since some users reported LODs refusing to
|
|
||||||
// load at their expected higher-detail levels
|
|
||||||
// this check is specifically for N-sized world generators where the higher quality
|
|
||||||
// data source may not exist yet, this is done to prevent holes while waiting for said generator
|
|
||||||
//&& renderSection.getFullDataSourceExists()
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
nodesNeedingLoading.add(renderSection);
|
nodesNeedingLoading.add(renderSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// queue world gen if needed
|
|
||||||
if (!renderSection.isFullyGenerated())
|
|
||||||
{
|
|
||||||
nodesNeedingRetrieval.add(renderSection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update debug if needed
|
// update debug if needed
|
||||||
if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get())
|
if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get())
|
||||||
{
|
{
|
||||||
@@ -394,7 +457,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
|
|
||||||
// wait for the parent to disable before enabling this section, so we don't have a hole
|
// wait for the parent to disable before enabling this section, so we don't have a hole
|
||||||
if (!parentSectionIsRendering && renderSection.canRender())
|
if (!parentSectionIsRendering
|
||||||
|
&& renderSection.canRender())
|
||||||
{
|
{
|
||||||
// if rendering is already enabled we don't have to re-enable it
|
// if rendering is already enabled we don't have to re-enable it
|
||||||
if (!renderSection.getRenderingEnabled())
|
if (!renderSection.getRenderingEnabled())
|
||||||
@@ -425,6 +489,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
// needs to be fired after the children are disabled so beacons render correctly
|
// needs to be fired after the children are disabled so beacons render correctly
|
||||||
renderSection.onRenderingEnabled();
|
renderSection.onRenderingEnabled();
|
||||||
|
|
||||||
|
// since this section wants to render
|
||||||
|
// check if it needs any generation to do so
|
||||||
|
this.tryQueuePosForRetrieval(renderSection.pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,15 +514,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// the section only needs to be updated if a buffer is currently present
|
// the section only needs to be updated if a buffer is currently present
|
||||||
LodRenderSection renderSection = this.getValue(pos);
|
LodRenderSection renderSection = this.tryGetValue(pos);
|
||||||
if (renderSection != null)
|
if (renderSection != null)
|
||||||
{
|
{
|
||||||
// this data source may now exist
|
|
||||||
renderSection.updateFullDataSourceExists();
|
|
||||||
|
|
||||||
if (renderSection.canRender())
|
if (renderSection.canRender())
|
||||||
{
|
{
|
||||||
if (renderSection.gpuUploadInProgress()
|
if (renderSection.gpuUploadInProgress()
|
||||||
@@ -470,9 +532,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException e)
|
|
||||||
{ /* the section is now out of bounds, it doesn't need to be reloaded */ }
|
|
||||||
}
|
|
||||||
this.sectionsToReload.addAll(positionsToRequeue);
|
this.sectionsToReload.addAll(positionsToRequeue);
|
||||||
}
|
}
|
||||||
private void loadQueuedSections(DhBlockPos2D playerPos, HashSet<LodRenderSection> nodesNeedingLoading)
|
private void loadQueuedSections(DhBlockPos2D playerPos, HashSet<LodRenderSection> nodesNeedingLoading)
|
||||||
@@ -488,18 +547,188 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
for (int i = 0; i < loadSectionList.size(); i++)
|
for (int i = 0; i < loadSectionList.size(); i++)
|
||||||
{
|
{
|
||||||
LodRenderSection renderSection = loadSectionList.get(i);
|
LodRenderSection renderSection = loadSectionList.get(i);
|
||||||
if (!renderSection.gpuUploadInProgress() && renderSection.bufferContainer == null)
|
if (!renderSection.gpuUploadInProgress()
|
||||||
|
&& renderSection.bufferContainer == null)
|
||||||
{
|
{
|
||||||
renderSection.uploadRenderDataToGpuAsync();
|
renderSection.uploadRenderDataToGpuAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion tick update
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=================================//
|
||||||
|
// full data retrieval (world gen) //
|
||||||
|
//=================================//
|
||||||
|
//region world gen
|
||||||
|
|
||||||
|
private void startQueuedRetrievalTasks(DhBlockPos2D playerPos)
|
||||||
|
{
|
||||||
|
// sort the nodes from nearest to farthest
|
||||||
|
this.sortedMissingPosList.clear();
|
||||||
|
this.sortedMissingPosList.addAll(this.missingGenerationPosSet);
|
||||||
|
this.sortedMissingPosList.sort((posA, posB) ->
|
||||||
|
{
|
||||||
|
int aDist = DhSectionPos.getManhattanBlockDistance(posA, playerPos);
|
||||||
|
int bDist = DhSectionPos.getManhattanBlockDistance(posB, playerPos);
|
||||||
|
return Integer.compare(aDist, bDist);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==================================//
|
||||||
|
// add retrieval tasks to the queue //
|
||||||
|
//==================================//
|
||||||
|
|
||||||
|
for (int i = 0; i < this.sortedMissingPosList.size(); i++)
|
||||||
|
{
|
||||||
|
if (!this.fullDataSourceProvider.canQueueRetrievalNow())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
long missingPos = this.sortedMissingPosList.get(i);
|
||||||
|
|
||||||
|
// is this position within acceptable generator range?
|
||||||
|
boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
|
||||||
|
missingPos,
|
||||||
|
Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(),
|
||||||
|
Config.Common.WorldGenerator.generationMaxChunkRadius.get()
|
||||||
|
);
|
||||||
|
if (!posInRange)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<DataSourceRetrievalResult> genFuture = this.fullDataSourceProvider.queuePositionForRetrieval(missingPos);
|
||||||
|
boolean positionQueued = (genFuture != null && !genFuture.isCompletedExceptionally());
|
||||||
|
if (positionQueued)
|
||||||
|
{
|
||||||
|
this.queuedGenerationPosSet.add(missingPos);
|
||||||
|
this.missingGenerationPosSet.remove(missingPos);
|
||||||
|
|
||||||
|
genFuture.exceptionally((Throwable throwable) ->
|
||||||
|
{
|
||||||
|
// gen task failed,
|
||||||
|
// requeue so we can try again in the future
|
||||||
|
|
||||||
|
this.queuedGenerationPosSet.remove(missingPos);
|
||||||
|
this.missingGenerationPosSet.add(missingPos);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
genFuture.thenAccept((DataSourceRetrievalResult result) ->
|
||||||
|
{
|
||||||
|
// task finished
|
||||||
|
this.queuedGenerationPosSet.remove(missingPos);
|
||||||
|
|
||||||
|
if (result.state == ERetrievalResultState.REQUIRES_SPLITTING)
|
||||||
|
{
|
||||||
|
DhSectionPos.forEachChild(missingPos, (long childPos) ->
|
||||||
|
{
|
||||||
|
this.tryQueuePosForRetrieval(childPos);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==========================//
|
||||||
|
// calc task count estimate //
|
||||||
|
//==========================//
|
||||||
|
|
||||||
|
// calculate an estimate for the max number of chunks for the queue
|
||||||
|
int totalWorldGenChunkCount = 0;
|
||||||
|
int totalWorldGenTaskCount = 0;
|
||||||
|
for (int i = 0; i < this.sortedMissingPosList.size(); i++)
|
||||||
|
{
|
||||||
|
long missingPos = this.sortedMissingPosList.get(i);
|
||||||
|
|
||||||
|
// chunk count
|
||||||
|
int sectionWidthInChunks = DhSectionPos.getChunkWidth(missingPos);
|
||||||
|
totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks;
|
||||||
|
|
||||||
|
// task count
|
||||||
|
totalWorldGenTaskCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount);
|
||||||
|
this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigValueSet()
|
||||||
|
{
|
||||||
|
boolean generatorEnabled = Config.Common.WorldGenerator.enableDistantGeneration.get();
|
||||||
|
if (generatorEnabled)
|
||||||
|
{
|
||||||
|
// world gen tasks will need to be re-queued
|
||||||
|
// since all the render sections will already have been loaded
|
||||||
|
this.requeueAllRetrievalTasksRef.set(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// generation is disabled, clear the queues
|
||||||
|
this.missingGenerationPosSet.clear();
|
||||||
|
this.queuedGenerationPosSet.clear();
|
||||||
|
|
||||||
|
this.requeueAllRetrievalTasksRef.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needed to get all necessary retrieval requests
|
||||||
|
* after the quad tree has already been loaded.
|
||||||
|
*/
|
||||||
|
private void checkAllNodesForRetrievalRequests()
|
||||||
|
{
|
||||||
|
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
|
||||||
|
while (nodeIterator.hasNext())
|
||||||
|
{
|
||||||
|
QuadNode<LodRenderSection> node = nodeIterator.next();
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
LodRenderSection renderSection = node.value;
|
||||||
|
if (renderSection != null
|
||||||
|
&& renderSection.getRenderingEnabled())
|
||||||
|
{
|
||||||
|
this.tryQueuePosForRetrieval(renderSection.pos);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Does nothing if the missing positions are already queued. */
|
||||||
|
private void tryQueuePosForRetrieval(long pos)
|
||||||
|
{
|
||||||
|
LongArrayList missingPosList = this.fullDataSourceProvider.getPositionsToRetrieve(pos);
|
||||||
|
if (missingPosList == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < missingPosList.size(); i++)
|
||||||
|
{
|
||||||
|
long missingPos = missingPosList.getLong(i);
|
||||||
|
if (!this.queuedGenerationPosSet.contains(missingPos))
|
||||||
|
{
|
||||||
|
this.missingGenerationPosSet.add(missingPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion world gen
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//====================//
|
//====================//
|
||||||
// detail level logic //
|
// detail level logic //
|
||||||
//====================//
|
//====================//
|
||||||
|
//region detail level logic
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will compute the detail level based on player position and section pos
|
* This method will compute the detail level based on player position and section pos
|
||||||
@@ -553,22 +782,24 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
this.minRootRenderDetailLevel = (byte) Math.max(minSectionDetailLevel, this.maxLeafRenderDetailLevel); // respect the user's selected max resolution if it is lower detail (IE they want 2x2 block, but minSectionDetailLevel is specifically for 1x1 block render resolution)
|
this.minRootRenderDetailLevel = (byte) Math.max(minSectionDetailLevel, this.maxLeafRenderDetailLevel); // respect the user's selected max resolution if it is lower detail (IE they want 2x2 block, but minSectionDetailLevel is specifically for 1x1 block render resolution)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion detail level logic
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
|
||||||
// render data //
|
//==========================//
|
||||||
//=============//
|
// external render requests //
|
||||||
|
//==========================//
|
||||||
|
//region external render requests
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-creates the color, render data.
|
* Re-creates the color, render data.
|
||||||
* This method should be called after resource packs are changed or LOD settings are modified.
|
* This method should be called after resource packs are changed or LOD settings are modified.
|
||||||
*/
|
*/
|
||||||
public void clearRenderDataCache()
|
public void clearRenderDataCache()
|
||||||
{
|
|
||||||
if (this.treeReadWriteLock.tryLock()) // TODO make async, can lock render thread
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
this.treeLock.lock();
|
||||||
LOGGER.info("Disposing render data...");
|
LOGGER.info("Disposing render data...");
|
||||||
|
|
||||||
// clear the tree
|
// clear the tree
|
||||||
@@ -591,8 +822,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
this.treeReadWriteLock.unlock();
|
this.treeLock.unlock();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,79 +850,14 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion external render requests
|
||||||
//=================================//
|
|
||||||
// full data retrieval (world gen) //
|
|
||||||
//=================================//
|
|
||||||
|
|
||||||
private void queueFullDataRetrievalTasks(DhBlockPos2D playerPos, HashSet<LodRenderSection> nodesNeedingRetrieval)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// sort the nodes from nearest to farthest
|
|
||||||
ArrayList<LodRenderSection> nodeList = new ArrayList<>(nodesNeedingRetrieval);
|
|
||||||
nodeList.sort((a, b) ->
|
|
||||||
{
|
|
||||||
int aDist = DhSectionPos.getManhattanBlockDistance(a.pos, playerPos);
|
|
||||||
int bDist = DhSectionPos.getManhattanBlockDistance(b.pos, playerPos);
|
|
||||||
return Integer.compare(aDist, bDist);
|
|
||||||
});
|
|
||||||
|
|
||||||
// add retrieval tasks to the queue
|
|
||||||
for (int i = 0; i < nodeList.size(); i++)
|
|
||||||
{
|
|
||||||
LodRenderSection renderSection = nodeList.get(i);
|
|
||||||
if (!this.fullDataSourceProvider.canQueueRetrieval())
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSection.tryQueuingMissingLodRetrieval();
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate an estimate for the max number of chunks for the queue
|
|
||||||
int totalWorldGenChunkCount = 0;
|
|
||||||
int totalWorldGenTaskCount = 0;
|
|
||||||
for (int i = 0; i < nodeList.size(); i++)
|
|
||||||
{
|
|
||||||
LodRenderSection renderSection = nodeList.get(i);
|
|
||||||
if (!renderSection.missingPositionsCalculated())
|
|
||||||
{
|
|
||||||
// chunk count
|
|
||||||
int sectionWidthInChunks = DhSectionPos.getChunkWidth(renderSection.pos);
|
|
||||||
totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks;
|
|
||||||
|
|
||||||
// task count
|
|
||||||
totalWorldGenTaskCount += renderSection.ungeneratedPositionCount();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
totalWorldGenChunkCount += renderSection.ungeneratedChunkCount();
|
|
||||||
|
|
||||||
// 1 since we assume the position can be generated in a single go
|
|
||||||
// TODO this is a bad assumption, can we determine what the world gen supports and determine it from that?
|
|
||||||
totalWorldGenTaskCount += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount);
|
|
||||||
this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.error("Unexpected error: "+e.getMessage(), e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
this.fullDataRetrievalQueueRunning.set(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//===========//
|
//===========//
|
||||||
// debugging //
|
// debugging //
|
||||||
//===========//
|
//===========//
|
||||||
|
//region debugging
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void debugRender(DebugRenderer debugRenderer)
|
public void debugRender(DebugRenderer debugRenderer)
|
||||||
@@ -739,11 +904,14 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion debugging
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
// base methods //
|
// base methods //
|
||||||
//==============//
|
//==============//
|
||||||
|
//region base methods
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
@@ -751,13 +919,15 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
LOGGER.info("Shutting down LodQuadTree...");
|
LOGGER.info("Shutting down LodQuadTree...");
|
||||||
|
|
||||||
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
||||||
|
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
|
||||||
|
|
||||||
|
|
||||||
ThreadPoolExecutor mainCleanupExecutor = ThreadPoolUtil.getCleanupExecutor();
|
ThreadPoolExecutor mainCleanupExecutor = ThreadPoolUtil.getCleanupExecutor();
|
||||||
// closing every node may take a few moments
|
// closing every node may take a few moments
|
||||||
// so this is run on a separate thread to prevent lagging the render thread
|
// so this is run on a separate thread to prevent lagging the render thread
|
||||||
mainCleanupExecutor.execute(() ->
|
mainCleanupExecutor.execute(() ->
|
||||||
{
|
{
|
||||||
this.treeReadWriteLock.lock();
|
this.treeLock.lock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// walk through each node
|
// walk through each node
|
||||||
@@ -775,7 +945,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
this.treeReadWriteLock.unlock();
|
this.treeLock.unlock();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -783,6 +953,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
LOGGER.info("Finished shutting down LodQuadTree");
|
LOGGER.info("Finished shutting down LodQuadTree");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion base methods
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.render;
|
package com.seibel.distanthorizons.core.render;
|
||||||
|
|
||||||
import com.google.common.base.Supplier;
|
|
||||||
import com.google.common.base.Suppliers;
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
|
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
|
||||||
@@ -42,12 +40,10 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
|
|||||||
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
|
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
||||||
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
|
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
|
||||||
import com.seibel.distanthorizons.core.util.WorldGenUtil;
|
|
||||||
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import javax.annotation.WillNotClose;
|
import javax.annotation.WillNotClose;
|
||||||
@@ -81,7 +77,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
* contains the list of beacons currently being rendered in this section
|
* 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.
|
* 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
|
@Nullable
|
||||||
public final BeaconRenderHandler beaconRenderHandler;
|
public final BeaconRenderHandler beaconRenderHandler;
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -115,24 +111,12 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
*/
|
*/
|
||||||
private CompletableFuture<LodBufferContainer> bufferUploadFuture = null;
|
private CompletableFuture<LodBufferContainer> bufferUploadFuture = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* should be an empty array if no positions need to be generated
|
|
||||||
*
|
|
||||||
* @deprecated see the comment where this variable is set
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
@Deprecated
|
|
||||||
private Supplier<LongArrayList> missingGenerationPosFunc;
|
|
||||||
private LongArrayList getMissingGenerationPos() { return this.missingGenerationPosFunc != null ? this.missingGenerationPosFunc.get() : null; }
|
|
||||||
|
|
||||||
private boolean checkedIfFullDataSourceExists = false;
|
|
||||||
private boolean fullDataSourceExists = false;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region constructor
|
||||||
|
|
||||||
public LodRenderSection(
|
public LodRenderSection(
|
||||||
long pos,
|
long pos,
|
||||||
@@ -153,11 +137,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
|
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion constructor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//======================================//
|
//======================================//
|
||||||
// render data generation and uploading //
|
// render data generation and uploading //
|
||||||
//======================================//
|
//======================================//
|
||||||
|
//region render data uploading
|
||||||
|
|
||||||
/** @return true if the upload started, false if it wasn't able to for any reason */
|
/** @return true if the upload started, false if it wasn't able to for any reason */
|
||||||
public synchronized boolean uploadRenderDataToGpuAsync()
|
public synchronized boolean uploadRenderDataToGpuAsync()
|
||||||
@@ -254,24 +241,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
// get the adjacent positions
|
// get the adjacent positions
|
||||||
// needs to be done async to prevent threads waiting on the same positions to be processed
|
// needs to be done async to prevent threads waiting on the same positions to be processed
|
||||||
final CompletableFuture<ColumnRenderSource>[] adjacentLoadFutures = new CompletableFuture[4];
|
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[0] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.NORTH);
|
||||||
adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.SOUTH);
|
adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.SOUTH);
|
||||||
adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.EAST);
|
adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.EAST);
|
||||||
adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.WEST);
|
adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.WEST);
|
||||||
}
|
|
||||||
|
|
||||||
return CompletableFuture.allOf(adjacentLoadFutures).thenRun(() ->
|
return CompletableFuture.allOf(adjacentLoadFutures).thenRun(() ->
|
||||||
{
|
{
|
||||||
try (ColumnRenderSource northRenderSource = adjacentLoadFutures[0].get();
|
try (ColumnRenderSource northRenderSource = adjacentLoadFutures[0].get();
|
||||||
@@ -314,7 +287,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/** async is done so each thread can run without waiting on others */
|
/**
|
||||||
|
* async is done so each thread can run without waiting on others
|
||||||
|
* @param direction the direction to load relative to the given position, null will return the given position
|
||||||
|
*/
|
||||||
private CompletableFuture<ColumnRenderSource> getRenderSourceForPosAsync(long pos, @Nullable EDhDirection direction)
|
private CompletableFuture<ColumnRenderSource> getRenderSourceForPosAsync(long pos, @Nullable EDhDirection direction)
|
||||||
{
|
{
|
||||||
if (direction != null)
|
if (direction != null)
|
||||||
@@ -400,11 +376,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion render data uploading
|
||||||
|
|
||||||
|
|
||||||
//========================//
|
|
||||||
// getters and properties //
|
//====================//
|
||||||
//========================//
|
// enabling rendering //
|
||||||
|
//====================//
|
||||||
|
//region enabling rendering
|
||||||
|
|
||||||
public boolean canRender() { return this.bufferContainer != null; }
|
public boolean canRender() { return this.bufferContainer != null; }
|
||||||
|
|
||||||
@@ -439,123 +418,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
public boolean gpuUploadInProgress() { return this.getAndBuildRenderDataFuture != null; }
|
public boolean gpuUploadInProgress() { return this.getAndBuildRenderDataFuture != null; }
|
||||||
|
|
||||||
|
//endregion enabling rendering
|
||||||
|
|
||||||
//=================================//
|
|
||||||
// full data retrieval (world gen) //
|
|
||||||
//=================================//
|
|
||||||
|
|
||||||
public boolean isFullyGenerated()
|
|
||||||
{
|
|
||||||
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
|
|
||||||
return missingGenerationPos != null && missingGenerationPos.isEmpty();
|
|
||||||
}
|
|
||||||
/** Returns true if an LOD exists, regardless of what data is in it */
|
|
||||||
public boolean getFullDataSourceExists()
|
|
||||||
{
|
|
||||||
if (!this.checkedIfFullDataSourceExists)
|
|
||||||
{
|
|
||||||
this.fullDataSourceExists = this.fullDataSourceProvider.repo.existsWithKey(this.pos);
|
|
||||||
this.checkedIfFullDataSourceExists = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.fullDataSourceExists;
|
|
||||||
}
|
|
||||||
public void updateFullDataSourceExists()
|
|
||||||
{
|
|
||||||
// we don't have any ability to remove LODs so we only
|
|
||||||
// need to check if an LOD was previously missing
|
|
||||||
if (!this.fullDataSourceExists)
|
|
||||||
{
|
|
||||||
this.checkedIfFullDataSourceExists = false;
|
|
||||||
this.getFullDataSourceExists();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean missingPositionsCalculated() { return this.getMissingGenerationPos() != null; }
|
|
||||||
public int ungeneratedPositionCount()
|
|
||||||
{
|
|
||||||
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
|
|
||||||
return missingGenerationPos != null ? missingGenerationPos.size() : 0;
|
|
||||||
}
|
|
||||||
public int ungeneratedChunkCount()
|
|
||||||
{
|
|
||||||
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
|
|
||||||
if (missingGenerationPos == null)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int chunkCount = 0;
|
|
||||||
// get the number of chunks each position contains
|
|
||||||
for (int i = 0; i < missingGenerationPos.size(); i++)
|
|
||||||
{
|
|
||||||
int chunkWidth = DhSectionPos.getChunkWidth(missingGenerationPos.getLong(i));
|
|
||||||
chunkCount += (chunkWidth * chunkWidth);
|
|
||||||
}
|
|
||||||
return chunkCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tryQueuingMissingLodRetrieval()
|
|
||||||
{
|
|
||||||
if (this.fullDataSourceProvider.canRetrieveMissingDataSources()
|
|
||||||
&& this.fullDataSourceProvider.canQueueRetrieval())
|
|
||||||
{
|
|
||||||
// calculate the missing positions if not already done
|
|
||||||
if (this.missingGenerationPosFunc == null)
|
|
||||||
{
|
|
||||||
// TODO memoization is needed for multiplayer, otherwise
|
|
||||||
// new retrieval requests won't be submitted.
|
|
||||||
// TODO why is that the case? Shouldn't the missing positions be un-changing?
|
|
||||||
// TODO setting this value to low can cause world gen to slow down significantly
|
|
||||||
// due to a race condition where the world gen thinks it is finished, but the results
|
|
||||||
// haven't been saved to file yet, causing the gen to fire again
|
|
||||||
this.missingGenerationPosFunc = Suppliers.memoizeWithExpiration(
|
|
||||||
() -> this.fullDataSourceProvider.getPositionsToRetrieve(this.pos),
|
|
||||||
10, TimeUnit.MINUTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
|
|
||||||
if (missingGenerationPos != null)
|
|
||||||
{
|
|
||||||
// queue from last to first to prevent shifting the array unnecessarily
|
|
||||||
for (int i = missingGenerationPos.size() - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
if (!this.fullDataSourceProvider.canQueueRetrieval())
|
|
||||||
{
|
|
||||||
// the data source provider isn't accepting any more jobs
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
long pos = missingGenerationPos.removeLong(i);
|
|
||||||
|
|
||||||
boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
|
|
||||||
pos,
|
|
||||||
Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(),
|
|
||||||
Config.Common.WorldGenerator.generationMaxChunkRadius.get()
|
|
||||||
);
|
|
||||||
if (!posInRange)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
boolean positionQueued = (this.fullDataSourceProvider.queuePositionForRetrieval(pos) != null);
|
|
||||||
if (!positionQueued)
|
|
||||||
{
|
|
||||||
// shouldn't normally happen, but just in case
|
|
||||||
missingGenerationPos.add(pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
//=================//
|
||||||
// beacon handling //
|
// beacon handling //
|
||||||
//=================//
|
//=================//
|
||||||
|
//region beacon handling
|
||||||
|
|
||||||
/** gets the active beacon list and stops/starts beacon rendering as necessary */
|
/** gets the active beacon list and stops/starts beacon rendering as necessary */
|
||||||
private void getAndRefreshRenderingBeacons()
|
private void getAndRefreshRenderingBeacons()
|
||||||
@@ -576,20 +446,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
|
|
||||||
// stop rendering current beacons
|
// stop rendering current beacons
|
||||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
|
||||||
{
|
|
||||||
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// swap old and new active beacon list
|
// swap old and new active beacon list
|
||||||
this.activeBeaconList.clear();
|
this.activeBeaconList.clear();
|
||||||
this.activeBeaconList.addAll(activeBeacons);
|
this.activeBeaconList.addAll(activeBeacons);
|
||||||
|
|
||||||
// start rendering new beacon list
|
// start rendering new beacon list
|
||||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||||
{
|
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
|
||||||
this.beaconRenderHandler.startRenderingBeacon(beam);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,10 +469,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
synchronized (this.activeBeaconList)
|
synchronized (this.activeBeaconList)
|
||||||
{
|
{
|
||||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
|
||||||
{
|
|
||||||
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,18 +484,19 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
synchronized (this.activeBeaconList)
|
synchronized (this.activeBeaconList)
|
||||||
{
|
{
|
||||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||||
{
|
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
|
||||||
this.beaconRenderHandler.startRenderingBeacon(beam);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion beacon handling
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//==============//
|
//==============//
|
||||||
// base methods //
|
// base methods //
|
||||||
//==============//
|
//==============//
|
||||||
|
//region base methods
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void debugRender(DebugRenderer debugRenderer)
|
public void debugRender(DebugRenderer debugRenderer)
|
||||||
@@ -693,13 +556,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
{
|
{
|
||||||
// remove the task from our executor if present
|
// remove the task from our executor if present
|
||||||
// note: don't cancel the task since that prevents cleanup, we just don't want it to run
|
// note: don't cancel the task since that prevents cleanup, we just don't want it to run
|
||||||
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor();
|
PriorityTaskPicker.Executor renderLoaderExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
|
||||||
if (executor != null && !executor.isTerminated())
|
if (renderLoaderExecutor != null
|
||||||
|
&& !renderLoaderExecutor.isTerminated())
|
||||||
{
|
{
|
||||||
Runnable runnable = this.getAndBuildRenderDataRunnable;
|
Runnable runnable = this.getAndBuildRenderDataRunnable;
|
||||||
if (runnable != null)
|
if (runnable != null)
|
||||||
{
|
{
|
||||||
executor.remove(runnable);
|
renderLoaderExecutor.remove(runnable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -710,16 +574,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
uploadFuture.cancel(true);
|
uploadFuture.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// remove any active world gen requests that may be for this position
|
|
||||||
ThreadPoolExecutor executor = ThreadPoolUtil.getCleanupExecutor();
|
|
||||||
// while this should generally be a fast operation
|
|
||||||
// this is run on a separate thread to prevent lag on the render thread
|
|
||||||
executor.execute(() -> this.fullDataSourceProvider.removeRetrievalRequestIf((genPos) -> DhSectionPos.contains(this.pos, genPos)));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion base methods
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,13 +56,13 @@ public class GLProxy
|
|||||||
|
|
||||||
public static final Set<String> LOGGED_GL_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
public static final Set<String> LOGGED_GL_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
|
||||||
|
|
||||||
|
private static final ConcurrentLinkedQueue<Runnable> RENDER_THREAD_RUNNABLE_QUEUE = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static GLProxy instance = null;
|
private static GLProxy instance = null;
|
||||||
|
|
||||||
|
|
||||||
private final ConcurrentLinkedQueue<Runnable> renderThreadRunnableQueue = new ConcurrentLinkedQueue<>();
|
|
||||||
|
|
||||||
/** Minecraft's GL capabilities */
|
/** Minecraft's GL capabilities */
|
||||||
public final GLCapabilities glCapabilities;
|
public final GLCapabilities glCapabilities;
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ public class GLProxy
|
|||||||
return uploadOverride;
|
return uploadOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean runningOnRenderThread()
|
public static boolean runningOnRenderThread()
|
||||||
{
|
{
|
||||||
long currentContext = GLFW.glfwGetCurrentContext();
|
long currentContext = GLFW.glfwGetCurrentContext();
|
||||||
return currentContext != 0L; // if the context isn't null, it's the MC context
|
return currentContext != 0L; // if the context isn't null, it's the MC context
|
||||||
@@ -243,12 +243,12 @@ public class GLProxy
|
|||||||
// Worker Thread Runnables //
|
// Worker Thread Runnables //
|
||||||
//=========================//
|
//=========================//
|
||||||
|
|
||||||
public void queueRunningOnRenderThread(Runnable renderCall)
|
public static void queueRunningOnRenderThread(Runnable renderCall)
|
||||||
{
|
{
|
||||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||||
this.renderThreadRunnableQueue.add(() -> this.runOpenGlCall(renderCall, stackTrace));
|
RENDER_THREAD_RUNNABLE_QUEUE.add(() -> runOpenGlCall(renderCall, stackTrace));
|
||||||
}
|
}
|
||||||
private void runOpenGlCall(Runnable renderCall, StackTraceElement[] stackTrace)
|
private static void runOpenGlCall(Runnable renderCall, StackTraceElement[] stackTrace)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -266,11 +266,11 @@ public class GLProxy
|
|||||||
* Doesn't do any thread/GL Context validation.
|
* Doesn't do any thread/GL Context validation.
|
||||||
* Running this outside of the render thread may cause crashes or other issues.
|
* Running this outside of the render thread may cause crashes or other issues.
|
||||||
*/
|
*/
|
||||||
public void runRenderThreadTasks()
|
public static void runRenderThreadTasks()
|
||||||
{
|
{
|
||||||
long startTime = System.nanoTime();
|
long startTime = System.nanoTime();
|
||||||
|
|
||||||
Runnable runnable = this.renderThreadRunnableQueue.poll();
|
Runnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
|
||||||
while(runnable != null)
|
while(runnable != null)
|
||||||
{
|
{
|
||||||
runnable.run();
|
runnable.run();
|
||||||
@@ -283,7 +283,7 @@ public class GLProxy
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
runnable = this.renderThreadRunnableQueue.poll();
|
runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -100,7 +100,7 @@ public class GLBuffer implements AutoCloseable
|
|||||||
|
|
||||||
protected void create(boolean asBufferStorage)
|
protected void create(boolean asBufferStorage)
|
||||||
{
|
{
|
||||||
if (!GLProxy.getInstance().runningOnRenderThread())
|
if (!GLProxy.runningOnRenderThread())
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("Thread ["+Thread.currentThread()+"] tried to create a GLBuffer outside the MC render thread.");
|
LodUtil.assertNotReach("Thread ["+Thread.currentThread()+"] tried to create a GLBuffer outside the MC render thread.");
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ public class GLBuffer implements AutoCloseable
|
|||||||
BUFFER_ID_TO_PHANTOM.remove(id);
|
BUFFER_ID_TO_PHANTOM.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
GLProxy.queueRunningOnRenderThread(() ->
|
||||||
{
|
{
|
||||||
// destroy the buffer if it exists,
|
// destroy the buffer if it exists,
|
||||||
// the buffer may not exist if the destroy method is called twice
|
// the buffer may not exist if the destroy method is called twice
|
||||||
|
|||||||
+2
-3
@@ -44,7 +44,7 @@ public class QuadElementBuffer extends GLElementBuffer
|
|||||||
|
|
||||||
public int getCapacity()
|
public int getCapacity()
|
||||||
{
|
{
|
||||||
return super.getSize() / GLEnums.getTypeSize(getType());
|
return super.getSize() / GLEnums.getTypeSize(this.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void buildBufferByte(int quadCount, ByteBuffer buffer)
|
private static void buildBufferByte(int quadCount, ByteBuffer buffer)
|
||||||
@@ -140,7 +140,6 @@ public class QuadElementBuffer extends GLElementBuffer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int vertexCount = quadCount * 4; // 4 vertices per quad
|
int vertexCount = quadCount * 4; // 4 vertices per quad
|
||||||
GLProxy gl = GLProxy.getInstance();
|
|
||||||
|
|
||||||
if (vertexCount < 255)
|
if (vertexCount < 255)
|
||||||
{ // Reserve 1 for the reset index
|
{ // Reserve 1 for the reset index
|
||||||
@@ -158,7 +157,7 @@ public class QuadElementBuffer extends GLElementBuffer
|
|||||||
|
|
||||||
ByteBuffer buffer = MemoryUtil.memAlloc(this.indicesCount * GLEnums.getTypeSize(this.type));
|
ByteBuffer buffer = MemoryUtil.memAlloc(this.indicesCount * GLEnums.getTypeSize(this.type));
|
||||||
buildBuffer(quadCount, buffer, this.type);
|
buildBuffer(quadCount, buffer, this.type);
|
||||||
if (!gl.bufferStorageSupported)
|
if (!GLProxy.getInstance().bufferStorageSupported)
|
||||||
{
|
{
|
||||||
|
|
||||||
this.bind();
|
this.bind();
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ public class ShaderProgram
|
|||||||
|
|
||||||
for (int i = 0; i < attributes.length; i++)
|
for (int i = 0; i < attributes.length; i++)
|
||||||
{
|
{
|
||||||
GL32.glBindAttribLocation(id, i, attributes[i]);
|
GL32.glBindAttribLocation(this.id, i, attributes[i]);
|
||||||
}
|
}
|
||||||
GL32.glLinkProgram(this.id);
|
GL32.glLinkProgram(this.id);
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -59,8 +59,10 @@ public final class VertexPointer
|
|||||||
/** Always aligned to 4 bytes */
|
/** Always aligned to 4 bytes */
|
||||||
public static VertexPointer addUnsignedBytePointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4, useInteger); }
|
public static VertexPointer addUnsignedBytePointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4, useInteger); }
|
||||||
/** aligned to 4 bytes */
|
/** aligned to 4 bytes */
|
||||||
public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, _align(elementCount), useInteger); }
|
public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized, boolean useInteger)
|
||||||
public static VertexPointer addUnsignedShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount * 2), useInteger); }
|
{ return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, _align(elementCount), useInteger); }
|
||||||
|
public static VertexPointer addUnsignedShortsPointer(int elementCount, boolean normalized, boolean useInteger)
|
||||||
|
{ return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount * 2), useInteger); }
|
||||||
public static VertexPointer addShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_SHORT, normalized, _align(elementCount * 2), useInteger); }
|
public static VertexPointer addShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_SHORT, normalized, _align(elementCount * 2), useInteger); }
|
||||||
public static VertexPointer addIntPointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_INT, normalized, 4, useInteger); }
|
public static VertexPointer addIntPointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_INT, normalized, 4, useInteger); }
|
||||||
public static VertexPointer addIVec2Pointer(boolean normalized, boolean useInteger) { return new VertexPointer(2, GL32.GL_INT, normalized, 8, useInteger); }
|
public static VertexPointer addIVec2Pointer(boolean normalized, boolean useInteger) { return new VertexPointer(2, GL32.GL_INT, normalized, 8, useInteger); }
|
||||||
|
|||||||
+444
@@ -0,0 +1,444 @@
|
|||||||
|
package com.seibel.distanthorizons.core.render.renderer;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||||
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
|
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
|
||||||
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.joml.Vector4f;
|
||||||
|
import org.lwjgl.opengl.GL32;
|
||||||
|
import org.lwjgl.opengl.GL46;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class DepthCalculator
|
||||||
|
{
|
||||||
|
|
||||||
|
public static DepthCalculator INSTANCE = new DepthCalculator();
|
||||||
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
|
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||||
|
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
|
||||||
|
private static final IMinecraftGLWrapper GLMC = SingletonInjector.INSTANCE.get(IMinecraftGLWrapper.class);
|
||||||
|
|
||||||
|
|
||||||
|
private float[] mcOpaqueDepthTextureValues = new float[1];
|
||||||
|
private float[] mcTransparentDepthTextureValues = new float[1];
|
||||||
|
private float[] dhDepthTextureValues = new float[1];
|
||||||
|
private float[] outDepthTextureValues = new float[1];
|
||||||
|
private float lastClosestDhDepth = 1.0f;
|
||||||
|
|
||||||
|
public float actualMcBlockDistance = 8 * 16; // needs to be non-zero to start DH rendering
|
||||||
|
@Deprecated // Replace with thread pool and an AtomicBool for running state
|
||||||
|
public Thread thread = null;
|
||||||
|
public boolean pause = false;
|
||||||
|
|
||||||
|
private boolean gotDhDepthThisFrame = false;
|
||||||
|
private boolean gotMcDepthThisFrame = false;
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
|
||||||
|
private DepthCalculator() { }
|
||||||
|
|
||||||
|
|
||||||
|
//======//
|
||||||
|
// test //
|
||||||
|
//======//
|
||||||
|
|
||||||
|
public void getMcOpaqueDepthTexture() { this.gotMcDepthThisFrame = this.trySetDepthTexture(MC_RENDER.getDepthTextureId(), this.mcOpaqueDepthTextureValues); }
|
||||||
|
public void getMcTransparentDepthTexture() { this.gotMcDepthThisFrame = this.trySetDepthTexture(MC_RENDER.getDepthTextureId(), this.mcTransparentDepthTextureValues); }
|
||||||
|
public void trySetDhDepthTexture() { this.gotDhDepthThisFrame = this.trySetDepthTexture(LodRenderer.INSTANCE.getActiveDepthTextureId(), this.dhDepthTextureValues); }
|
||||||
|
private boolean trySetDepthTexture(int id, float[] outputRef)
|
||||||
|
{
|
||||||
|
// don't change the texture if a process is already running
|
||||||
|
if (this.thread != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resizeTexturesIfNeeded();
|
||||||
|
if (id == -1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME this is slow and causes frame stuttering
|
||||||
|
GL46.glGetTextureImage(id, 0, GL32.GL_DEPTH_COMPONENT, GL32.GL_FLOAT, outputRef);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
private void resizeTexturesIfNeeded()
|
||||||
|
{
|
||||||
|
int width = MC_RENDER.getTargetFramebufferViewportWidth();
|
||||||
|
int height = MC_RENDER.getTargetFramebufferViewportHeight();
|
||||||
|
int elementCount = width * height;
|
||||||
|
if (this.dhDepthTextureValues.length != elementCount)
|
||||||
|
{
|
||||||
|
this.mcOpaqueDepthTextureValues = new float[width * height];
|
||||||
|
this.mcTransparentDepthTextureValues = new float[width * height];
|
||||||
|
|
||||||
|
this.dhDepthTextureValues = new float[width * height];
|
||||||
|
this.outDepthTextureValues = new float[width * height];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void tryCalculateAsync()
|
||||||
|
{
|
||||||
|
if (this.thread != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if (!this.gotDhDepthThisFrame
|
||||||
|
// || !this.gotMcDepthThisFrame)
|
||||||
|
//{
|
||||||
|
// return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (this.pause)
|
||||||
|
{
|
||||||
|
int k = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.thread = new Thread(() ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.calculateDepth();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("async test: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.thread = null;
|
||||||
|
this.pause = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.thread.start();
|
||||||
|
}
|
||||||
|
public void calculateDepth()
|
||||||
|
{
|
||||||
|
int width = MC_RENDER.getTargetFramebufferViewportWidth();
|
||||||
|
int height = MC_RENDER.getTargetFramebufferViewportHeight();
|
||||||
|
int elementCount = width * height;
|
||||||
|
|
||||||
|
|
||||||
|
// used to calculate the DH render matrices
|
||||||
|
RenderParams renderParams =
|
||||||
|
new RenderParams(
|
||||||
|
EDhApiRenderPass.OPAQUE,
|
||||||
|
ClientApi.RENDER_STATE.frameTime,
|
||||||
|
ClientApi.RENDER_STATE.mcProjectionMatrix, ClientApi.RENDER_STATE.mcModelViewMatrix,
|
||||||
|
ClientApi.RENDER_STATE.clientLevelWrapper
|
||||||
|
);
|
||||||
|
|
||||||
|
Mat4f dhInvProj = new Mat4f(renderParams.dhProjectionMatrix);
|
||||||
|
dhInvProj.invert();
|
||||||
|
Matrix4f dhInvProjJoml = dhInvProj.createJomlMatrix();
|
||||||
|
|
||||||
|
Mat4f dhInvMvm = new Mat4f(renderParams.dhModelViewMatrix);
|
||||||
|
dhInvMvm.invert();
|
||||||
|
Matrix4f dhInvMvmJoml = dhInvMvm.createJomlMatrix(); // TODO can we use JOML for MC 1.16?
|
||||||
|
|
||||||
|
|
||||||
|
float[] sampledDistances = new float[9];
|
||||||
|
|
||||||
|
// find the closest depth value MC hasn't drawn to
|
||||||
|
float closestDhDepth = 1.0f; float closestDhDistance = Float.MAX_VALUE;
|
||||||
|
int closeUIndex = 0; int closeVIndex = 0;
|
||||||
|
for (int u = 0; u < width; u++) // x
|
||||||
|
{
|
||||||
|
for (int v = 0; v < height; v++) // y
|
||||||
|
{
|
||||||
|
int invertedV = height - 1 - v;
|
||||||
|
int i = (invertedV * width) + u;
|
||||||
|
|
||||||
|
|
||||||
|
this.outDepthTextureValues[i] = 0.0f;
|
||||||
|
|
||||||
|
float mcDepth = Config.Client.dynamicFadeUseOpaqueMcDepth.get()
|
||||||
|
? this.mcOpaqueDepthTextureValues[i]
|
||||||
|
: this.mcTransparentDepthTextureValues[i];
|
||||||
|
if (mcDepth < 1.0f) // ignore positions MC has drawn to
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dhDepth = this.dhDepthTextureValues[i];
|
||||||
|
if (dhDepth == 1.0f) // ignore positions DH has NOT drawn to
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.outDepthTextureValues[i] = dhDepth;
|
||||||
|
|
||||||
|
|
||||||
|
// calculate this point's distance from the camera
|
||||||
|
float ndcU = u / (float)width;
|
||||||
|
float ndcV = invertedV / (float)height;
|
||||||
|
float dist = convertDepthToBlockDistance(ndcU, ndcV, dhInvProjJoml, dhInvMvmJoml, dhDepth);
|
||||||
|
|
||||||
|
//// sample the 9 surrounding pixels to account for off-by-one errors between the MC and DH depth textures
|
||||||
|
//int sampleIndex = 0;
|
||||||
|
//for (int relU = -1; relU < 1; relU++)
|
||||||
|
//{
|
||||||
|
// for (int relV = -1; relV < 1; relV++)
|
||||||
|
// {
|
||||||
|
// invertedV = height - 1 - v + relV;
|
||||||
|
// i = (invertedV * width) + u + relU;
|
||||||
|
// dhDepth = this.dhDepthTextureValues[i];
|
||||||
|
//
|
||||||
|
// if (v + relV < 0 || v + relV > height
|
||||||
|
// || u + relU < 0 || u + relU > width)
|
||||||
|
// {
|
||||||
|
// sampledDistances[sampleIndex] = 0.0f;
|
||||||
|
// sampleIndex++;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // calculate this point's distance from the camera
|
||||||
|
// float ndcU = u / (float)width;
|
||||||
|
// float ndcV = invertedV / (float)height;
|
||||||
|
// float dist = convertDepthToBlockDistance(ndcU, ndcV, dhInvProjJoml, dhInvMvmJoml, dhDepth);
|
||||||
|
//
|
||||||
|
// sampledDistances[sampleIndex] = dist;
|
||||||
|
// sampleIndex++;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//Arrays.sort(sampledDistances);
|
||||||
|
//// return the median element
|
||||||
|
//float dist = sampledDistances[sampledDistances.length / 2];
|
||||||
|
//if (dist == 0.0f)
|
||||||
|
//{
|
||||||
|
// // the median was 0, return the smallest non-zero element
|
||||||
|
// for (sampleIndex = 0; sampleIndex < sampledDistances.length; sampleIndex++)
|
||||||
|
// {
|
||||||
|
// if (sampledDistances[sampleIndex] != 0.0f)
|
||||||
|
// {
|
||||||
|
// dist = sampledDistances[sampleIndex];
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (dist < closestDhDistance)
|
||||||
|
{
|
||||||
|
closestDhDepth = dhDepth;
|
||||||
|
closestDhDistance = dist;
|
||||||
|
|
||||||
|
closeUIndex = u;
|
||||||
|
closeVIndex = invertedV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this.lastClosestDhDepth != closestDhDepth)
|
||||||
|
{
|
||||||
|
NumberFormat numForm = NumberFormat.getNumberInstance();
|
||||||
|
ClientApi.INSTANCE.showChatMessageNextFrame(
|
||||||
|
"closest: ["+numForm.format(closestDhDepth)+"]-b["+numForm.format(closestDhDistance)+"]c["+numForm.format(closestDhDistance/16)+"] ("+(closeUIndex)+","+(closeVIndex)+")" +
|
||||||
|
"");
|
||||||
|
|
||||||
|
this.actualMcBlockDistance = closestDhDistance;
|
||||||
|
}
|
||||||
|
this.lastClosestDhDepth = closestDhDepth;
|
||||||
|
|
||||||
|
|
||||||
|
if (this.pause)
|
||||||
|
{
|
||||||
|
// find the range of depth values used by both textures for clearer exporting
|
||||||
|
float closestMcDepth = 1.0f; float furthestMcDepth = 0.0f;
|
||||||
|
float minDhDepth = 1.0f; float maxDhDepth = 0.0f;
|
||||||
|
for (int i = 0; i < elementCount; i++)
|
||||||
|
{
|
||||||
|
float mcDepth = this.mcOpaqueDepthTextureValues[i];
|
||||||
|
if (mcDepth != 0.0f && mcDepth != 1.0f)
|
||||||
|
{
|
||||||
|
if (mcDepth < closestMcDepth)
|
||||||
|
{
|
||||||
|
closestMcDepth = mcDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mcDepth > furthestMcDepth)
|
||||||
|
{
|
||||||
|
furthestMcDepth = mcDepth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float dhDepth = this.dhDepthTextureValues[i];
|
||||||
|
if (dhDepth != 0.0f && dhDepth != 1.0f)
|
||||||
|
{
|
||||||
|
if (dhDepth < minDhDepth)
|
||||||
|
{
|
||||||
|
minDhDepth = dhDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dhDepth > maxDhDepth)
|
||||||
|
{
|
||||||
|
maxDhDepth = dhDepth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String exportPath = Config.Client.dynamicFadeExportPath.get();
|
||||||
|
|
||||||
|
ClientApi.INSTANCE.showChatMessageNextFrame("exporting debug textures to: ["+exportPath+"]...");
|
||||||
|
|
||||||
|
// mc opaque
|
||||||
|
createImg(
|
||||||
|
this.mcOpaqueDepthTextureValues,
|
||||||
|
closeUIndex, closeVIndex,
|
||||||
|
(Float depth) -> { return null; },
|
||||||
|
width, height, closestMcDepth, furthestMcDepth,
|
||||||
|
exportPath+"mc-opaque_depth.png");
|
||||||
|
|
||||||
|
// mc transparent
|
||||||
|
createImg(
|
||||||
|
this.mcTransparentDepthTextureValues,
|
||||||
|
closeUIndex, closeVIndex,
|
||||||
|
(Float depth) -> { return null; },
|
||||||
|
width, height, closestMcDepth, furthestMcDepth,
|
||||||
|
exportPath+"mc-tran_depth.png");
|
||||||
|
|
||||||
|
// dh
|
||||||
|
Function<Float, Color> customColorFunc = (Float depth) ->
|
||||||
|
{
|
||||||
|
if (depth == this.lastClosestDhDepth)
|
||||||
|
{
|
||||||
|
return Color.RED;
|
||||||
|
}
|
||||||
|
//else if (depth <= (lastClosestDhDepth + 0.01f))
|
||||||
|
//{
|
||||||
|
// return Color.ORANGE;
|
||||||
|
//}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
createImg(
|
||||||
|
this.dhDepthTextureValues,
|
||||||
|
closeUIndex, closeVIndex,
|
||||||
|
customColorFunc,
|
||||||
|
width, height, minDhDepth, maxDhDepth,
|
||||||
|
exportPath+"dh_depth.png");
|
||||||
|
|
||||||
|
// temp
|
||||||
|
|
||||||
|
createImg(
|
||||||
|
this.outDepthTextureValues,
|
||||||
|
closeUIndex, closeVIndex,
|
||||||
|
(Float depth) -> { return null; },
|
||||||
|
width, height, minDhDepth, maxDhDepth,
|
||||||
|
exportPath+"temp_depth.png");
|
||||||
|
|
||||||
|
int breakPoint = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** NDC (Normalized Device Coordinates) must be between 0.0 and 1.0 (inclusive) */
|
||||||
|
private static float convertDepthToBlockDistance(float ndcU, float ndcV, Matrix4f invProj, Matrix4f invMvm, float depth)
|
||||||
|
{
|
||||||
|
// This assumes depth is scaled to [0, 1]
|
||||||
|
// Transform depth to clip space Z value
|
||||||
|
float z = depth * 2.0f - 1.0f;
|
||||||
|
|
||||||
|
// Create a vector in clip space
|
||||||
|
Vector4f clipSpacePosition = new Vector4f(ndcU, ndcV, z, 1.0f);
|
||||||
|
|
||||||
|
// Transform to world space
|
||||||
|
Vector4f worldSpacePosition = clipSpacePosition.mul(invProj);
|
||||||
|
worldSpacePosition.div(worldSpacePosition.w); // Perform perspective divide
|
||||||
|
|
||||||
|
// Finally apply the inverse model-view matrix to get world space coordinates
|
||||||
|
worldSpacePosition = worldSpacePosition.mul(invMvm);
|
||||||
|
|
||||||
|
// calculate distance from the camera
|
||||||
|
float distance = worldSpacePosition.distance(0, 0, 0, 0);
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
private static void createImg(
|
||||||
|
float[] tex,
|
||||||
|
int nearestU, int nearestV,
|
||||||
|
Function<Float, Color> customColorFunc,
|
||||||
|
int width, int height,
|
||||||
|
float minDepth, float maxDepth,
|
||||||
|
String filePath)
|
||||||
|
{
|
||||||
|
// Create a BufferedImage
|
||||||
|
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||||
|
|
||||||
|
for (int u = 0; u < width; u++)
|
||||||
|
{
|
||||||
|
for (int v = 0; v < height; v++)
|
||||||
|
{
|
||||||
|
int invertedY = height - 1 - v;
|
||||||
|
|
||||||
|
// Normalize the depth value to a grayscale pixel
|
||||||
|
float depthValue = tex[(invertedY * width) + u];
|
||||||
|
Color color = customColorFunc.apply(depthValue);
|
||||||
|
if (color == null)
|
||||||
|
{
|
||||||
|
float normalizedDepth = (depthValue - minDepth) / (maxDepth - minDepth); // Normalize to 0.0 to 1.0
|
||||||
|
normalizedDepth = Math.max(0.0f, Math.min(1.0f, normalizedDepth)); // Clamp to valid range
|
||||||
|
|
||||||
|
int gray = (int) (normalizedDepth * 255); // Map to 0-255
|
||||||
|
gray = Math.max(0, Math.min(255, gray)); // Clamp to valid range
|
||||||
|
color = new Color(gray, gray, gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (depthValue == 1.0f)
|
||||||
|
{
|
||||||
|
color = new Color(0,0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u == nearestU)
|
||||||
|
{
|
||||||
|
color = Color.MAGENTA;
|
||||||
|
}
|
||||||
|
else if (invertedY == nearestV)
|
||||||
|
{
|
||||||
|
color = Color.ORANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
image.setRGB(u, v, color.getRGB());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the image to a file
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ImageIO.write(image, "png", new File(filePath));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unable to write texture to file, error: ["+e.getMessage()+"].", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+38
-29
@@ -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.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||||
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
|
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
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.GLProxy;
|
||||||
import com.seibel.distanthorizons.core.render.glObject.shader.Shader;
|
import com.seibel.distanthorizons.core.render.glObject.shader.Shader;
|
||||||
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
|
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.
|
* Handles rendering the normal LOD terrain.
|
||||||
|
* @see LodQuadBuilder
|
||||||
*/
|
*/
|
||||||
public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShaderProgram
|
public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShaderProgram
|
||||||
{
|
{
|
||||||
@@ -46,16 +48,14 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
|||||||
public int uCombinedMatrix = -1;
|
public int uCombinedMatrix = -1;
|
||||||
public int uModelOffset = -1;
|
public int uModelOffset = -1;
|
||||||
public int uWorldYOffset = -1;
|
public int uWorldYOffset = -1;
|
||||||
public int uDitherDhRendering = -1;
|
|
||||||
|
|
||||||
public int uMircoOffset = -1;
|
public int uMircoOffset = -1;
|
||||||
|
|
||||||
public int uEarthRadius = -1;
|
public int uEarthRadius = -1;
|
||||||
|
|
||||||
public int uLightMap = -1;
|
public int uLightMap = -1;
|
||||||
|
|
||||||
// Fog/Clip Uniforms
|
// fragment shader uniforms
|
||||||
public int uClipDistance = -1;
|
public int uClipDistance = -1;
|
||||||
|
public int uDitherDhRendering = -1;
|
||||||
|
|
||||||
// Noise Uniforms
|
// Noise Uniforms
|
||||||
public int uNoiseEnabled = -1;
|
public int uNoiseEnabled = -1;
|
||||||
@@ -64,7 +64,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
|||||||
public int uNoiseDropoff = -1;
|
public int uNoiseDropoff = -1;
|
||||||
|
|
||||||
// Debug Uniform
|
// Debug Uniform
|
||||||
public int uWhiteWorld = -1;
|
public int uIsWhiteWorld = -1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -76,19 +76,16 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
|||||||
public DhTerrainShaderProgram()
|
public DhTerrainShaderProgram()
|
||||||
{
|
{
|
||||||
super(
|
super(
|
||||||
() -> Shader.loadFile(Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get() != 0
|
() -> Shader.loadFile("shaders/standard.vert", false, new StringBuilder()).toString(),
|
||||||
? "shaders/curve.vert"
|
|
||||||
: "shaders/standard.vert",
|
|
||||||
false, new StringBuilder()).toString(),
|
|
||||||
() -> Shader.loadFile("shaders/flat_shaded.frag", false, new StringBuilder()).toString(),
|
() -> Shader.loadFile("shaders/flat_shaded.frag", false, new StringBuilder()).toString(),
|
||||||
"fragColor", new String[]{"vPosition", "color"});
|
"fragColor", new String[]{"vPosition", "color"});
|
||||||
|
|
||||||
this.uCombinedMatrix = this.getUniformLocation("uCombinedMatrix");
|
this.uCombinedMatrix = this.getUniformLocation("uCombinedMatrix");
|
||||||
this.uModelOffset = this.getUniformLocation("uModelOffset");
|
this.uModelOffset = this.getUniformLocation("uModelOffset");
|
||||||
this.uWorldYOffset = this.tryGetUniformLocation("uWorldYOffset");
|
this.uWorldYOffset = this.getUniformLocation("uWorldYOffset");
|
||||||
this.uDitherDhRendering = this.tryGetUniformLocation("uDitherDhRendering");
|
this.uDitherDhRendering = this.getUniformLocation("uDitherDhRendering");
|
||||||
this.uMircoOffset = this.getUniformLocation("uMircoOffset");
|
this.uMircoOffset = this.getUniformLocation("uMircoOffset");
|
||||||
this.uEarthRadius = this.tryGetUniformLocation("uEarthRadius");
|
this.uEarthRadius = this.getUniformLocation("uEarthRadius");
|
||||||
|
|
||||||
this.uLightMap = this.getUniformLocation("uLightMap");
|
this.uLightMap = this.getUniformLocation("uLightMap");
|
||||||
|
|
||||||
@@ -102,7 +99,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
|||||||
this.uNoiseDropoff = this.getUniformLocation("uNoiseDropoff");
|
this.uNoiseDropoff = this.getUniformLocation("uNoiseDropoff");
|
||||||
|
|
||||||
// Debug Uniform
|
// Debug Uniform
|
||||||
this.uWhiteWorld = this.getUniformLocation("uWhiteWorld");
|
this.uIsWhiteWorld = this.getUniformLocation("uIsWhiteWorld");
|
||||||
|
|
||||||
|
|
||||||
// TODO: Add better use of the LODFormat thing
|
// TODO: Add better use of the LODFormat thing
|
||||||
@@ -117,10 +114,13 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
|||||||
}
|
}
|
||||||
this.vao.bind();
|
this.vao.bind();
|
||||||
|
|
||||||
// TODO comment what each attribute represents
|
// short: x, y, z, meta
|
||||||
this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true)); // 2+2+2+2 // TODO probably color, blockpos
|
// meta: byte skylight, byte blocklight, byte microOffset
|
||||||
this.vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false)); // +4 // TODO ?
|
this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true));
|
||||||
this.vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true)); // +4 // TODO probably normal index and Iris block ID
|
// 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
|
try
|
||||||
{
|
{
|
||||||
@@ -178,12 +178,21 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
|||||||
// setUniform(skyLightUniform, skyLight);
|
// setUniform(skyLightUniform, skyLight);
|
||||||
this.setUniform(this.uLightMap, 0); // TODO this should probably be passed in
|
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,
|
float curveRatio = Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get();
|
||||||
/*6371KM*/ 6371000.0f / 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
|
// Noise Uniforms
|
||||||
this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get());
|
this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get());
|
||||||
@@ -192,20 +201,20 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
|||||||
this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get());
|
this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get());
|
||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
this.setUniform(this.uWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
|
this.setUniform(this.uIsWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
|
||||||
|
|
||||||
// Clip Uniform
|
// Clip Uniform
|
||||||
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks);
|
float dhNearClipDistance = 0.1f;//RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks);
|
||||||
if (!Config.Client.Advanced.Debugging.lodOnlyMode.get())
|
//if (!Config.Client.Advanced.Debugging.lodOnlyMode.get())
|
||||||
{
|
//{
|
||||||
// this added value prevents the near clip plane and discard circle from touching, which looks bad
|
// // this added value prevents the near clip plane and discard circle from touching, which looks bad
|
||||||
dhNearClipDistance += 16f;
|
// dhNearClipDistance += 16f;
|
||||||
}
|
//}
|
||||||
// if the player is very high up and the near clip plane has been modified, disable the distance clipping
|
// if the player is very high up and the near clip plane has been modified, disable the distance clipping
|
||||||
// we're high enough that nothing will render on top of the player and this can cause issues otherwise
|
// we're high enough that nothing will render on top of the player and this can cause issues otherwise
|
||||||
if (RenderUtil.getHeightBasedNearClipOverride() != -1)
|
if (RenderUtil.getHeightBasedNearClipOverride() != -1)
|
||||||
{
|
{
|
||||||
dhNearClipDistance = 1.0f;
|
dhNearClipDistance = 1.0f; // TODO does this actually disable anything?
|
||||||
}
|
}
|
||||||
this.setUniform(this.uClipDistance, dhNearClipDistance);
|
this.setUniform(this.uClipDistance, dhNearClipDistance);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,6 +222,8 @@ public class LodRenderer
|
|||||||
profiler.popPush("LOD Opaque");
|
profiler.popPush("LOD Opaque");
|
||||||
this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ true);
|
this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ true);
|
||||||
|
|
||||||
|
DepthCalculator.INSTANCE.trySetDhDepthTexture();
|
||||||
|
|
||||||
// custom objects with SSAO
|
// custom objects with SSAO
|
||||||
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
|
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
|
||||||
{
|
{
|
||||||
@@ -493,12 +495,8 @@ public class LodRenderer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GLProxy.hasInstance())
|
// GLProxy should have already been created by this point, but just in case create it now
|
||||||
{
|
GLProxy.getInstance();
|
||||||
// shouldn't normally happen, but just in case
|
|
||||||
LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -540,7 +538,7 @@ public class LodRenderer
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings( "deprecation" )
|
@SuppressWarnings( "deprecation" ) // done to ignore DhApiColorDepthTextureCreatedEvent
|
||||||
private void createAndBindTextures()
|
private void createAndBindTextures()
|
||||||
{
|
{
|
||||||
int oldWidth = this.textureWidth;
|
int oldWidth = this.textureWidth;
|
||||||
|
|||||||
+3
-1
@@ -30,6 +30,7 @@ public class RenderParams extends DhApiRenderParam
|
|||||||
|
|
||||||
public IDhClientWorld dhClientWorld;
|
public IDhClientWorld dhClientWorld;
|
||||||
public IDhClientLevel dhClientLevel;
|
public IDhClientLevel dhClientLevel;
|
||||||
|
/** more specific override of the API value {@link DhApiRenderParam#clientLevelWrapper} */
|
||||||
public IClientLevelWrapper clientLevelWrapper;
|
public IClientLevelWrapper clientLevelWrapper;
|
||||||
public ILightMapWrapper lightmap;
|
public ILightMapWrapper lightmap;
|
||||||
public RenderBufferHandler renderBufferHandler;
|
public RenderBufferHandler renderBufferHandler;
|
||||||
@@ -56,7 +57,8 @@ public class RenderParams extends DhApiRenderParam
|
|||||||
RenderUtil.getNearClipPlaneDistanceInBlocks(newPartialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(),
|
RenderUtil.getNearClipPlaneDistanceInBlocks(newPartialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(),
|
||||||
newMcProjectionMatrix, newMcModelViewMatrix,
|
newMcProjectionMatrix, newMcModelViewMatrix,
|
||||||
RenderUtil.createLodProjectionMatrix(newMcProjectionMatrix, newPartialTicks), RenderUtil.createLodModelViewMatrix(newMcModelViewMatrix),
|
RenderUtil.createLodProjectionMatrix(newMcProjectionMatrix, newPartialTicks), RenderUtil.createLodModelViewMatrix(newMcModelViewMatrix),
|
||||||
clientLevelWrapper.getMinHeight());
|
clientLevelWrapper.getMinHeight(),
|
||||||
|
clientLevelWrapper);
|
||||||
|
|
||||||
|
|
||||||
this.dhClientWorld = SharedApi.tryGetDhClientWorld();
|
this.dhClientWorld = SharedApi.tryGetDhClientWorld();
|
||||||
|
|||||||
+94
-7
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShad
|
|||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
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.pos.blockPos.DhBlockPos;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
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 com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@@ -54,6 +54,8 @@ public class BeaconRenderHandler
|
|||||||
/** how often should we check if a beacon should be culled? */
|
/** 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 int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
|
||||||
|
|
||||||
|
private static final Comparator<BeaconBeamDTO> NEGATIVE_BLOCKPOS_COMPARATOR = new NegativeInfiniteBlockPosComparator();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private final ReentrantLock updateLock = new ReentrantLock();
|
private final ReentrantLock updateLock = new ReentrantLock();
|
||||||
@@ -89,18 +91,72 @@ public class BeaconRenderHandler
|
|||||||
// render handling //
|
// render handling //
|
||||||
//=================//
|
//=================//
|
||||||
|
|
||||||
public void startRenderingBeacon(BeaconBeamDTO beacon)
|
public void startRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList, byte detailLevel)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.updateLock.lock();
|
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();
|
int maxBeaconBeamHeight = Config.Client.Advanced.Graphics.GenericRendering.beaconRenderHeight.get();
|
||||||
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
|
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
|
||||||
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
|
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
|
||||||
new DhApiVec3d(beacon.blockPos.getX() + 1, maxBeaconBeamHeight, beacon.blockPos.getZ() + 1),
|
new DhApiVec3d(beacon.blockPos.getX() + beaconBlockWidth, maxBeaconBeamHeight, beacon.blockPos.getZ() + beaconBlockWidth),
|
||||||
beacon.color,
|
beacon.color,
|
||||||
EDhApiBlockMaterial.ILLUMINATED
|
EDhApiBlockMaterial.ILLUMINATED
|
||||||
);
|
);
|
||||||
@@ -116,14 +172,21 @@ public class BeaconRenderHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopRenderingBeaconAtPos(DhBlockPos beaconPos)
|
public void stopRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.updateLock.lock();
|
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) ->
|
Predicate<DhApiRenderableBox> removePredicate = (DhApiRenderableBox box) ->
|
||||||
{
|
{
|
||||||
return box.minPos.x == beaconPos.getX()
|
return box.minPos.x == beaconPos.getX()
|
||||||
@@ -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
|
private static boolean[][] getCloudsFromTexture() throws FileNotFoundException, IOException
|
||||||
{
|
{
|
||||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
final ClassLoader loader = CloudRenderHandler.class.getClassLoader();
|
||||||
|
|
||||||
boolean[][] whitePixels = null;
|
boolean[][] whitePixels = null;
|
||||||
try(InputStream imageInputStream = loader.getResourceAsStream(CLOUD_RESOURCE_TEXTURE_PATH))
|
try(InputStream imageInputStream = loader.getResourceAsStream(CLOUD_RESOURCE_TEXTURE_PATH))
|
||||||
|
|||||||
+1
-1
@@ -337,7 +337,7 @@ public class RenderableBoxGroup
|
|||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
{
|
{
|
||||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
GLProxy.queueRunningOnRenderThread(() ->
|
||||||
{
|
{
|
||||||
if (this.instanceChunkPosVbo != 0)
|
if (this.instanceChunkPosVbo != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
+8
@@ -25,8 +25,10 @@ import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
|
|||||||
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.SSAORenderer;
|
import com.seibel.distanthorizons.core.render.renderer.SSAORenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
|
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
|
||||||
|
import com.seibel.distanthorizons.core.util.NumberUtil;
|
||||||
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
|
||||||
|
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||||
import org.lwjgl.opengl.GL32;
|
import org.lwjgl.opengl.GL32;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,6 +60,7 @@ public class SSAOShader extends AbstractShaderRenderer
|
|||||||
public int uMinLight;
|
public int uMinLight;
|
||||||
public int uBias;
|
public int uBias;
|
||||||
public int uDepthMap;
|
public int uDepthMap;
|
||||||
|
public int uFadeDistanceInBlocks;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -81,6 +84,7 @@ public class SSAOShader extends AbstractShaderRenderer
|
|||||||
this.uMinLight = this.shader.getUniformLocation("uMinLight");
|
this.uMinLight = this.shader.getUniformLocation("uMinLight");
|
||||||
this.uBias = this.shader.getUniformLocation("uBias");
|
this.uBias = this.shader.getUniformLocation("uBias");
|
||||||
this.uDepthMap = this.shader.getUniformLocation("uDepthMap");
|
this.uDepthMap = this.shader.getUniformLocation("uDepthMap");
|
||||||
|
this.uFadeDistanceInBlocks = this.shader.getUniformLocation("uFadeDistanceInBlocks");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -120,6 +124,10 @@ public class SSAOShader extends AbstractShaderRenderer
|
|||||||
this.shader.setUniform(this.uBias, bias.floatValue());
|
this.shader.setUniform(this.uBias, bias.floatValue());
|
||||||
|
|
||||||
GL32.glUniform1i(this.uDepthMap, 0);
|
GL32.glUniform1i(this.uDepthMap, 0);
|
||||||
|
|
||||||
|
float fadeDistanceInBlocks = Config.Client.Advanced.Graphics.Ssao.fadeDistanceInBlocks.get().floatValue();
|
||||||
|
fadeDistanceInBlocks = MathUtil.clamp(0.0f, fadeDistanceInBlocks, Float.MAX_VALUE); // clamp to prevent accidentally setting a negative number
|
||||||
|
this.shader.setUniform(this.uFadeDistanceInBlocks, fadeDistanceInBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.render.renderer.shaders;
|
package com.seibel.distanthorizons.core.render.renderer.shaders;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
|
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
|
||||||
@@ -114,7 +115,7 @@ public class VanillaFadeShader extends AbstractShaderRenderer
|
|||||||
|
|
||||||
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(partialTicks);
|
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(partialTicks);
|
||||||
// this added value prevents the near clip plane and discard circle from touching, which looks bad
|
// this added value prevents the near clip plane and discard circle from touching, which looks bad
|
||||||
dhNearClipDistance += 16f;
|
//dhNearClipDistance += 16f;
|
||||||
|
|
||||||
// measured in blocks
|
// measured in blocks
|
||||||
// these multipliers in James' tests should provide a fairly smooth transition
|
// these multipliers in James' tests should provide a fairly smooth transition
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ public class DatabaseUpdater
|
|||||||
/** @throws NullPointerException if any of the script files failed to be read. */
|
/** @throws NullPointerException if any of the script files failed to be read. */
|
||||||
private static ArrayList<SqlScript> getAutoUpdateScripts() throws NullPointerException, IOException
|
private static ArrayList<SqlScript> getAutoUpdateScripts() throws NullPointerException, IOException
|
||||||
{
|
{
|
||||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
final ClassLoader loader = DatabaseUpdater.class.getClassLoader();
|
||||||
|
|
||||||
|
|
||||||
// get the script list
|
// get the script list
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>, INetworkObject
|
|||||||
public void close()
|
public void close()
|
||||||
{ /* no closing needed */ }
|
{ /* no closing needed */ }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() { return this.blockPos + " " + this.color; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,10 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.util;
|
package com.seibel.distanthorizons.core.util;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
|
import com.seibel.distanthorizons.core.render.renderer.DepthCalculator;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||||
@@ -127,13 +129,16 @@ public class RenderUtil
|
|||||||
}
|
}
|
||||||
public static float getNearClipPlaneInBlocksForFading(float partialTicks)
|
public static float getNearClipPlaneInBlocksForFading(float partialTicks)
|
||||||
{
|
{
|
||||||
float overdraw = getAutoOverdrawPrevention();
|
//float overdraw = getAutoOverdrawPrevention();
|
||||||
return getNearClipPlaneDistanceInBlocks(partialTicks, overdraw);
|
//return getNearClipPlaneDistanceInBlocks(partialTicks, overdraw);
|
||||||
|
|
||||||
|
return DepthCalculator.INSTANCE.actualMcBlockDistance;
|
||||||
}
|
}
|
||||||
private static float getNearClipPlaneDistanceInBlocks(float partialTicks, float overdrawPreventionPercent)
|
private static float getNearClipPlaneDistanceInBlocks(float partialTicks, float overdrawPreventionPercent)
|
||||||
{
|
{
|
||||||
int chunkRenderDistance = MC_RENDER.getRenderDistance();
|
int chunkRenderDistance = MC_RENDER.getRenderDistance();
|
||||||
int vanillaBlockRenderedDistance = chunkRenderDistance * LodUtil.CHUNK_WIDTH;
|
//float chunkRenderDistance = ClientApi.actualMcBlockDistance / 16.0f;
|
||||||
|
float vanillaBlockRenderedDistance = chunkRenderDistance * LodUtil.CHUNK_WIDTH;
|
||||||
|
|
||||||
float nearClipPlane;
|
float nearClipPlane;
|
||||||
if (Config.Client.Advanced.Debugging.lodOnlyMode.get())
|
if (Config.Client.Advanced.Debugging.lodOnlyMode.get())
|
||||||
|
|||||||
+35
-49
@@ -31,12 +31,10 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
|||||||
import com.seibel.distanthorizons.core.util.gridList.MovableGridRingList;
|
import com.seibel.distanthorizons.core.util.gridList.MovableGridRingList;
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
||||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.LongConsumer;
|
import java.util.function.LongConsumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,9 +96,27 @@ public class QuadTree<T>
|
|||||||
// getters and setters //
|
// 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 */
|
/** @return the node at the given section position */
|
||||||
@Nullable
|
@Nullable
|
||||||
public final QuadNode<T> getNode(long pos) throws IndexOutOfBoundsException { return this.getOrSetNode(pos, false, null, true); }
|
public final QuadNode<T> getNode(long pos) throws IndexOutOfBoundsException { return this.getOrSetNode(pos, false, null, true); }
|
||||||
|
|
||||||
/** @return the value at the given section position */
|
/** @return the value at the given section position */
|
||||||
@Nullable
|
@Nullable
|
||||||
public final T getValue(long pos) throws IndexOutOfBoundsException
|
public final T getValue(long pos) throws IndexOutOfBoundsException
|
||||||
@@ -122,16 +138,24 @@ public class QuadTree<T>
|
|||||||
return previousValue;
|
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
|
@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))
|
||||||
|
{
|
||||||
|
// how should out-of-bounds positions be handled?
|
||||||
|
if (throwIfOutOfBounds)
|
||||||
{
|
{
|
||||||
int radius = this.diameterInBlocks() / 2;
|
int radius = this.diameterInBlocks() / 2;
|
||||||
DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
|
DhBlockPos2D minBlockPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
|
||||||
DhBlockPos2D maxPos = 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 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));
|
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);
|
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; }
|
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
|
||||||
@@ -544,7 +528,9 @@ public class QuadTree<T>
|
|||||||
&& this.rootNodeIterator.hasNext())
|
&& this.rootNodeIterator.hasNext())
|
||||||
{
|
{
|
||||||
long sectionPos = this.rootNodeIterator.nextLong();
|
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)
|
if (rootNode != null)
|
||||||
{
|
{
|
||||||
nodeIterator = this.onlyReturnLeaves ? rootNode.getLeafNodeIterator() : rootNode.getNodeIterator(this.stopIteratingFunc);
|
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>>
|
public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||||
{
|
{
|
||||||
/** lowest numerical value, inclusive */
|
/** lowest numerical value, inclusive */
|
||||||
private final byte highestDetailLevel;
|
private final byte leafDetailLevel;
|
||||||
|
|
||||||
|
|
||||||
private final Queue<QuadNode<T>> validNodesForDetailLevel = new ArrayDeque<>();
|
private final Queue<QuadNode<T>> validNodesForDetailLevel = new ArrayDeque<>();
|
||||||
@@ -48,8 +48,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
|||||||
{
|
{
|
||||||
this.onlyReturnLeafValues = onlyReturnLeafValues;
|
this.onlyReturnLeafValues = onlyReturnLeafValues;
|
||||||
this.stopIteratingFunc = stopIteratingFunc;
|
this.stopIteratingFunc = stopIteratingFunc;
|
||||||
// TODO the naming conversion for these are flipped in a lot of places
|
this.leafDetailLevel = rootNode.parentTreeLeafDetailLevel;
|
||||||
this.highestDetailLevel = rootNode.parentTreeLeafDetailLevel;
|
|
||||||
this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos);
|
this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos);
|
||||||
|
|
||||||
|
|
||||||
@@ -110,9 +109,9 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
|||||||
@Override
|
@Override
|
||||||
public QuadNode<T> next()
|
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)
|
if (this.iteratorNodeQueue.size() == 0)
|
||||||
{
|
{
|
||||||
@@ -133,7 +132,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
|||||||
|
|
||||||
this.iteratorDetailLevel--;
|
this.iteratorDetailLevel--;
|
||||||
// only continue if we can go down farther
|
// 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);
|
Queue<QuadNode<T>> parentNodes = new LinkedList<>(this.validNodesForDetailLevel);
|
||||||
this.validNodesForDetailLevel.clear();
|
this.validNodesForDetailLevel.clear();
|
||||||
|
|||||||
+7
-1
@@ -69,10 +69,14 @@ public class ThreadPoolUtil
|
|||||||
@Nullable
|
@Nullable
|
||||||
public static ThreadPoolExecutor getBeaconCullingExecutor() { return beaconCullingThreadPool; }
|
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;
|
private static PriorityTaskPicker.Executor networkCompressionThreadPool;
|
||||||
@Nullable
|
@Nullable
|
||||||
public static PriorityTaskPicker.Executor getNetworkCompressionExecutor() { return networkCompressionThreadPool; }
|
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";
|
public static final String FULL_DATA_MIGRATION_THREAD_NAME = "Full Data Migration";
|
||||||
@@ -103,7 +107,8 @@ public class ThreadPoolUtil
|
|||||||
}
|
}
|
||||||
taskPicker = new PriorityTaskPicker();
|
taskPicker = new PriorityTaskPicker();
|
||||||
|
|
||||||
networkCompressionThreadPool = taskPicker.createExecutor("Network");
|
networkCompressionThreadPool = taskPicker.createExecutor("Network Compression");
|
||||||
|
networkClientHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Network Client Handler");
|
||||||
fileHandlerThreadPool = taskPicker.createExecutor("IO");
|
fileHandlerThreadPool = taskPicker.createExecutor("IO");
|
||||||
renderSectionLoadThreadPool = taskPicker.createExecutor("Render Loader");
|
renderSectionLoadThreadPool = taskPicker.createExecutor("Render Loader");
|
||||||
chunkToLodBuilderThreadPool = taskPicker.createExecutor("LOD Builder");
|
chunkToLodBuilderThreadPool = taskPicker.createExecutor("LOD Builder");
|
||||||
@@ -133,6 +138,7 @@ public class ThreadPoolUtil
|
|||||||
public static void shutdownThreadPools()
|
public static void shutdownThreadPools()
|
||||||
{
|
{
|
||||||
// standalone threads
|
// standalone threads
|
||||||
|
networkClientHandlerThreadPool.shutdownNow();
|
||||||
taskPicker.shutdownNow();
|
taskPicker.shutdownNow();
|
||||||
beaconCullingThreadPool.shutdown();
|
beaconCullingThreadPool.shutdown();
|
||||||
fullDataMigrationThreadPool.shutdown();
|
fullDataMigrationThreadPool.shutdown();
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package com.seibel.distanthorizons.core.world;
|
package com.seibel.distanthorizons.core.world;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
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.level.DhClientServerLevel;
|
||||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
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);
|
LOGGER.fatal("Failed to load client-server level, error: ["+e.getMessage()+"].", e);
|
||||||
|
|
||||||
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
|
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.");
|
"Unable to load level ["+levelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package com.seibel.distanthorizons.core.world;
|
package com.seibel.distanthorizons.core.world;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
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.file.structure.ClientOnlySaveStructure;
|
||||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||||
@@ -94,8 +95,8 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
|||||||
{
|
{
|
||||||
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
|
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
|
||||||
|
|
||||||
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
|
ClientApi.INSTANCE.showChatMessageNextFrame(
|
||||||
"\u00A7c" + "Distant Horizons: Client level loading failed." + "\u00A7r \n" +
|
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.");
|
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package com.seibel.distanthorizons.core.world;
|
package com.seibel.distanthorizons.core.world;
|
||||||
|
|
||||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
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.level.DhServerLevel;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||||
@@ -62,8 +63,8 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
|||||||
{
|
{
|
||||||
LOGGER.fatal("Failed to load server level, error: ["+e.getMessage()+"].", e);
|
LOGGER.fatal("Failed to load server level, error: ["+e.getMessage()+"].", e);
|
||||||
|
|
||||||
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
|
ClientApi.INSTANCE.showChatMessageNextFrame(
|
||||||
"\u00A7c" + "Distant Horizons: Server level loading failed." + "\u00A7r \n" +
|
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.");
|
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+5
-2
@@ -63,6 +63,8 @@ public interface IChunkWrapper extends IBindable
|
|||||||
*/
|
*/
|
||||||
int getMaxNonEmptyHeight();
|
int getMaxNonEmptyHeight();
|
||||||
|
|
||||||
|
void createDhHeightMaps();
|
||||||
|
|
||||||
/** @return The highest y position of a solid block at the given relative chunk position. */
|
/** @return The highest y position of a solid block at the given relative chunk position. */
|
||||||
int getSolidHeightMapValue(int xRel, int zRel);
|
int getSolidHeightMapValue(int xRel, int zRel);
|
||||||
/**
|
/**
|
||||||
@@ -126,8 +128,6 @@ public interface IChunkWrapper extends IBindable
|
|||||||
|
|
||||||
IBiomeWrapper getBiome(int relX, int relY, int relZ);
|
IBiomeWrapper getBiome(int relX, int relY, int relZ);
|
||||||
|
|
||||||
boolean isStillValid();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//========================//
|
//========================//
|
||||||
@@ -406,5 +406,8 @@ public interface IChunkWrapper extends IBindable
|
|||||||
return beaconTintBlockFound ? new Color(red, green, blue) : Color.WHITE;
|
return beaconTintBlockFound ? new Color(red, green, blue) : Color.WHITE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IChunkWrapper copy();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -63,7 +63,9 @@ public interface IMinecraftRenderWrapper extends IBindable
|
|||||||
/** @return -1 if no valid framebuffer is available yet */
|
/** @return -1 if no valid framebuffer is available yet */
|
||||||
int getTargetFramebuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances)
|
int getTargetFramebuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances)
|
||||||
// Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
|
// Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
|
||||||
|
/** @return -1 if there was an issue or no texture exists */
|
||||||
int getDepthTextureId();
|
int getDepthTextureId();
|
||||||
|
/** @return -1 if there was an issue or no texture exists */
|
||||||
int getColorTextureId();
|
int getColorTextureId();
|
||||||
int getTargetFramebufferViewportWidth();
|
int getTargetFramebufferViewportWidth();
|
||||||
int getTargetFramebufferViewportHeight();
|
int getTargetFramebufferViewportHeight();
|
||||||
|
|||||||
+1
-3
@@ -54,7 +54,7 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Includes both the namespace and name. <br>
|
* Includes both the namespace and name. <br>
|
||||||
* example: "minecraft@@overworld"
|
* example: "minecraft:overworld"
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
String getDimensionName();
|
String getDimensionName();
|
||||||
@@ -85,8 +85,6 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
|
|||||||
@Override
|
@Override
|
||||||
int getMinHeight();
|
int getMinHeight();
|
||||||
|
|
||||||
default IChunkWrapper tryGetChunk(DhChunkPos pos) { return null; }
|
|
||||||
|
|
||||||
/** Fired when the level is being unloaded. Doesn't unload the level. */
|
/** Fired when the level is being unloaded. Doesn't unload the level. */
|
||||||
void onUnload();
|
void onUnload();
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,12 @@
|
|||||||
"distanthorizons.config.client.optionsButton.@tooltip":
|
"distanthorizons.config.client.optionsButton.@tooltip":
|
||||||
"Show the config button to the left of the fov button",
|
"Show the config button to the left of the fov button",
|
||||||
|
|
||||||
|
"distanthorizons.config.client.dynamicFadeUseOpaqueMcDepth":
|
||||||
|
"Dynamic Fade Use MC Opaque Depth",
|
||||||
|
"distanthorizons.config.client.dynamicFadeExportPath":
|
||||||
|
"Dynamic Fade Export Path",
|
||||||
|
"distanthorizons.config.client.dynamicFadeExportPath.@tooltip":
|
||||||
|
"Press 'p' to export the depth textures for troubleshooting",
|
||||||
|
|
||||||
|
|
||||||
"distanthorizons.config.client.advanced":
|
"distanthorizons.config.client.advanced":
|
||||||
@@ -208,6 +214,10 @@
|
|||||||
"Blur Radius",
|
"Blur Radius",
|
||||||
"distanthorizons.config.client.advanced.graphics.ssao.blurRadius.@tooltip":
|
"distanthorizons.config.client.advanced.graphics.ssao.blurRadius.@tooltip":
|
||||||
"The radius, measured in pixels, that blurring is calculated for the SSAO. \nHigher numbers will reduce banding at the cost of GPU performance.",
|
"The radius, measured in pixels, that blurring is calculated for the SSAO. \nHigher numbers will reduce banding at the cost of GPU performance.",
|
||||||
|
"distanthorizons.config.client.advanced.graphics.ssao.fadeDistanceInBlocks":
|
||||||
|
"Fade Distance",
|
||||||
|
"distanthorizons.config.client.advanced.graphics.ssao.fadeDistanceInBlocks.@tooltip":
|
||||||
|
"The distance in blocks from the camera where the SSAO will fade out to. \nThis is done to prevent banding and noise at extreme distances.",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -223,6 +233,10 @@
|
|||||||
"Beacon render height",
|
"Beacon render height",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip":
|
"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.",
|
"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":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering.@tooltip":
|
||||||
"If true LOD beacon beams will be rendered.",
|
"If true LOD beacon beams will be rendered.",
|
||||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableCloudRendering":
|
"distanthorizons.config.client.advanced.graphics.genericRendering.enableCloudRendering":
|
||||||
@@ -396,14 +410,13 @@
|
|||||||
"Experimental",
|
"Experimental",
|
||||||
|
|
||||||
"distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio":
|
"distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio":
|
||||||
"Earth Curve Ratio §6(EXPERIMENTAL)§r",
|
"Earth Curve Ratio",
|
||||||
"distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio.@tooltip":
|
"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.",
|
"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.ignoredDimensionCsv":
|
||||||
"distanthorizons.config.client.advanced.graphics.experimental.onlyLoadCenterLods":
|
"Ignored Dimension CSV",
|
||||||
"Only load center LODs",
|
"distanthorizons.config.client.advanced.graphics.experimental.ignoredDimensionCsv.@tooltip":
|
||||||
"distanthorizons.config.client.advanced.graphics.experimental.onlyLoadCenterLods.@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.",
|
||||||
"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",
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -714,6 +727,8 @@
|
|||||||
"OpenGL Events - Chat",
|
"OpenGL Events - Chat",
|
||||||
"distanthorizons.config.common.logging.logNetworkEventToFile":
|
"distanthorizons.config.common.logging.logNetworkEventToFile":
|
||||||
"Network Events - File",
|
"Network Events - File",
|
||||||
|
"distanthorizons.config.common.logging.logConnectionConfigChangesToFile":
|
||||||
|
"Network Connection Config Changes - File",
|
||||||
|
|
||||||
"distanthorizons.config.common.logging.warning":
|
"distanthorizons.config.common.logging.warning":
|
||||||
"Warnings",
|
"Warnings",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
@@ -1,79 +0,0 @@
|
|||||||
#version 150 core
|
|
||||||
|
|
||||||
in uvec4 vPosition;
|
|
||||||
out vec4 vPos;
|
|
||||||
in vec4 color;
|
|
||||||
|
|
||||||
out vec4 vertexColor;
|
|
||||||
out vec3 vertexWorldPos;
|
|
||||||
out float vertexYPos;
|
|
||||||
|
|
||||||
uniform bool uWhiteWorld;
|
|
||||||
|
|
||||||
uniform mat4 uCombinedMatrix;
|
|
||||||
uniform vec3 uModelOffset;
|
|
||||||
uniform float uWorldYOffset;
|
|
||||||
|
|
||||||
uniform int uWorldSkyLight;
|
|
||||||
uniform sampler2D uLightMap;
|
|
||||||
uniform float uMircoOffset;
|
|
||||||
|
|
||||||
uniform float uEarthRadius;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO in the future this and standard.vert should be merged together to prevent inconsistencies between the two
|
|
||||||
*
|
|
||||||
* Vertex Shader
|
|
||||||
*
|
|
||||||
* author: James Seibel
|
|
||||||
* author: TomTheFurry
|
|
||||||
* author: stduhpf
|
|
||||||
* updated: coolGi
|
|
||||||
* version: 24-1-2023
|
|
||||||
*/
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
vPos = vPosition; // This is so it can be passed to the fragment shader
|
|
||||||
|
|
||||||
vertexWorldPos = vPosition.xyz + uModelOffset;
|
|
||||||
|
|
||||||
vertexYPos = vPosition.y + uWorldYOffset;
|
|
||||||
|
|
||||||
uint meta = vPosition.a;
|
|
||||||
|
|
||||||
uint mirco = (meta & 0xFF00u) >> 8u; // mirco offset which is a xyz 2bit value
|
|
||||||
// 0b00 = no offset
|
|
||||||
// 0b01 = positive offset
|
|
||||||
// 0b11 = negative offset
|
|
||||||
// format is: 0b00zzyyxx
|
|
||||||
float mx = (mirco & 1u) != 0u ? uMircoOffset : 0.0;
|
|
||||||
mx = (mirco & 2u) != 0u ? -mx : mx;
|
|
||||||
float my = (mirco & 4u) != 0u ? uMircoOffset : 0.0;
|
|
||||||
my = (mirco & 8u) != 0u ? -my : my;
|
|
||||||
float mz = (mirco & 16u) != 0u ? uMircoOffset : 0.0;
|
|
||||||
mz = (mirco & 32u) != 0u ? -mz : mz;
|
|
||||||
vertexWorldPos.x += mx;
|
|
||||||
vertexWorldPos.y += my;
|
|
||||||
vertexWorldPos.z += mz;
|
|
||||||
|
|
||||||
|
|
||||||
// vertex transformation logic - stduhpf
|
|
||||||
float localRadius = uEarthRadius + vertexYPos;
|
|
||||||
float phi = length(vertexWorldPos.xz) / localRadius;
|
|
||||||
vertexWorldPos.y += (cos(phi) - 1.0) * localRadius;
|
|
||||||
vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi;
|
|
||||||
|
|
||||||
|
|
||||||
uint lights = meta & 0xFFu;
|
|
||||||
|
|
||||||
float light2 = (mod(float(lights), 16.0) + 0.5) / 16.0;
|
|
||||||
float light = (float(lights / 16u) + 0.5) / 16.0;
|
|
||||||
vertexColor = vec4(texture(uLightMap, vec2(light, light2)).xyz, 1.0);
|
|
||||||
|
|
||||||
if (!uWhiteWorld)
|
|
||||||
{
|
|
||||||
vertexColor *= color;
|
|
||||||
}
|
|
||||||
|
|
||||||
gl_Position = uCombinedMatrix * vec4(vertexWorldPos, 1.0);
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,7 @@ uniform float uMinLight;
|
|||||||
uniform float uBias;
|
uniform float uBias;
|
||||||
uniform mat4 uInvProj;
|
uniform mat4 uInvProj;
|
||||||
uniform mat4 uProj;
|
uniform mat4 uProj;
|
||||||
|
uniform float uFadeDistanceInBlocks;
|
||||||
|
|
||||||
const float EPSILON = 1.e-6;
|
const float EPSILON = 1.e-6;
|
||||||
const float GOLDEN_ANGLE = 2.39996323;
|
const float GOLDEN_ANGLE = 2.39996323;
|
||||||
@@ -99,6 +100,12 @@ void main()
|
|||||||
{
|
{
|
||||||
vec3 viewPos = calcViewPosition(vec3(TexCoord, fragmentDepth));
|
vec3 viewPos = calcViewPosition(vec3(TexCoord, fragmentDepth));
|
||||||
|
|
||||||
|
// fading is done to prevent banding/noise
|
||||||
|
// at super far distance
|
||||||
|
float distanceFromCamera = length(viewPos);
|
||||||
|
float fadeDistance = uFadeDistanceInBlocks;
|
||||||
|
if (distanceFromCamera < fadeDistance)
|
||||||
|
{
|
||||||
#ifdef GL_ARB_derivative_control
|
#ifdef GL_ARB_derivative_control
|
||||||
// Get higher precision derivatives when available
|
// Get higher precision derivatives when available
|
||||||
vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz));
|
vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz));
|
||||||
@@ -107,8 +114,16 @@ void main()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
viewNormal = normalize(viewNormal);
|
viewNormal = normalize(viewNormal);
|
||||||
|
|
||||||
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);
|
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);
|
||||||
|
|
||||||
|
// linearly fade with distance
|
||||||
|
occlusion *= (fadeDistance - distanceFromCamera) / fadeDistance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we're out of range, no need to do any SSAO calculations
|
||||||
|
occlusion = 0.0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fragColor = vec4(vec3(1.0 - occlusion), 1.0);
|
fragColor = vec4(vec3(1.0 - occlusion), 1.0);
|
||||||
|
|||||||
@@ -8,26 +8,26 @@ out vec4 vertexColor;
|
|||||||
out vec3 vertexWorldPos;
|
out vec3 vertexWorldPos;
|
||||||
out float vertexYPos;
|
out float vertexYPos;
|
||||||
|
|
||||||
uniform bool uWhiteWorld;
|
uniform bool uIsWhiteWorld;
|
||||||
|
|
||||||
uniform mat4 uCombinedMatrix;
|
uniform mat4 uCombinedMatrix;
|
||||||
uniform vec3 uModelOffset;
|
uniform vec3 uModelOffset;
|
||||||
uniform float uWorldYOffset;
|
uniform float uWorldYOffset;
|
||||||
|
|
||||||
uniform int uWorldSkyLight;
|
|
||||||
uniform sampler2D uLightMap;
|
uniform sampler2D uLightMap;
|
||||||
uniform float uMircoOffset;
|
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
|
* Vertex Shader
|
||||||
*
|
*
|
||||||
* author: James Seibel
|
* author: James Seibel
|
||||||
* updated: TomTheFurry
|
* author: TomTheFurry
|
||||||
|
* author: stduhpf
|
||||||
* updated: coolGi
|
* updated: coolGi
|
||||||
* version: 2023-6-25
|
*
|
||||||
|
* version: 2025-12-22
|
||||||
*/
|
*/
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
@@ -46,21 +46,34 @@ void main()
|
|||||||
// format is: 0b00zzyyxx
|
// format is: 0b00zzyyxx
|
||||||
float mx = (mirco & 1u)!=0u ? uMircoOffset : 0.0;
|
float mx = (mirco & 1u)!=0u ? uMircoOffset : 0.0;
|
||||||
mx = (mirco & 2u)!=0u ? -mx : mx;
|
mx = (mirco & 2u)!=0u ? -mx : mx;
|
||||||
float my = (mirco & 4u)!=0u ? uMircoOffset : 0.0;
|
//float my = (mirco & 4u)!=0u ? uMircoOffset : 0.0;
|
||||||
my = (mirco & 8u)!=0u ? -my : my;
|
//my = (mirco & 8u)!=0u ? -my : my;
|
||||||
float mz = (mirco & 16u)!=0u ? uMircoOffset : 0.0;
|
float mz = (mirco & 16u)!=0u ? uMircoOffset : 0.0;
|
||||||
mz = (mirco & 32u)!=0u ? -mz : mz;
|
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;
|
uint lights = meta & 0xFFu;
|
||||||
|
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);
|
||||||
|
|
||||||
float light2 = (mod(float(lights), 16.0)+0.5) / 16.0;
|
if (!uIsWhiteWorld)
|
||||||
float light = (float(lights/16u)+0.5) / 16.0;
|
|
||||||
vertexColor = vec4(texture(uLightMap, vec2(light, light2)).xyz, 1.0);
|
|
||||||
|
|
||||||
if (!uWhiteWorld)
|
|
||||||
{
|
{
|
||||||
vertexColor *= color;
|
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