Compare commits
92 Commits
2.4.0b
...
dynamicFade
| 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 | |||
| aabb90ada6 | |||
| 963a8dc53f | |||
| aa6d69385b | |||
| f42c9cf8fb | |||
| 92e0011c8d | |||
| c20d95a7c7 | |||
| 353aa1ed2c | |||
| 5aa43ebcc8 | |||
| b6145461b6 | |||
| 478e431076 | |||
| 6feb7f1b42 | |||
| 016fc66293 | |||
| 6d3e30d425 | |||
| 5be5c5a5bc | |||
| ed5aeb8951 | |||
| 7f0ddadf26 |
+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
|
||||
* 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},
|
||||
* and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
|
||||
|
||||
+20
-8
@@ -20,6 +20,8 @@
|
||||
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
|
||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldLoadEvent;
|
||||
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
||||
|
||||
@@ -27,7 +29,7 @@ import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
|
||||
* Contains information relevant to Distant Horizons and Minecraft rendering.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2024-1-31
|
||||
* @version 2025-12-23
|
||||
* @since API 1.0.0
|
||||
*/
|
||||
public class DhApiRenderParam implements IDhApiEventParam
|
||||
@@ -61,6 +63,13 @@ public class DhApiRenderParam implements IDhApiEventParam
|
||||
|
||||
public final int worldYOffset;
|
||||
|
||||
/**
|
||||
* The level currently being rendered.
|
||||
*
|
||||
* @since API 5.1.0
|
||||
*/
|
||||
public final IDhApiLevelWrapper clientLevelWrapper;
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
@@ -70,12 +79,13 @@ public class DhApiRenderParam implements IDhApiEventParam
|
||||
public DhApiRenderParam(DhApiRenderParam parent)
|
||||
{
|
||||
this(
|
||||
parent.renderPass,
|
||||
parent.partialTicks,
|
||||
parent.nearClipPlane, parent.farClipPlane,
|
||||
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
|
||||
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
|
||||
parent.worldYOffset
|
||||
parent.renderPass,
|
||||
parent.partialTicks,
|
||||
parent.nearClipPlane, parent.farClipPlane,
|
||||
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
|
||||
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
|
||||
parent.worldYOffset,
|
||||
parent.clientLevelWrapper
|
||||
);
|
||||
}
|
||||
public DhApiRenderParam(
|
||||
@@ -84,7 +94,8 @@ public class DhApiRenderParam implements IDhApiEventParam
|
||||
float nearClipPlane, float farClipPlane,
|
||||
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
|
||||
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
|
||||
int worldYOffset
|
||||
int worldYOffset,
|
||||
IDhApiLevelWrapper clientLevelWrapper
|
||||
)
|
||||
{
|
||||
this.renderPass = renderPass;
|
||||
@@ -101,6 +112,7 @@ public class DhApiRenderParam implements IDhApiEventParam
|
||||
this.dhModelViewMatrix = newDhModelViewMatrix;
|
||||
|
||||
this.worldYOffset = worldYOffset;
|
||||
this.clientLevelWrapper = clientLevelWrapper;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -38,14 +38,14 @@ public final class ModInfo
|
||||
public static final String NAME = "DistantHorizons";
|
||||
/** Human-readable version of NAME */
|
||||
public static final String READABLE_NAME = "Distant Horizons";
|
||||
public static final String VERSION = "2.4.0-b";
|
||||
public static final String VERSION = "2.4.6-b-dev";
|
||||
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
||||
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
||||
|
||||
/** This version should only be updated when breaking changes are introduced to the DH API */
|
||||
public static final int API_MAJOR_VERSION = 5;
|
||||
/** This version should be updated whenever new methods are added to the DH API */
|
||||
public static final int API_MINOR_VERSION = 0;
|
||||
public static final int API_MINOR_VERSION = 1;
|
||||
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
||||
public static final int API_PATCH_VERSION = 0;
|
||||
|
||||
|
||||
@@ -20,36 +20,43 @@
|
||||
package com.seibel.distanthorizons.core;
|
||||
|
||||
import com.github.luben.zstd.ZstdOutputStream;
|
||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
|
||||
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
|
||||
import com.seibel.distanthorizons.core.api.external.methods.config.DhApiConfig;
|
||||
import com.seibel.distanthorizons.core.api.external.methods.data.DhApiTerrainDataRepo;
|
||||
import com.seibel.distanthorizons.api.DhApi;
|
||||
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
import net.jpountz.lz4.LZ4FrameOutputStream;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.sqlite.SQLiteJDBCLoader;
|
||||
import org.sqlite.util.OSInfo;
|
||||
import org.tukaani.xz.XZOutputStream;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.lang.management.GarbageCollectorMXBean;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.List;
|
||||
|
||||
/** Handles first time Core setup. */
|
||||
public class Initializer
|
||||
{
|
||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||
|
||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||
|
||||
|
||||
|
||||
public static void init()
|
||||
{
|
||||
LOGGER.info("Running library validation...");
|
||||
|
||||
// confirm that all referenced libraries are available to use
|
||||
try
|
||||
{
|
||||
@@ -57,6 +64,17 @@ public class Initializer
|
||||
// will throw an error (not an exception)
|
||||
Class<?> lz4Compressor = LZ4FrameOutputStream.class;
|
||||
Class<?> zstdCompressor = ZstdOutputStream.class;
|
||||
|
||||
{
|
||||
byte[] testCompressByteArray = new byte[1024];
|
||||
for (int i = 0; i < testCompressByteArray.length; i++)
|
||||
{
|
||||
testCompressByteArray[i] = (byte) (i % 126);
|
||||
}
|
||||
byte[] compressedBytes = com.github.luben.zstd.Zstd.compress(testCompressByteArray);
|
||||
com.github.luben.zstd.Zstd.decompress(compressedBytes);
|
||||
}
|
||||
|
||||
Class<?> lzmaCompressor = XZOutputStream.class;
|
||||
//Class<?> networking = ByteBuf.class;
|
||||
Class<?> config = com.electronwill.nightconfig.core.Config.class;
|
||||
@@ -73,9 +91,7 @@ public class Initializer
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
LOGGER.fatal("Critical programmer error: One or more libraries aren't present. Error: [" + e.getMessage() + "].", e);
|
||||
// throwing here should crash the game, notifying the developer that something is wrong
|
||||
throw new RuntimeException(e);
|
||||
MC_CLIENT.crashMinecraft("Distant Horizons critical setup error: One or more libraries are either in-accessible, corrupted, or overwritten by another mod. Error: [" + e.getMessage() + "].", e);
|
||||
}
|
||||
|
||||
// confirm the resource directory is present
|
||||
@@ -89,8 +105,7 @@ public class Initializer
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.fatal("Critical programmer error: Can't read SQL Scripts resource folder is either missing or malformed. Error: [" + e.getMessage() + "].");
|
||||
throw new RuntimeException(e);
|
||||
MC_CLIENT.crashMinecraft("Critical programmer error: Can't read SQL Scripts resource folder is either missing or malformed. Error: [" + e.getMessage() + "].", e);
|
||||
}
|
||||
|
||||
// This code has been disabled since it can cause Mac
|
||||
@@ -121,6 +136,45 @@ public class Initializer
|
||||
LOGGER.error("Programmer Error: No ["+IWrapperFactory.class.getSimpleName()+"] assigned to the DhApi.");
|
||||
}
|
||||
|
||||
// log a warning if G1GC is being used
|
||||
// (this garbage collector is known to cause stuttering)
|
||||
{
|
||||
boolean g1GcInUse = false;
|
||||
|
||||
StringBuilder garbageCollectorNames = new StringBuilder();
|
||||
List<GarbageCollectorMXBean> gcMxBeans = ManagementFactory.getGarbageCollectorMXBeans();
|
||||
for (GarbageCollectorMXBean gcMxBean : gcMxBeans)
|
||||
{
|
||||
if (!garbageCollectorNames.toString().isEmpty())
|
||||
{
|
||||
garbageCollectorNames.append(", ");
|
||||
}
|
||||
garbageCollectorNames.append(gcMxBean.getName());
|
||||
|
||||
// "G1 Young Generation" // "G1 Concurrent GC" // "G1 Old Generation"
|
||||
if (gcMxBean.getName().toLowerCase().contains("g1 "))
|
||||
{
|
||||
g1GcInUse = true;
|
||||
}
|
||||
}
|
||||
LOGGER.info("Garbage collectors: ["+garbageCollectorNames+"]");
|
||||
|
||||
|
||||
if (g1GcInUse
|
||||
&& Config.Common.Logging.Warning.logGarbageCollectorWarning.get())
|
||||
{
|
||||
LOGGER.warn(
|
||||
"Distant Horizons: G1 Garbage collector detected. \n" +
|
||||
"This garbage collector can cause FPS stuttering. \n" +
|
||||
"It's recommended to use a concurrent garbage collector \n" +
|
||||
"like ZGC (Java 21+) or Shenandoah (Java 8 through 17) for a smoother experience. \n" +
|
||||
"");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DhApi.events.bind(DhApiBeforeRenderEvent.class, IgnoredDimensionCsvHandler.INSTANCE);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiHeightFogCo
|
||||
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.api.converters.ApiFogDrawModeConverter;
|
||||
import com.seibel.distanthorizons.core.config.api.converters.InvertedBoolConverter;
|
||||
|
||||
public class DhApiFogConfig implements IDhApiFogConfig
|
||||
{
|
||||
@@ -67,7 +68,7 @@ public class DhApiFogConfig implements IDhApiFogConfig
|
||||
@Override
|
||||
@Deprecated
|
||||
public IDhApiConfigValue<Boolean> disableVanillaFog()
|
||||
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.disableVanillaFog); }
|
||||
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog, new InvertedBoolConverter()); }
|
||||
@Override
|
||||
public IDhApiConfigValue<Boolean> enableVanillaFog()
|
||||
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog); }
|
||||
|
||||
@@ -23,7 +23,9 @@ import com.seibel.distanthorizons.api.DhApi;
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
|
||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
|
||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
|
||||
import com.seibel.distanthorizons.api.objects.DhApiResult;
|
||||
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
|
||||
@@ -31,21 +33,21 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
|
||||
import com.seibel.distanthorizons.core.render.renderer.*;
|
||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||
import com.seibel.distanthorizons.core.util.math.Mat4f;
|
||||
import com.seibel.distanthorizons.core.util.objects.Pair;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
|
||||
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
|
||||
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
|
||||
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
||||
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
|
||||
import com.seibel.distanthorizons.core.world.DhClientWorld;
|
||||
import com.seibel.distanthorizons.core.world.IDhClientWorld;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
@@ -53,11 +55,23 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL46;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* This holds the methods that should be called
|
||||
@@ -153,10 +167,10 @@ public class ClientApi
|
||||
|
||||
if (Config.Common.Logging.Warning.showReplayWarningOnStartup.get())
|
||||
{
|
||||
MC_CLIENT.sendChatMessage("\u00A76" + "Distant Horizons: Replay detected." + "\u00A7r"); // gold color
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.ORANGE + "Distant Horizons: Replay detected." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage("DH may behave strangely or have missing functionality.");
|
||||
MC_CLIENT.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:");
|
||||
MC_CLIENT.sendChatMessage("\u00A77"+".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+"\u00A7r"); // light gray color
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.GRAY +".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+ MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
|
||||
MC_CLIENT.sendChatMessage("");
|
||||
}
|
||||
@@ -242,7 +256,7 @@ public class ClientApi
|
||||
}
|
||||
}
|
||||
|
||||
public void clientLevelLoadEvent(IClientLevelWrapper levelWrapper)
|
||||
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
|
||||
{
|
||||
// wait a moment before loading the level to give the server a chance to handle the client's login request
|
||||
if (MC_CLIENT.clientConnectedToDedicatedServer())
|
||||
@@ -318,35 +332,6 @@ public class ClientApi
|
||||
|
||||
|
||||
|
||||
//============//
|
||||
// clint tick //
|
||||
//============//
|
||||
|
||||
@Deprecated
|
||||
public void clientTickEvent()
|
||||
{
|
||||
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
|
||||
profiler.push("DH-ClientTick");
|
||||
|
||||
try
|
||||
{
|
||||
IDhClientWorld clientWorld = SharedApi.tryGetDhClientWorld();
|
||||
if (clientWorld != null)
|
||||
{
|
||||
clientWorld.clientTick();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// handle errors here to prevent blowing up a mixin or API up stream
|
||||
LOGGER.error("Unexpected error in ClientApi.clientTickEvent(), error: "+e.getMessage(), e);
|
||||
}
|
||||
|
||||
profiler.pop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//============//
|
||||
// networking //
|
||||
//============//
|
||||
@@ -358,10 +343,27 @@ public class ClientApi
|
||||
*/
|
||||
public void pluginMessageReceived(@NotNull AbstractNetworkMessage message)
|
||||
{
|
||||
NetworkSession networkSession = this.pluginChannelApi.networkSession;
|
||||
if (networkSession != null)
|
||||
@Nullable ThreadPoolExecutor executor = ThreadPoolUtil.networkClientHandlerExecutor();
|
||||
if (executor == null)
|
||||
{
|
||||
networkSession.tryHandleMessage(message);
|
||||
LOGGER.warn("warn");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
executor.execute(() ->
|
||||
{
|
||||
NetworkSession networkSession = this.pluginChannelApi.networkSession;
|
||||
if (networkSession != null)
|
||||
{
|
||||
networkSession.tryHandleMessage(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (RejectedExecutionException e)
|
||||
{
|
||||
LOGGER.warn("Plugin message executor rejected");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,8 +407,11 @@ public class ClientApi
|
||||
|
||||
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
|
||||
GLProxy.getInstance().runRenderThreadTasks();
|
||||
GLProxy.runRenderThreadTasks();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -534,10 +539,10 @@ public class ClientApi
|
||||
this.rendererDisabledBecauseOfExceptions = true;
|
||||
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
|
||||
|
||||
MC_CLIENT.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
|
||||
MC_CLIENT.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption.");
|
||||
MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
|
||||
MC_CLIENT.sendChatMessage("\u00A74Error: " + e);
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "" + MinecraftTextFormat.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Renderer disabled to try preventing GL state corruption." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Toggle DH rendering via the config UI to re-activate DH rendering." + MinecraftTextFormat.CLEAR_FORMATTING);
|
||||
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
|
||||
}
|
||||
|
||||
|
||||
@@ -556,8 +561,11 @@ public class ClientApi
|
||||
* The first fade pass.
|
||||
* Called after MC finishes rendering the opaque passes.
|
||||
*/
|
||||
public void renderFadeOpaque()
|
||||
public void renderFadeOpaque() // TODO this is actually the transparent pass
|
||||
{
|
||||
DepthCalculator.INSTANCE.getMcTransparentDepthTexture();
|
||||
DepthCalculator.INSTANCE.tryCalculateAsync();
|
||||
|
||||
// only fade when DH is rendering
|
||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT
|
||||
&&
|
||||
@@ -578,8 +586,10 @@ public class ClientApi
|
||||
* Called after MC finishes rendering both opaque
|
||||
* and transparent passes.
|
||||
*/
|
||||
public void renderFadeTransparent()
|
||||
public void renderFadeTransparent() // TODO this is actually the opaque pass
|
||||
{
|
||||
DepthCalculator.INSTANCE.getMcOpaqueDepthTexture();
|
||||
|
||||
// only fade when DH is rendering
|
||||
if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT)
|
||||
{
|
||||
@@ -608,27 +618,30 @@ public class ClientApi
|
||||
/** Trigger once on key press, with CLIENT PLAYER. */
|
||||
public void keyPressedEvent(int glfwKey)
|
||||
{
|
||||
if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get())
|
||||
{
|
||||
// keybindings are disabled
|
||||
return;
|
||||
}
|
||||
//if (!Config.Client.Advanced.Debugging.enableDebugKeybindings.get())
|
||||
//{
|
||||
// // keybindings are disabled
|
||||
// return;
|
||||
//}
|
||||
|
||||
|
||||
if (glfwKey == GLFW.GLFW_KEY_F8)
|
||||
{
|
||||
Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
|
||||
MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
|
||||
DepthCalculator.INSTANCE.pause = true;
|
||||
//Config.Client.Advanced.Debugging.debugRendering.set(EDhApiDebugRendering.next(Config.Client.Advanced.Debugging.debugRendering.get()));
|
||||
//MC_CLIENT.sendChatMessage("F8: Set debug mode to " + Config.Client.Advanced.Debugging.debugRendering.get());
|
||||
}
|
||||
else if (glfwKey == GLFW.GLFW_KEY_F6)
|
||||
{
|
||||
Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
|
||||
MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
|
||||
DepthCalculator.INSTANCE.pause = true;
|
||||
//Config.Client.Advanced.Debugging.rendererMode.set(EDhApiRendererMode.next(Config.Client.Advanced.Debugging.rendererMode.get()));
|
||||
//MC_CLIENT.sendChatMessage("F6: Set rendering to " + Config.Client.Advanced.Debugging.rendererMode.get());
|
||||
}
|
||||
else if (glfwKey == GLFW.GLFW_KEY_P)
|
||||
{
|
||||
prefLoggerEnabled = !prefLoggerEnabled;
|
||||
MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
|
||||
DepthCalculator.INSTANCE.pause = true;
|
||||
//prefLoggerEnabled = !prefLoggerEnabled;
|
||||
//MC_CLIENT.sendChatMessage("P: Debug Pref Logger is " + (prefLoggerEnabled ? "enabled" : "disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,15 +687,15 @@ public class ClientApi
|
||||
{
|
||||
// dev build
|
||||
if (ModInfo.IS_DEV_BUILD
|
||||
&& !this.isDevBuildMessagePrinted && MC_CLIENT.playerExists())
|
||||
&& !this.isDevBuildMessagePrinted
|
||||
&& MC_CLIENT.playerExists())
|
||||
{
|
||||
this.isDevBuildMessagePrinted = true;
|
||||
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
|
||||
|
||||
// remind the user that this is a development build
|
||||
String message =
|
||||
// green text
|
||||
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" +
|
||||
MinecraftTextFormat.DARK_GREEN + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Issues may occur with this version.\n" +
|
||||
"Here be dragons!\n";
|
||||
MC_CLIENT.sendChatMessage(message);
|
||||
@@ -706,7 +719,7 @@ public class ClientApi
|
||||
{
|
||||
String message =
|
||||
// orange text
|
||||
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" +
|
||||
MinecraftTextFormat.ORANGE + "Distant Horizons: Low memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Stuttering or low FPS may occur. \n" +
|
||||
"Please increase Minecraft's available memory to 4 GB or more. \n" +
|
||||
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
|
||||
@@ -720,22 +733,21 @@ public class ClientApi
|
||||
if (!this.highVanillaRenderDistanceWarningPrinted
|
||||
&& Config.Common.Logging.Warning.showHighVanillaRenderDistanceWarning.get())
|
||||
{
|
||||
this.highVanillaRenderDistanceWarningPrinted = true;
|
||||
|
||||
// DH generally doesn't need a vanilla render distance above 12
|
||||
if (MC_RENDER.getRenderDistance() > 12)
|
||||
{
|
||||
this.highVanillaRenderDistanceWarningPrinted = true;
|
||||
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
|
||||
|
||||
String message =
|
||||
// yellow text
|
||||
"\u00A7e" + "Distant Horizons: High vanilla render distance detected." + "\u00A7r \n" +
|
||||
"Using a high vanilla render distance uses a lot of CPU power \n" +
|
||||
"and doesn't improve graphics much after about 12.\n" +
|
||||
"Lowing your vanilla render distance will give you better FPS\n" +
|
||||
"and reduce stuttering at a similar visual quality.\n" +
|
||||
// gray text
|
||||
"\u00A77" + "A vanilla render distance of 8 is recommended." + "\u00A7r \n" +
|
||||
"This message can be disabled in DH's config under Advanced -> Logging.\n";
|
||||
MinecraftTextFormat.YELLOW + "Distant Horizons: High vanilla render distance detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Using a high vanilla render distance uses a lot of CPU power \n" +
|
||||
"and doesn't improve graphics much after about 12.\n" +
|
||||
"Lowering your vanilla render distance will give you better FPS\n" +
|
||||
"and reduce stuttering at a similar visual quality.\n" +
|
||||
MinecraftTextFormat.GRAY + "A vanilla render distance of 8 is recommended." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"This message can be disabled in DH's config under Advanced -> Logging.\n";
|
||||
MC_CLIENT.sendChatMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ public class ClientPluginChannelApi
|
||||
|
||||
LOGGER.info("Server level key received: [" + msg.levelKey + "].");
|
||||
|
||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
||||
GLProxy.queueRunningOnRenderThread(() ->
|
||||
{
|
||||
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
|
||||
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
|
||||
|
||||
@@ -82,15 +82,15 @@ public class ServerApi
|
||||
// level events //
|
||||
//==============//
|
||||
|
||||
public void serverLevelLoadEvent(IServerLevelWrapper level)
|
||||
public void serverLevelLoadEvent(IServerLevelWrapper levelWrapper)
|
||||
{
|
||||
LOGGER.debug("Server Level " + level + " loading");
|
||||
LOGGER.debug("Server Level " + levelWrapper + " loading");
|
||||
|
||||
AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld();
|
||||
if (serverWorld != null)
|
||||
{
|
||||
serverWorld.getOrLoadLevel(level);
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level));
|
||||
serverWorld.getOrLoadLevel(levelWrapper);
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
|
||||
}
|
||||
}
|
||||
public void serverLevelUnloadEvent(IServerLevelWrapper level)
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.Initializer;
|
||||
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData;
|
||||
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
|
||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||
@@ -184,12 +185,11 @@ public class SharedApi
|
||||
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, false); }
|
||||
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, true); }
|
||||
|
||||
//public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks) { this.applyChunkUpdate(chunkWrapper, level, canGetNeighboringChunks, false); }
|
||||
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded)
|
||||
{
|
||||
//========================//
|
||||
// world and level checks //
|
||||
//========================//
|
||||
//===================//
|
||||
// validation checks //
|
||||
//===================//
|
||||
|
||||
if (chunkWrapper == null)
|
||||
{
|
||||
@@ -217,7 +217,6 @@ public class SharedApi
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// only continue if the level is loaded
|
||||
IDhLevel dhLevel = dhWorld.getLevel(level);
|
||||
if (dhLevel == null)
|
||||
@@ -232,6 +231,7 @@ public class SharedApi
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore chunk updates if the network should handle them
|
||||
if (dhLevel instanceof DhClientLevel)
|
||||
{
|
||||
if (!((DhClientLevel) dhLevel).shouldProcessChunkUpdate(chunkWrapper.getChunkPos()))
|
||||
@@ -240,7 +240,14 @@ public class SharedApi
|
||||
}
|
||||
}
|
||||
|
||||
// shoudln't normally happen, but just in case
|
||||
// ignore chunk updates for non-rendered levels
|
||||
String dimName = dhLevel.getLevelWrapper().getDimensionName();
|
||||
if (IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// shouldn't normally happen, but just in case
|
||||
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
|
||||
{
|
||||
// TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded
|
||||
@@ -248,94 +255,20 @@ public class SharedApi
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===============================//
|
||||
// update the necessary chunk(s) //
|
||||
//===============================//
|
||||
|
||||
if (!canGetNeighboringChunks)
|
||||
{
|
||||
// only update the center chunk
|
||||
queueChunkUpdate(chunkWrapper, null, dhLevel, false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ArrayList<IChunkWrapper> neighboringChunkList = getNeighborChunkListForChunk(chunkWrapper, dhLevel);
|
||||
|
||||
if (newlyLoaded)
|
||||
{
|
||||
// this means this chunkWrapper is a newly loaded chunk
|
||||
// which may be missing some neighboring chunk data
|
||||
// because it is bordering the render distance
|
||||
// thus, only the chunks neighboring this chunkWrapper will get updated
|
||||
// because those are more likely to have their full neighboring chunk data
|
||||
//TODO this does not prevent those neighboring chunks from updating
|
||||
// this newly loaded chunk that were just skipped
|
||||
// leading to occasional lighting issues
|
||||
for (IChunkWrapper neighboringChunk : neighboringChunkList)
|
||||
{
|
||||
if (neighboringChunk == chunkWrapper)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.applyChunkUpdate(neighboringChunk, level, true, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if not all neighboring chunk data is available, do not try to update
|
||||
if (neighboringChunkList.size() < 9)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// update the center with any existing neighbour chunks.
|
||||
// this is done so lighting changes are propagated correctly
|
||||
queueChunkUpdate(chunkWrapper, neighboringChunkList, dhLevel, true);
|
||||
}
|
||||
}
|
||||
private static ArrayList<IChunkWrapper> getNeighborChunkListForChunk(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
|
||||
{
|
||||
// get the neighboring chunk list
|
||||
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
|
||||
for (int xOffset = -1; xOffset <= 1; xOffset++)
|
||||
{
|
||||
for (int zOffset = -1; zOffset <= 1; zOffset++)
|
||||
{
|
||||
if (xOffset == 0 && zOffset == 0)
|
||||
{
|
||||
// center chunk
|
||||
neighborChunkList.add(chunkWrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
// neighboring chunk
|
||||
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
|
||||
IChunkWrapper neighborChunk = dhLevel.getLevelWrapper().tryGetChunk(neighborPos);
|
||||
if (neighborChunk != null)
|
||||
{
|
||||
neighborChunkList.add(neighborChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return neighborChunkList;
|
||||
queueChunkUpdate(chunkWrapper, dhLevel);
|
||||
}
|
||||
|
||||
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighboringChunks)
|
||||
private static void queueChunkUpdate(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
|
||||
{
|
||||
|
||||
// return if the chunk is already queued
|
||||
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// add chunk update data to preUpdate queue
|
||||
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, neighborChunkList, dhLevel, canGetNeighboringChunks);
|
||||
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel);
|
||||
CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData);
|
||||
|
||||
|
||||
@@ -343,7 +276,8 @@ public class SharedApi
|
||||
// (this prevents doing extra work queuing tasks that may not be necessary)
|
||||
// and makes sure the chunks closest to the player are updated first
|
||||
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
|
||||
if (executor != null && executor.getQueueSize() < executor.getPoolSize())
|
||||
if (executor != null
|
||||
&& executor.getQueueSize() < executor.getPoolSize())
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -383,10 +317,7 @@ public class SharedApi
|
||||
// update the necessary chunk(s) //
|
||||
//===============================//
|
||||
|
||||
// process preUpdate queue
|
||||
processQueuedChunkPreUpdate();
|
||||
|
||||
// process update queue
|
||||
processQueuedChunkUpdate();
|
||||
|
||||
// queue the next position if there are still positions to process
|
||||
@@ -415,8 +346,7 @@ public class SharedApi
|
||||
|
||||
IDhLevel dhLevel = preUpdateData.dhLevel;
|
||||
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
|
||||
boolean canGetNeighboringChunks = preUpdateData.canGetNeighboringChunks;
|
||||
ArrayList<IChunkWrapper> neighborChunkList = preUpdateData.neighborChunkList;
|
||||
chunkWrapper.createDhHeightMaps();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -433,34 +363,6 @@ public class SharedApi
|
||||
// do not update the chunk if the hash is the same
|
||||
return;
|
||||
}
|
||||
|
||||
// if this chunk will update and can get neighbors
|
||||
// then queue neighboring chunks to update as well
|
||||
// neighboring chunk will get added directly to the update queue
|
||||
// so they won't queue further chunk updates
|
||||
if (neighborChunkList != null
|
||||
&& !neighborChunkList.isEmpty())
|
||||
{
|
||||
for (IChunkWrapper adjacentChunk : neighborChunkList)
|
||||
{
|
||||
// pulling a new chunkWrapper is necessary to prevent concurrent modification on the existing chunkWrappers
|
||||
IChunkWrapper newCenterChunk = dhLevel.getLevelWrapper().tryGetChunk(adjacentChunk.getChunkPos());
|
||||
if (newCenterChunk != null)
|
||||
{
|
||||
ChunkUpdateData newUpdateData;
|
||||
if (canGetNeighboringChunks)
|
||||
{
|
||||
newUpdateData = new ChunkUpdateData(newCenterChunk, getNeighborChunkListForChunk(newCenterChunk, dhLevel), dhLevel, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
newUpdateData = new ChunkUpdateData(newCenterChunk, null, dhLevel, false);
|
||||
}
|
||||
|
||||
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(newCenterChunk.getChunkPos(), newUpdateData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
|
||||
@@ -473,8 +375,6 @@ public class SharedApi
|
||||
|
||||
private static void processQueuedChunkUpdate()
|
||||
{
|
||||
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
|
||||
|
||||
ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
|
||||
if (updateData == null)
|
||||
{
|
||||
@@ -484,15 +384,11 @@ public class SharedApi
|
||||
IChunkWrapper chunkWrapper = updateData.chunkWrapper;
|
||||
IDhLevel dhLevel = updateData.dhLevel;
|
||||
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
|
||||
// having a list of the nearby chunks is needed for lighting and beacon generation
|
||||
@Nullable ArrayList<IChunkWrapper> nearbyChunkList = updateData.neighborChunkList;
|
||||
|
||||
// a non-null list is needed for the lighting engine
|
||||
if (nearbyChunkList == null)
|
||||
{
|
||||
nearbyChunkList = new ArrayList<IChunkWrapper>();
|
||||
nearbyChunkList.add(chunkWrapper);
|
||||
}
|
||||
// having a list of the nearby chunks is needed for lighting and beacon generation
|
||||
ArrayList<IChunkWrapper> nearbyChunkList = tryGetNeighborChunkListForChunk(chunkWrapper);
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
@@ -508,6 +404,35 @@ public class SharedApi
|
||||
{
|
||||
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
|
||||
}
|
||||
|
||||
CHUNK_UPDATE_QUEUE_MANAGER.queuedChunkWrapperByChunkPos.remove(updateData.chunkWrapper.getChunkPos());
|
||||
}
|
||||
private static ArrayList<IChunkWrapper> tryGetNeighborChunkListForChunk(IChunkWrapper chunkWrapper)
|
||||
{
|
||||
// get the neighboring chunk list
|
||||
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
|
||||
for (int xOffset = -1; xOffset <= 1; xOffset++)
|
||||
{
|
||||
for (int zOffset = -1; zOffset <= 1; zOffset++)
|
||||
{
|
||||
if (xOffset == 0 && zOffset == 0)
|
||||
{
|
||||
// center chunk
|
||||
neighborChunkList.add(chunkWrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
// neighboring chunk
|
||||
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
|
||||
IChunkWrapper neighborChunk = CHUNK_UPDATE_QUEUE_MANAGER.tryGetChunk(neighborPos);
|
||||
if (neighborChunk != null)
|
||||
{
|
||||
neighborChunkList.add(neighborChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return neighborChunkList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -105,6 +106,7 @@ public class ChunkPosQueue
|
||||
this.furthestQueue.remove(closest);
|
||||
return this.updateDataByChunkPos.remove(closest);
|
||||
}
|
||||
@Nullable
|
||||
public ChunkUpdateData popFurthest()
|
||||
{
|
||||
if (this.furthestQueue.isEmpty())
|
||||
|
||||
+1
-6
@@ -9,18 +9,13 @@ import java.util.ArrayList;
|
||||
public class ChunkUpdateData
|
||||
{
|
||||
public IChunkWrapper chunkWrapper;
|
||||
@Nullable
|
||||
public ArrayList<IChunkWrapper> neighborChunkList;
|
||||
public IDhLevel dhLevel;
|
||||
public boolean canGetNeighboringChunks;
|
||||
|
||||
|
||||
|
||||
public ChunkUpdateData(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighborChunks)
|
||||
public ChunkUpdateData(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
|
||||
{
|
||||
this.chunkWrapper = chunkWrapper;
|
||||
this.neighborChunkList = neighborChunkList;
|
||||
this.dhLevel = dhLevel;
|
||||
this.canGetNeighboringChunks = canGetNeighborChunks;
|
||||
}
|
||||
}
|
||||
|
||||
+45
-26
@@ -1,16 +1,22 @@
|
||||
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.api.internal.SharedApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ChunkUpdateQueueManager
|
||||
{
|
||||
@@ -21,6 +27,12 @@ public class ChunkUpdateQueueManager
|
||||
public final ChunkPosQueue preUpdateQueue;
|
||||
public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
public final ConcurrentMap<DhChunkPos, IChunkWrapper> queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(20, TimeUnit.SECONDS)
|
||||
.<DhChunkPos, IChunkWrapper>build()
|
||||
.asMap();
|
||||
|
||||
/** dynamically changes based on the number of threads currently available */
|
||||
public int maxSize = 500;
|
||||
|
||||
private static long lastOverloadedLogMessageMsTime = 0;
|
||||
@@ -68,22 +80,27 @@ public class ChunkUpdateQueueManager
|
||||
* If there are no more slots, replaces the item furthest from the center in the update queue.
|
||||
*/
|
||||
public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
|
||||
{ this.addItemToQueue(pos, updateData, this.preUpdateQueue); }
|
||||
|
||||
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
|
||||
{ this.addItemToQueue(pos, updateData, this.updateQueue); }
|
||||
|
||||
private void addItemToQueue(DhChunkPos pos, ChunkUpdateData updateData, ChunkPosQueue queue)
|
||||
{
|
||||
int remainingSlots = this.maxSize - this.getQueuedCount();
|
||||
|
||||
// If no slots are left, get one by removing the item furthest from the center
|
||||
if (remainingSlots <= 0)
|
||||
{
|
||||
if (!this.updateQueue.isEmpty())
|
||||
ChunkUpdateData removedData = queue.popFurthest();
|
||||
if (removedData != null)
|
||||
{
|
||||
this.updateQueue.popFurthest();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.preUpdateQueue.popFurthest();
|
||||
this.queuedChunkWrapperByChunkPos.remove(removedData.chunkWrapper.getChunkPos());
|
||||
}
|
||||
}
|
||||
this.preUpdateQueue.addItem(pos, updateData);
|
||||
|
||||
queue.addItem(pos,updateData);
|
||||
this.queuedChunkWrapperByChunkPos.putIfAbsent(pos, updateData.chunkWrapper);
|
||||
|
||||
remainingSlots = this.maxSize - this.getQueuedCount();
|
||||
if (remainingSlots <= 0)
|
||||
@@ -92,24 +109,6 @@ public class ChunkUpdateQueueManager
|
||||
}
|
||||
}
|
||||
|
||||
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
|
||||
{
|
||||
int remainingSlots = this.maxSize - this.getQueuedCount();
|
||||
|
||||
// If no slots are left, get one by removing the item furthest from the center
|
||||
if (remainingSlots <= 0)
|
||||
{
|
||||
this.updateQueue.popFurthest();
|
||||
}
|
||||
|
||||
this.updateQueue.addItem(pos,updateData);
|
||||
|
||||
remainingSlots = this.maxSize - this.getQueuedCount();
|
||||
if (remainingSlots <= 0)
|
||||
{
|
||||
this.sendOverloadMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendOverloadMessage()
|
||||
{
|
||||
@@ -119,7 +118,7 @@ public class ChunkUpdateQueueManager
|
||||
{
|
||||
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
|
||||
|
||||
String message = "\u00A76" + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + "\u00A7r" +
|
||||
String message = MinecraftTextFormat.ORANGE + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + MinecraftTextFormat.CLEAR_FORMATTING +
|
||||
"\nThis may result in holes in your LODs. " +
|
||||
"\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " +
|
||||
"\nMax queue count [" + SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.maxSize + "] ([" + SharedApi.MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players).";
|
||||
@@ -140,6 +139,26 @@ public class ChunkUpdateQueueManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to return a cloned chunk wrapper from memory.
|
||||
* Returns null if no chunk is available.
|
||||
* <br><br>
|
||||
* This is done instead of accessing the MC level since
|
||||
* accessing the level often requires running on the render or server
|
||||
* thread, which causes stuttering.
|
||||
*/
|
||||
@Nullable
|
||||
public IChunkWrapper tryGetChunk(DhChunkPos pos)
|
||||
{
|
||||
IChunkWrapper existingWrapper = this.queuedChunkWrapperByChunkPos.get(pos);
|
||||
if (existingWrapper == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return existingWrapper.copy();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
|
||||
@@ -33,13 +33,12 @@ import com.seibel.distanthorizons.core.config.types.enums.*;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.NativeDialogUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.lwjgl.util.tinyfd.TinyFileDialogs;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
@@ -119,6 +118,20 @@ public class Config
|
||||
|
||||
|
||||
|
||||
public static ConfigEntry<Boolean> dynamicFadeUseOpaqueMcDepth = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<String> dynamicFadeExportPath = new ConfigEntry.Builder<String>()
|
||||
.set("C:/Users/James_Seibel/Desktop/")
|
||||
.comment(""
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
public static class Advanced
|
||||
{
|
||||
// common config links need to have their destination
|
||||
@@ -420,6 +433,14 @@ public class Config
|
||||
"")
|
||||
.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
|
||||
@@ -450,6 +471,15 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Boolean> expandDistantBeacons = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
+ "If true LOD beacon beams will be rendered wider at extreme distances, \n"
|
||||
+ "making them easier to see. \n"
|
||||
+ "If false all LOD beacon beams will only ever be 1 block wide. \n"
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Boolean> enableCloudRendering = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
@@ -498,11 +528,6 @@ public class Config
|
||||
+ "Note: Other mods may conflict with this setting. \n"
|
||||
+ "")
|
||||
.build();
|
||||
@Deprecated
|
||||
public static ConfigEntry<Boolean> disableVanillaFog = new ConfigEntry.Builder<Boolean>()
|
||||
.set(!enableVanillaFog.get())
|
||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
@@ -564,14 +589,6 @@ public class Config
|
||||
|
||||
|
||||
|
||||
static
|
||||
{
|
||||
disableVanillaFog.addListener(
|
||||
new ConfigChangeListener<Boolean>(disableVanillaFog,
|
||||
(disableVanillaFog) -> enableVanillaFog.setApiValue(disableVanillaFog))
|
||||
);
|
||||
}
|
||||
|
||||
public static class HeightFog
|
||||
{
|
||||
public static ConfigUIComment heightFogHeader = new ConfigUIComment.Builder().setParentConfigClass(HeightFog.class).build();
|
||||
@@ -786,6 +803,11 @@ public class Config
|
||||
+ "A comma separated list of block resource locations that won't be rendered by DH. \n"
|
||||
+ "Air is always included in this list. \n"
|
||||
+ "Requires a restart to change. \n"
|
||||
+ "\n"
|
||||
+ "Note:\n"
|
||||
+ "If you see gaps, or holes you may have to change\n"
|
||||
+ "worldCompression to ["+EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS+"] and re-generate the LODs.\n"
|
||||
+ "Black spots may happen occur to block lighting being zero for covered blocks.\n"
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
@@ -834,7 +856,7 @@ public class Config
|
||||
public static ConfigUIComment experimentalHeader = new ConfigUIComment.Builder().setParentConfigClass(Experimental.class).build();
|
||||
|
||||
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
|
||||
.setMinDefaultMax(0, 0, 5000)
|
||||
.setMinDefaultMax(-5000, 0, 5000)
|
||||
.comment(""
|
||||
+ "This is the earth size ratio when applying the curvature shader effect. \n"
|
||||
+ "Note: Enabling this feature may cause rendering bugs. \n"
|
||||
@@ -844,24 +866,26 @@ public class Config
|
||||
+ "100 = 1 to 100 (63,710 blocks) \n"
|
||||
+ "10000 = 1 to 10000 (637.1 blocks) \n"
|
||||
+ "\n"
|
||||
+ "Note: Due to current limitations, the min value is 50 \n"
|
||||
+ "Note: Due to current limitations, the min value is ["+WorldCurvatureConfigEventHandler.MIN_VALID_CURVE_VALUE+"] \n"
|
||||
+ "and the max value is 5000. Any values outside this range \n"
|
||||
+ "will be set to 0 (disabled).")
|
||||
.addListener(WorldCurvatureConfigEventHandler.INSTANCE)
|
||||
.build();
|
||||
|
||||
// TODO should be replaced with a better long-term solution
|
||||
@Deprecated
|
||||
public static ConfigEntry<Boolean> onlyLoadCenterLods = new ConfigEntry.Builder<Boolean>()
|
||||
.set(false)
|
||||
.comment(""
|
||||
+ "For internal testing:\n"
|
||||
+ "Skips loading adjacent LODs to significantly reduce load times (~5x)\n"
|
||||
+ "but causes lighting on LOD borders to appear as full-bright\n"
|
||||
+ "and other graphical bugs.\n"
|
||||
+ "")
|
||||
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE)
|
||||
.build();
|
||||
public static ConfigEntry<String> ignoredDimensionCsv = new ConfigEntry.Builder<String>()
|
||||
.set("")
|
||||
.comment(""
|
||||
+ "A comma separated list of dimension resource locations where DH won't render. \n"
|
||||
+ "\n"
|
||||
+ "Example: \"minecraft:the_nether,minecraft:the_end\"\n"
|
||||
+ "\n"
|
||||
+ "Note:\n"
|
||||
+ "Some DH settings will be disabled and/or changed to improve \n"
|
||||
+ "visuals when DH rendering is disabled. \n"
|
||||
+ "")
|
||||
.addListener(IgnoredDimensionCsvHandler.INSTANCE)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1243,23 +1267,17 @@ public class Config
|
||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE) // no GUI renderer set up currently
|
||||
.build();
|
||||
|
||||
public static ConfigUIButton uiButtonTest = new ConfigUIButton(() ->
|
||||
public static ConfigUIButton uiButtonTest = new ConfigUIButton(() ->
|
||||
{
|
||||
// running on a separate thread is necessary to prevent locking
|
||||
new Thread(() ->
|
||||
{
|
||||
if (!GraphicsEnvironment.isHeadless())
|
||||
{
|
||||
LOGGER.info("Attempting to show tinyfd message box...");
|
||||
boolean buttonPress = TinyFileDialogs.tinyfd_messageBox("Button pressed!", "UITester dialog", "ok", "info", false);
|
||||
LOGGER.info("dialog returned with ["+(buttonPress ? "TRUE" : "FALSE")+"]");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.info("button pressed!");
|
||||
}
|
||||
}).start();
|
||||
new Thread(() -> onButtonPressed()).start();
|
||||
});
|
||||
public static void onButtonPressed()
|
||||
{
|
||||
LOGGER.info("Attempting to show tinyfd message box...");
|
||||
boolean buttonPress = NativeDialogUtil.showDialog("Button pressed!", "UITester dialog", "ok", "info");
|
||||
LOGGER.info("dialog returned with ["+(buttonPress ? "TRUE" : "FALSE")+"]");
|
||||
}
|
||||
|
||||
public static ConfigCategory categoryTest = new ConfigCategory.Builder().set(CategoryTest.class).build();
|
||||
|
||||
@@ -1416,7 +1434,7 @@ public class Config
|
||||
.set(false)
|
||||
// enabling this can be quite detrimental to performance,
|
||||
// so hiding it in the config file should reduce people accidentally enabling it
|
||||
.setAppearance(isRunningInDevEnvironment() ? EConfigEntryAppearance.ALL : EConfigEntryAppearance.ONLY_IN_FILE)
|
||||
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
|
||||
.comment(""
|
||||
+ "Enabling this will drastically increase chunk processing time\n"
|
||||
+ "and you may need to increase your CPU load to handle it.\n"
|
||||
@@ -1597,6 +1615,14 @@ public class Config
|
||||
+ "This can be useful for debugging.")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<EDhApiLoggerLevel> logConnectionConfigChangesToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
|
||||
.setChatCommandName("logging.logConnectionConfigChanges")
|
||||
.set(EDhApiLoggerLevel.WARN)
|
||||
.comment(""
|
||||
+ "If enabled, config changes sent by the server will be logged. \n"
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigCategory warning = new ConfigCategory.Builder().set(Warning.class).build();
|
||||
|
||||
|
||||
@@ -1661,6 +1687,15 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Boolean> logGarbageCollectorWarning = new ConfigEntry.Builder<Boolean>()
|
||||
.set(true)
|
||||
.comment(""
|
||||
+ "If enabled, a message will be logged if the garbage \n"
|
||||
+ "collector Java is currently using is known \n"
|
||||
+ "to cause stutters and/or issues. \n"
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1850,6 +1885,8 @@ public class Config
|
||||
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
||||
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
||||
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
|
||||
|
||||
IgnoredDimensionCsvHandler.INSTANCE.onConfigValueSet();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
+15
-11
@@ -17,23 +17,27 @@
|
||||
* 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
|
||||
* @version 2022-11-25
|
||||
* Used to support deprecated config options that may be identical
|
||||
* in implementation but with the On/Off values flipped.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2025-12-22
|
||||
*/
|
||||
public 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)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
@Override
|
||||
public Boolean convertToApiType(Boolean api)
|
||||
{ return !api; }
|
||||
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.distanthorizons.core.config.eventHandlers;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
|
||||
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
|
||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
|
||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
|
||||
public class IgnoredDimensionCsvHandler extends DhApiBeforeRenderEvent implements IConfigListener
|
||||
{
|
||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||
|
||||
public static IgnoredDimensionCsvHandler INSTANCE = new IgnoredDimensionCsvHandler();
|
||||
|
||||
private String[] dimensionNames = null;
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
/** private since we only ever need one handler at a time */
|
||||
private IgnoredDimensionCsvHandler() { }
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// config handling //
|
||||
//=================//
|
||||
|
||||
@Override
|
||||
public void onConfigValueSet()
|
||||
{
|
||||
String ignoredDimensionCsvString = Config.Client.Advanced.Graphics.Experimental.ignoredDimensionCsv.get();
|
||||
if (ignoredDimensionCsvString == null
|
||||
|| ignoredDimensionCsvString.isEmpty())
|
||||
{
|
||||
LOGGER.info("Dimension ignoring disabled, DH will render all dimensions.");
|
||||
this.dimensionNames = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
this.dimensionNames = ignoredDimensionCsvString.split(",");
|
||||
LOGGER.info("DH set to ignore dimensions: ["+ StringUtil.join(", ", this.dimensionNames)+"].");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Failed to separate ignored dimensions from CSV string, error: ["+e.getMessage()+"].", e);
|
||||
this.dimensionNames = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===================//
|
||||
// external handling //
|
||||
//===================//
|
||||
|
||||
@Override
|
||||
public void beforeRender(DhApiCancelableEventParam<DhApiRenderParam> event)
|
||||
{
|
||||
String dimName = event.value.clientLevelWrapper.getDimensionName();
|
||||
if (IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName))
|
||||
{
|
||||
event.cancelEvent();
|
||||
Config.Client.Advanced.Graphics.Fog.enableVanillaFog.setApiValue(true);
|
||||
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.setApiValue(EDhApiMcRenderingFadeMode.NONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
Config.Client.Advanced.Graphics.Fog.enableVanillaFog.setApiValue(null);
|
||||
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.setApiValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean dimensionNameShouldBeIgnored(String dimName)
|
||||
{
|
||||
if (this.dimensionNames == null
|
||||
|| this.dimensionNames.length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.dimensionNames.length; i++)
|
||||
{
|
||||
String dimNameToIgnore = this.dimensionNames[i];
|
||||
if (dimName.equalsIgnoreCase(dimNameToIgnore))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+6
-1
@@ -35,7 +35,7 @@ public class WorldCurvatureConfigEventHandler implements IConfigListener
|
||||
{
|
||||
public static WorldCurvatureConfigEventHandler INSTANCE = new WorldCurvatureConfigEventHandler();
|
||||
|
||||
private static final int MIN_VALID_CURVE_VALUE = 50;
|
||||
public static final int MIN_VALID_CURVE_VALUE = 50;
|
||||
|
||||
|
||||
/** private since we only ever need one handler at a time */
|
||||
@@ -52,6 +52,11 @@ public class WorldCurvatureConfigEventHandler implements IConfigListener
|
||||
// shouldn't update the UI, otherwise we may end up fighting the user
|
||||
Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(MIN_VALID_CURVE_VALUE);
|
||||
}
|
||||
else if (curveRatio < 0 && curveRatio > -MIN_VALID_CURVE_VALUE)
|
||||
{
|
||||
// same as above, but in the negative direction
|
||||
Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(-MIN_VALID_CURVE_VALUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -121,7 +121,7 @@ public class FullDataSourceV2
|
||||
public Boolean applyToChildren = null;
|
||||
|
||||
/** 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)
|
||||
{
|
||||
FullDataPointIdMap map = new FullDataPointIdMap(pos);
|
||||
return new FullDataSourceV2(
|
||||
pos, new FullDataPointIdMap(pos),
|
||||
pos, map,
|
||||
// data points, genSteps, and columnCompression are all null since
|
||||
// nothing has been generated yet.
|
||||
// Using the default value of all 0's is adequate
|
||||
@@ -1296,7 +1297,7 @@ public class FullDataSourceV2
|
||||
// API methods //
|
||||
//=============//
|
||||
|
||||
public void setRunApiChunkValidation(boolean runValidation) { this.runApiChunkValidation = runValidation; }
|
||||
public void setRunApiSetterValidation(boolean runValidation) { this.runApiSetterValidation = runValidation; }
|
||||
|
||||
@Override
|
||||
public int getWidthInDataColumns() { return WIDTH; }
|
||||
@@ -1308,7 +1309,7 @@ public class FullDataSourceV2
|
||||
try
|
||||
{
|
||||
LodDataBuilder.putListInTopDownOrder(columnDataPoints);
|
||||
if (this.runApiChunkValidation)
|
||||
if (this.runApiSetterValidation)
|
||||
{
|
||||
LodDataBuilder.validateOrThrowApiDataColumn(columnDataPoints);
|
||||
}
|
||||
|
||||
+11
-14
@@ -107,7 +107,7 @@ public class LodBufferContainer implements AutoCloseable
|
||||
|
||||
|
||||
// upload on MC's render thread
|
||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
||||
GLProxy.queueRunningOnRenderThread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -295,24 +295,21 @@ public class LodBufferContainer implements AutoCloseable
|
||||
{
|
||||
this.buffersUploaded = false;
|
||||
|
||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
||||
for (GLVertexBuffer buffer : this.vbos)
|
||||
{
|
||||
for (GLVertexBuffer buffer : this.vbos)
|
||||
if (buffer != null)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
buffer.destroyAsync();
|
||||
}
|
||||
buffer.destroyAsync();
|
||||
}
|
||||
|
||||
for (GLVertexBuffer buffer : this.vbosTransparent)
|
||||
}
|
||||
|
||||
for (GLVertexBuffer buffer : this.vbosTransparent)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
buffer.destroyAsync();
|
||||
}
|
||||
buffer.destroyAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-20
@@ -372,10 +372,10 @@ public class LodQuadBuilder
|
||||
if (quad.direction.axis.isHorizontal() || quad.direction == EDhDirection.DOWN)
|
||||
{
|
||||
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
|
||||
// if we want the color to fade, only apply the dirt color to the bottom vertices
|
||||
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
|
||||
// always render the bottom as dirt
|
||||
|| quad.direction == EDhDirection.DOWN)
|
||||
// if we want the color to fade, only apply the dirt color to the bottom vertices
|
||||
|| (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
|
||||
// always render the bottom as dirt
|
||||
|| quad.direction == EDhDirection.DOWN)
|
||||
{
|
||||
// for horizontal and bottom faces of grass blocks, use the dirt color to
|
||||
// prevent green cliff walls
|
||||
@@ -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)
|
||||
{
|
||||
skylight %= 16;
|
||||
blocklight %= 16;
|
||||
|
||||
bb.putShort(x);
|
||||
bb.putShort(y);
|
||||
bb.putShort(z);
|
||||
|
||||
short meta = 0;
|
||||
meta |= (skylight | (blocklight << 4));
|
||||
byte mirco = 0;
|
||||
// mirco offset which is a xyz 2bit value
|
||||
// 0b00 = no offset
|
||||
// 0b01 = positive offset
|
||||
// 0b11 = negative offset
|
||||
// format is: 0b00zzyyxx
|
||||
if (mx != 0) mirco |= mx > 0 ? 0b01 : 0b11;
|
||||
if (my != 0) mirco |= my > 0 ? 0b0100 : 0b1100;
|
||||
if (mz != 0) mirco |= mz > 0 ? 0b010000 : 0b110000;
|
||||
meta |= mirco << 8;
|
||||
|
||||
{
|
||||
skylight %= 16;
|
||||
blocklight %= 16;
|
||||
meta |= (short) (skylight | (blocklight << 4));
|
||||
|
||||
byte mircoOffset = 0;
|
||||
// mirco offset which is a xyz 2bit value
|
||||
// 0b00 = no offset
|
||||
// 0b01 = positive offset
|
||||
// 0b11 = negative offset
|
||||
// format is: 0b00zzyyxx
|
||||
if (mx != 0) { mircoOffset |= (byte) (mx > 0 ? 0b01 : 0b11); }
|
||||
if (my != 0) { mircoOffset |= (byte) (my > 0 ? 0b0100 : 0b1100); }
|
||||
if (mz != 0) { mircoOffset |= (byte) (mz > 0 ? 0b010000 : 0b110000); }
|
||||
meta |= (short) (mircoOffset << 8);
|
||||
}
|
||||
bb.putShort(meta);
|
||||
|
||||
byte r = (byte) ColorUtil.getRed(color);
|
||||
byte g = (byte) ColorUtil.getGreen(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(g);
|
||||
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
|
||||
memoryDataSource = FullDataSourceV2.createEmpty(inputPos);
|
||||
pair = new DataSourceSavedTimePair(memoryDataSource);
|
||||
this.dataSourceByPosition.put(inputPos, pair);
|
||||
DataSourceSavedTimePair oldPair = this.dataSourceByPosition.put(inputPos, pair);
|
||||
if (oldPair != null)
|
||||
{
|
||||
// shouldn't happen, but just in case
|
||||
this.handleDataSourceRemoval(oldPair.dataSource);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+64
-98
@@ -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.generation.DhLightingEngine;
|
||||
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
@@ -38,6 +38,7 @@ import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
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.threading.PriorityTaskPicker;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
@@ -68,7 +69,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
* TODO this should be dynamically allocated based on CPU load
|
||||
* and abilities.
|
||||
*/
|
||||
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);
|
||||
@@ -85,15 +88,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException
|
||||
{ this(level, saveStructure, null); }
|
||||
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException
|
||||
{
|
||||
super(level, saveStructure, saveDirOverride);
|
||||
|
||||
this.addDataSourceUpdateListener((@NotNull FullDataSourceV2 updatedData) ->
|
||||
{
|
||||
this.onWorldGenTaskComplete(WorldGenResult.CreateSuccess(updatedData.getPos()), null);
|
||||
});
|
||||
|
||||
}
|
||||
{ super(level, saveStructure, saveDirOverride); }
|
||||
|
||||
|
||||
|
||||
@@ -122,32 +117,43 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
// events //
|
||||
//========//
|
||||
|
||||
private void onWorldGenTaskComplete(WorldGenResult genTaskResult, Throwable exception)
|
||||
private void onWorldGenTaskComplete(@NotNull Long genPos, @Nullable DataSourceRetrievalResult genTaskResult, @Nullable Throwable exception)
|
||||
{
|
||||
if (exception != null)
|
||||
try
|
||||
{
|
||||
// don't log shutdown exceptions
|
||||
if (!(exception instanceof CancellationException || exception.getCause() instanceof CancellationException))
|
||||
if (exception != null)
|
||||
{
|
||||
LOGGER.error("Uncaught Gen Task Exception at ["+genTaskResult.pos+"], error: ["+exception.getMessage()+"].", exception);
|
||||
// don't log shutdown exceptions
|
||||
if (!ExceptionUtil.isInterruptOrReject(exception))
|
||||
{
|
||||
LOGGER.error("Uncaught Gen Task Exception at [" + genPos + "], error: [" + exception.getMessage() + "].", exception);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(genTaskResult);
|
||||
if (genTaskResult.state == ERetrievalResultState.SUCCESS)
|
||||
{
|
||||
LodUtil.assertTrue(genTaskResult.dataSource != null, "Successful retrieval object should have a datasource.");
|
||||
|
||||
this.dataUpdater.updateDataSource(genTaskResult.dataSource);
|
||||
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
|
||||
genTaskResult.dataSource.close();
|
||||
}
|
||||
else if (genTaskResult.state == ERetrievalResultState.REQUIRES_SPLITTING)
|
||||
{
|
||||
// task was split
|
||||
LodUtil.assertTrue(genTaskResult.dataSource == null, "Split retrieval object should not have a datasource.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// shouldn't happen, but just in case
|
||||
LOGGER.warn("Unexpected gen Task state at: [" + DhSectionPos.toString(genTaskResult.pos) + "], state: [" + genTaskResult.state + "], datasource: NULL, exception: NULL.");
|
||||
}
|
||||
}
|
||||
else if (genTaskResult.success)
|
||||
catch (Exception e)
|
||||
{
|
||||
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// generation didn't complete
|
||||
LOGGER.debug("Gen Task Failed at " + genTaskResult.pos);
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
public boolean canQueueRetrieval() { return this.canQueueRetrieval(false); }
|
||||
public boolean canQueueRetrieval(boolean pruneWaitingTasksAboveLimit)
|
||||
public boolean canQueueRetrievalNow() { return this.canQueueRetrievalNow(false); }
|
||||
public boolean canQueueRetrievalNow(boolean pruneWaitingTasksAboveLimit)
|
||||
{
|
||||
if (!super.canQueueRetrieval())
|
||||
if (!super.canQueueRetrievalNow())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -270,12 +276,16 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
|
||||
|
||||
int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount();
|
||||
if (availableTaskSlots <= 0)
|
||||
if (availableTaskSlots == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (availableTaskSlots < 0)
|
||||
{
|
||||
if (pruneWaitingTasksAboveLimit)
|
||||
{
|
||||
AtomicInteger tasksToCancel = new AtomicInteger(-availableTaskSlots + 1);
|
||||
worldGenQueue.removeRetrievalRequestIf(x -> tasksToCancel.getAndDecrement() > 0);
|
||||
AtomicInteger tasksToCancel = new AtomicInteger(availableTaskSlots * -1);
|
||||
worldGenQueue.removeRetrievalRequestIf(taskPos -> tasksToCancel.getAndDecrement() > 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -288,7 +298,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorldGenResult> queuePositionForRetrieval(Long genPos)
|
||||
public CompletableFuture<DataSourceRetrievalResult> queuePositionForRetrieval(Long genPos)
|
||||
{
|
||||
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
|
||||
if (worldGenQueue == null)
|
||||
@@ -296,13 +306,8 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
return null;
|
||||
}
|
||||
|
||||
WorldGenTaskTracker genTaskTracker = new WorldGenTaskTracker(genPos);
|
||||
CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTaskTracker);
|
||||
worldGenFuture.whenComplete((genTaskResult, ex) ->
|
||||
{
|
||||
//LOGGER.info("gen task complete ["+DhSectionPos.toString(genPos)+"]");
|
||||
//this.onWorldGenTaskComplete(genTaskResult, ex);
|
||||
});
|
||||
CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
|
||||
worldGenFuture.whenComplete((r, e) -> this.onWorldGenTaskComplete(genPos, r, e));
|
||||
|
||||
return worldGenFuture;
|
||||
}
|
||||
@@ -321,22 +326,20 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
public void clearRetrievalQueue() { this.worldGenQueueRef.set(null); }
|
||||
|
||||
|
||||
public boolean isFullyGenerated(ByteArrayList columnGenerationSteps)
|
||||
public boolean generationStepsAreFullyGenerated(ByteArrayList columnGenerationSteps)
|
||||
{
|
||||
return IntStream.range(0, columnGenerationSteps.size())
|
||||
.noneMatch(i ->
|
||||
{
|
||||
byte value = columnGenerationSteps.getByte(i);
|
||||
return value == EDhApiWorldGenerationStep.EMPTY.value
|
||||
|| value == EDhApiWorldGenerationStep.DOWN_SAMPLED.value;
|
||||
});
|
||||
.noneMatch((int intValue) ->
|
||||
{
|
||||
byte value = columnGenerationSteps.getByte(intValue);
|
||||
return value == EDhApiWorldGenerationStep.EMPTY.value
|
||||
|| value == EDhApiWorldGenerationStep.DOWN_SAMPLED.value;
|
||||
});
|
||||
}
|
||||
|
||||
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Generated Provider");
|
||||
|
||||
|
||||
@Override
|
||||
public LongArrayList getPositionsToRetrieve(Long pos)
|
||||
public LongArrayList getPositionsToRetrieve(long pos)
|
||||
{
|
||||
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
|
||||
if (worldGenQueue == null)
|
||||
@@ -352,7 +355,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
{
|
||||
ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH);
|
||||
this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray);
|
||||
if (!columnGenStepArray.isEmpty())
|
||||
if (columnGenStepArray.size() != 0)
|
||||
{
|
||||
boolean positionFullyGenerated = true;
|
||||
|
||||
@@ -378,12 +381,11 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
|
||||
|
||||
// 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();
|
||||
|
||||
byte lowestGeneratorDetailLevel = (byte) Math.min(
|
||||
worldGenQueue.lowestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL,
|
||||
DhSectionPos.getDetailLevel(pos));
|
||||
worldGenQueue.lowestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL,
|
||||
DhSectionPos.getDetailLevel(pos));
|
||||
|
||||
DhSectionPos.forEachChildAtDetailLevel(pos, lowestGeneratorDetailLevel, (genPos) ->
|
||||
{
|
||||
@@ -471,48 +473,13 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
// 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)
|
||||
{
|
||||
// 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
|
||||
// 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);
|
||||
}
|
||||
@@ -524,7 +491,6 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
|
||||
{
|
||||
boolean shouldDoWorldGen();
|
||||
|
||||
@Nullable
|
||||
DhBlockPos2D getTargetPosForGeneration();
|
||||
|
||||
/** Fired whenever a section has completed generating */
|
||||
|
||||
+17
-5
@@ -23,9 +23,11 @@ import com.google.common.cache.CacheBuilder;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
|
||||
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.level.LodRequestModule;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -74,7 +76,7 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide
|
||||
//==================//
|
||||
|
||||
@Override
|
||||
public boolean canQueueRetrieval() { return this.canQueueRetrieval(true); }
|
||||
public boolean canQueueRetrievalNow() { return this.canQueueRetrievalNow(true); }
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@@ -102,10 +104,20 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide
|
||||
Long timestamp = this.getTimestampForPos(pos);
|
||||
if (timestamp != null)
|
||||
{
|
||||
this.syncOnLoadRequestQueue.submitRequest(pos, timestamp, fullDataSource ->
|
||||
{
|
||||
this.updateDataSourceAsync(fullDataSource).whenComplete((result, throwable) -> fullDataSource.close());
|
||||
});
|
||||
this.syncOnLoadRequestQueue.submitRequest(pos, timestamp)
|
||||
.thenAccept((DataSourceRetrievalResult result) ->
|
||||
{
|
||||
if (result.state == ERetrievalResultState.SUCCESS
|
||||
&& result.dataSource != null)
|
||||
{
|
||||
this.updateDataSourceAsync(result.dataSource)
|
||||
.handle((voidObj, throwable) ->
|
||||
{
|
||||
result.dataSource.close();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return super.get(pos);
|
||||
|
||||
+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.file.fullDatafile.IDataSourceUpdateListenerFunc;
|
||||
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.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
@@ -48,7 +48,6 @@ import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Handles reading/writing {@link FullDataSourceV2}
|
||||
@@ -86,9 +85,9 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
||||
protected final String levelId;
|
||||
|
||||
|
||||
private final FullDataUpdaterV2 dataUpdater;
|
||||
private final FullDataUpdatePropagatorV2 updatePropagator;
|
||||
private final DataMigratorV1 dataMigratorV1;
|
||||
protected final FullDataUpdaterV2 dataUpdater;
|
||||
protected final FullDataUpdatePropagatorV2 updatePropagator;
|
||||
protected final DataMigratorV1 dataMigratorV1;
|
||||
|
||||
|
||||
|
||||
@@ -201,9 +200,10 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
||||
return FullDataSourceV2.createEmpty(pos);
|
||||
}
|
||||
|
||||
FullDataSourceV2 dataSource = null;
|
||||
try
|
||||
{
|
||||
FullDataSourceV2 dataSource = this.createDataSourceFromDto(dto);
|
||||
dataSource = this.createDataSourceFromDto(dto);
|
||||
|
||||
// automatically create and save adjacent data if missing
|
||||
if (dto.dataFormatVersion == FullDataSourceV2DTO.DATA_FORMAT.V1_NO_ADJACENT_DATA)
|
||||
@@ -222,6 +222,15 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
||||
this.tryLogCorruptedDataError(DhSectionPos.toString(pos), e);
|
||||
this.repo.deleteWithKey(pos);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (dataSource != null)
|
||||
{
|
||||
dataSource.close();
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ignore) { }
|
||||
catch (IOException e)
|
||||
@@ -243,6 +252,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
||||
catch (Exception e)
|
||||
{
|
||||
String message = e.getMessage();
|
||||
if (message == null)
|
||||
{
|
||||
message = "NULL";
|
||||
}
|
||||
|
||||
if (CORRUPT_DATA_ERRORS_LOGGED.add(message))
|
||||
{
|
||||
LOGGER.warn("Unexpected error getting: [" + DhSectionPos.toString(pos) + "], this error message will only be logged once, error: [" + message + "].", e);
|
||||
@@ -360,7 +374,7 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
|
||||
* to the beginning of your override.
|
||||
* Otherwise, parent retrieval limits will be ignored.
|
||||
*/
|
||||
public boolean canQueueRetrieval()
|
||||
public boolean canQueueRetrievalNow()
|
||||
{
|
||||
// Retrieval shouldn't happen while an unknown number of
|
||||
// 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
|
||||
*/
|
||||
@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
|
||||
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 */
|
||||
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { }
|
||||
|
||||
+1
-1
@@ -185,7 +185,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
|
||||
parentLocked = true;
|
||||
this.dataUpdater.lockedPosSet.add(parentUpdatePos);
|
||||
|
||||
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos)) // TODO can we cache anything in memory to speed up the propagation process? Compression/Disk IO is by far the slowest part of this process
|
||||
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos))
|
||||
{
|
||||
// will return null if the file handler is shutting down
|
||||
if (parentDataSource != null)
|
||||
|
||||
+134
-83
@@ -118,15 +118,19 @@ public class DhLightingEngine
|
||||
* @param centerChunk the chunk we want to apply lighting to
|
||||
* @param nearbyChunkList should also contain centerChunk
|
||||
* @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,
|
||||
int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight)
|
||||
{
|
||||
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
|
||||
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
|
||||
StableLightPosStack blockLightWorldPosQueue = null;
|
||||
StableLightPosStack skyLightWorldPosQueue = null;
|
||||
@@ -245,13 +249,15 @@ public class DhLightingEngine
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// block light
|
||||
if (updateBlockLight)
|
||||
{
|
||||
// done to prevent a rare issue where the light values are incorrectly set to -1
|
||||
centerChunk.clearDhBlockLighting();
|
||||
|
||||
this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
|
||||
posIterations += this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
|
||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
|
||||
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
|
||||
true);
|
||||
@@ -262,7 +268,7 @@ public class DhLightingEngine
|
||||
{
|
||||
centerChunk.clearDhSkyLighting();
|
||||
|
||||
this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
|
||||
posIterations += this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
|
||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
|
||||
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
|
||||
false);
|
||||
@@ -287,10 +293,12 @@ public class DhLightingEngine
|
||||
{
|
||||
centerChunk.setIsDhSkyLightCorrect(true);
|
||||
}
|
||||
|
||||
return posIterations;
|
||||
}
|
||||
|
||||
/** 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,
|
||||
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc,
|
||||
boolean propagatingBlockLights)
|
||||
@@ -320,66 +328,89 @@ public class DhLightingEngine
|
||||
IBlockStateWrapper previousBlockState = null;
|
||||
|
||||
|
||||
// update each light position
|
||||
while (!lightPosQueue.isEmpty())
|
||||
int iterations = 0;
|
||||
|
||||
// update each light level
|
||||
for (int currentLightLevel = LodUtil.MAX_MC_LIGHT; currentLightLevel >= LodUtil.MIN_MC_LIGHT; currentLightLevel--)
|
||||
{
|
||||
// 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)
|
||||
lightPosQueue.popMutate(lightPos);
|
||||
// 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.
|
||||
|
||||
int lightValue = lightPos.lightValue;
|
||||
lightPos.lightValue = currentLightLevel;
|
||||
|
||||
|
||||
// propagate the lighting in each cardinal direction, IE: -x, +x, -y, +y, -z, +z
|
||||
for (EDhDirection direction : EDhDirection.ALL) // since this is an array instead of an ArrayList this advanced for-loop shouldn't cause any GC issues
|
||||
// update each light position
|
||||
while (!lightPosQueue.isLightLevelEmpty(currentLightLevel))
|
||||
{
|
||||
lightPos.mutateOffset(direction, neighbourBlockPos);
|
||||
neighbourBlockPos.mutateToChunkRelativePos(relNeighbourBlockPos);
|
||||
// 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)
|
||||
lightPosQueue.popMutate(lightPos, currentLightLevel);
|
||||
iterations++;
|
||||
|
||||
int lightValue = lightPos.lightValue;
|
||||
|
||||
|
||||
// only continue if the light position is inside one of our chunks
|
||||
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.getX(), neighbourBlockPos.getZ());
|
||||
if (neighbourChunk == null)
|
||||
// propagate the lighting in each cardinal direction, IE: -x, +x, -y, +y, -z, +z
|
||||
for (EDhDirection direction : EDhDirection.ALL) // since this is an array instead of an ArrayList this advanced for-loop shouldn't cause any GC issues
|
||||
{
|
||||
// the light pos is outside our generator's range, ignore it
|
||||
continue;
|
||||
}
|
||||
|
||||
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight()
|
||||
|| relNeighbourBlockPos.getY() >= neighbourChunk.getExclusiveMaxBuildHeight())
|
||||
{
|
||||
// the light pos is outside the chunk's min/max height,
|
||||
// this can happen if given a chunk that hasn't finished generating
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int currentBlockLight = getLightFunc.getLight(neighbourChunk, relNeighbourBlockPos);
|
||||
if (currentBlockLight >= (lightValue - 1))
|
||||
{
|
||||
// short circuit for when the light value at this position
|
||||
// is already greater-than what we could set it
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
IBlockStateWrapper neighbourBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState);
|
||||
previousBlockState = neighbourBlockState;
|
||||
|
||||
// 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());
|
||||
if (targetLevel > currentBlockLight)
|
||||
{
|
||||
// this position is darker than the new light value, update/set it
|
||||
setLightFunc.setLight(neighbourChunk, relNeighbourBlockPos, targetLevel);
|
||||
lightPos.mutateOffset(direction, neighbourBlockPos);
|
||||
neighbourBlockPos.mutateToChunkRelativePos(relNeighbourBlockPos);
|
||||
|
||||
// now that light has been propagated to this blockPos
|
||||
// we need to queue it up so its neighbours can be propagated as well
|
||||
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLevel);
|
||||
|
||||
// only continue if the light position is inside one of our chunks
|
||||
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.getX(), neighbourBlockPos.getZ());
|
||||
if (neighbourChunk == null)
|
||||
{
|
||||
// the light pos is outside our generator's range, ignore it
|
||||
continue;
|
||||
}
|
||||
|
||||
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight()
|
||||
|| relNeighbourBlockPos.getY() >= neighbourChunk.getExclusiveMaxBuildHeight())
|
||||
{
|
||||
// the light pos is outside the chunk's min/max height,
|
||||
// this can happen if given a chunk that hasn't finished generating
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int currentBlockLight = getLightFunc.getLight(neighbourChunk, relNeighbourBlockPos);
|
||||
if (currentBlockLight >= (lightValue - 1))
|
||||
{
|
||||
// short circuit for when the light value at this position
|
||||
// is already greater-than what we could set it
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
IBlockStateWrapper neighbourBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos, mcBlockPos, previousBlockState);
|
||||
previousBlockState = neighbourBlockState;
|
||||
|
||||
// Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles.
|
||||
int targetLightLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
|
||||
if (targetLightLevel > currentBlockLight)
|
||||
{
|
||||
// this position is darker than the new light value, update/set it
|
||||
setLightFunc.setLight(neighbourChunk, relNeighbourBlockPos, targetLightLevel);
|
||||
|
||||
// now that light has been propagated to this blockPos
|
||||
// we need to queue it up so its neighbours can be propagated as well
|
||||
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
|
||||
if (RENDER_BLOCK_LIGHT_WIREFRAME
|
||||
@@ -395,6 +426,7 @@ public class DhLightingEngine
|
||||
|
||||
|
||||
// propagation complete
|
||||
return iterations;
|
||||
}
|
||||
|
||||
|
||||
@@ -748,16 +780,24 @@ public class DhLightingEngine
|
||||
private static final Queue<StableLightPosStack> lightArrayCache = new ArrayDeque<>();
|
||||
|
||||
/** 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. */
|
||||
public static final int INTS_PER_LIGHT_POS = 4;
|
||||
/** x, y, z */
|
||||
public static final int INTS_PER_LIGHT_POS = 3;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private final IntArrayList lightPositions = new IntArrayList(40_000 * INTS_PER_LIGHT_POS);
|
||||
private final IntArrayList[] lightPositionsByLightLevel = new IntArrayList[LodUtil.MAX_MC_LIGHT + 1];
|
||||
|
||||
|
||||
public StableLightPosStack()
|
||||
{
|
||||
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 //
|
||||
//===============//
|
||||
|
||||
public boolean isEmpty() { return this.index == -1; }
|
||||
public int size() { return this.index+1; }
|
||||
public boolean isLightLevelEmpty(int lightLevel) { return this.indexByLightLevel[lightLevel] == -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++;
|
||||
int subIndex = this.index * INTS_PER_LIGHT_POS;
|
||||
if (subIndex < this.lightPositions.size())
|
||||
IntArrayList lightPositions = this.lightPositionsByLightLevel[lightLevel];
|
||||
|
||||
this.indexByLightLevel[lightLevel]++;
|
||||
int subIndex = this.indexByLightLevel[lightLevel] * INTS_PER_LIGHT_POS;
|
||||
if (subIndex < lightPositions.size())
|
||||
{
|
||||
this.lightPositions.set(subIndex, blockX);
|
||||
this.lightPositions.set(subIndex + 1, blockY);
|
||||
this.lightPositions.set(subIndex + 2, blockZ);
|
||||
this.lightPositions.set(subIndex + 3, lightValue);
|
||||
lightPositions.set(subIndex, blockX);
|
||||
lightPositions.set(subIndex + 1, blockY);
|
||||
lightPositions.set(subIndex + 2, blockZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a new pos
|
||||
this.lightPositions.add(blockX);
|
||||
this.lightPositions.add(blockY);
|
||||
this.lightPositions.add(blockZ);
|
||||
this.lightPositions.add(lightValue);
|
||||
lightPositions.add(blockX);
|
||||
lightPositions.add(blockY);
|
||||
lightPositions.add(blockZ);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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.setY(this.lightPositions.getInt(subIndex + 1));
|
||||
pos.setZ(this.lightPositions.getInt(subIndex + 2));
|
||||
pos.lightValue = this.lightPositions.getInt(subIndex + 3);
|
||||
pos.setX(lightPositions.getInt(subIndex));
|
||||
pos.setY(lightPositions.getInt(subIndex + 1));
|
||||
pos.setZ(lightPositions.getInt(subIndex + 2));
|
||||
|
||||
this.index--;
|
||||
this.indexByLightLevel[lightLevel]--;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.render.LodQuadTree;
|
||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.util.List;
|
||||
@@ -92,7 +90,7 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
|
||||
*/
|
||||
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.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.util.FormatUtil;
|
||||
@@ -148,20 +150,22 @@ public class PregenManager
|
||||
this.fullDataSourceProvider.getAsync(nextSectionPos)
|
||||
.thenAccept(fullDataSource ->
|
||||
{
|
||||
if (this.fullDataSourceProvider.isFullyGenerated(fullDataSource.columnGenerationSteps))
|
||||
if (this.fullDataSourceProvider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps))
|
||||
{
|
||||
this.pendingGenerations.invalidate(fullDataSource.getPos());
|
||||
}
|
||||
else
|
||||
{
|
||||
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos()).thenAccept(result -> {
|
||||
if (!result.success)
|
||||
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos())
|
||||
.whenComplete((DataSourceRetrievalResult result, Throwable throwable) ->
|
||||
{
|
||||
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
|
||||
}
|
||||
|
||||
this.pendingGenerations.invalidate(result.pos);
|
||||
});
|
||||
if (throwable != null)
|
||||
{
|
||||
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
|
||||
}
|
||||
|
||||
this.pendingGenerations.invalidate(result.pos);
|
||||
});
|
||||
}
|
||||
|
||||
fullDataSource.close();
|
||||
|
||||
+27
-46
@@ -1,8 +1,8 @@
|
||||
package com.seibel.distanthorizons.core.generation;
|
||||
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.multiplayer.client.AbstractFullDataNetworkRequestQueue;
|
||||
@@ -14,10 +14,8 @@ import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.WorldGenUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
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; }
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorldGenResult> submitRetrievalTask(long sectionPos, byte requiredDataDetail, IWorldGenTaskTracker tracker)
|
||||
public CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long sectionPos, byte requiredDataDetail)
|
||||
{
|
||||
long generationStartMsTime = System.currentTimeMillis();
|
||||
|
||||
|
||||
return super.submitRequest(sectionPos, fullDataSource -> {
|
||||
Objects.requireNonNull(tracker.getDataSourceConsumer()).accept(fullDataSource);
|
||||
fullDataSource.close();
|
||||
})
|
||||
.thenApply(requestResult ->
|
||||
{
|
||||
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
|
||||
|
||||
int chunkWidth = DhSectionPos.getChunkWidth(sectionPos);
|
||||
int chunkCount = chunkWidth * chunkWidth;
|
||||
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
|
||||
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 WorldGenResult.CreateSplit(childFutures);
|
||||
}
|
||||
|
||||
LodUtil.assertNotReach("Unexpected and unhandled request response result: ["+requestResult+"]");
|
||||
return WorldGenResult.CreateFail();
|
||||
});
|
||||
CompletableFuture<DataSourceRetrievalResult> future = super.submitRequest(sectionPos, /* client timestamp */null);
|
||||
future.thenAccept((DataSourceRetrievalResult result) ->
|
||||
{
|
||||
if (result.state == ERetrievalResultState.SUCCESS)
|
||||
{
|
||||
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
|
||||
|
||||
int chunkWidth = DhSectionPos.getChunkWidth(sectionPos);
|
||||
int chunkCount = chunkWidth * chunkWidth;
|
||||
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);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,7 +89,7 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
|
||||
@Override
|
||||
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getGenerationRequestRateLimit(); }
|
||||
@Override
|
||||
protected boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos)
|
||||
protected boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos)
|
||||
{
|
||||
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;
|
||||
}
|
||||
@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
|
||||
&& !Config.Server.Experimental.enableNSizedGeneration.get())
|
||||
// split up large requests if N-sized gen isn't enabled
|
||||
if (!Config.Server.Experimental.enableNSizedGeneration.get()
|
||||
&& DhSectionPos.getDetailLevel(sectionPos) > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
|
||||
{
|
||||
future.complete(ERequestResult.REQUIRES_SPLITTING);
|
||||
future.complete(DataSourceRetrievalResult.CreateSplit());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+338
-276
@@ -26,11 +26,8 @@ import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
|
||||
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.InProgressWorldGenTaskGroup;
|
||||
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.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalTask;
|
||||
import com.seibel.distanthorizons.core.level.IDhServerLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
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.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
|
||||
{
|
||||
@@ -71,9 +66,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
private final IDhServerLevel level;
|
||||
|
||||
/** contains the positions that need to be generated */
|
||||
private final ConcurrentHashMap<Long, WorldGenTask> waitingTasks = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentHashMap<Long, InProgressWorldGenTaskGroup> inProgressGenTasksByLodPos = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, DataSourceRetrievalTask> waitingTasks = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, DataSourceRetrievalTask> inProgressGenTasksByLodPos = new ConcurrentHashMap<>();
|
||||
|
||||
/** largest numerical detail level allowed */
|
||||
public final byte lowestDataDetail;
|
||||
@@ -98,13 +92,14 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
private int estimatedRemainingChunkCount = 0;
|
||||
|
||||
private final RollingAverage rollingAverageChunkGenTimeInMs = new RollingAverage(Runtime.getRuntime().availableProcessors() * 500);
|
||||
public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
|
||||
@Override public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
//=============//
|
||||
// constructor //
|
||||
//=============//
|
||||
///region constructor
|
||||
|
||||
public WorldGenerationQueue(IDhApiWorldGenerator generator, IDhServerLevel level)
|
||||
{
|
||||
@@ -118,20 +113,31 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
LOGGER.info("Created world gen queue");
|
||||
}
|
||||
|
||||
///endregion constructor
|
||||
|
||||
|
||||
//=================//
|
||||
// world generator //
|
||||
// task handling //
|
||||
//=================//
|
||||
|
||||
//===============//
|
||||
// task handling //
|
||||
//===============//
|
||||
///region task handling
|
||||
|
||||
@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
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
CompletableFuture<WorldGenResult> future = new CompletableFuture<>();
|
||||
this.waitingTasks.put(pos, new WorldGenTask(pos, requiredDataDetail, tracker, future));
|
||||
return future;
|
||||
DataSourceRetrievalTask genTask = new DataSourceRetrievalTask(pos, requiredDataDetail);
|
||||
this.waitingTasks.put(pos, genTask);
|
||||
return genTask.future;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -161,11 +166,17 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
{
|
||||
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,
|
||||
entry -> new TaskDistancePair(entry.getValue(), DhSectionPos.getSectionBBoxPos(entry.getValue().pos).getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())),
|
||||
(TaskDistancePair aTaskPair, TaskDistancePair bTaskPair) -> (aTaskPair.dist < bTaskPair.dist) ? aTaskPair : bTaskPair);
|
||||
// get the target distance for each task
|
||||
(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)
|
||||
{
|
||||
// FIXME concurrency issue
|
||||
// the waitingTasks was modified while this check was running
|
||||
return false;
|
||||
}
|
||||
|
||||
WorldGenTask closestTask = closestTaskPair.task;
|
||||
DataSourceRetrievalTask closestTask = closestTaskPair.task;
|
||||
|
||||
// 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);
|
||||
@@ -269,76 +289,69 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
{
|
||||
// detail level is correct for generation, start generation
|
||||
|
||||
WorldGenTaskGroup closestTaskGroup = new WorldGenTaskGroup(closestTask.pos, (byte)(closestTask.pos - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
|
||||
closestTaskGroup.worldGenTasks.add(closestTask);
|
||||
|
||||
if (!this.inProgressGenTasksByLodPos.containsKey(closestTask.pos))
|
||||
DataSourceRetrievalTask existingTask = this.inProgressGenTasksByLodPos.get(closestTask.pos);
|
||||
if (existingTask == null)
|
||||
{
|
||||
// no task exists for this position, start one
|
||||
InProgressWorldGenTaskGroup newTaskGroup = new InProgressWorldGenTaskGroup(closestTaskGroup);
|
||||
this.startWorldGenTaskGroup(newTaskGroup);
|
||||
this.startWorldGenTaskGroup(closestTask);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO replace the previous inProgress task if one exists
|
||||
// Note: Due to concurrency reasons, even if the currently running task is compatible with
|
||||
// the newly selected task, we cannot use it,
|
||||
// as some chunks may have already been written into.
|
||||
// shouldn't normally happen, but if
|
||||
// we somehow queued the same task twice:
|
||||
// merge the two futures so they both complete
|
||||
|
||||
//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
|
||||
{
|
||||
// detail level is too high (if the detail level was too low, the generator would've ignored the request),
|
||||
// split up the task
|
||||
|
||||
|
||||
// split up the task and add each 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);
|
||||
this.waitingTasks.put(newGenTask.pos, newGenTask);
|
||||
});
|
||||
|
||||
// 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;
|
||||
closestTask.future.complete(DataSourceRetrievalResult.CreateSplit());
|
||||
}
|
||||
}
|
||||
private void startWorldGenTaskGroup(InProgressWorldGenTaskGroup newTaskGroup)
|
||||
{
|
||||
byte taskDetailLevel = newTaskGroup.group.dataDetail;
|
||||
long taskPos = newTaskGroup.group.pos;
|
||||
LodUtil.assertTrue(taskDetailLevel >= this.highestDataDetail && taskDetailLevel <= this.lowestDataDetail);
|
||||
|
||||
int generationRequestChunkWidthCount = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(taskPos) - taskDetailLevel - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale
|
||||
|
||||
// a task has been started or queued,
|
||||
// queue another task
|
||||
return true;
|
||||
}
|
||||
private boolean canGenerateDetailLevel(byte taskDetailLevel)
|
||||
{
|
||||
byte requestedDetailLevel = (byte) (taskDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||
return (this.highestDataDetail <= requestedDetailLevel && requestedDetailLevel <= this.lowestDataDetail);
|
||||
}
|
||||
private void startWorldGenTaskGroup(DataSourceRetrievalTask worldGenTask)
|
||||
{
|
||||
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();
|
||||
CompletableFuture<Void> generationFuture = this.startGenerationEvent(taskPos, taskDetailLevel, generationRequestChunkWidthCount, newTaskGroup.group::consumeDataSource);
|
||||
CompletableFuture<FullDataSourceV2> generationFuture = this.startGenerationEvent(worldGenTask);
|
||||
|
||||
// calculate generation speed
|
||||
generationFuture.thenRun(() ->
|
||||
{
|
||||
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
|
||||
int chunkCount = generationRequestChunkWidthCount * generationRequestChunkWidthCount;
|
||||
int chunkCount = worldGenTask.widthInChunks * worldGenTask.widthInChunks;
|
||||
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
|
||||
this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
|
||||
});
|
||||
|
||||
newTaskGroup.genFuture = generationFuture;
|
||||
LodUtil.assertTrue(newTaskGroup.genFuture != null);
|
||||
|
||||
newTaskGroup.genFuture.whenComplete((voidObj, exception) ->
|
||||
generationFuture.handle((FullDataSourceV2 fullDataSource, Throwable exception) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -350,157 +363,51 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e);
|
||||
worldGenTask.future.completeExceptionally(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.tryQueueNewWorldGenRequestsAsync();
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
this.inProgressGenTasksByLodPos.put(taskPos, newTaskGroup);
|
||||
}
|
||||
private CompletableFuture<Void> startGenerationEvent(
|
||||
long requestPos,
|
||||
byte targetDataDetail,
|
||||
int generationRequestChunkWidthCount,
|
||||
Consumer<FullDataSourceV2> dataSourceConsumer
|
||||
)
|
||||
private CompletableFuture<FullDataSourceV2> startGenerationEvent(DataSourceRetrievalTask task)
|
||||
{
|
||||
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();
|
||||
EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType();
|
||||
switch (returnType)
|
||||
{
|
||||
case VANILLA_CHUNKS:
|
||||
{
|
||||
return this.generator.generateChunks(
|
||||
chunkPosMin.getX(), chunkPosMin.getZ(),
|
||||
generationRequestChunkWidthCount,
|
||||
targetDataDetail,
|
||||
generatorMode,
|
||||
ThreadPoolUtil.getWorldGenExecutor(),
|
||||
(Object[] generatedObjectArray) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
|
||||
|
||||
// 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)
|
||||
{
|
||||
LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected world generator error. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
{
|
||||
return this.startVanillaChunkGenerationEvent(task, chunkPosMin, generatorMode);
|
||||
}
|
||||
case API_CHUNKS:
|
||||
{
|
||||
return this.generator.generateApiChunks(
|
||||
chunkPosMin.getX(), chunkPosMin.getZ(),
|
||||
generationRequestChunkWidthCount,
|
||||
targetDataDetail,
|
||||
generatorMode,
|
||||
ThreadPoolUtil.getWorldGenExecutor(),
|
||||
(DhApiChunk dataPoints) ->
|
||||
{
|
||||
try(FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints, this.generator.runApiValidation()))
|
||||
{
|
||||
dataSourceConsumer.accept(dataSource);
|
||||
}
|
||||
catch (DataCorruptedException | IllegalArgumentException e)
|
||||
{
|
||||
LOGGER.error("World generator returned a corrupt 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);
|
||||
}
|
||||
}
|
||||
);
|
||||
return this.startApiChunkGenerationEvent(task, chunkPosMin, generatorMode);
|
||||
}
|
||||
case API_DATA_SOURCES:
|
||||
{
|
||||
// done to reduce GC overhead
|
||||
FullDataSourceV2 pooledDataSource = FullDataSourceV2.createEmpty(requestPos);
|
||||
// set here so the API user doesn't have to pass in this value anywhere themselves
|
||||
pooledDataSource.setRunApiChunkValidation(this.generator.runApiValidation());
|
||||
|
||||
// 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.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12;
|
||||
|
||||
|
||||
return this.generator.generateLod(
|
||||
chunkPosMin.getX(), chunkPosMin.getZ(),
|
||||
DhSectionPos.getX(requestPos), DhSectionPos.getZ(requestPos),
|
||||
(byte) (DhSectionPos.getDetailLevel(requestPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL),
|
||||
pooledDataSource,
|
||||
generatorMode,
|
||||
ThreadPoolUtil.getWorldGenExecutor(),
|
||||
(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);
|
||||
}
|
||||
}
|
||||
);
|
||||
return this.startApiDataSourceGenerationEvent(task, chunkPosMin, generatorMode);
|
||||
}
|
||||
default:
|
||||
{
|
||||
@@ -509,30 +416,181 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
}
|
||||
}
|
||||
}
|
||||
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(),
|
||||
task.widthInChunks,
|
||||
task.requestDetailLevel,
|
||||
generatorMode,
|
||||
ThreadPoolUtil.getWorldGenExecutor(),
|
||||
(Object[] generatedObjectArray) ->
|
||||
{
|
||||
try
|
||||
{
|
||||
IChunkWrapper chunkWrapper = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray);
|
||||
generatedChunks.add(chunkWrapper);
|
||||
}
|
||||
catch (ClassCastException e)
|
||||
{
|
||||
LOGGER.error("World generator return type incorrect. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected world generator error. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
chunkGenFuture.exceptionally((throwable) ->
|
||||
{
|
||||
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(),
|
||||
task.widthInChunks,
|
||||
task.requestDetailLevel,
|
||||
generatorMode,
|
||||
ThreadPoolUtil.getWorldGenExecutor(),
|
||||
(DhApiChunk apiChunk) -> { generatedChunks.add(apiChunk); }
|
||||
);
|
||||
|
||||
|
||||
chunkGenFuture.exceptionally((throwable) ->
|
||||
{
|
||||
returnFuture.completeExceptionally(throwable);
|
||||
return null;
|
||||
});
|
||||
chunkGenFuture.thenRun(() ->
|
||||
{
|
||||
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)
|
||||
{
|
||||
LOGGER.error("World generator returned a corrupt API chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
||||
Config.Common.WorldGenerator.enableDistantGeneration.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
returnFuture.complete(requestedDataSource);
|
||||
});
|
||||
|
||||
return returnFuture;
|
||||
}
|
||||
private CompletableFuture<FullDataSourceV2> startApiDataSourceGenerationEvent(
|
||||
DataSourceRetrievalTask task, DhChunkPos chunkPosMin, EDhApiDistantGeneratorMode generatorMode)
|
||||
{
|
||||
final CompletableFuture<FullDataSourceV2> returnFuture = new CompletableFuture<>();
|
||||
|
||||
|
||||
// done to reduce GC overhead
|
||||
FullDataSourceV2 pooledDataSource = FullDataSourceV2.createEmpty(task.pos);
|
||||
// set here so the API user doesn't have to pass in this value anywhere themselves
|
||||
pooledDataSource.setRunApiSetterValidation(this.generator.runApiValidation());
|
||||
|
||||
// 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.applyToParent = DhSectionPos.getDetailLevel(pooledDataSource.getPos()) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12; // TODO what does this 12 reference?
|
||||
|
||||
CompletableFuture<Void> lodGenFuture = this.generator.generateLod(
|
||||
chunkPosMin.getX(), chunkPosMin.getZ(),
|
||||
DhSectionPos.getX(task.pos), DhSectionPos.getZ(task.pos),
|
||||
(byte) (DhSectionPos.getDetailLevel(task.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL),
|
||||
pooledDataSource,
|
||||
generatorMode,
|
||||
ThreadPoolUtil.getWorldGenExecutor(),
|
||||
(IDhApiFullDataSource apiDataSource) -> { }
|
||||
);
|
||||
|
||||
|
||||
lodGenFuture.exceptionally((throwable) ->
|
||||
{
|
||||
returnFuture.completeExceptionally(throwable);
|
||||
pooledDataSource.close();
|
||||
return null;
|
||||
});
|
||||
lodGenFuture.thenRun(() ->
|
||||
{
|
||||
returnFuture.complete(pooledDataSource);
|
||||
});
|
||||
|
||||
return returnFuture;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//===================//
|
||||
// getters / setters //
|
||||
//===================//
|
||||
///region getters/setters
|
||||
|
||||
@Override public int getWaitingTaskCount() { return this.waitingTasks.size(); }
|
||||
@Override public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); }
|
||||
|
||||
@Override
|
||||
public byte lowestDataDetail() { return this.lowestDataDetail; }
|
||||
@Override
|
||||
public byte highestDataDetail() { return this.highestDataDetail; }
|
||||
@Override public byte lowestDataDetail() { return this.lowestDataDetail; }
|
||||
@Override public byte highestDataDetail() { return this.highestDataDetail; }
|
||||
|
||||
@Override
|
||||
public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; }
|
||||
@Override
|
||||
public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; }
|
||||
@Override public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; }
|
||||
@Override public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; }
|
||||
|
||||
@Override
|
||||
public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; }
|
||||
@Override
|
||||
public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; }
|
||||
@Override public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; }
|
||||
@Override public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; }
|
||||
|
||||
@Override
|
||||
public void addDebugMenuStringsToList(List<String> messageList) { }
|
||||
@@ -550,13 +608,55 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
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 //
|
||||
//==========//
|
||||
///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");
|
||||
this.queueingThread.shutdownNow();
|
||||
@@ -564,33 +664,32 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
|
||||
// stop and remove any in progress tasks
|
||||
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
|
||||
if (genFuture == null)
|
||||
{
|
||||
// genFuture's shouldn't be null, but sometimes they are...
|
||||
LOGGER.info("Null gen future: "+runningTaskGroup.group.pos);
|
||||
return;
|
||||
}
|
||||
|
||||
CompletableFuture<DataSourceRetrievalResult> genFuture = genTask.future;
|
||||
|
||||
if (cancelCurrentGeneration)
|
||||
{
|
||||
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)
|
||||
&& !(exception instanceof CancellationException))
|
||||
if (!UncheckedInterruptedException.isInterrupt(throwable)
|
||||
&& !(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;
|
||||
@@ -623,7 +722,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
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));
|
||||
|
||||
|
||||
@@ -644,63 +743,22 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
LOGGER.info("Finished closing " + WorldGenerationQueue.class.getSimpleName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=======//
|
||||
// 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);
|
||||
}
|
||||
///endregion shutdown
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
///region helper classes
|
||||
|
||||
/** Used during task starting to determine the closest task */
|
||||
private static class TaskDistancePair
|
||||
{
|
||||
public final WorldGenTask task;
|
||||
public final DataSourceRetrievalTask task;
|
||||
public final int dist;
|
||||
|
||||
public TaskDistancePair(WorldGenTask task, int dist)
|
||||
public TaskDistancePair(DataSourceRetrievalTask task, int dist)
|
||||
{
|
||||
this.task = task;
|
||||
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;
|
||||
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author Leetom
|
||||
* @version 2022-11-25
|
||||
* @see DataSourceRetrievalTask
|
||||
*/
|
||||
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
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Leetom
|
||||
* @version 2022-11-25
|
||||
* @see DataSourceRetrievalResult
|
||||
*/
|
||||
public final class WorldGenTask
|
||||
public final class DataSourceRetrievalTask
|
||||
{
|
||||
public final long pos;
|
||||
public final byte dataDetailLevel;
|
||||
public final IWorldGenTaskTracker taskTracker;
|
||||
public final CompletableFuture<WorldGenResult> future;
|
||||
/**
|
||||
* Usually the same as {@link DataSourceRetrievalTask#pos}, but
|
||||
* 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.taskTracker = taskTracker;
|
||||
this.future = future;
|
||||
this.requestDetailLevel = dataDetail;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||
import com.seibel.distanthorizons.core.sql.repo.FullDataSourceV2Repo;
|
||||
import com.seibel.distanthorizons.core.util.NativeDialogUtil;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.core.jar.gui.BaseJFrame;
|
||||
import com.seibel.distanthorizons.core.jar.gui.cusomJObject.JBox;
|
||||
@@ -31,7 +32,6 @@ import com.seibel.distanthorizons.core.jar.installer.WebDownloader;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.lwjgl.util.tinyfd.TinyFileDialogs;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -441,7 +441,7 @@ public class JarMain
|
||||
installMod.addActionListener(e -> {
|
||||
if (minecraftDirPop.getSelectedFile() == null)
|
||||
{
|
||||
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, "Please select your install directory", "ok", "warning", false);
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, "Please select your install directory", "ok", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -455,11 +455,11 @@ public class JarMain
|
||||
ModInfo.NAME + "-" + ModrinthGetter.releaseNames.get(downloadID.get()) + ".jar"
|
||||
).toFile());
|
||||
|
||||
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, "Installation done. \nYou can now close the installer", "ok", "info", false);
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, "Installation done. \nYou can now close the installer", "ok", "info");
|
||||
}
|
||||
catch (Exception f)
|
||||
{
|
||||
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, "Download failed. Check your internet connection \nStacktrace: " + f.getMessage(), "error", "info", false);
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, "Download failed. Check your internet connection \nStacktrace: " + f.getMessage(), "error", "info");
|
||||
}
|
||||
});
|
||||
frame.add(installMod);
|
||||
|
||||
@@ -104,7 +104,7 @@ public class JarUtils
|
||||
*/
|
||||
public static InputStream accessFile(String resource)
|
||||
{
|
||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
final ClassLoader loader = JarUtils.class.getClassLoader();
|
||||
// this is the path within the jar file
|
||||
InputStream input = loader.getResourceAsStream(resource);
|
||||
if (input == null)
|
||||
|
||||
@@ -29,12 +29,12 @@ import com.seibel.distanthorizons.core.jar.installer.ModrinthGetter;
|
||||
import com.seibel.distanthorizons.core.jar.installer.WebDownloader;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
import com.seibel.distanthorizons.core.util.NativeDialogUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
import com.seibel.distanthorizons.coreapi.util.jar.DeleteOnUnlock;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.lwjgl.util.tinyfd.TinyFileDialogs;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -174,7 +174,7 @@ public class SelfUpdater
|
||||
|
||||
if (!GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).containsKey(mcVersion))
|
||||
{
|
||||
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Gitlab, findable versions are ["+ StringUtil.join(",", GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).keySet().toArray()) +"].");
|
||||
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Gitlab, findable versions are ["+ StringUtil.join(", ", GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).keySet().toArray()) +"].");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -258,14 +258,13 @@ public class SelfUpdater
|
||||
|
||||
deleteOldJarOnJvmShutdown = true;
|
||||
|
||||
// TODO one of these messages contains something TinyFd doesn't like, find it and fix it
|
||||
String successMessage = "Distant Horizons successfully updated. It will apply on game's relaunch";
|
||||
String successMessage = "Distant Horizons successfully updated. It will apply on game`s relaunch";
|
||||
LOGGER.info(successMessage);
|
||||
new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, successMessage, "ok", "info", false);
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
||||
}
|
||||
catch (Exception ignore) { }
|
||||
}).start();
|
||||
@@ -288,7 +287,7 @@ public class SelfUpdater
|
||||
LOGGER.error(failMessage, e);
|
||||
try
|
||||
{
|
||||
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, failMessage, "ok", "error", false);
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
||||
}
|
||||
catch (Exception ignore) { }
|
||||
|
||||
@@ -386,7 +385,7 @@ public class SelfUpdater
|
||||
{
|
||||
try
|
||||
{
|
||||
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, successMessage, "ok", "info", false);
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, successMessage, "ok", "info");
|
||||
}
|
||||
catch (Exception ignore) { }
|
||||
}).start();
|
||||
@@ -424,7 +423,7 @@ public class SelfUpdater
|
||||
LOGGER.error(failMessage, e);
|
||||
try
|
||||
{
|
||||
TinyFileDialogs.tinyfd_messageBox(ModInfo.READABLE_NAME, failMessage, "ok", "error", false);
|
||||
NativeDialogUtil.showDialog(ModInfo.READABLE_NAME, failMessage, "ok", "error");
|
||||
}
|
||||
catch (Exception ignore) { }
|
||||
|
||||
|
||||
@@ -192,23 +192,30 @@ public abstract class AbstractDhLevel implements IDhLevel
|
||||
return this.updateDataSourcesAsync(fullDataSource)
|
||||
.thenRun(() ->
|
||||
{
|
||||
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
|
||||
if (updatedChunkPosSet != null)
|
||||
try
|
||||
{
|
||||
for (DhChunkPos chunkPos : updatedChunkPosSet)
|
||||
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos());
|
||||
if (updatedChunkPosSet != null)
|
||||
{
|
||||
// save after the data source has been updated to prevent saving the hash without the associated datasource
|
||||
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
|
||||
if (this.chunkHashRepo != null && chunkHash != null)
|
||||
for (DhChunkPos chunkPos : updatedChunkPosSet)
|
||||
{
|
||||
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
|
||||
}
|
||||
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(
|
||||
// save after the data source has been updated to prevent saving the hash without the associated datasource
|
||||
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
|
||||
if (this.chunkHashRepo != null && chunkHash != null)
|
||||
{
|
||||
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
|
||||
}
|
||||
|
||||
ApiEventInjector.INSTANCE.fireAllEvents(
|
||||
DhApiChunkModifiedEvent.class,
|
||||
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected issue after onDataSourceSaveAsync, error: ["+e.getMessage()+"].", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -102,13 +102,12 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
|
||||
{ return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); }
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public DhBlockPos2D getTargetPosForGeneration()
|
||||
{
|
||||
IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek();
|
||||
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
|
||||
|
||||
@@ -247,7 +247,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); }
|
||||
|
||||
|
||||
@@ -259,13 +258,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -379,9 +371,9 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class DhServerLevel extends AbstractDhServerLevel
|
||||
return true; //todo;
|
||||
}
|
||||
@Override
|
||||
public @Nullable DhBlockPos2D getTargetPosForGeneration()
|
||||
public DhBlockPos2D getTargetPosForGeneration()
|
||||
{
|
||||
DhBlockPos2D targetPos = super.getTargetPosForGeneration();
|
||||
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.types.ConfigEntry;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
@@ -302,32 +303,32 @@ public class DhLogger implements IConfigListener
|
||||
String prefix = "[" + ModInfo.READABLE_NAME + "] ";
|
||||
if (logLevel == Level.ERROR)
|
||||
{
|
||||
prefix += "\u00A74";
|
||||
prefix += MinecraftTextFormat.DARK_RED;
|
||||
}
|
||||
else if (logLevel == Level.WARN)
|
||||
{
|
||||
prefix += "\u00A76";
|
||||
prefix += MinecraftTextFormat.ORANGE;
|
||||
}
|
||||
else if (logLevel == Level.INFO)
|
||||
{
|
||||
prefix += "\u00A7f";
|
||||
prefix += MinecraftTextFormat.AQUA;
|
||||
}
|
||||
else if (logLevel == Level.DEBUG)
|
||||
{
|
||||
prefix += "\u00A77";
|
||||
prefix += MinecraftTextFormat.GREEN;
|
||||
}
|
||||
else if (logLevel == Level.TRACE)
|
||||
{
|
||||
prefix += "\u00A78";
|
||||
prefix += MinecraftTextFormat.DARK_GRAY;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix += "\u00A7f";
|
||||
prefix += MinecraftTextFormat.WHITE;
|
||||
}
|
||||
|
||||
prefix += "\u00A7l\u00A7u";
|
||||
prefix += MinecraftTextFormat.BOLD + "" + MinecraftTextFormat.WHITE;
|
||||
prefix += logLevel.name();
|
||||
prefix += ":\u00A7r ";
|
||||
prefix += MinecraftTextFormat.CLEAR_FORMATTING + " ";
|
||||
|
||||
mc_client.sendChatMessage(prefix + message);
|
||||
}
|
||||
|
||||
+181
-204
@@ -6,6 +6,7 @@ import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
|
||||
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
@@ -33,9 +34,7 @@ import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRenderable, AutoCloseable
|
||||
{
|
||||
@@ -58,7 +57,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
|
||||
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
|
||||
* 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 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 boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos);
|
||||
protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<ERequestResult> future);
|
||||
protected abstract boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos);
|
||||
protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future);
|
||||
|
||||
protected abstract String getQueueName();
|
||||
|
||||
@@ -119,74 +108,57 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
// request submitting //
|
||||
//====================//
|
||||
|
||||
public CompletableFuture<ERequestResult> submitRequest(long sectionPos, Consumer<FullDataSourceV2> dataSourceConsumer)
|
||||
{ return this.submitRequest(sectionPos, null, dataSourceConsumer); }
|
||||
public CompletableFuture<ERequestResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp, Consumer<FullDataSourceV2> dataSourceConsumer)
|
||||
public CompletableFuture<DataSourceRetrievalResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp)
|
||||
{
|
||||
if (this.succeededPositions.contains(sectionPos))
|
||||
NetRequestTask requestEntry = this.waitingTasksBySectionPos.compute(sectionPos, (Long pos, NetRequestTask existingNetTask) ->
|
||||
{
|
||||
return CompletableFuture.completedFuture(ERequestResult.FAILED);
|
||||
}
|
||||
|
||||
if (this.requiresSplittingPositions.contains(sectionPos))
|
||||
{
|
||||
return CompletableFuture.completedFuture(ERequestResult.REQUIRES_SPLITTING);
|
||||
}
|
||||
|
||||
AtomicBoolean added = new AtomicBoolean(false);
|
||||
RequestQueueEntry entry = this.waitingTasksBySectionPos.compute(sectionPos, (pos, existingQueueEntry) ->
|
||||
{
|
||||
if (existingQueueEntry != null)
|
||||
// ignore already queued tasks
|
||||
if (existingNetTask != null)
|
||||
{
|
||||
return existingQueueEntry;
|
||||
return existingNetTask;
|
||||
}
|
||||
|
||||
RequestQueueEntry newEntry = new RequestQueueEntry(dataSourceConsumer, clientTimestamp);
|
||||
newEntry.future.whenComplete((requestResult, throwable) ->
|
||||
|
||||
NetRequestTask newRequestEntry = new NetRequestTask(pos, clientTimestamp);
|
||||
newRequestEntry.future.whenComplete((DataSourceRetrievalResult requestResult, Throwable throwable) ->
|
||||
{
|
||||
this.waitingTasksBySectionPos.remove(sectionPos);
|
||||
this.waitingTasksBySectionPos.remove(pos);
|
||||
|
||||
switch (requestResult)
|
||||
if (throwable != null)
|
||||
{
|
||||
case SUCCEEDED:
|
||||
this.finishedRequests.incrementAndGet();
|
||||
this.succeededPositions.add(pos);
|
||||
return;
|
||||
case REQUIRES_SPLITTING:
|
||||
this.requiresSplittingPositions.add(sectionPos);
|
||||
return;
|
||||
case FAILED:
|
||||
if (!(throwable instanceof CancellationException))
|
||||
{
|
||||
this.failedRequests.incrementAndGet();
|
||||
return;
|
||||
default:
|
||||
if (throwable != null && !(throwable instanceof CancellationException))
|
||||
{
|
||||
this.failedRequests.incrementAndGet();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestResult.state)
|
||||
{
|
||||
case SUCCESS:
|
||||
this.finishedRequests.incrementAndGet();
|
||||
break;
|
||||
case REQUIRES_SPLITTING:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
added.set(true);
|
||||
return newEntry;
|
||||
return newRequestEntry;
|
||||
});
|
||||
|
||||
if (!added.get())
|
||||
{
|
||||
return CompletableFuture.completedFuture(ERequestResult.FAILED);
|
||||
}
|
||||
|
||||
return entry.future;
|
||||
return requestEntry.future;
|
||||
}
|
||||
|
||||
public synchronized boolean tick(DhBlockPos2D targetPos)
|
||||
{
|
||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
if (DhApiWorldProxy.INSTANCE.worldLoaded()
|
||||
&& DhApiWorldProxy.INSTANCE.getReadOnly())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.closingFuture != null || !this.networkState.isReady())
|
||||
if (this.closingFuture != null
|
||||
|| !this.networkState.isReady())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -209,145 +181,132 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
}
|
||||
private void sendNextRequest(DhBlockPos2D targetPos)
|
||||
{
|
||||
Map.Entry<Long, RequestQueueEntry> mapEntry = this.waitingTasksBySectionPos.entrySet().stream()
|
||||
.filter(task -> task.getValue().networkDataSourceFuture == null)
|
||||
.min(Comparator.comparingInt(x -> DhSectionPos.getChebyshevSignedBlockDistance(x.getKey(), targetPos)))
|
||||
.orElse(null);
|
||||
Map.Entry<Long, NetRequestTask> nearestMapEntry = this.waitingTasksBySectionPos
|
||||
.entrySet().stream()
|
||||
.filter(task -> task.getValue().networkDataSourceFuture == null)
|
||||
.min(Comparator.comparingInt(mapEntry -> DhSectionPos.getChebyshevSignedBlockDistance(mapEntry.getKey(), targetPos)))
|
||||
.orElse(null);
|
||||
|
||||
if (mapEntry == null)
|
||||
if (nearestMapEntry == null)
|
||||
{
|
||||
this.pendingTasksSemaphore.release();
|
||||
return;
|
||||
}
|
||||
|
||||
long sectionPos = mapEntry.getKey();
|
||||
RequestQueueEntry entry = mapEntry.getValue();
|
||||
long requestPos = nearestMapEntry.getKey();
|
||||
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();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.onBeforeRequest(sectionPos, entry.future))
|
||||
if (!this.onBeforeRequest(requestPos, requestTask.future))
|
||||
{
|
||||
this.pendingTasksSemaphore.release();
|
||||
return;
|
||||
}
|
||||
|
||||
Long offsetEntryTimestamp = entry.updateTimestamp != null
|
||||
? entry.updateTimestamp + this.networkState.getServerTimeOffset()
|
||||
Long offsetEntryTimestamp = requestTask.updateTimestamp != null
|
||||
? requestTask.updateTimestamp + this.networkState.getServerTimeOffset()
|
||||
: null;
|
||||
|
||||
CompletableFuture<FullDataSourceResponseMessage> dataSourceFuture = this.networkState.getSession().sendRequest(
|
||||
new FullDataSourceRequestMessage(this.level.getLevelWrapper(), sectionPos, offsetEntryTimestamp),
|
||||
CompletableFuture<FullDataSourceResponseMessage> dataSourceNetworkFuture = this.networkState.getSession().sendRequest(
|
||||
new FullDataSourceRequestMessage(this.level.getLevelWrapper(), requestPos, offsetEntryTimestamp),
|
||||
FullDataSourceResponseMessage.class
|
||||
);
|
||||
entry.networkDataSourceFuture = dataSourceFuture;
|
||||
dataSourceFuture.handle((response, throwable) ->
|
||||
requestTask.networkDataSourceFuture = dataSourceNetworkFuture;
|
||||
|
||||
Executor networkCompressionExecutor = ThreadPoolUtil.getNetworkCompressionExecutor();
|
||||
if (networkCompressionExecutor == null)
|
||||
{
|
||||
this.pendingTasksSemaphore.release();
|
||||
|
||||
try
|
||||
{
|
||||
if (throwable != null)
|
||||
{
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
if (response.payload != null)
|
||||
{
|
||||
FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(response.payload);
|
||||
|
||||
// set application flags based on the received detail level,
|
||||
// this is needed so the data sources propagate correctly
|
||||
dataSourceDto.applyToChildren = DhSectionPos.getDetailLevel(dataSourceDto.pos) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
|
||||
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);
|
||||
|
||||
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper(), null);
|
||||
entry.dataSourceConsumer.accept(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)
|
||||
{
|
||||
return entry.future.complete(ERequestResult.REQUIRES_SPLITTING);
|
||||
}
|
||||
catch (SessionClosedException | CancellationException ignored)
|
||||
{
|
||||
return entry.future.cancel(false);
|
||||
}
|
||||
catch (RequestRejectedException e)
|
||||
{
|
||||
LOGGER.info("Request rejected by the server: " + e.getMessage());
|
||||
return entry.future.complete(ERequestResult.FAILED);
|
||||
}
|
||||
catch (RateLimitedException e)
|
||||
{
|
||||
LOGGER.info("Rate limited by server, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage());
|
||||
|
||||
// Skip all requests for 1 second
|
||||
this.rateLimiter.acquireAll();
|
||||
|
||||
entry.networkDataSourceFuture = null;
|
||||
return null;
|
||||
}
|
||||
catch (RequestOutOfRangeException e)
|
||||
{
|
||||
LOGGER.debug("Out of range, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage());
|
||||
|
||||
entry.networkDataSourceFuture = null;
|
||||
return null;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
entry.retryAttempts--;
|
||||
LOGGER.error("Error while fetching full data source, attempts left: {} / {}", entry.retryAttempts, MAX_RETRY_ATTEMPTS, e);
|
||||
|
||||
// Retry logic
|
||||
if (entry.retryAttempts > 0)
|
||||
{
|
||||
entry.networkDataSourceFuture = null;
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return entry.future.complete(ERequestResult.FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
return entry.future.complete(ERequestResult.SUCCEEDED);
|
||||
});
|
||||
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();
|
||||
|
||||
try
|
||||
{
|
||||
if (throwable != null)
|
||||
{
|
||||
throw throwable;
|
||||
}
|
||||
|
||||
if (response.payload == null)
|
||||
{
|
||||
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,
|
||||
// this is needed so the data sources propagate correctly
|
||||
dataSourceDto.applyToChildren = DhSectionPos.getDetailLevel(dataSourceDto.pos) > DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL;
|
||||
dataSourceDto.applyToParent = DhSectionPos.getDetailLevel(dataSourceDto.pos) < DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL + 12;
|
||||
|
||||
|
||||
this.level.updateBeaconBeamsForSectionPos(dataSourceDto.pos, response.payload.beaconBeams);
|
||||
|
||||
FullDataSourceV2 fullDataSource = dataSourceDto.createDataSource(this.level.getLevelWrapper(), null);
|
||||
requestTask.future.complete(DataSourceRetrievalResult.CreateSuccess(dataSourceDto.pos, fullDataSource));
|
||||
}
|
||||
}
|
||||
catch (SectionRequiresSplittingException ignored)
|
||||
{
|
||||
requestTask.future.complete(DataSourceRetrievalResult.CreateSplit());
|
||||
}
|
||||
catch (SessionClosedException | CancellationException ignored)
|
||||
{
|
||||
requestTask.future.cancel(false);
|
||||
}
|
||||
catch (RequestRejectedException e)
|
||||
{
|
||||
LOGGER.info("Request rejected by the server, message: [" + e.getMessage() + "].");
|
||||
requestTask.future.completeExceptionally(e);
|
||||
}
|
||||
catch (RateLimitedException e)
|
||||
{
|
||||
LOGGER.info("Rate limited by server, re-queueing task [" + DhSectionPos.toString(requestTask.pos) + "], message: [" + e.getMessage() + "].");
|
||||
|
||||
// Skip all requests for 1 second
|
||||
this.rateLimiter.acquireAll();
|
||||
|
||||
requestTask.networkDataSourceFuture = null;
|
||||
}
|
||||
catch (RequestOutOfRangeException e)
|
||||
{
|
||||
LOGGER.debug("Out of range, re-queueing task [" + DhSectionPos.toString(requestTask.pos) + "], message: [" + e.getMessage() + "].");
|
||||
|
||||
requestTask.networkDataSourceFuture = null;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
requestTask.retryAttempts--;
|
||||
LOGGER.error("Unexpected error: ["+e.getMessage()+"] while fetching full data source, attempts left: ["+requestTask.retryAttempts+"] / ["+MAX_RETRY_ATTEMPTS+"]", e);
|
||||
|
||||
// Retry logic
|
||||
if (requestTask.retryAttempts > 0)
|
||||
{
|
||||
requestTask.networkDataSourceFuture = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
requestTask.future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -357,22 +316,30 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
|
||||
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf)
|
||||
{
|
||||
for (Map.Entry<Long, RequestQueueEntry> mapEntry : (Iterable<? extends Map.Entry<Long, RequestQueueEntry>>) this.waitingTasksBySectionPos.entrySet().stream()
|
||||
.sorted(Comparator.comparingInt((Map.Entry<Long, RequestQueueEntry> entry) -> DhSectionPos.getChebyshevSignedBlockDistance(entry.getKey(), Objects.requireNonNull(this.level.getTargetPosForGeneration()))).reversed())
|
||||
::iterator)
|
||||
// remove tasks furthest
|
||||
Iterator<Map.Entry<Long, NetRequestTask>> farestTaskIterator = this.waitingTasksBySectionPos
|
||||
.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();
|
||||
RequestQueueEntry entry = mapEntry.getValue();
|
||||
NetRequestTask entry = mapEntry.getValue();
|
||||
|
||||
if (removeIf.accept(pos))
|
||||
{
|
||||
LOGGER.debug("Removing request [" + mapEntry.getKey() + "]...");
|
||||
|
||||
entry.future.cancel(false);
|
||||
if (entry.networkDataSourceFuture != null)
|
||||
{
|
||||
entry.networkDataSourceFuture.cancel(false);
|
||||
}
|
||||
entry.future.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,7 +367,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
|
||||
do
|
||||
{
|
||||
for (RequestQueueEntry entry : this.waitingTasksBySectionPos.values())
|
||||
for (NetRequestTask entry : this.waitingTasksBySectionPos.values())
|
||||
{
|
||||
entry.future.cancel(alsoInterruptRunning);
|
||||
if (entry.networkDataSourceFuture != null && entry.networkDataSourceFuture.cancel(alsoInterruptRunning))
|
||||
@@ -438,13 +405,31 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
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,
|
||||
mapEntry.getValue().networkDataSourceFuture != null ? Color.red
|
||||
: this.isSectionAllowedToGenerate(mapEntry.getKey(), Objects.requireNonNull(this.level.getTargetPosForGeneration())) ? Color.gray
|
||||
: Color.darkGray
|
||||
));
|
||||
long pos = mapEntry.getKey();
|
||||
NetRequestTask task = mapEntry.getValue();
|
||||
|
||||
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 //
|
||||
//================//
|
||||
|
||||
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 */
|
||||
public final CompletableFuture<ERequestResult> future = new CompletableFuture<>();
|
||||
public final Consumer<FullDataSourceV2> dataSourceConsumer;
|
||||
public final CompletableFuture<DataSourceRetrievalResult> future = new CompletableFuture<>();
|
||||
/** will be null if we want to retrieve the LOD regardless of when it was last updated */
|
||||
@Nullable
|
||||
public final Long updateTimestamp;
|
||||
@@ -477,23 +463,14 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
// constructor //
|
||||
//=============//
|
||||
|
||||
public RequestQueueEntry(
|
||||
Consumer<FullDataSourceV2> dataSourceConsumer,
|
||||
@Nullable Long updateTimestamp)
|
||||
public NetRequestTask(long pos, @Nullable Long updateTimestamp)
|
||||
{
|
||||
this.dataSourceConsumer = dataSourceConsumer;
|
||||
this.pos = pos;
|
||||
this.updateTimestamp = updateTimestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum ERequestResult
|
||||
{
|
||||
SUCCEEDED,
|
||||
REQUIRES_SPLITTING,
|
||||
FAILED,
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+20
-1
@@ -1,8 +1,10 @@
|
||||
package com.seibel.distanthorizons.core.multiplayer.client;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
|
||||
@@ -18,6 +20,7 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartial
|
||||
import com.seibel.distanthorizons.core.network.session.NetworkSession;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.Closeable;
|
||||
@@ -29,6 +32,10 @@ public class ClientNetworkState implements Closeable
|
||||
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
|
||||
.build();
|
||||
|
||||
protected static final DhLogger CONFIG_CHANGE_LOGGER = new DhLoggerBuilder()
|
||||
.fileLevelConfig(Config.Common.Logging.logConnectionConfigChangesToFile)
|
||||
.build();
|
||||
|
||||
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
|
||||
|
||||
|
||||
@@ -44,6 +51,7 @@ public class ClientNetworkState implements Closeable
|
||||
*/
|
||||
public NetworkSession getSession() { return this.networkSession; }
|
||||
|
||||
@NotNull
|
||||
public SessionConfig sessionConfig = new SessionConfig();
|
||||
|
||||
private volatile boolean configReceived = false;
|
||||
@@ -89,6 +97,15 @@ public class ClientNetworkState implements Closeable
|
||||
|| Math.abs(event.protocolVersion - ModInfo.PROTOCOL_VERSION) < this.closestProtocolVersion)
|
||||
{
|
||||
this.closestProtocolVersion = event.protocolVersion;
|
||||
|
||||
if (ModInfo.PROTOCOL_VERSION < event.protocolVersion)
|
||||
{
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(MinecraftTextFormat.ORANGE + "Distant Horizons: Your mod is outdated. Update to receive LODs on this server.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(MinecraftTextFormat.ORANGE + "Distant Horizons: The server's mod is outdated. Ask the server's owner to update.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -129,7 +146,9 @@ public class ClientNetworkState implements Closeable
|
||||
{
|
||||
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.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.generation.RemoteWorldRetrievalQueue;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||
@@ -35,12 +36,12 @@ public class SyncOnLoadRequestQueue extends AbstractFullDataNetworkRequestQueue
|
||||
@Override
|
||||
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getSyncOnLoginRateLimit(); }
|
||||
@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;
|
||||
}
|
||||
@Override
|
||||
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<ERequestResult> future) { return true; }
|
||||
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future) { return true; }
|
||||
|
||||
@Override
|
||||
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 //
|
||||
//================//
|
||||
|
||||
+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.util.LodUtil;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.buffer.UnpooledByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -36,7 +36,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
||||
{
|
||||
if (message.isFirst)
|
||||
{
|
||||
composite = UnpooledByteBufAllocator.DEFAULT.compositeBuffer();
|
||||
composite = Unpooled.compositeBuffer();
|
||||
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
|
||||
}
|
||||
else if (composite == null)
|
||||
@@ -45,6 +45,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
||||
return null;
|
||||
}
|
||||
|
||||
message.buffer.readerIndex(0);
|
||||
composite.addComponent(message.buffer);
|
||||
composite.writerIndex(composite.writerIndex() + message.buffer.writerIndex());
|
||||
LOGGER.debug("Updated full data buffer [" + message.bufferId + "]: [" + composite + "].");
|
||||
@@ -55,12 +56,11 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
||||
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
||||
{
|
||||
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
||||
LodUtil.assertTrue(compositeByteBuffer != null);
|
||||
LodUtil.assertTrue(compositeByteBuffer != null, "decoded data source missing byte buffer");
|
||||
|
||||
try
|
||||
{
|
||||
FullDataSourceV2DTO dataSourceDto = INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer);
|
||||
LOGGER.debug("Buffer {} DTO: {}", payload.dtoBufferId, dataSourceDto);
|
||||
return dataSourceDto;
|
||||
}
|
||||
finally
|
||||
|
||||
+24
-8
@@ -174,12 +174,21 @@ public class FullDataSourceRequestHandler implements AutoCloseable
|
||||
DataSourceRequestGroup requestGroup = this.requestGroupsByPos.computeIfAbsent(requestData.sectionPos(), pos ->
|
||||
{
|
||||
DataSourceRequestGroup newGroup = new DataSourceRequestGroup(pos);
|
||||
newGroup.tryAddRequest(requestData);
|
||||
createdNewGroup.set(true);
|
||||
try
|
||||
{
|
||||
newGroup.tryAddRequest(requestData);
|
||||
createdNewGroup.set(true);
|
||||
|
||||
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
|
||||
|
||||
LOGGER.debug("[" + this.getLevelIdentifier() + "] Created request group for pos [" + DhSectionPos.toString(pos) + "].");
|
||||
return newGroup;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unable to queue request for pos: ["+DhSectionPos.toString(requestData.sectionPos())+"], error: ["+e.getMessage()+"].", e);
|
||||
}
|
||||
|
||||
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
|
||||
|
||||
LOGGER.debug("[" + this.getLevelIdentifier() + "] Created request group for pos [" + DhSectionPos.toString(pos) + "].");
|
||||
return newGroup;
|
||||
});
|
||||
|
||||
@@ -229,10 +238,14 @@ public class FullDataSourceRequestHandler implements AutoCloseable
|
||||
|
||||
private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
|
||||
{
|
||||
this.fullDataSourceProvider().getAsync(pos).thenAccept(fullDataSource ->
|
||||
final GeneratedFullDataSourceProvider provider = this.fullDataSourceProvider();
|
||||
|
||||
provider.getAsync(pos)
|
||||
.thenAccept((FullDataSourceV2 fullDataSource) ->
|
||||
{
|
||||
if (this.fullDataSourceProvider().isFullyGenerated(fullDataSource.columnGenerationSteps))
|
||||
if (provider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps))
|
||||
{
|
||||
//LOGGER.info("sending - complete [" + DhSectionPos.toString(pos) + "]");
|
||||
requestGroup.fullDataSource = fullDataSource;
|
||||
return;
|
||||
}
|
||||
@@ -247,11 +260,14 @@ public class FullDataSourceRequestHandler implements AutoCloseable
|
||||
this.requestGroupsByPos.remove(pos);
|
||||
if (!requestGroup.tryClose())
|
||||
{
|
||||
//LOGGER.info("closing [" + DhSectionPos.toString(pos) + "]");
|
||||
return;
|
||||
}
|
||||
|
||||
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
|
||||
{
|
||||
//LOGGER.info("sending [" + DhSectionPos.toString(pos) + "] - ["+DhSectionPos.toString(requestData.sectionPos())+"]");
|
||||
|
||||
this.requestGroupsByFutureId.remove(requestData.futureId());
|
||||
requestData.rateLimiterSet.generationRequestRateLimiter.release();
|
||||
requestData.message.sendResponse(new SectionRequiresSplittingException());
|
||||
@@ -264,7 +280,7 @@ public class FullDataSourceRequestHandler implements AutoCloseable
|
||||
}
|
||||
else
|
||||
{
|
||||
//LOGGER.info("sending - queueing [" + DhSectionPos.toString(pos) + "]");
|
||||
//LOGGER.info("queueing incomplete world gen [" + DhSectionPos.toString(pos) + "]");
|
||||
this.fullDataSourceProvider().queuePositionForRetrieval(pos);
|
||||
}
|
||||
});
|
||||
|
||||
+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("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. */
|
||||
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.");
|
||||
}
|
||||
|
||||
+12
-12
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* This keeps track of all the poolable
|
||||
@@ -33,7 +34,7 @@ public class PhantomArrayListCheckout implements AutoCloseable
|
||||
|
||||
/** Will be null if the parent pool doesn't want leak stack tracing */
|
||||
@Nullable
|
||||
public final String allocationStackTrace;
|
||||
public String allocationStackTrace = null;
|
||||
|
||||
private final ArrayList<ByteArrayList> byteArrayLists = new ArrayList<>();
|
||||
private final ArrayList<ShortArrayList> shortArrayLists = new ArrayList<>();
|
||||
@@ -47,21 +48,20 @@ public class PhantomArrayListCheckout implements AutoCloseable
|
||||
|
||||
public PhantomArrayListCheckout(@NotNull PhantomArrayListPool owningPool)
|
||||
{
|
||||
if (owningPool.logGarbageCollectedStacks)
|
||||
{
|
||||
// TODO remove the top 4 or so lines since those will always be the same (relating to the phantom allocations)
|
||||
// and aren't helpful when debugging
|
||||
this.allocationStackTrace = StringUtil.join("\n", Thread.currentThread().getStackTrace());
|
||||
}
|
||||
else
|
||||
{
|
||||
this.allocationStackTrace = null;
|
||||
}
|
||||
|
||||
this.owningPool = owningPool;
|
||||
this.ownerSoftReference = new SoftReference<>(this);
|
||||
}
|
||||
|
||||
public void onCheckout()
|
||||
{
|
||||
if (this.owningPool.logGarbageCollectedStacks)
|
||||
{
|
||||
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
|
||||
StackTraceElement[] trimmedElements = Arrays.copyOfRange(stackTraceElements, 4, stackTraceElements.length);
|
||||
this.allocationStackTrace = StringUtil.join("\n", trimmedElements).intern();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.pooling;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
@@ -12,7 +13,6 @@ import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -159,6 +159,7 @@ public class PhantomArrayListPool
|
||||
{
|
||||
// pool is empty, create new checkout
|
||||
checkout = new PhantomArrayListCheckout(this);
|
||||
checkout.onCheckout();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -166,6 +167,7 @@ public class PhantomArrayListPool
|
||||
if (checkout != null)
|
||||
{
|
||||
// use pooled checkout
|
||||
checkout.onCheckout();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -176,8 +178,7 @@ public class PhantomArrayListPool
|
||||
{
|
||||
lowMemoryWarningLogged = true;
|
||||
|
||||
// orange text
|
||||
String message = "\u00A76" + "Distant Horizons: Insufficient memory detected." + "\u00A7r \n" +
|
||||
String message = MinecraftTextFormat.ORANGE + "Distant Horizons: Insufficient memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"This may cause stuttering or crashing. \n" +
|
||||
"Potential causes: \n" +
|
||||
"1. your allocated memory isn't high enough \n" +
|
||||
|
||||
@@ -23,7 +23,9 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/** immutable */
|
||||
@Deprecated // TODO why does this exist vs blockpos2d?
|
||||
public class Pos2D
|
||||
{
|
||||
public static final Pos2D ZERO = new Pos2D(0, 0);
|
||||
|
||||
@@ -75,6 +75,30 @@ public class DhBlockPos2D
|
||||
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); }
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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.enums.EDhDirection;
|
||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
|
||||
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
|
||||
import com.seibel.distanthorizons.core.level.IDhClientLevel;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
@@ -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.GenericObjectRenderer;
|
||||
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.WorldGenUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
|
||||
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
|
||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -47,6 +50,7 @@ import javax.annotation.WillNotClose;
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -57,11 +61,11 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
* This quadTree structure is our core data structure and holds
|
||||
* all rendering data.
|
||||
*/
|
||||
public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRenderable, AutoCloseable
|
||||
public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRenderable, IConfigListener, AutoCloseable
|
||||
{
|
||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||
/** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */
|
||||
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;
|
||||
@@ -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.
|
||||
*/
|
||||
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
|
||||
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference!
|
||||
private final ReentrantLock treeReadWriteLock = new ReentrantLock();
|
||||
private final AtomicBoolean fullDataRetrievalQueueRunning = new AtomicBoolean(false);
|
||||
private final IDhClientLevel level;
|
||||
/**
|
||||
* Note: this doesn't lock all operations as some other threads/operations
|
||||
* that may traverse the tree while it's being modified.
|
||||
* IE {@link RenderBufferHandler} will walk through the tree each frame.
|
||||
*/
|
||||
private final ReentrantLock treeLock = new ReentrantLock();
|
||||
|
||||
private ArrayList<LodRenderSection> debugRenderSections = new ArrayList<>();
|
||||
private ArrayList<LodRenderSection> altDebugRenderSections = new ArrayList<>();
|
||||
@@ -88,7 +96,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
* as further sections are loaded before closer ones.
|
||||
* Only queuing a few of the sections at a time solves this problem.
|
||||
*/
|
||||
public final AtomicInteger uploadTaskCountRef = new AtomicInteger(0);
|
||||
private final AtomicInteger uploadTaskCountRef = new AtomicInteger(0);
|
||||
private final AtomicBoolean requeueAllRetrievalTasksRef = new AtomicBoolean(false);
|
||||
private final AtomicBoolean queueThreadRunningRef = new AtomicBoolean(false);
|
||||
|
||||
|
||||
@Nullable
|
||||
@@ -104,11 +114,18 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
/** used to calculate when a detail drop will occur */
|
||||
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(
|
||||
IDhClientLevel level, int viewDiameterInBlocks,
|
||||
@@ -126,13 +143,18 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
GenericObjectRenderer genericObjectRenderer = this.level.getGenericRenderer();
|
||||
this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null;
|
||||
|
||||
Config.Common.WorldGenerator.enableDistantGeneration.addListener(this);
|
||||
|
||||
}
|
||||
|
||||
//endregion constructor
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
// tick update //
|
||||
//=============//
|
||||
//region tick update
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
// the level hasn't finished loading yet
|
||||
// TODO sometimes null pointers still happen, when logging back into a world (maybe the old level isn't null but isn't valid either?)
|
||||
// the quad tree was created before a level reference was created
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// this shouldn't be updated while the tree is being iterated through
|
||||
this.updateDetailLevelVariables();
|
||||
|
||||
// don't traverse the tree if it is being modified
|
||||
if (this.treeReadWriteLock.tryLock())
|
||||
// don't tick the tree if a modification is still going
|
||||
// TODO is this lock necessary for anything beyond this tick method?
|
||||
if (this.treeLock.tryLock())
|
||||
{
|
||||
// this shouldn't be updated while the tree is being iterated through
|
||||
this.updateDetailLevelVariables();
|
||||
|
||||
try
|
||||
{
|
||||
// recenter if necessary, removing out of bounds sections
|
||||
this.setCenterBlockPos(playerPos, LodRenderSection::close);
|
||||
// recenter if necessary...
|
||||
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);
|
||||
}
|
||||
@@ -169,7 +201,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.treeReadWriteLock.unlock();
|
||||
this.treeLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +229,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
|
||||
|
||||
// walk through each root node
|
||||
HashSet<LodRenderSection> nodesNeedingRetrieval = new HashSet<>();
|
||||
HashSet<LodRenderSection> nodesNeedingLoading = new HashSet<>();
|
||||
LongIterator rootPosIterator = this.rootNodePosIterator();
|
||||
while (rootPosIterator.hasNext())
|
||||
@@ -210,17 +241,61 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
}
|
||||
|
||||
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
|
||||
if (nodesNeedingRetrieval.size() != 0
|
||||
&& !this.fullDataRetrievalQueueRunning.get()
|
||||
&& this.fullDataSourceProvider.canQueueRetrieval())
|
||||
// requeue everything if needed
|
||||
if (this.requeueAllRetrievalTasksRef.get()
|
||||
&& !this.queueThreadRunningRef.get())
|
||||
{
|
||||
this.fullDataRetrievalQueueRunning.set(true);
|
||||
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() -> this.queueFullDataRetrievalTasks(playerPos, nodesNeedingRetrieval));
|
||||
this.queueThreadRunningRef.set(true);
|
||||
this.requeueAllRetrievalTasksRef.set(false);
|
||||
|
||||
// running on a separate thread allows for faster loading
|
||||
// of finished LODs
|
||||
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() ->
|
||||
{
|
||||
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,
|
||||
QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> quadNode, long sectionPos,
|
||||
boolean parentSectionIsRendering,
|
||||
HashSet<LodRenderSection> nodesNeedingRetrieval,
|
||||
HashSet<LodRenderSection> nodesNeedingLoading)
|
||||
{
|
||||
//=====================//
|
||||
@@ -245,7 +319,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
//=====================//
|
||||
|
||||
// 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));
|
||||
quadNode = rootNode.getNode(sectionPos);
|
||||
@@ -288,7 +363,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
for (int i = 0; i < 4; 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;
|
||||
}
|
||||
|
||||
@@ -347,7 +422,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
for (int i = 0; i < 4; 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
|
||||
@@ -368,23 +443,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
|
||||
// prepare this section for rendering
|
||||
if (!renderSection.gpuUploadInProgress()
|
||||
&& 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()
|
||||
)
|
||||
&& renderSection.bufferContainer == null)
|
||||
{
|
||||
nodesNeedingLoading.add(renderSection);
|
||||
}
|
||||
|
||||
// queue world gen if needed
|
||||
if (!renderSection.isFullyGenerated())
|
||||
{
|
||||
nodesNeedingRetrieval.add(renderSection);
|
||||
}
|
||||
|
||||
// update debug if needed
|
||||
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
|
||||
if (!parentSectionIsRendering && renderSection.canRender())
|
||||
if (!parentSectionIsRendering
|
||||
&& renderSection.canRender())
|
||||
{
|
||||
// if rendering is already enabled we don't have to re-enable it
|
||||
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
|
||||
renderSection.onRenderingEnabled();
|
||||
|
||||
// since this section wants to render
|
||||
// check if it needs any generation to do so
|
||||
this.tryQueuePosForRetrieval(renderSection.pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,31 +514,23 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
// the section only needs to be updated if a buffer is currently present
|
||||
LodRenderSection renderSection = this.tryGetValue(pos);
|
||||
if (renderSection != null)
|
||||
{
|
||||
// the section only needs to be updated if a buffer is currently present
|
||||
LodRenderSection renderSection = this.getValue(pos);
|
||||
if (renderSection != null)
|
||||
if (renderSection.canRender())
|
||||
{
|
||||
// this data source may now exist
|
||||
renderSection.updateFullDataSourceExists();
|
||||
|
||||
if (renderSection.canRender())
|
||||
if (renderSection.gpuUploadInProgress()
|
||||
|| !renderSection.uploadRenderDataToGpuAsync())
|
||||
{
|
||||
if (renderSection.gpuUploadInProgress()
|
||||
|| !renderSection.uploadRenderDataToGpuAsync())
|
||||
{
|
||||
// if a section is already loading or failed to start upload
|
||||
// we need to wait to trigger it again
|
||||
// if we don't trigger it again the LOD will be out of date
|
||||
// and may be invisible/missing
|
||||
positionsToRequeue.add(pos);
|
||||
}
|
||||
// if a section is already loading or failed to start upload
|
||||
// we need to wait to trigger it again
|
||||
// if we don't trigger it again the LOD will be out of date
|
||||
// and may be invisible/missing
|
||||
positionsToRequeue.add(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfBoundsException e)
|
||||
{ /* the section is now out of bounds, it doesn't need to be reloaded */ }
|
||||
}
|
||||
this.sectionsToReload.addAll(positionsToRequeue);
|
||||
}
|
||||
@@ -488,18 +547,188 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
for (int i = 0; i < loadSectionList.size(); i++)
|
||||
{
|
||||
LodRenderSection renderSection = loadSectionList.get(i);
|
||||
if (!renderSection.gpuUploadInProgress() && renderSection.bufferContainer == null)
|
||||
if (!renderSection.gpuUploadInProgress()
|
||||
&& renderSection.bufferContainer == null)
|
||||
{
|
||||
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 //
|
||||
//====================//
|
||||
//region detail level logic
|
||||
|
||||
/**
|
||||
* This method will compute the detail level based on player position and section pos
|
||||
@@ -553,11 +782,14 @@ 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)
|
||||
}
|
||||
|
||||
//endregion detail level logic
|
||||
|
||||
|
||||
//=============//
|
||||
// render data //
|
||||
//=============//
|
||||
|
||||
//==========================//
|
||||
// external render requests //
|
||||
//==========================//
|
||||
//region external render requests
|
||||
|
||||
/**
|
||||
* Re-creates the color, render data.
|
||||
@@ -565,34 +797,32 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
*/
|
||||
public void clearRenderDataCache()
|
||||
{
|
||||
if (this.treeReadWriteLock.tryLock()) // TODO make async, can lock render thread
|
||||
try
|
||||
{
|
||||
try
|
||||
this.treeLock.lock();
|
||||
LOGGER.info("Disposing render data...");
|
||||
|
||||
// clear the tree
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
LOGGER.info("Disposing render data...");
|
||||
|
||||
// clear the tree
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
QuadNode<LodRenderSection> quadNode = nodeIterator.next();
|
||||
if (quadNode.value != null)
|
||||
{
|
||||
QuadNode<LodRenderSection> quadNode = nodeIterator.next();
|
||||
if (quadNode.value != null)
|
||||
{
|
||||
quadNode.value.close();
|
||||
quadNode.value = null;
|
||||
}
|
||||
quadNode.value.close();
|
||||
quadNode.value = null;
|
||||
}
|
||||
|
||||
LOGGER.info("Render data cleared, please wait a moment for everything to reload...");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected error when clearing LodQuadTree render cache: " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.treeReadWriteLock.unlock();
|
||||
}
|
||||
|
||||
LOGGER.info("Render data cleared, please wait a moment for everything to reload...");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected error when clearing LodQuadTree render cache: " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.treeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,79 +850,14 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//=================================//
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
//endregion external render requests
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// debugging //
|
||||
//===========//
|
||||
//region debugging
|
||||
|
||||
@Override
|
||||
public void debugRender(DebugRenderer debugRenderer)
|
||||
@@ -739,11 +904,14 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
}
|
||||
}
|
||||
|
||||
//endregion debugging
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// base methods //
|
||||
//==============//
|
||||
//region base methods
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
@@ -751,13 +919,15 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
LOGGER.info("Shutting down LodQuadTree...");
|
||||
|
||||
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
||||
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
|
||||
|
||||
|
||||
ThreadPoolExecutor mainCleanupExecutor = ThreadPoolUtil.getCleanupExecutor();
|
||||
// closing every node may take a few moments
|
||||
// so this is run on a separate thread to prevent lagging the render thread
|
||||
mainCleanupExecutor.execute(() ->
|
||||
{
|
||||
this.treeReadWriteLock.lock();
|
||||
this.treeLock.lock();
|
||||
try
|
||||
{
|
||||
// walk through each node
|
||||
@@ -775,7 +945,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
//endregion base methods
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
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.dataObjects.fullData.sources.FullDataSourceV2;
|
||||
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.sql.dto.BeaconBeamDTO;
|
||||
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.ThreadPoolUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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
|
||||
* if this list is modified the {@link LodRenderSection#beaconRenderHandler} should be updated to match.
|
||||
*/
|
||||
private final List<BeaconBeamDTO> activeBeaconList = new ArrayList<>();
|
||||
private final ArrayList<BeaconBeamDTO> activeBeaconList = new ArrayList<>();
|
||||
@Nullable
|
||||
public final BeaconRenderHandler beaconRenderHandler;
|
||||
@Nullable
|
||||
@@ -114,25 +110,13 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
* different threads (buffer uploading is on the MC render thread) and need to be canceled separately.
|
||||
*/
|
||||
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 //
|
||||
//=============//
|
||||
//region constructor
|
||||
|
||||
public LodRenderSection(
|
||||
long pos,
|
||||
@@ -149,15 +133,18 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
this.beaconRenderHandler = this.quadTree.beaconRenderHandler;
|
||||
this.beaconBeamRepo = this.level.getBeaconBeamRepo();
|
||||
|
||||
|
||||
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
|
||||
}
|
||||
|
||||
//endregion constructor
|
||||
|
||||
|
||||
|
||||
//======================================//
|
||||
// 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 */
|
||||
public synchronized boolean uploadRenderDataToGpuAsync()
|
||||
@@ -254,24 +241,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
// get the adjacent positions
|
||||
// needs to be done async to prevent threads waiting on the same positions to be processed
|
||||
final CompletableFuture<ColumnRenderSource>[] adjacentLoadFutures = new CompletableFuture[4];
|
||||
|
||||
if (Config.Client.Advanced.Graphics.Experimental.onlyLoadCenterLods.get())
|
||||
{
|
||||
// TODO temporary test, long term something else should be done to so we can get adjacent lighting data
|
||||
// probably a change to the LOD data format
|
||||
adjacentLoadFutures[0] = CompletableFuture.completedFuture(null);
|
||||
adjacentLoadFutures[1] = CompletableFuture.completedFuture(null);
|
||||
adjacentLoadFutures[2] = CompletableFuture.completedFuture(null);
|
||||
adjacentLoadFutures[3] = CompletableFuture.completedFuture(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
adjacentLoadFutures[0] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.NORTH);
|
||||
adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.SOUTH);
|
||||
adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.EAST);
|
||||
adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.WEST);
|
||||
}
|
||||
|
||||
adjacentLoadFutures[0] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.NORTH);
|
||||
adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.SOUTH);
|
||||
adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.EAST);
|
||||
adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.WEST);
|
||||
return CompletableFuture.allOf(adjacentLoadFutures).thenRun(() ->
|
||||
{
|
||||
try (ColumnRenderSource northRenderSource = adjacentLoadFutures[0].get();
|
||||
@@ -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)
|
||||
{
|
||||
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; }
|
||||
|
||||
@@ -439,123 +418,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
public boolean gpuUploadInProgress() { return this.getAndBuildRenderDataFuture != null; }
|
||||
|
||||
|
||||
|
||||
//=================================//
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion enabling rendering
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// beacon handling //
|
||||
//=================//
|
||||
//region beacon handling
|
||||
|
||||
/** gets the active beacon list and stops/starts beacon rendering as necessary */
|
||||
private void getAndRefreshRenderingBeacons()
|
||||
@@ -576,20 +446,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
|
||||
// stop rendering current beacons
|
||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
||||
{
|
||||
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
|
||||
}
|
||||
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
|
||||
|
||||
// swap old and new active beacon list
|
||||
this.activeBeaconList.clear();
|
||||
this.activeBeaconList.addAll(activeBeacons);
|
||||
|
||||
// start rendering new beacon list
|
||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
||||
{
|
||||
this.beaconRenderHandler.startRenderingBeacon(beam);
|
||||
}
|
||||
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,10 +469,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
synchronized (this.activeBeaconList)
|
||||
{
|
||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
||||
{
|
||||
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
|
||||
}
|
||||
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,18 +484,19 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
|
||||
synchronized (this.activeBeaconList)
|
||||
{
|
||||
for (BeaconBeamDTO beam : this.activeBeaconList)
|
||||
{
|
||||
this.beaconRenderHandler.startRenderingBeacon(beam);
|
||||
}
|
||||
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
|
||||
}
|
||||
}
|
||||
|
||||
//endregion beacon handling
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// base methods //
|
||||
//==============//
|
||||
//region base methods
|
||||
|
||||
@Override
|
||||
public void debugRender(DebugRenderer debugRenderer)
|
||||
@@ -693,13 +556,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
{
|
||||
// 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
|
||||
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor();
|
||||
if (executor != null && !executor.isTerminated())
|
||||
PriorityTaskPicker.Executor renderLoaderExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
|
||||
if (renderLoaderExecutor != null
|
||||
&& !renderLoaderExecutor.isTerminated())
|
||||
{
|
||||
Runnable runnable = this.getAndBuildRenderDataRunnable;
|
||||
if (runnable != null)
|
||||
{
|
||||
executor.remove(runnable);
|
||||
renderLoaderExecutor.remove(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -710,16 +574,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
||||
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>());
|
||||
|
||||
private static final ConcurrentLinkedQueue<Runnable> RENDER_THREAD_RUNNABLE_QUEUE = new ConcurrentLinkedQueue<>();
|
||||
|
||||
|
||||
|
||||
private static GLProxy instance = null;
|
||||
|
||||
|
||||
private final ConcurrentLinkedQueue<Runnable> renderThreadRunnableQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/** Minecraft's GL capabilities */
|
||||
public final GLCapabilities glCapabilities;
|
||||
|
||||
@@ -231,7 +231,7 @@ public class GLProxy
|
||||
return uploadOverride;
|
||||
}
|
||||
|
||||
public boolean runningOnRenderThread()
|
||||
public static boolean runningOnRenderThread()
|
||||
{
|
||||
long currentContext = GLFW.glfwGetCurrentContext();
|
||||
return currentContext != 0L; // if the context isn't null, it's the MC context
|
||||
@@ -243,12 +243,12 @@ public class GLProxy
|
||||
// Worker Thread Runnables //
|
||||
//=========================//
|
||||
|
||||
public void queueRunningOnRenderThread(Runnable renderCall)
|
||||
public static void queueRunningOnRenderThread(Runnable renderCall)
|
||||
{
|
||||
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
|
||||
{
|
||||
@@ -266,11 +266,11 @@ public class GLProxy
|
||||
* Doesn't do any thread/GL Context validation.
|
||||
* Running this outside of the render thread may cause crashes or other issues.
|
||||
*/
|
||||
public void runRenderThreadTasks()
|
||||
public static void runRenderThreadTasks()
|
||||
{
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
Runnable runnable = this.renderThreadRunnableQueue.poll();
|
||||
Runnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
|
||||
while(runnable != null)
|
||||
{
|
||||
runnable.run();
|
||||
@@ -283,7 +283,7 @@ public class GLProxy
|
||||
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)
|
||||
{
|
||||
if (!GLProxy.getInstance().runningOnRenderThread())
|
||||
if (!GLProxy.runningOnRenderThread())
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
||||
GLProxy.queueRunningOnRenderThread(() ->
|
||||
{
|
||||
// destroy the buffer if it exists,
|
||||
// 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()
|
||||
{
|
||||
return super.getSize() / GLEnums.getTypeSize(getType());
|
||||
return super.getSize() / GLEnums.getTypeSize(this.getType());
|
||||
}
|
||||
|
||||
private static void buildBufferByte(int quadCount, ByteBuffer buffer)
|
||||
@@ -140,7 +140,6 @@ public class QuadElementBuffer extends GLElementBuffer
|
||||
return;
|
||||
}
|
||||
int vertexCount = quadCount * 4; // 4 vertices per quad
|
||||
GLProxy gl = GLProxy.getInstance();
|
||||
|
||||
if (vertexCount < 255)
|
||||
{ // 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));
|
||||
buildBuffer(quadCount, buffer, this.type);
|
||||
if (!gl.bufferStorageSupported)
|
||||
if (!GLProxy.getInstance().bufferStorageSupported)
|
||||
{
|
||||
|
||||
this.bind();
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ public class ShaderProgram
|
||||
|
||||
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);
|
||||
|
||||
|
||||
+4
-2
@@ -59,8 +59,10 @@ public final class VertexPointer
|
||||
/** Always aligned to 4 bytes */
|
||||
public static VertexPointer addUnsignedBytePointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4, useInteger); }
|
||||
/** 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 addUnsignedShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount * 2), useInteger); }
|
||||
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 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 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); }
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
+11
-8
@@ -96,16 +96,19 @@ public class DhFadeRenderer
|
||||
}
|
||||
|
||||
this.fadeTexture = GL32.glGenTextures();
|
||||
GLMC.glBindTexture(this.fadeTexture);
|
||||
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
|
||||
|
||||
// disable mip-mapping since DH is just going to draw straight to the screen
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
{
|
||||
GLMC.glBindTexture(this.fadeTexture);
|
||||
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
|
||||
|
||||
// disable mip-mapping since DH is just going to draw straight to the screen
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
}
|
||||
|
||||
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+40
-31
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShader
|
||||
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
|
||||
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder;
|
||||
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
|
||||
import com.seibel.distanthorizons.core.render.glObject.shader.Shader;
|
||||
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
|
||||
@@ -37,6 +38,7 @@ import com.seibel.distanthorizons.core.util.math.Vec3f;
|
||||
|
||||
/**
|
||||
* Handles rendering the normal LOD terrain.
|
||||
* @see LodQuadBuilder
|
||||
*/
|
||||
public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShaderProgram
|
||||
{
|
||||
@@ -46,16 +48,14 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
||||
public int uCombinedMatrix = -1;
|
||||
public int uModelOffset = -1;
|
||||
public int uWorldYOffset = -1;
|
||||
public int uDitherDhRendering = -1;
|
||||
|
||||
public int uMircoOffset = -1;
|
||||
|
||||
public int uEarthRadius = -1;
|
||||
|
||||
public int uLightMap = -1;
|
||||
|
||||
// Fog/Clip Uniforms
|
||||
// fragment shader uniforms
|
||||
public int uClipDistance = -1;
|
||||
public int uDitherDhRendering = -1;
|
||||
|
||||
// Noise Uniforms
|
||||
public int uNoiseEnabled = -1;
|
||||
@@ -64,7 +64,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
||||
public int uNoiseDropoff = -1;
|
||||
|
||||
// Debug Uniform
|
||||
public int uWhiteWorld = -1;
|
||||
public int uIsWhiteWorld = -1;
|
||||
|
||||
|
||||
|
||||
@@ -76,19 +76,16 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
||||
public DhTerrainShaderProgram()
|
||||
{
|
||||
super(
|
||||
() -> Shader.loadFile(Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get() != 0
|
||||
? "shaders/curve.vert"
|
||||
: "shaders/standard.vert",
|
||||
false, new StringBuilder()).toString(),
|
||||
() -> Shader.loadFile("shaders/flat_shaded.frag", false, new StringBuilder()).toString(),
|
||||
"fragColor", new String[]{"vPosition", "color"});
|
||||
() -> Shader.loadFile("shaders/standard.vert", false, new StringBuilder()).toString(),
|
||||
() -> Shader.loadFile("shaders/flat_shaded.frag", false, new StringBuilder()).toString(),
|
||||
"fragColor", new String[]{"vPosition", "color"});
|
||||
|
||||
this.uCombinedMatrix = this.getUniformLocation("uCombinedMatrix");
|
||||
this.uModelOffset = this.getUniformLocation("uModelOffset");
|
||||
this.uWorldYOffset = this.tryGetUniformLocation("uWorldYOffset");
|
||||
this.uDitherDhRendering = this.tryGetUniformLocation("uDitherDhRendering");
|
||||
this.uWorldYOffset = this.getUniformLocation("uWorldYOffset");
|
||||
this.uDitherDhRendering = this.getUniformLocation("uDitherDhRendering");
|
||||
this.uMircoOffset = this.getUniformLocation("uMircoOffset");
|
||||
this.uEarthRadius = this.tryGetUniformLocation("uEarthRadius");
|
||||
this.uEarthRadius = this.getUniformLocation("uEarthRadius");
|
||||
|
||||
this.uLightMap = this.getUniformLocation("uLightMap");
|
||||
|
||||
@@ -102,7 +99,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
||||
this.uNoiseDropoff = this.getUniformLocation("uNoiseDropoff");
|
||||
|
||||
// Debug Uniform
|
||||
this.uWhiteWorld = this.getUniformLocation("uWhiteWorld");
|
||||
this.uIsWhiteWorld = this.getUniformLocation("uIsWhiteWorld");
|
||||
|
||||
|
||||
// TODO: Add better use of the LODFormat thing
|
||||
@@ -117,10 +114,13 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
||||
}
|
||||
this.vao.bind();
|
||||
|
||||
// TODO comment what each attribute represents
|
||||
this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true)); // 2+2+2+2 // TODO probably color, blockpos
|
||||
this.vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false)); // +4 // TODO ?
|
||||
this.vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true)); // +4 // TODO probably normal index and Iris block ID
|
||||
// short: x, y, z, meta
|
||||
// meta: byte skylight, byte blocklight, byte microOffset
|
||||
this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true));
|
||||
// byte: r, g, b, a
|
||||
this.vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false));
|
||||
// byte: iris material ID, normal index, 2 spacers
|
||||
this.vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true));
|
||||
|
||||
try
|
||||
{
|
||||
@@ -178,12 +178,21 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
||||
// setUniform(skyLightUniform, skyLight);
|
||||
this.setUniform(this.uLightMap, 0); // TODO this should probably be passed in
|
||||
|
||||
if (this.uWorldYOffset != -1) this.setUniform(this.uWorldYOffset, (float) renderParameters.worldYOffset);
|
||||
this.setUniform(this.uWorldYOffset, (float) renderParameters.worldYOffset);
|
||||
|
||||
if (this.uDitherDhRendering != -1) this.setUniform(this.uDitherDhRendering, Config.Client.Advanced.Graphics.Quality.ditherDhFade.get());
|
||||
this.setUniform(this.uDitherDhRendering, Config.Client.Advanced.Graphics.Quality.ditherDhFade.get());
|
||||
|
||||
if (this.uEarthRadius != -1) this.setUniform(this.uEarthRadius,
|
||||
/*6371KM*/ 6371000.0f / Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get());
|
||||
float curveRatio = Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get();
|
||||
if (curveRatio < -1.0f || curveRatio > 1.0f)
|
||||
{
|
||||
curveRatio = /*6371KM*/ 6371000.0f / curveRatio;
|
||||
}
|
||||
else
|
||||
{
|
||||
// disable curvature if the config value is between -1 and 1
|
||||
curveRatio = 0.0f;
|
||||
}
|
||||
this.setUniform(this.uEarthRadius, curveRatio);
|
||||
|
||||
// Noise Uniforms
|
||||
this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get());
|
||||
@@ -192,20 +201,20 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
|
||||
this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get());
|
||||
|
||||
// Debug
|
||||
this.setUniform(this.uWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
|
||||
this.setUniform(this.uIsWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
|
||||
|
||||
// Clip Uniform
|
||||
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks);
|
||||
if (!Config.Client.Advanced.Debugging.lodOnlyMode.get())
|
||||
{
|
||||
// this added value prevents the near clip plane and discard circle from touching, which looks bad
|
||||
dhNearClipDistance += 16f;
|
||||
}
|
||||
float dhNearClipDistance = 0.1f;//RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks);
|
||||
//if (!Config.Client.Advanced.Debugging.lodOnlyMode.get())
|
||||
//{
|
||||
// // this added value prevents the near clip plane and discard circle from touching, which looks bad
|
||||
// dhNearClipDistance += 16f;
|
||||
//}
|
||||
// if the player is very high up and the near clip plane has been modified, disable the distance clipping
|
||||
// we're high enough that nothing will render on top of the player and this can cause issues otherwise
|
||||
if (RenderUtil.getHeightBasedNearClipOverride() != -1)
|
||||
{
|
||||
dhNearClipDistance = 1.0f;
|
||||
dhNearClipDistance = 1.0f; // TODO does this actually disable anything?
|
||||
}
|
||||
this.setUniform(this.uClipDistance, dhNearClipDistance);
|
||||
}
|
||||
|
||||
+11
-9
@@ -88,15 +88,17 @@ public class FogRenderer
|
||||
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fogFramebuffer);
|
||||
|
||||
this.fogTexture = GLMC.glGenTextures();
|
||||
GLMC.glBindTexture(this.fogTexture);
|
||||
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
|
||||
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fogTexture, 0);
|
||||
|
||||
// disable mip-mapping since DH is just going to draw straight to the screen
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
{
|
||||
GLMC.glBindTexture(this.fogTexture);
|
||||
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
|
||||
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fogTexture, 0);
|
||||
|
||||
// disable mip-mapping since DH is just going to draw straight to the screen
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -222,6 +222,8 @@ public class LodRenderer
|
||||
profiler.popPush("LOD Opaque");
|
||||
this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ true);
|
||||
|
||||
DepthCalculator.INSTANCE.trySetDhDepthTexture();
|
||||
|
||||
// custom objects with SSAO
|
||||
if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get())
|
||||
{
|
||||
@@ -434,7 +436,7 @@ public class LodRenderer
|
||||
|
||||
// resize the textures if needed
|
||||
if (MC_RENDER.getTargetFramebufferViewportWidth() != this.textureWidth
|
||||
|| MC_RENDER.getTargetFramebufferViewportHeight() != this.textureHeight)
|
||||
|| MC_RENDER.getTargetFramebufferViewportHeight() != this.textureHeight)
|
||||
{
|
||||
// just resizing the textures doesn't work when Optifine is present,
|
||||
// so recreate the textures with the new size instead
|
||||
@@ -493,12 +495,8 @@ public class LodRenderer
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GLProxy.hasInstance())
|
||||
{
|
||||
// shouldn't normally happen, but just in case
|
||||
LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
|
||||
return false;
|
||||
}
|
||||
// GLProxy should have already been created by this point, but just in case create it now
|
||||
GLProxy.getInstance();
|
||||
|
||||
|
||||
|
||||
@@ -540,7 +538,7 @@ public class LodRenderer
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings( "deprecation" )
|
||||
@SuppressWarnings( "deprecation" ) // done to ignore DhApiColorDepthTextureCreatedEvent
|
||||
private void createAndBindTextures()
|
||||
{
|
||||
int oldWidth = this.textureWidth;
|
||||
|
||||
+3
-1
@@ -30,6 +30,7 @@ public class RenderParams extends DhApiRenderParam
|
||||
|
||||
public IDhClientWorld dhClientWorld;
|
||||
public IDhClientLevel dhClientLevel;
|
||||
/** more specific override of the API value {@link DhApiRenderParam#clientLevelWrapper} */
|
||||
public IClientLevelWrapper clientLevelWrapper;
|
||||
public ILightMapWrapper lightmap;
|
||||
public RenderBufferHandler renderBufferHandler;
|
||||
@@ -56,7 +57,8 @@ public class RenderParams extends DhApiRenderParam
|
||||
RenderUtil.getNearClipPlaneDistanceInBlocks(newPartialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(),
|
||||
newMcProjectionMatrix, newMcModelViewMatrix,
|
||||
RenderUtil.createLodProjectionMatrix(newMcProjectionMatrix, newPartialTicks), RenderUtil.createLodModelViewMatrix(newMcModelViewMatrix),
|
||||
clientLevelWrapper.getMinHeight());
|
||||
clientLevelWrapper.getMinHeight(),
|
||||
clientLevelWrapper);
|
||||
|
||||
|
||||
this.dhClientWorld = SharedApi.tryGetDhClientWorld();
|
||||
|
||||
+10
-8
@@ -88,14 +88,16 @@ public class SSAORenderer
|
||||
GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.ssaoFramebuffer);
|
||||
|
||||
this.ssaoTexture = GLMC.glGenTextures();
|
||||
GLMC.glBindTexture(this.ssaoTexture);
|
||||
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_R16F, width, height, 0, GL32.GL_RED, GL32.GL_HALF_FLOAT, (ByteBuffer) null);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
|
||||
|
||||
// disable mip-mapping since DH is just going to draw straight to the screen
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
{
|
||||
GLMC.glBindTexture(this.ssaoTexture);
|
||||
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_R16F, width, height, 0, GL32.GL_RED, GL32.GL_HALF_FLOAT, (ByteBuffer) null);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
|
||||
|
||||
// disable mip-mapping since DH is just going to draw straight to the screen
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0);
|
||||
GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
}
|
||||
|
||||
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.ssaoTexture, 0);
|
||||
}
|
||||
|
||||
+99
-12
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShad
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
@@ -39,8 +40,7 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -54,6 +54,8 @@ public class BeaconRenderHandler
|
||||
/** how often should we check if a beacon should be culled? */
|
||||
private static final int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
|
||||
|
||||
private static final Comparator<BeaconBeamDTO> NEGATIVE_BLOCKPOS_COMPARATOR = new NegativeInfiniteBlockPosComparator();
|
||||
|
||||
|
||||
|
||||
private final ReentrantLock updateLock = new ReentrantLock();
|
||||
@@ -89,20 +91,74 @@ public class BeaconRenderHandler
|
||||
// render handling //
|
||||
//=================//
|
||||
|
||||
public void startRenderingBeacon(BeaconBeamDTO beacon)
|
||||
public void startRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList, byte detailLevel)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.updateLock.lock();
|
||||
|
||||
if (this.beaconBlockPosSet.add(beacon.blockPos))
|
||||
|
||||
// how wide should each beacon be?
|
||||
int beaconBlockWidth = 1;
|
||||
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
||||
{
|
||||
beaconBlockWidth = DhSectionPos.getBlockWidth(detailLevel);
|
||||
}
|
||||
|
||||
|
||||
ArrayList<BeaconBeamDTO> sortedBeaconList = new ArrayList<>(beaconList);
|
||||
|
||||
// merge distant beams if requested
|
||||
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
|
||||
{
|
||||
// sort beacons from neg inf -> pos inf
|
||||
// so we can consistently merge adjacent beacons
|
||||
sortedBeaconList.sort(NEGATIVE_BLOCKPOS_COMPARATOR);
|
||||
|
||||
// go through each beacon...
|
||||
for (int outerIndex = 0; outerIndex < sortedBeaconList.size(); outerIndex++)
|
||||
{
|
||||
BeaconBeamDTO outerBeacon = sortedBeaconList.get(outerIndex);
|
||||
DhBlockPos outerBlockPos = outerBeacon.blockPos;
|
||||
|
||||
// ...and remove any beacons that are within the block width to prevent overlaps
|
||||
for (int mergeIndex = outerIndex + 1; mergeIndex < sortedBeaconList.size(); mergeIndex++)
|
||||
{
|
||||
BeaconBeamDTO beaconToMerge = sortedBeaconList.get(mergeIndex);
|
||||
DhBlockPos mergeBlockPos = beaconToMerge.blockPos;
|
||||
|
||||
int xDiff = mergeBlockPos.getX() - outerBlockPos.getX();
|
||||
int zDiff = mergeBlockPos.getZ() - outerBlockPos.getZ();
|
||||
|
||||
// merge (remove) this beacon if
|
||||
// it's close to the outer beacon
|
||||
if (xDiff < beaconBlockWidth
|
||||
&& zDiff < beaconBlockWidth)
|
||||
{
|
||||
sortedBeaconList.remove(mergeIndex);
|
||||
mergeIndex--; // minus 1 so we don't go past the end of the array when incrementing in the for loop up top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// add each beacon to the renderer
|
||||
for (int i = 0; i < sortedBeaconList.size(); i++)
|
||||
{
|
||||
BeaconBeamDTO beacon = sortedBeaconList.get(i);
|
||||
if (!this.beaconBlockPosSet.add(beacon.blockPos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int maxBeaconBeamHeight = Config.Client.Advanced.Graphics.GenericRendering.beaconRenderHeight.get();
|
||||
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
|
||||
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
|
||||
new DhApiVec3d(beacon.blockPos.getX() + 1, maxBeaconBeamHeight, beacon.blockPos.getZ() + 1),
|
||||
beacon.color,
|
||||
EDhApiBlockMaterial.ILLUMINATED
|
||||
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
|
||||
new DhApiVec3d(beacon.blockPos.getX() + beaconBlockWidth, maxBeaconBeamHeight, beacon.blockPos.getZ() + beaconBlockWidth),
|
||||
beacon.color,
|
||||
EDhApiBlockMaterial.ILLUMINATED
|
||||
);
|
||||
|
||||
this.beaconBoxGroup.add(beaconBox);
|
||||
@@ -116,19 +172,26 @@ public class BeaconRenderHandler
|
||||
}
|
||||
}
|
||||
|
||||
public void stopRenderingBeaconAtPos(DhBlockPos beaconPos)
|
||||
public void stopRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.updateLock.lock();
|
||||
|
||||
if (this.beaconBlockPosSet.remove(beaconPos))
|
||||
for (int i = 0; i < beaconList.size(); i++)
|
||||
{
|
||||
BeaconBeamDTO beacon = beaconList.get(i);
|
||||
DhBlockPos beaconPos = beacon.blockPos;
|
||||
if (!this.beaconBlockPosSet.remove(beaconPos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Predicate<DhApiRenderableBox> removePredicate = (DhApiRenderableBox box) ->
|
||||
{
|
||||
return box.minPos.x == beaconPos.getX()
|
||||
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
|
||||
&& box.minPos.z == beaconPos.getZ();
|
||||
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
|
||||
&& box.minPos.z == beaconPos.getZ();
|
||||
};
|
||||
this.beaconBoxGroup.removeIf(removePredicate);
|
||||
this.fullBeaconBoxList.removeIf(removePredicate);
|
||||
@@ -255,4 +318,28 @@ public class BeaconRenderHandler
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
private static class NegativeInfiniteBlockPosComparator implements Comparator<BeaconBeamDTO>
|
||||
{
|
||||
@Override
|
||||
public int compare(BeaconBeamDTO beacon1, BeaconBeamDTO beacon2)
|
||||
{
|
||||
DhBlockPos blockPos1 = beacon1.blockPos;
|
||||
DhBlockPos blockPos2 = beacon2.blockPos;
|
||||
|
||||
// sort by X, then by Z
|
||||
if (blockPos1.getX() != blockPos2.getX())
|
||||
{
|
||||
return Integer.compare(blockPos1.getX(), blockPos2.getX());
|
||||
}
|
||||
return Integer.compare(blockPos1.getZ(), blockPos2.getZ());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -493,7 +493,7 @@ public class CloudRenderHandler
|
||||
|
||||
private static boolean[][] getCloudsFromTexture() throws FileNotFoundException, IOException
|
||||
{
|
||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
final ClassLoader loader = CloudRenderHandler.class.getClassLoader();
|
||||
|
||||
boolean[][] whitePixels = null;
|
||||
try(InputStream imageInputStream = loader.getResourceAsStream(CLOUD_RESOURCE_TEXTURE_PATH))
|
||||
|
||||
+1
-1
@@ -337,7 +337,7 @@ public class RenderableBoxGroup
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
GLProxy.getInstance().queueRunningOnRenderThread(() ->
|
||||
GLProxy.queueRunningOnRenderThread(() ->
|
||||
{
|
||||
if (this.instanceChunkPosVbo != 0)
|
||||
{
|
||||
|
||||
+8
@@ -178,6 +178,14 @@ public class FogShader extends AbstractShaderRenderer
|
||||
float farFogMax = Config.Client.Advanced.Graphics.Fog.farFogMax.get().floatValue();
|
||||
float farFogDensity = Config.Client.Advanced.Graphics.Fog.farFogDensity.get().floatValue();
|
||||
|
||||
// override fog if underwater
|
||||
if (MC_RENDER.isFogStateSpecial())
|
||||
{
|
||||
// hide everything behind fog
|
||||
farFogStart = 0.0f;
|
||||
farFogEnd = 0.0f;
|
||||
}
|
||||
|
||||
this.shader.setUniform(this.uFarFogStart, farFogStart);
|
||||
this.shader.setUniform(this.uFarFogLength, farFogEnd - farFogStart);
|
||||
this.shader.setUniform(this.uFarFogMin, farFogMin);
|
||||
|
||||
+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.SSAORenderer;
|
||||
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.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
/**
|
||||
@@ -58,6 +60,7 @@ public class SSAOShader extends AbstractShaderRenderer
|
||||
public int uMinLight;
|
||||
public int uBias;
|
||||
public int uDepthMap;
|
||||
public int uFadeDistanceInBlocks;
|
||||
|
||||
|
||||
|
||||
@@ -81,6 +84,7 @@ public class SSAOShader extends AbstractShaderRenderer
|
||||
this.uMinLight = this.shader.getUniformLocation("uMinLight");
|
||||
this.uBias = this.shader.getUniformLocation("uBias");
|
||||
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());
|
||||
|
||||
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;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
|
||||
@@ -114,7 +115,7 @@ public class VanillaFadeShader extends AbstractShaderRenderer
|
||||
|
||||
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(partialTicks);
|
||||
// this added value prevents the near clip plane and discard circle from touching, which looks bad
|
||||
dhNearClipDistance += 16f;
|
||||
//dhNearClipDistance += 16f;
|
||||
|
||||
// measured in blocks
|
||||
// these multipliers in James' tests should provide a fairly smooth transition
|
||||
|
||||
@@ -173,7 +173,7 @@ public class DatabaseUpdater
|
||||
/** @throws NullPointerException if any of the script files failed to be read. */
|
||||
private static ArrayList<SqlScript> getAutoUpdateScripts() throws NullPointerException, IOException
|
||||
{
|
||||
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
final ClassLoader loader = DatabaseUpdater.class.getClassLoader();
|
||||
|
||||
|
||||
// get the script list
|
||||
|
||||
@@ -76,6 +76,9 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>, INetworkObject
|
||||
public void close()
|
||||
{ /* no closing needed */ }
|
||||
|
||||
@Override
|
||||
public String toString() { return this.blockPos + " " + this.color; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.seibel.distanthorizons.core.util;
|
||||
|
||||
import org.lwjgl.util.tinyfd.TinyFileDialogs;
|
||||
|
||||
/**
|
||||
* Should be used instead of the direct call to {@link TinyFileDialogs}
|
||||
* so we can run additional validation and/or string cleanup.
|
||||
* Otherwise, we may get error messages back.
|
||||
*
|
||||
* @see TinyFileDialogs
|
||||
*/
|
||||
public class NativeDialogUtil
|
||||
{
|
||||
/**
|
||||
* @param dialogType the dialog type. One of:<br><table><tr><td>"ok"</td><td>"okcancel"</td><td>"yesno"</td><td>"yesnocancel"</td></tr></table>
|
||||
* @param iconType the icon type. One of:<br><table><tr><td>"info"</td><td>"warning"</td><td>"error"</td><td>"question"</td></tr></table>
|
||||
*/
|
||||
public static boolean showDialog(String title, String message, String dialogType, String iconType)
|
||||
{
|
||||
// Tinyfd doesn't support the following characters, attempting to display them will cause the message
|
||||
// to be replaced with an error message
|
||||
String unsafeCharsRegex = "['\"`]";
|
||||
|
||||
title = title.replaceAll(unsafeCharsRegex, "`");
|
||||
message = message.replaceAll(unsafeCharsRegex, "`");
|
||||
|
||||
return TinyFileDialogs.tinyfd_messageBox(title, message, dialogType, iconType, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,8 +19,10 @@
|
||||
|
||||
package com.seibel.distanthorizons.core.util;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.config.Config;
|
||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||
import com.seibel.distanthorizons.core.render.renderer.DepthCalculator;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
@@ -127,13 +129,16 @@ public class RenderUtil
|
||||
}
|
||||
public static float getNearClipPlaneInBlocksForFading(float partialTicks)
|
||||
{
|
||||
float overdraw = getAutoOverdrawPrevention();
|
||||
return getNearClipPlaneDistanceInBlocks(partialTicks, overdraw);
|
||||
//float overdraw = getAutoOverdrawPrevention();
|
||||
//return getNearClipPlaneDistanceInBlocks(partialTicks, overdraw);
|
||||
|
||||
return DepthCalculator.INSTANCE.actualMcBlockDistance;
|
||||
}
|
||||
private static float getNearClipPlaneDistanceInBlocks(float partialTicks, float overdrawPreventionPercent)
|
||||
{
|
||||
int chunkRenderDistance = MC_RENDER.getRenderDistance();
|
||||
int vanillaBlockRenderedDistance = chunkRenderDistance * LodUtil.CHUNK_WIDTH;
|
||||
//float chunkRenderDistance = ClientApi.actualMcBlockDistance / 16.0f;
|
||||
float vanillaBlockRenderedDistance = chunkRenderDistance * LodUtil.CHUNK_WIDTH;
|
||||
|
||||
float nearClipPlane;
|
||||
if (Config.Client.Advanced.Debugging.lodOnlyMode.get())
|
||||
|
||||
@@ -1,102 +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.util.objects;
|
||||
|
||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.coreapi.ModInfo;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class EventLoop implements AutoCloseable
|
||||
{
|
||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||
|
||||
private final boolean PAUSE_ON_ERROR = ModInfo.IS_DEV_BUILD;
|
||||
private final ExecutorService executorService;
|
||||
|
||||
private final Runnable runnable;
|
||||
/** the future related to the given runnable */
|
||||
private CompletableFuture<Void> runnableFuture;
|
||||
|
||||
private boolean isRunning = true;
|
||||
|
||||
|
||||
|
||||
public EventLoop(ExecutorService executorService, Runnable runnable)
|
||||
{
|
||||
this.executorService = executorService;
|
||||
this.runnable = runnable;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void tick()
|
||||
{
|
||||
if (runnableFuture != null && runnableFuture.isDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
runnableFuture.join();
|
||||
}
|
||||
catch (CompletionException ce)
|
||||
{
|
||||
LOGGER.error("Uncaught exception in event loop", ce.getCause());
|
||||
if (PAUSE_ON_ERROR)
|
||||
{
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Exception in event loop", e);
|
||||
if (PAUSE_ON_ERROR)
|
||||
{
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
runnableFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (runnableFuture == null && isRunning)
|
||||
{
|
||||
runnableFuture = CompletableFuture.runAsync(runnable, executorService);
|
||||
}
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
if (runnableFuture != null)
|
||||
{
|
||||
runnableFuture.cancel(true);
|
||||
}
|
||||
|
||||
runnableFuture = null;
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
public boolean isRunning() { return runnableFuture != null && !runnableFuture.isDone(); }
|
||||
|
||||
}
|
||||
+36
-50
@@ -31,12 +31,10 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||
import com.seibel.distanthorizons.core.util.gridList.MovableGridRingList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
/**
|
||||
@@ -98,9 +96,27 @@ public class QuadTree<T>
|
||||
// getters and setters //
|
||||
//=====================//
|
||||
|
||||
/** @return the value at the given section position. Null will be returned if the value is missing or the position is out of bounds. */
|
||||
@Nullable
|
||||
public final T tryGetValue(long pos)
|
||||
{
|
||||
QuadNode<T> node = this.tryGetNode(pos);
|
||||
if (node != null)
|
||||
{
|
||||
return node.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return the node at the given section position, null if out of bounds */
|
||||
@Nullable
|
||||
public final QuadNode<T> tryGetNode(long pos) { return this.getOrSetNode(pos, false, null, false); }
|
||||
|
||||
|
||||
/** @return the node at the given section position */
|
||||
@Nullable
|
||||
public final QuadNode<T> getNode(long pos) throws IndexOutOfBoundsException { return this.getOrSetNode(pos, false, null, true); }
|
||||
|
||||
/** @return the value at the given section position */
|
||||
@Nullable
|
||||
public final T getValue(long pos) throws IndexOutOfBoundsException
|
||||
@@ -122,16 +138,24 @@ public class QuadTree<T>
|
||||
return previousValue;
|
||||
}
|
||||
|
||||
/** @param runBoundaryChecks should only ever be set to true internally for removing out of bound nodes */
|
||||
/** @param throwIfOutOfBounds if false returns null */
|
||||
@Nullable
|
||||
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean runBoundaryChecks) throws IndexOutOfBoundsException
|
||||
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean throwIfOutOfBounds) throws IndexOutOfBoundsException
|
||||
{
|
||||
if (runBoundaryChecks && !this.isSectionPosInBounds(pos))
|
||||
if (!this.isSectionPosInBounds(pos))
|
||||
{
|
||||
int radius = this.diameterInBlocks() / 2;
|
||||
DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
|
||||
DhBlockPos2D maxPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
|
||||
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeLeafDetailLevel + ", max detail level: " + this.treeRootDetailLevel + ". Given Position: [" + DhSectionPos.toString(pos) + "] = block pos: " + DhSectionPos.convertToDetailLevel(pos, LodUtil.BLOCK_DETAIL_LEVEL));
|
||||
// how should out-of-bounds positions be handled?
|
||||
if (throwIfOutOfBounds)
|
||||
{
|
||||
int radius = this.diameterInBlocks() / 2;
|
||||
DhBlockPos2D minBlockPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
|
||||
DhBlockPos2D maxBlockPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
|
||||
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min block pos: [" + minBlockPos + "], max block pos: [" + maxBlockPos + "], leaf detail level: [" + this.treeLeafDetailLevel + "], root detail level: [" + this.treeRootDetailLevel + "]. Requested section pos: [" + DhSectionPos.toString(pos) + "].");
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -278,46 +302,6 @@ public class QuadTree<T>
|
||||
removedItemConsumer.accept(quadNode.value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// // remove out of bound nodes and clean up empty nodes
|
||||
// // Note: this will iterate over a lot of unnecessary nodes, hopefully speed won't be an issue
|
||||
// Iterator<DhSectionPos> rootNodePosIterator = this.rootNodePosIterator();
|
||||
// while (rootNodePosIterator.hasNext())
|
||||
// {
|
||||
// // get the root node (regular nodeIterators won't return them if they are out of bounds)
|
||||
// DhSectionPos rootPos = rootNodePosIterator.next();
|
||||
// QuadNode<T> rootNode = this.getOrSetNode(rootPos, false, null, false);
|
||||
// if (rootNode == null)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// // remove any child nodes that are out of bounds
|
||||
// Iterator<QuadNode<T>> nodeIterator = this.nodeIterator();
|
||||
// while (nodeIterator.hasNext())
|
||||
// {
|
||||
// QuadNode<T> node = nodeIterator.next();
|
||||
// if(!this.isSectionPosInBounds(node.sectionPos))
|
||||
// {
|
||||
// // node is out of bounds
|
||||
//
|
||||
// // FIXME(?) this appears to potentially return large nodes that are partially or entirely in bounds
|
||||
//
|
||||
// if (node.getNonNullChildCount() == 0)
|
||||
// {
|
||||
// // no child nodes, can be safely removed
|
||||
// nodeIterator.remove();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // node can't be removed, but its value can be set to null
|
||||
// node.value = null;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
|
||||
@@ -544,7 +528,9 @@ public class QuadTree<T>
|
||||
&& this.rootNodeIterator.hasNext())
|
||||
{
|
||||
long sectionPos = this.rootNodeIterator.nextLong();
|
||||
QuadNode<T> rootNode = QuadTree.this.getNode(sectionPos);
|
||||
|
||||
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
|
||||
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
|
||||
if (rootNode != null)
|
||||
{
|
||||
nodeIterator = this.onlyReturnLeaves ? rootNode.getLeafNodeIterator() : rootNode.getNodeIterator(this.stopIteratingFunc);
|
||||
|
||||
+5
-6
@@ -30,7 +30,7 @@ import java.util.function.Consumer;
|
||||
public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
{
|
||||
/** lowest numerical value, inclusive */
|
||||
private final byte highestDetailLevel;
|
||||
private final byte leafDetailLevel;
|
||||
|
||||
|
||||
private final Queue<QuadNode<T>> validNodesForDetailLevel = new ArrayDeque<>();
|
||||
@@ -48,8 +48,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
{
|
||||
this.onlyReturnLeafValues = onlyReturnLeafValues;
|
||||
this.stopIteratingFunc = stopIteratingFunc;
|
||||
// TODO the naming conversion for these are flipped in a lot of places
|
||||
this.highestDetailLevel = rootNode.parentTreeLeafDetailLevel;
|
||||
this.leafDetailLevel = rootNode.parentTreeLeafDetailLevel;
|
||||
this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos);
|
||||
|
||||
|
||||
@@ -110,9 +109,9 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
@Override
|
||||
public QuadNode<T> next()
|
||||
{
|
||||
if (this.iteratorDetailLevel < this.highestDetailLevel)
|
||||
if (this.iteratorDetailLevel < this.leafDetailLevel)
|
||||
{
|
||||
throw new NoSuchElementException("Highest detail level reached [" + this.highestDetailLevel + "].");
|
||||
throw new NoSuchElementException("Leaf detail level reached [" + this.leafDetailLevel + "].");
|
||||
}
|
||||
if (this.iteratorNodeQueue.size() == 0)
|
||||
{
|
||||
@@ -133,7 +132,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
|
||||
this.iteratorDetailLevel--;
|
||||
// only continue if we can go down farther
|
||||
if (this.iteratorDetailLevel >= this.highestDetailLevel)
|
||||
if (this.iteratorDetailLevel >= this.leafDetailLevel)
|
||||
{
|
||||
Queue<QuadNode<T>> parentNodes = new LinkedList<>(this.validNodesForDetailLevel);
|
||||
this.validNodesForDetailLevel.clear();
|
||||
|
||||
+7
-1
@@ -69,10 +69,14 @@ public class ThreadPoolUtil
|
||||
@Nullable
|
||||
public static ThreadPoolExecutor getBeaconCullingExecutor() { return beaconCullingThreadPool; }
|
||||
|
||||
// The main distinction between these thread pools is that one for compression has multiple threads and client handler is single-threaded
|
||||
private static PriorityTaskPicker.Executor networkCompressionThreadPool;
|
||||
@Nullable
|
||||
public static PriorityTaskPicker.Executor getNetworkCompressionExecutor() { return networkCompressionThreadPool; }
|
||||
|
||||
private static ThreadPoolExecutor networkClientHandlerThreadPool;
|
||||
@Nullable
|
||||
public static ThreadPoolExecutor networkClientHandlerExecutor() { return networkClientHandlerThreadPool; }
|
||||
|
||||
|
||||
public static final String FULL_DATA_MIGRATION_THREAD_NAME = "Full Data Migration";
|
||||
@@ -103,7 +107,8 @@ public class ThreadPoolUtil
|
||||
}
|
||||
taskPicker = new PriorityTaskPicker();
|
||||
|
||||
networkCompressionThreadPool = taskPicker.createExecutor("Network");
|
||||
networkCompressionThreadPool = taskPicker.createExecutor("Network Compression");
|
||||
networkClientHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Network Client Handler");
|
||||
fileHandlerThreadPool = taskPicker.createExecutor("IO");
|
||||
renderSectionLoadThreadPool = taskPicker.createExecutor("Render Loader");
|
||||
chunkToLodBuilderThreadPool = taskPicker.createExecutor("LOD Builder");
|
||||
@@ -133,6 +138,7 @@ public class ThreadPoolUtil
|
||||
public static void shutdownThreadPools()
|
||||
{
|
||||
// standalone threads
|
||||
networkClientHandlerThreadPool.shutdownNow();
|
||||
taskPicker.shutdownNow();
|
||||
beaconCullingThreadPool.shutdown();
|
||||
fullDataMigrationThreadPool.shutdown();
|
||||
|
||||
@@ -20,28 +20,23 @@
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
|
||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.EventLoop;
|
||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld
|
||||
{
|
||||
private final Set<DhClientServerLevel> dhLevels = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
public ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client Server World Ticker", 2);
|
||||
public EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick); //TODO: Rate-limit the loop
|
||||
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
|
||||
|
||||
|
||||
|
||||
@@ -53,6 +48,15 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
||||
{
|
||||
super(EWorldEnvironment.CLIENT_SERVER);
|
||||
LOGGER.info("Started DhWorld of type " + this.environment);
|
||||
|
||||
this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
DhClientServerWorld.this.dhLevels.forEach(DhClientServerLevel::clientTick);
|
||||
}
|
||||
}, 0, IDhClientWorld.TICK_RATE_IN_MS);
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +83,7 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
||||
LOGGER.fatal("Failed to load client-server level, error: ["+e.getMessage()+"].", e);
|
||||
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
|
||||
"\u00A7c" + "Distant Horizons: ClientServer level loading failed." + "\u00A7r \n" +
|
||||
MinecraftTextFormat.RED + "Distant Horizons: ClientServer level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Unable to load level ["+levelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
|
||||
return null;
|
||||
@@ -136,19 +140,6 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
||||
}
|
||||
}
|
||||
|
||||
private void _clientTick()
|
||||
{
|
||||
//LOGGER.info("Client world tick with {} levels", levels.size());
|
||||
this.dhLevels.forEach(DhClientServerLevel::clientTick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clientTick()
|
||||
{
|
||||
//LOGGER.info("Client world tick");
|
||||
this.eventLoop.tick();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
@@ -194,8 +185,8 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
|
||||
|
||||
|
||||
this.dhLevelByLevelWrapper.clear();
|
||||
this.eventLoop.close();
|
||||
LOGGER.info("Closed DhWorld of type " + this.environment);
|
||||
this.clientTickTimer.cancel();
|
||||
LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,21 +20,22 @@
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
|
||||
import com.seibel.distanthorizons.core.level.DhClientLevel;
|
||||
import com.seibel.distanthorizons.core.level.IDhLevel;
|
||||
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
|
||||
import com.seibel.distanthorizons.core.util.ThreadUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.EventLoop;
|
||||
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
{
|
||||
@@ -42,8 +43,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
public final ClientOnlySaveStructure saveStructure;
|
||||
public final ClientNetworkState networkState = new ClientNetworkState();
|
||||
|
||||
public final ExecutorService dhTickerThread = ThreadUtil.makeSingleThreadPool("Client World Ticker");
|
||||
public final EventLoop eventLoop = new EventLoop(this.dhTickerThread, this::_clientTick);
|
||||
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
|
||||
|
||||
|
||||
|
||||
@@ -59,6 +59,15 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
this.levels = new ConcurrentHashMap<>();
|
||||
|
||||
LOGGER.info("Started DhWorld of type " + this.environment);
|
||||
|
||||
this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
DhClientWorld.this.levels.values().forEach(DhClientLevel::clientTick);
|
||||
}
|
||||
}, 0, IDhClientWorld.TICK_RATE_IN_MS);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,9 +95,9 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
{
|
||||
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
|
||||
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
|
||||
"\u00A7c" + "Distant Horizons: Client level loading failed." + "\u00A7r \n" +
|
||||
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(
|
||||
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -127,11 +136,6 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
}
|
||||
}
|
||||
|
||||
private void _clientTick() { this.levels.values().forEach(DhClientLevel::clientTick); }
|
||||
|
||||
@Override
|
||||
public void clientTick() { this.eventLoop.tick(); }
|
||||
|
||||
@Override
|
||||
public void addDebugMenuStringsToList(List<String> messageList)
|
||||
{
|
||||
@@ -143,7 +147,6 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
public void close()
|
||||
{
|
||||
this.networkState.close();
|
||||
this.dhTickerThread.shutdownNow();
|
||||
|
||||
ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>();
|
||||
for (DhClientLevel dhClientLevel : this.levels.values())
|
||||
@@ -175,7 +178,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
|
||||
}
|
||||
|
||||
this.levels.clear();
|
||||
this.eventLoop.close();
|
||||
this.clientTickTimer.cancel();
|
||||
LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.seibel.distanthorizons.core.world;
|
||||
|
||||
import com.seibel.distanthorizons.core.api.internal.ClientApi;
|
||||
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
|
||||
import com.seibel.distanthorizons.core.level.DhServerLevel;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
|
||||
@@ -62,9 +63,9 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
|
||||
{
|
||||
LOGGER.fatal("Failed to load server level, error: ["+e.getMessage()+"].", e);
|
||||
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
|
||||
"\u00A7c" + "Distant Horizons: Server level loading failed." + "\u00A7r \n" +
|
||||
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
ClientApi.INSTANCE.showChatMessageNextFrame(
|
||||
MinecraftTextFormat.RED + "Distant Horizons: Server level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
|
||||
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
|
||||
|
||||
public interface IDhClientWorld extends IDhWorld
|
||||
{
|
||||
void clientTick();
|
||||
/** how long in between client ticks in milliseconds */
|
||||
long TICK_RATE_IN_MS = 100L;
|
||||
|
||||
default IDhClientLevel getOrLoadClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getOrLoadLevel(levelWrapper); }
|
||||
default IDhClientLevel getClientLevel(ILevelWrapper levelWrapper) { return (IDhClientLevel) this.getLevel(levelWrapper); }
|
||||
|
||||
+5
-2
@@ -63,6 +63,8 @@ public interface IChunkWrapper extends IBindable
|
||||
*/
|
||||
int getMaxNonEmptyHeight();
|
||||
|
||||
void createDhHeightMaps();
|
||||
|
||||
/** @return The highest y position of a solid block at the given relative chunk position. */
|
||||
int getSolidHeightMapValue(int xRel, int zRel);
|
||||
/**
|
||||
@@ -126,8 +128,6 @@ public interface IChunkWrapper extends IBindable
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
IChunkWrapper copy();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -63,7 +63,9 @@ public interface IMinecraftRenderWrapper extends IBindable
|
||||
/** @return -1 if no valid framebuffer is available yet */
|
||||
int getTargetFramebuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances)
|
||||
// Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
|
||||
/** @return -1 if there was an issue or no texture exists */
|
||||
int getDepthTextureId();
|
||||
/** @return -1 if there was an issue or no texture exists */
|
||||
int getColorTextureId();
|
||||
int getTargetFramebufferViewportWidth();
|
||||
int getTargetFramebufferViewportHeight();
|
||||
|
||||
+4
-2
@@ -52,6 +52,10 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
|
||||
@Override
|
||||
IDimensionTypeWrapper getDimensionType();
|
||||
|
||||
/**
|
||||
* Includes both the namespace and name. <br>
|
||||
* example: "minecraft:overworld"
|
||||
*/
|
||||
@Override
|
||||
String getDimensionName();
|
||||
|
||||
@@ -81,8 +85,6 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
|
||||
@Override
|
||||
int getMinHeight();
|
||||
|
||||
default IChunkWrapper tryGetChunk(DhChunkPos pos) { return null; }
|
||||
|
||||
/** Fired when the level is being unloaded. Doesn't unload the level. */
|
||||
void onUnload();
|
||||
|
||||
|
||||
@@ -84,7 +84,13 @@
|
||||
"Show The Options Button",
|
||||
"distanthorizons.config.client.optionsButton.@tooltip":
|
||||
"Show the config button to the left of the fov button",
|
||||
|
||||
|
||||
"distanthorizons.config.client.dynamicFadeUseOpaqueMcDepth":
|
||||
"Dynamic Fade Use MC Opaque Depth",
|
||||
"distanthorizons.config.client.dynamicFadeExportPath":
|
||||
"Dynamic Fade Export Path",
|
||||
"distanthorizons.config.client.dynamicFadeExportPath.@tooltip":
|
||||
"Press 'p' to export the depth textures for troubleshooting",
|
||||
|
||||
|
||||
"distanthorizons.config.client.advanced":
|
||||
@@ -208,6 +214,10 @@
|
||||
"Blur Radius",
|
||||
"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.",
|
||||
"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",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip":
|
||||
"Sets the maximum height at which beacons will render. \nThis will only affect new beacons coming into LOD render distance. \nBeacons currently visible in LOD chunks will not be affected.",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons":
|
||||
"Expand Distant Beacons",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons.@tooltip":
|
||||
"If true LOD beacon beams will be rendered wider at extreme distances, \nmaking them easier to see. \nIf false all LOD beacon beams will only ever be 1 block wide.",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering.@tooltip":
|
||||
"If true LOD beacon beams will be rendered.",
|
||||
"distanthorizons.config.client.advanced.graphics.genericRendering.enableCloudRendering":
|
||||
@@ -396,14 +410,13 @@
|
||||
"Experimental",
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio":
|
||||
"Earth Curve Ratio §6(EXPERIMENTAL)§r",
|
||||
"Earth Curve Ratio",
|
||||
"distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio.@tooltip":
|
||||
"A value of 1 is equivalent to the curvature of Earth in real life. \nThe minimum accepted value is 50 and the maximum value is 5000. \nEverything between 1 and 49 will be rounded up to 50.",
|
||||
|
||||
"distanthorizons.config.client.advanced.graphics.experimental.onlyLoadCenterLods":
|
||||
"Only load center LODs",
|
||||
"distanthorizons.config.client.advanced.graphics.experimental.onlyLoadCenterLods.@tooltip":
|
||||
"Skips loading adjacent LODs to significantly reduce load times (~5x)\nbut causes lighting on LOD borders to appear as full-bright\nand other graphical bugs.\n",
|
||||
"distanthorizons.config.client.advanced.graphics.experimental.ignoredDimensionCsv":
|
||||
"Ignored Dimension CSV",
|
||||
"distanthorizons.config.client.advanced.graphics.experimental.ignoredDimensionCsv.@tooltip":
|
||||
"A comma separated list of dimension resource locations where DH won't render. Example: \"minecraft:the_nether,minecraft:the_end\" \n\nNote: \nSome DH settings will be disabled and/or changed to improve \nvisuals when DH rendering is disabled.",
|
||||
|
||||
|
||||
|
||||
@@ -714,6 +727,8 @@
|
||||
"OpenGL Events - Chat",
|
||||
"distanthorizons.config.common.logging.logNetworkEventToFile":
|
||||
"Network Events - File",
|
||||
"distanthorizons.config.common.logging.logConnectionConfigChangesToFile":
|
||||
"Network Connection Config Changes - File",
|
||||
|
||||
"distanthorizons.config.common.logging.warning":
|
||||
"Warnings",
|
||||
@@ -733,6 +748,8 @@
|
||||
"Show Slow World Gen Warnings",
|
||||
"distanthorizons.config.common.logging.warning.showModCompatibilityWarningsOnStartup":
|
||||
"Show Mod Compatibility Warnings",
|
||||
"distanthorizons.config.common.logging.warning.logGarbageCollectorWarning":
|
||||
"Log Garbage Collector Warning",
|
||||
|
||||
|
||||
|
||||
|
||||
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 mat4 uInvProj;
|
||||
uniform mat4 uProj;
|
||||
uniform float uFadeDistanceInBlocks;
|
||||
|
||||
const float EPSILON = 1.e-6;
|
||||
const float GOLDEN_ANGLE = 2.39996323;
|
||||
@@ -99,16 +100,30 @@ void main()
|
||||
{
|
||||
vec3 viewPos = calcViewPosition(vec3(TexCoord, fragmentDepth));
|
||||
|
||||
#ifdef GL_ARB_derivative_control
|
||||
// Get higher precision derivatives when available
|
||||
vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz));
|
||||
#else
|
||||
vec3 viewNormal = cross(dFdx(viewPos.xyz), dFdy(viewPos.xyz));
|
||||
#endif
|
||||
// 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
|
||||
// Get higher precision derivatives when available
|
||||
vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz));
|
||||
#else
|
||||
vec3 viewNormal = cross(dFdx(viewPos.xyz), dFdy(viewPos.xyz));
|
||||
#endif
|
||||
|
||||
viewNormal = normalize(viewNormal);
|
||||
|
||||
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);
|
||||
viewNormal = normalize(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);
|
||||
|
||||
@@ -8,37 +8,37 @@ out vec4 vertexColor;
|
||||
out vec3 vertexWorldPos;
|
||||
out float vertexYPos;
|
||||
|
||||
uniform bool uWhiteWorld;
|
||||
uniform bool uIsWhiteWorld;
|
||||
|
||||
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 curve.vert should be merged together to prevent inconsistencies between the two
|
||||
*
|
||||
* Vertex Shader
|
||||
*
|
||||
* author: James Seibel
|
||||
* updated: TomTheFurry
|
||||
* author: TomTheFurry
|
||||
* author: stduhpf
|
||||
* updated: coolGi
|
||||
* version: 2023-6-25
|
||||
*
|
||||
* version: 2025-12-22
|
||||
*/
|
||||
void main()
|
||||
{
|
||||
vPos = vPosition; // This is so it can be passed to the fragment shader
|
||||
|
||||
|
||||
vertexWorldPos = vPosition.xyz + uModelOffset;
|
||||
|
||||
|
||||
vertexYPos = vPosition.y + uWorldYOffset;
|
||||
|
||||
|
||||
uint meta = vPosition.a;
|
||||
|
||||
|
||||
uint mirco = (meta & 0xFF00u) >> 8u; // mirco offset which is a xyz 2bit value
|
||||
// 0b00 = no offset
|
||||
// 0b01 = positive offset
|
||||
@@ -46,21 +46,34 @@ void main()
|
||||
// format is: 0b00zzyyxx
|
||||
float mx = (mirco & 1u)!=0u ? uMircoOffset : 0.0;
|
||||
mx = (mirco & 2u)!=0u ? -mx : mx;
|
||||
float my = (mirco & 4u)!=0u ? uMircoOffset : 0.0;
|
||||
my = (mirco & 8u)!=0u ? -my : my;
|
||||
//float my = (mirco & 4u)!=0u ? uMircoOffset : 0.0;
|
||||
//my = (mirco & 8u)!=0u ? -my : my;
|
||||
float mz = (mirco & 16u)!=0u ? uMircoOffset : 0.0;
|
||||
mz = (mirco & 32u)!=0u ? -mz : mz;
|
||||
|
||||
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)
|
||||
vertexWorldPos.x += mx;
|
||||
//vertexWorldPos.y += my;
|
||||
vertexWorldPos.z += mz;
|
||||
|
||||
// apply the earth curvature if needed
|
||||
if (uEarthRadius < -1.0f || uEarthRadius > 1.0f)
|
||||
{
|
||||
// vertex transformation logic - stduhpf
|
||||
float localRadius = uEarthRadius + vertexYPos;
|
||||
float phi = length(vertexWorldPos.xz) / localRadius;
|
||||
vertexWorldPos.y += (cos(phi) - 1.0) * localRadius;
|
||||
vertexWorldPos.xz = vertexWorldPos.xz * sin(phi) / phi;
|
||||
}
|
||||
|
||||
uint lights = meta & 0xFFu;
|
||||
float skyLight = (float(lights/16u)+0.5) / 16.0;
|
||||
float blockLight = (mod(float(lights), 16.0)+0.5) / 16.0;
|
||||
vertexColor = vec4(texture(uLightMap, vec2(skyLight, blockLight)).xyz, 1.0);
|
||||
|
||||
if (!uIsWhiteWorld)
|
||||
{
|
||||
vertexColor *= color;
|
||||
}
|
||||
|
||||
gl_Position = uCombinedMatrix * vec4(vertexWorldPos + vec3(mx, 0, mz), 1.0);
|
||||
|
||||
gl_Position = uCombinedMatrix * vec4(vertexWorldPos, 1.0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user