Compare commits

...

54 Commits

Author SHA1 Message Date
James Seibel 7c4ac2bd7e remove dev from version number 2025-12-23 22:55:40 -06:00
James Seibel 8c13c2cf47 Fix toggling world gen not recreating queue 2025-12-23 22:55:40 -06:00
James Seibel 802019ff72 up DH api version 5.0.0 -> 5.1.0 2025-12-23 20:01:06 -06:00
James Seibel 141890556c Revert "remove deprecated getHeight() from DhApiLevelWrapper"
This reverts commit 50bdb73a52.
2025-12-23 19:56:28 -06:00
James Seibel 353838db41 add experimental option to ignore rendering dimensions by name 2025-12-23 12:22:00 -06:00
James Seibel f1547477c9 add clientLevelWrapper to DhApiRenderParam 2025-12-23 12:20:42 -06:00
James Seibel 535a645a84 minor internal API cleanup 2025-12-23 12:19:14 -06:00
James Seibel 2dc7f02b32 Remove experimental option onlyLoadCenterLods
option is now merged into main
2025-12-23 12:18:28 -06:00
James Seibel 50bdb73a52 remove deprecated getHeight() from DhApiLevelWrapper
use getMaxHeight() instead
2025-12-23 12:06:15 -06:00
James Seibel 53e6c95432 commenting DhTerrainShaderProgram 2025-12-23 08:57:51 -06:00
James Seibel 36f0029e45 Fix earth curvature shader compiling 2025-12-23 08:47:44 -06:00
s809 5067e970a2 Use another method to create a buffer 2025-12-23 12:50:02 +05:00
James Seibel 167ca94e69 Remove deprecated disableVanillaFog config 2025-12-22 20:31:24 -06:00
James Seibel 8d94b86bfd Hide network config changes by default 2025-12-22 14:51:29 -06:00
James Seibel a29567430e Net only log changed config values 2025-12-22 14:37:34 -06:00
James Seibel fb2dae48e2 re-enable remote timestamp getting 2025-12-22 14:21:12 -06:00
James Seibel 948b4bfd9c comment out debug log 2025-12-22 14:17:57 -06:00
James Seibel ca44256ca9 disable full data debug phantom array stacks 2025-12-22 14:17:47 -06:00
James Seibel a29b6a5aab remove unnecessary config appearance check 2025-12-22 14:17:13 -06:00
James Seibel 868254ccc8 try fixing rare leak in delayed data source cache
Didn't fix the problem, but shouldn't hurt
2025-12-22 14:16:34 -06:00
James Seibel 195fde8d73 quad tree spilt request cleanup 2025-12-22 13:58:26 -06:00
James Seibel ce7b9b94b6 fix/improve world gen/retrieval error handling 2025-12-22 13:58:26 -06:00
James Seibel 1f0c2e286a fix network splitting requests 2025-12-22 13:58:26 -06:00
James Seibel f79fd5e06f error handling in AbstactDhLevel chunk update 2025-12-22 13:58:26 -06:00
James Seibel 47c1d3955f failed attmpt to fix leaks
Breaks split world gen requests
2025-12-22 13:57:49 -06:00
James Seibel 2c5f5a3d4c minor refactors 2025-12-22 09:46:21 -06:00
James Seibel 81c533051e close errored data sources in full data provider 2025-12-22 08:35:15 -06:00
James Seibel 5cbe5ecfd8 Fix dis/re-enabling world gen queuing 2025-12-21 19:44:48 -06:00
James Seibel d4b4d28c9f Fix null error log in Data source provider 2025-12-21 08:53:27 -06:00
James Seibel b8e653b5f7 Fix phantom checkout not updating stack trace 2025-12-21 08:52:44 -06:00
James Seibel 80fea09598 Fix concurrency error in LodQuadTree 2025-12-21 08:52:25 -06:00
James Seibel 1d4f914a9f Merge branch 'worldGenRefactor' 2025-12-20 10:53:39 -06:00
James Seibel bf92dea2eb reduce stuttering at the cost of lighting quality 2025-12-20 10:52:51 -06:00
s809 2dd675b8da Handle generated LOD updates outside the render thread 2025-12-20 15:22:26 +05:00
s809 ff3145336d Revert "Run plugin messages on a DH thread"
This reverts commit 280181c91e.
2025-12-20 14:32:39 +05:00
James Seibel 280181c91e Run plugin messages on a DH thread 2025-12-19 16:54:29 -06:00
James Seibel 60232e713b refactor world gen queue 2025-12-19 16:54:07 -06:00
James Seibel 55d9030954 Remove extra particle for world gen 2025-12-18 10:20:01 -06:00
James Seibel 452bd75f5d remove chunkWrapper.isStillValid() 2025-12-18 10:18:07 -06:00
James Seibel 72be1e2602 Remove LodRenderSection.isFullyGenerated() 2025-12-18 10:17:36 -06:00
James Seibel 1c30213aca up version number 2.4.3 -> 2.4.4-dev 2025-12-18 10:04:41 -06:00
James Seibel e9a044308f remove dev from version number 2025-12-18 09:35:07 -06:00
James Seibel 1aabc0c792 remove chunkWrapper.isStillValid() 2025-12-18 09:35:02 -06:00
James Seibel 4a1513ed65 fix compiling 2025-12-17 22:41:22 -06:00
James Seibel 6d98c9cb84 start world gen refactoring 2025-12-17 22:39:23 -06:00
James Seibel b1b0642fbe LodRenderSection commenting/regions 2025-12-17 09:32:12 -06:00
James Seibel eecb28d11f Fix GLProxy error in multiplayer
Make some GLProxy methods static to prevent setup order issues
2025-12-17 09:02:07 -06:00
James Seibel 90564f2537 fix javadoc in LevelWrapper 2025-12-16 16:39:03 -06:00
James Seibel ded0b979cf Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2025-12-16 14:45:57 -06:00
James Seibel ed9cc5485c Add SSAO fade out distance 2025-12-16 14:45:53 -06:00
s809 cbd5974657 Fix packet handle errors not showing on F3 screen 2025-12-17 00:15:55 +05:00
James Seibel 0e5fba58ab minor shader program refactor 2025-12-16 09:13:22 -06:00
James Seibel 2943e63382 slight light engine optimization 2025-12-15 14:37:15 -06:00
James Seibel 30564aade7 up version number 2.4.2 -> 2.4.3-dev 2025-12-15 10:17:28 -06:00
69 changed files with 1861 additions and 1691 deletions
@@ -169,7 +169,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
* *
* After the {@link IDhApiWorldGenerator} has been generated, it should be passed into the * After the {@link IDhApiWorldGenerator} has been generated, it should be passed into the
* resultConsumer's {@link Consumer#accept(Object)} method. * resultConsumer's {@link Consumer#accept(Object)} method.
* Note: if air blocks aren't included in the with the {@link DhApiChunk} with proper lighting, lower detail levels will appear as black/unlit. * Note: if air blocks aren't included in the {@link IDhApiFullDataSource} with proper lighting, lower detail levels will appear as black/unlit.
* *
* @implNote the default implementation of this method throws an {@link UnsupportedOperationException}, * @implNote the default implementation of this method throws an {@link UnsupportedOperationException},
* and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}. * and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
@@ -20,6 +20,8 @@
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects; package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiWorldLoadEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam; import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f; import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
@@ -27,7 +29,7 @@ import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
* Contains information relevant to Distant Horizons and Minecraft rendering. * Contains information relevant to Distant Horizons and Minecraft rendering.
* *
* @author James Seibel * @author James Seibel
* @version 2024-1-31 * @version 2025-12-23
* @since API 1.0.0 * @since API 1.0.0
*/ */
public class DhApiRenderParam implements IDhApiEventParam public class DhApiRenderParam implements IDhApiEventParam
@@ -61,6 +63,13 @@ public class DhApiRenderParam implements IDhApiEventParam
public final int worldYOffset; public final int worldYOffset;
/**
* The level currently being rendered.
*
* @since API 5.1.0
*/
public final IDhApiLevelWrapper clientLevelWrapper;
//==============// //==============//
@@ -70,12 +79,13 @@ public class DhApiRenderParam implements IDhApiEventParam
public DhApiRenderParam(DhApiRenderParam parent) public DhApiRenderParam(DhApiRenderParam parent)
{ {
this( this(
parent.renderPass, parent.renderPass,
parent.partialTicks, parent.partialTicks,
parent.nearClipPlane, parent.farClipPlane, parent.nearClipPlane, parent.farClipPlane,
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(), parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(), parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
parent.worldYOffset parent.worldYOffset,
parent.clientLevelWrapper
); );
} }
public DhApiRenderParam( public DhApiRenderParam(
@@ -84,7 +94,8 @@ public class DhApiRenderParam implements IDhApiEventParam
float nearClipPlane, float farClipPlane, float nearClipPlane, float farClipPlane,
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix, DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix, DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
int worldYOffset int worldYOffset,
IDhApiLevelWrapper clientLevelWrapper
) )
{ {
this.renderPass = renderPass; this.renderPass = renderPass;
@@ -101,6 +112,7 @@ public class DhApiRenderParam implements IDhApiEventParam
this.dhModelViewMatrix = newDhModelViewMatrix; this.dhModelViewMatrix = newDhModelViewMatrix;
this.worldYOffset = worldYOffset; this.worldYOffset = worldYOffset;
this.clientLevelWrapper = clientLevelWrapper;
} }
@@ -38,14 +38,14 @@ public final class ModInfo
public static final String NAME = "DistantHorizons"; public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */ /** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons"; public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.4.2-b"; public static final String VERSION = "2.4.4-b";
/** Returns true if the current build is an unstable developer build, false otherwise. */ /** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
/** This version should only be updated when breaking changes are introduced to the DH API */ /** This version should only be updated when breaking changes are introduced to the DH API */
public static final int API_MAJOR_VERSION = 5; public static final int API_MAJOR_VERSION = 5;
/** This version should be updated whenever new methods are added to the DH API */ /** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 0; public static final int API_MINOR_VERSION = 1;
/** This version should be updated whenever non-breaking fixes are added to the DH API */ /** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATCH_VERSION = 0; public static final int API_PATCH_VERSION = 0;
@@ -20,7 +20,9 @@
package com.seibel.distanthorizons.core; package com.seibel.distanthorizons.core;
import com.github.luben.zstd.ZstdOutputStream; import com.github.luben.zstd.ZstdOutputStream;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory; import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
@@ -171,6 +173,8 @@ public class Initializer
} }
} }
DhApi.events.bind(DhApiBeforeRenderEvent.class, IgnoredDimensionCsvHandler.INSTANCE);
} }
} }
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiHeightFogCo
import com.seibel.distanthorizons.core.config.api.DhApiConfigValue; import com.seibel.distanthorizons.core.config.api.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.api.converters.ApiFogDrawModeConverter; import com.seibel.distanthorizons.core.config.api.converters.ApiFogDrawModeConverter;
import com.seibel.distanthorizons.core.config.api.converters.InvertedBoolConverter;
public class DhApiFogConfig implements IDhApiFogConfig public class DhApiFogConfig implements IDhApiFogConfig
{ {
@@ -67,7 +68,7 @@ public class DhApiFogConfig implements IDhApiFogConfig
@Override @Override
@Deprecated @Deprecated
public IDhApiConfigValue<Boolean> disableVanillaFog() public IDhApiConfigValue<Boolean> disableVanillaFog()
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.disableVanillaFog); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog, new InvertedBoolConverter()); }
@Override @Override
public IDhApiConfigValue<Boolean> enableVanillaFog() public IDhApiConfigValue<Boolean> enableVanillaFog()
{ return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog); } { return new DhApiConfigValue<>(Config.Client.Advanced.Graphics.Fog.enableVanillaFog); }
@@ -45,7 +45,6 @@ import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld; import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.world.IDhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -56,8 +55,6 @@ import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import java.io.File; import java.io.File;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.*; import java.util.*;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
@@ -95,7 +92,6 @@ public class ClientApi
private boolean isDevBuildMessagePrinted = false; private boolean isDevBuildMessagePrinted = false;
private boolean lowMemoryWarningPrinted = false; private boolean lowMemoryWarningPrinted = false;
private boolean highVanillaRenderDistanceWarningPrinted = false; private boolean highVanillaRenderDistanceWarningPrinted = false;
private boolean g1GarbageCollectorWarningPrinted = false;
private long lastStaticWarningMessageSentMsTime = 0L; private long lastStaticWarningMessageSentMsTime = 0L;
@@ -245,7 +241,7 @@ public class ClientApi
} }
} }
public void clientLevelLoadEvent(IClientLevelWrapper levelWrapper) public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
{ {
// wait a moment before loading the level to give the server a chance to handle the client's login request // wait a moment before loading the level to give the server a chance to handle the client's login request
if (MC_CLIENT.clientConnectedToDedicatedServer()) if (MC_CLIENT.clientConnectedToDedicatedServer())
@@ -379,8 +375,11 @@ public class ClientApi
try try
{ {
// make sure the GLProxy is created for future use
GLProxy.getInstance();
// these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks // these tasks always need to be called, regardless of whether the renderer is enabled or not to prevent memory leaks
GLProxy.getInstance().runRenderThreadTasks(); GLProxy.runRenderThreadTasks();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -90,7 +90,7 @@ public class ClientPluginChannelApi
LOGGER.info("Server level key received: [" + msg.levelKey + "]."); LOGGER.info("Server level key received: [" + msg.levelKey + "].");
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true); IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(); IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel();
@@ -82,15 +82,15 @@ public class ServerApi
// level events // // level events //
//==============// //==============//
public void serverLevelLoadEvent(IServerLevelWrapper level) public void serverLevelLoadEvent(IServerLevelWrapper levelWrapper)
{ {
LOGGER.debug("Server Level " + level + " loading"); LOGGER.debug("Server Level " + levelWrapper + " loading");
AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld(); AbstractDhWorld serverWorld = SharedApi.getAbstractDhWorld();
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.getOrLoadLevel(level); serverWorld.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level)); ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
} }
} }
public void serverLevelUnloadEvent(IServerLevelWrapper level) public void serverLevelUnloadEvent(IServerLevelWrapper level)
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.core.Initializer;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData; import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateData;
import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager; import com.seibel.distanthorizons.core.api.internal.chunkUpdating.ChunkUpdateQueueManager;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
@@ -184,12 +185,11 @@ public class SharedApi
public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, false); } public void chunkBlockChangedEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, false); }
public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, true); } public void chunkLoadEvent(IChunkWrapper chunk, ILevelWrapper level) { this.applyChunkUpdate(chunk, level, true, true); }
//public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks) { this.applyChunkUpdate(chunkWrapper, level, canGetNeighboringChunks, false); }
public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded) public void applyChunkUpdate(IChunkWrapper chunkWrapper, ILevelWrapper level, boolean canGetNeighboringChunks, boolean newlyLoaded)
{ {
//========================// //===================//
// world and level checks // // validation checks //
//========================// //===================//
if (chunkWrapper == null) if (chunkWrapper == null)
{ {
@@ -217,7 +217,6 @@ public class SharedApi
return; return;
} }
// only continue if the level is loaded // only continue if the level is loaded
IDhLevel dhLevel = dhWorld.getLevel(level); IDhLevel dhLevel = dhWorld.getLevel(level);
if (dhLevel == null) if (dhLevel == null)
@@ -232,6 +231,7 @@ public class SharedApi
return; return;
} }
// ignore chunk updates if the network should handle them
if (dhLevel instanceof DhClientLevel) if (dhLevel instanceof DhClientLevel)
{ {
if (!((DhClientLevel) dhLevel).shouldProcessChunkUpdate(chunkWrapper.getChunkPos())) if (!((DhClientLevel) dhLevel).shouldProcessChunkUpdate(chunkWrapper.getChunkPos()))
@@ -240,7 +240,14 @@ public class SharedApi
} }
} }
// shoudln't normally happen, but just in case // ignore chunk updates for non-rendered levels
String dimName = dhLevel.getLevelWrapper().getDimensionName();
if (IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName))
{
return;
}
// shouldn't normally happen, but just in case
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos())) if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
{ {
// TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded // TODO this will prevent some LODs from updating across dimensions if multiple levels are loaded
@@ -248,94 +255,20 @@ public class SharedApi
} }
queueChunkUpdate(chunkWrapper, dhLevel);
//===============================//
// 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;
} }
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 // return if the chunk is already queued
if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos())) if (CHUNK_UPDATE_QUEUE_MANAGER.contains(chunkWrapper.getChunkPos()))
{ {
return; return;
} }
// add chunk update data to preUpdate queue // add chunk update data to preUpdate queue
ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, neighborChunkList, dhLevel, canGetNeighboringChunks); ChunkUpdateData updateData = new ChunkUpdateData(chunkWrapper, dhLevel);
CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData); CHUNK_UPDATE_QUEUE_MANAGER.addItemToPreUpdateQueue(chunkWrapper.getChunkPos(), updateData);
@@ -343,7 +276,8 @@ public class SharedApi
// (this prevents doing extra work queuing tasks that may not be necessary) // (this prevents doing extra work queuing tasks that may not be necessary)
// and makes sure the chunks closest to the player are updated first // and makes sure the chunks closest to the player are updated first
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor(); PriorityTaskPicker.Executor executor = ThreadPoolUtil.getChunkToLodBuilderExecutor();
if (executor != null && executor.getQueueSize() < executor.getPoolSize()) if (executor != null
&& executor.getQueueSize() < executor.getPoolSize())
{ {
try try
{ {
@@ -383,10 +317,7 @@ public class SharedApi
// update the necessary chunk(s) // // update the necessary chunk(s) //
//===============================// //===============================//
// process preUpdate queue
processQueuedChunkPreUpdate(); processQueuedChunkPreUpdate();
// process update queue
processQueuedChunkUpdate(); processQueuedChunkUpdate();
// queue the next position if there are still positions to process // queue the next position if there are still positions to process
@@ -415,8 +346,7 @@ public class SharedApi
IDhLevel dhLevel = preUpdateData.dhLevel; IDhLevel dhLevel = preUpdateData.dhLevel;
IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper; IChunkWrapper chunkWrapper = preUpdateData.chunkWrapper;
boolean canGetNeighboringChunks = preUpdateData.canGetNeighboringChunks; chunkWrapper.createDhHeightMaps();
ArrayList<IChunkWrapper> neighborChunkList = preUpdateData.neighborChunkList;
try try
{ {
@@ -433,34 +363,6 @@ public class SharedApi
// do not update the chunk if the hash is the same // do not update the chunk if the hash is the same
return; return;
} }
// if this chunk will update and can get neighbors
// then queue neighboring chunks to update as well
// neighboring chunk will get added directly to the update queue
// so they won't queue further chunk updates
if (neighborChunkList != null
&& !neighborChunkList.isEmpty())
{
for (IChunkWrapper adjacentChunk : neighborChunkList)
{
// pulling a new chunkWrapper is necessary to prevent concurrent modification on the existing chunkWrappers
IChunkWrapper newCenterChunk = dhLevel.getLevelWrapper().tryGetChunk(adjacentChunk.getChunkPos());
if (newCenterChunk != null)
{
ChunkUpdateData newUpdateData;
if (canGetNeighboringChunks)
{
newUpdateData = new ChunkUpdateData(newCenterChunk, getNeighborChunkListForChunk(newCenterChunk, dhLevel), dhLevel, true);
}
else
{
newUpdateData = new ChunkUpdateData(newCenterChunk, null, dhLevel, false);
}
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(newCenterChunk.getChunkPos(), newUpdateData);
}
}
}
} }
CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData); CHUNK_UPDATE_QUEUE_MANAGER.addItemToUpdateQueue(chunkWrapper.getChunkPos(), preUpdateData);
@@ -473,8 +375,6 @@ public class SharedApi
private static void processQueuedChunkUpdate() private static void processQueuedChunkUpdate()
{ {
//LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount());
ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest(); ChunkUpdateData updateData = CHUNK_UPDATE_QUEUE_MANAGER.updateQueue.popClosest();
if (updateData == null) if (updateData == null)
{ {
@@ -484,15 +384,11 @@ public class SharedApi
IChunkWrapper chunkWrapper = updateData.chunkWrapper; IChunkWrapper chunkWrapper = updateData.chunkWrapper;
IDhLevel dhLevel = updateData.dhLevel; IDhLevel dhLevel = updateData.dhLevel;
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper(); ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
// having a list of the nearby chunks is needed for lighting and beacon generation
@Nullable ArrayList<IChunkWrapper> nearbyChunkList = updateData.neighborChunkList;
// a non-null list is needed for the lighting engine // having a list of the nearby chunks is needed for lighting and beacon generation
if (nearbyChunkList == null) ArrayList<IChunkWrapper> nearbyChunkList = tryGetNeighborChunkListForChunk(chunkWrapper);
{
nearbyChunkList = new ArrayList<IChunkWrapper>();
nearbyChunkList.add(chunkWrapper);
}
try try
{ {
@@ -508,6 +404,35 @@ public class SharedApi
{ {
LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e); LOGGER.error("Unexpected error when updating chunk at pos: [" + chunkWrapper.getChunkPos() + "]", e);
} }
CHUNK_UPDATE_QUEUE_MANAGER.queuedChunkWrapperByChunkPos.remove(updateData.chunkWrapper.getChunkPos());
}
private static ArrayList<IChunkWrapper> tryGetNeighborChunkListForChunk(IChunkWrapper chunkWrapper)
{
// get the neighboring chunk list
ArrayList<IChunkWrapper> neighborChunkList = new ArrayList<>(9);
for (int xOffset = -1; xOffset <= 1; xOffset++)
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
if (xOffset == 0 && zOffset == 0)
{
// center chunk
neighborChunkList.add(chunkWrapper);
}
else
{
// neighboring chunk
DhChunkPos neighborPos = new DhChunkPos(chunkWrapper.getChunkPos().getX() + xOffset, chunkWrapper.getChunkPos().getZ() + zOffset);
IChunkWrapper neighborChunk = CHUNK_UPDATE_QUEUE_MANAGER.tryGetChunk(neighborPos);
if (neighborChunk != null)
{
neighborChunkList.add(neighborChunk);
}
}
}
}
return neighborChunkList;
} }
@@ -9,18 +9,13 @@ import java.util.ArrayList;
public class ChunkUpdateData public class ChunkUpdateData
{ {
public IChunkWrapper chunkWrapper; public IChunkWrapper chunkWrapper;
@Nullable
public ArrayList<IChunkWrapper> neighborChunkList;
public IDhLevel dhLevel; public IDhLevel dhLevel;
public boolean canGetNeighboringChunks;
public ChunkUpdateData(IChunkWrapper chunkWrapper, @Nullable ArrayList<IChunkWrapper> neighborChunkList, IDhLevel dhLevel, boolean canGetNeighborChunks) public ChunkUpdateData(IChunkWrapper chunkWrapper, IDhLevel dhLevel)
{ {
this.chunkWrapper = chunkWrapper; this.chunkWrapper = chunkWrapper;
this.neighborChunkList = neighborChunkList;
this.dhLevel = dhLevel; this.dhLevel = dhLevel;
this.canGetNeighboringChunks = canGetNeighborChunks;
} }
} }
@@ -1,16 +1,23 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating; package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.world.EWorldEnvironment; import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import org.jetbrains.annotations.Nullable;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
public class ChunkUpdateQueueManager public class ChunkUpdateQueueManager
{ {
@@ -21,6 +28,12 @@ public class ChunkUpdateQueueManager
public final ChunkPosQueue preUpdateQueue; public final ChunkPosQueue preUpdateQueue;
public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final Set<DhChunkPos> ignoredChunkPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final ConcurrentMap<DhChunkPos, IChunkWrapper> queuedChunkWrapperByChunkPos = CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.SECONDS)
.<DhChunkPos, IChunkWrapper>build()
.asMap();
/** dynamically changes based on the number of threads currently available */
public int maxSize = 500; public int maxSize = 500;
private static long lastOverloadedLogMessageMsTime = 0; private static long lastOverloadedLogMessageMsTime = 0;
@@ -68,22 +81,24 @@ public class ChunkUpdateQueueManager
* If there are no more slots, replaces the item furthest from the center in the update queue. * If there are no more slots, replaces the item furthest from the center in the update queue.
*/ */
public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData) public void addItemToPreUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
{ this.addItemToQueue(pos, updateData, this.preUpdateQueue); }
public void addItemToUpdateQueue(DhChunkPos pos, ChunkUpdateData updateData)
{ this.addItemToQueue(pos, updateData, this.updateQueue); }
private void addItemToQueue(DhChunkPos pos, ChunkUpdateData updateData, ChunkPosQueue queue)
{ {
int remainingSlots = this.maxSize - this.getQueuedCount(); int remainingSlots = this.maxSize - this.getQueuedCount();
// If no slots are left, get one by removing the item furthest from the center // If no slots are left, get one by removing the item furthest from the center
if (remainingSlots <= 0) if (remainingSlots <= 0)
{ {
if (!this.updateQueue.isEmpty()) ChunkUpdateData removedData = queue.popFurthest();
{ this.queuedChunkWrapperByChunkPos.remove(removedData.chunkWrapper.getChunkPos());
this.updateQueue.popFurthest();
}
else
{
this.preUpdateQueue.popFurthest();
}
} }
this.preUpdateQueue.addItem(pos, updateData);
queue.addItem(pos,updateData);
this.queuedChunkWrapperByChunkPos.putIfAbsent(pos, updateData.chunkWrapper);
remainingSlots = this.maxSize - this.getQueuedCount(); remainingSlots = this.maxSize - this.getQueuedCount();
if (remainingSlots <= 0) if (remainingSlots <= 0)
@@ -92,24 +107,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() private void sendOverloadMessage()
{ {
@@ -140,6 +137,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();
}
//=========// //=========//
@@ -419,6 +419,14 @@ public class Config
"") "")
.build(); .build();
public static ConfigEntry<Integer> fadeDistanceInBlocks = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 1_600, 30_000_000)
.comment("" +
"The distance in blocks from the camera where the SSAO will fade out to. \n"+
"This is done to prevent banding and noise at extreme distances. \n"+
"")
.build();
} }
public static class GenericRendering public static class GenericRendering
@@ -497,11 +505,6 @@ public class Config
+ "Note: Other mods may conflict with this setting. \n" + "Note: Other mods may conflict with this setting. \n"
+ "") + "")
.build(); .build();
@Deprecated
public static ConfigEntry<Boolean> disableVanillaFog = new ConfigEntry.Builder<Boolean>()
.set(!enableVanillaFog.get())
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.build();
@@ -563,14 +566,6 @@ public class Config
static
{
disableVanillaFog.addListener(
new ConfigChangeListener<Boolean>(disableVanillaFog,
(disableVanillaFog) -> enableVanillaFog.setApiValue(disableVanillaFog))
);
}
public static class HeightFog public static class HeightFog
{ {
public static ConfigUIComment heightFogHeader = new ConfigUIComment.Builder().setParentConfigClass(HeightFog.class).build(); public static ConfigUIComment heightFogHeader = new ConfigUIComment.Builder().setParentConfigClass(HeightFog.class).build();
@@ -838,7 +833,7 @@ public class Config
public static ConfigUIComment experimentalHeader = new ConfigUIComment.Builder().setParentConfigClass(Experimental.class).build(); public static ConfigUIComment experimentalHeader = new ConfigUIComment.Builder().setParentConfigClass(Experimental.class).build();
public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>() public static ConfigEntry<Integer> earthCurveRatio = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 0, 5000) .setMinDefaultMax(-5000, 0, 5000)
.comment("" .comment(""
+ "This is the earth size ratio when applying the curvature shader effect. \n" + "This is the earth size ratio when applying the curvature shader effect. \n"
+ "Note: Enabling this feature may cause rendering bugs. \n" + "Note: Enabling this feature may cause rendering bugs. \n"
@@ -848,24 +843,26 @@ public class Config
+ "100 = 1 to 100 (63,710 blocks) \n" + "100 = 1 to 100 (63,710 blocks) \n"
+ "10000 = 1 to 10000 (637.1 blocks) \n" + "10000 = 1 to 10000 (637.1 blocks) \n"
+ "\n" + "\n"
+ "Note: Due to current limitations, the min value is 50 \n" + "Note: Due to current limitations, the min value is ["+WorldCurvatureConfigEventHandler.MIN_VALID_CURVE_VALUE+"] \n"
+ "and the max value is 5000. Any values outside this range \n" + "and the max value is 5000. Any values outside this range \n"
+ "will be set to 0 (disabled).") + "will be set to 0 (disabled).")
.addListener(WorldCurvatureConfigEventHandler.INSTANCE) .addListener(WorldCurvatureConfigEventHandler.INSTANCE)
.build(); .build();
// TODO should be replaced with a better long-term solution public static ConfigEntry<String> ignoredDimensionCsv = new ConfigEntry.Builder<String>()
@Deprecated .set("")
public static ConfigEntry<Boolean> onlyLoadCenterLods = new ConfigEntry.Builder<Boolean>() .comment(""
.set(false) + "A comma separated list of dimension resource locations where DH won't render. \n"
.comment("" + "\n"
+ "For internal testing:\n" + "Example: \"minecraft:the_nether,minecraft:the_end\"\n"
+ "Skips loading adjacent LODs to significantly reduce load times (~5x)\n" + "\n"
+ "but causes lighting on LOD borders to appear as full-bright\n" + "Note:\n"
+ "and other graphical bugs.\n" + "Some DH settings will be disabled and/or changed to improve \n"
+ "") + "visuals when DH rendering is disabled. \n"
.addListener(ReloadLodsConfigEventHandler.DELAYED_INSTANCE) + "")
.build(); .addListener(IgnoredDimensionCsvHandler.INSTANCE)
.build();
} }
} }
@@ -1414,7 +1411,7 @@ public class Config
.set(false) .set(false)
// enabling this can be quite detrimental to performance, // enabling this can be quite detrimental to performance,
// so hiding it in the config file should reduce people accidentally enabling it // so hiding it in the config file should reduce people accidentally enabling it
.setAppearance(isRunningInDevEnvironment() ? EConfigEntryAppearance.ALL : EConfigEntryAppearance.ONLY_IN_FILE) .setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
.comment("" .comment(""
+ "Enabling this will drastically increase chunk processing time\n" + "Enabling this will drastically increase chunk processing time\n"
+ "and you may need to increase your CPU load to handle it.\n" + "and you may need to increase your CPU load to handle it.\n"
@@ -1595,6 +1592,14 @@ public class Config
+ "This can be useful for debugging.") + "This can be useful for debugging.")
.build(); .build();
public static ConfigEntry<EDhApiLoggerLevel> logConnectionConfigChangesToFile = new ConfigEntry.Builder<EDhApiLoggerLevel>()
.setChatCommandName("logging.logConnectionConfigChanges")
.set(EDhApiLoggerLevel.WARN)
.comment(""
+ "If enabled, config changes sent by the server will be logged. \n"
+ "")
.build();
public static ConfigCategory warning = new ConfigCategory.Builder().set(Warning.class).build(); public static ConfigCategory warning = new ConfigCategory.Builder().set(Warning.class).build();
@@ -1857,6 +1862,8 @@ public class Config
ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues();
IgnoredDimensionCsvHandler.INSTANCE.onConfigValueSet();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -17,23 +17,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.seibel.distanthorizons.core.generation.tasks; package com.seibel.distanthorizons.core.config.api.converters;
import java.util.concurrent.CompletableFuture; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.coreapi.interfaces.config.IConverter;
/** /**
* @author Leetom * Used to support deprecated config options that may be identical
* @version 2022-11-25 * in implementation but with the On/Off values flipped.
*
* @author James Seibel
* @version 2025-12-22
*/ */
public final class InProgressWorldGenTaskGroup public class InvertedBoolConverter implements IConverter<Boolean, Boolean>
{ {
public final WorldGenTaskGroup group;
public CompletableFuture<Void> genFuture = null;
@Override
public Boolean convertToCoreType(Boolean core)
{ return !core; }
public InProgressWorldGenTaskGroup(WorldGenTaskGroup group) @Override
{ public Boolean convertToApiType(Boolean api)
this.group = group; { return !api; }
}
} }
@@ -0,0 +1,125 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.config.eventHandlers;
import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
public class IgnoredDimensionCsvHandler extends DhApiBeforeRenderEvent implements IConfigListener
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
public static IgnoredDimensionCsvHandler INSTANCE = new IgnoredDimensionCsvHandler();
private String[] dimensionNames = null;
//=============//
// constructor //
//=============//
/** private since we only ever need one handler at a time */
private IgnoredDimensionCsvHandler() { }
//=================//
// config handling //
//=================//
@Override
public void onConfigValueSet()
{
String ignoredDimensionCsvString = Config.Client.Advanced.Graphics.Experimental.ignoredDimensionCsv.get();
if (ignoredDimensionCsvString == null
|| ignoredDimensionCsvString.isEmpty())
{
LOGGER.info("Dimension ignoring disabled, DH will render all dimensions.");
this.dimensionNames = null;
}
else
{
try
{
this.dimensionNames = ignoredDimensionCsvString.split(",");
LOGGER.info("DH set to ignore dimensions: ["+ StringUtil.join(", ", this.dimensionNames)+"].");
}
catch (Exception e)
{
LOGGER.error("Failed to separate ignored dimensions from CSV string, error: ["+e.getMessage()+"].", e);
this.dimensionNames = null;
}
}
}
//===================//
// external handling //
//===================//
@Override
public void beforeRender(DhApiCancelableEventParam<DhApiRenderParam> event)
{
String dimName = event.value.clientLevelWrapper.getDimensionName();
if (IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName))
{
event.cancelEvent();
Config.Client.Advanced.Graphics.Fog.enableVanillaFog.setApiValue(true);
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.setApiValue(EDhApiMcRenderingFadeMode.NONE);
}
else
{
Config.Client.Advanced.Graphics.Fog.enableVanillaFog.setApiValue(null);
Config.Client.Advanced.Graphics.Quality.vanillaFadeMode.setApiValue(null);
}
}
public boolean dimensionNameShouldBeIgnored(String dimName)
{
if (this.dimensionNames == null
|| this.dimensionNames.length == 0)
{
return false;
}
for (int i = 0; i < this.dimensionNames.length; i++)
{
String dimNameToIgnore = this.dimensionNames[i];
if (dimName.equalsIgnoreCase(dimNameToIgnore))
{
return true;
}
}
return false;
}
}
@@ -35,7 +35,7 @@ public class WorldCurvatureConfigEventHandler implements IConfigListener
{ {
public static WorldCurvatureConfigEventHandler INSTANCE = new WorldCurvatureConfigEventHandler(); public static WorldCurvatureConfigEventHandler INSTANCE = new WorldCurvatureConfigEventHandler();
private static final int MIN_VALID_CURVE_VALUE = 50; public static final int MIN_VALID_CURVE_VALUE = 50;
/** private since we only ever need one handler at a time */ /** private since we only ever need one handler at a time */
@@ -52,6 +52,11 @@ public class WorldCurvatureConfigEventHandler implements IConfigListener
// shouldn't update the UI, otherwise we may end up fighting the user // shouldn't update the UI, otherwise we may end up fighting the user
Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(MIN_VALID_CURVE_VALUE); Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(MIN_VALID_CURVE_VALUE);
} }
else if (curveRatio < 0 && curveRatio > -MIN_VALID_CURVE_VALUE)
{
// same as above, but in the negative direction
Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.set(-MIN_VALID_CURVE_VALUE);
}
} }
@@ -121,7 +121,7 @@ public class FullDataSourceV2
public Boolean applyToChildren = null; public Boolean applyToChildren = null;
/** should only be used by methods exposed via the DH API */ /** should only be used by methods exposed via the DH API */
private boolean runApiChunkValidation = false; private boolean runApiSetterValidation = false;
@@ -202,8 +202,9 @@ public class FullDataSourceV2
public static FullDataSourceV2 createEmpty(long pos) public static FullDataSourceV2 createEmpty(long pos)
{ {
FullDataPointIdMap map = new FullDataPointIdMap(pos);
return new FullDataSourceV2( return new FullDataSourceV2(
pos, new FullDataPointIdMap(pos), pos, map,
// data points, genSteps, and columnCompression are all null since // data points, genSteps, and columnCompression are all null since
// nothing has been generated yet. // nothing has been generated yet.
// Using the default value of all 0's is adequate // Using the default value of all 0's is adequate
@@ -1296,7 +1297,7 @@ public class FullDataSourceV2
// API methods // // API methods //
//=============// //=============//
public void setRunApiChunkValidation(boolean runValidation) { this.runApiChunkValidation = runValidation; } public void setRunApiSetterValidation(boolean runValidation) { this.runApiSetterValidation = runValidation; }
@Override @Override
public int getWidthInDataColumns() { return WIDTH; } public int getWidthInDataColumns() { return WIDTH; }
@@ -1308,7 +1309,7 @@ public class FullDataSourceV2
try try
{ {
LodDataBuilder.putListInTopDownOrder(columnDataPoints); LodDataBuilder.putListInTopDownOrder(columnDataPoints);
if (this.runApiChunkValidation) if (this.runApiSetterValidation)
{ {
LodDataBuilder.validateOrThrowApiDataColumn(columnDataPoints); LodDataBuilder.validateOrThrowApiDataColumn(columnDataPoints);
} }
@@ -107,7 +107,7 @@ public class LodBufferContainer implements AutoCloseable
// upload on MC's render thread // upload on MC's render thread
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
try try
{ {
@@ -295,7 +295,7 @@ public class LodBufferContainer implements AutoCloseable
{ {
this.buffersUploaded = false; this.buffersUploaded = false;
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
for (GLVertexBuffer buffer : this.vbos) for (GLVertexBuffer buffer : this.vbos)
{ {
@@ -372,10 +372,10 @@ public class LodQuadBuilder
if (quad.direction.axis.isHorizontal() || quad.direction == EDhDirection.DOWN) if (quad.direction.axis.isHorizontal() || quad.direction == EDhDirection.DOWN)
{ {
if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT if (this.grassSideRenderingMode == EDhApiGrassSideRendering.AS_DIRT
// if we want the color to fade, only apply the dirt color to the bottom vertices // 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) || (this.grassSideRenderingMode == EDhApiGrassSideRendering.FADE_TO_DIRT && quadBase[i][1] == 0)
// always render the bottom as dirt // always render the bottom as dirt
|| quad.direction == EDhDirection.DOWN) || quad.direction == EDhDirection.DOWN)
{ {
// for horizontal and bottom faces of grass blocks, use the dirt color to // for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls // 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) private void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte normalIndex, byte irisBlockMaterialId, byte skylight, byte blocklight, int mx, int my, int mz)
{ {
skylight %= 16;
blocklight %= 16;
bb.putShort(x); bb.putShort(x);
bb.putShort(y); bb.putShort(y);
bb.putShort(z); bb.putShort(z);
short meta = 0; short meta = 0;
meta |= (skylight | (blocklight << 4)); {
byte mirco = 0; skylight %= 16;
// mirco offset which is a xyz 2bit value blocklight %= 16;
// 0b00 = no offset meta |= (short) (skylight | (blocklight << 4));
// 0b01 = positive offset
// 0b11 = negative offset byte mircoOffset = 0;
// format is: 0b00zzyyxx // mirco offset which is a xyz 2bit value
if (mx != 0) mirco |= mx > 0 ? 0b01 : 0b11; // 0b00 = no offset
if (my != 0) mirco |= my > 0 ? 0b0100 : 0b1100; // 0b01 = positive offset
if (mz != 0) mirco |= mz > 0 ? 0b010000 : 0b110000; // 0b11 = negative offset
meta |= mirco << 8; // 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); bb.putShort(meta);
byte r = (byte) ColorUtil.getRed(color); byte r = (byte) ColorUtil.getRed(color);
byte g = (byte) ColorUtil.getGreen(color); byte g = (byte) ColorUtil.getGreen(color);
byte b = (byte) ColorUtil.getBlue(color); byte b = (byte) ColorUtil.getBlue(color);
byte a = this.doTransparency ? (byte) ColorUtil.getAlpha(color) : (byte) 255; // TODO should this be called here or happen somewhere else? byte a = this.doTransparency ? (byte) ColorUtil.getAlpha(color) : (byte) 255;
bb.put(r); bb.put(r);
bb.put(g); bb.put(g);
bb.put(b); bb.put(b);
@@ -96,7 +96,12 @@ public class DelayedFullDataSourceSaveCache implements AutoCloseable
// no data currently in the memory cache for this position // no data currently in the memory cache for this position
memoryDataSource = FullDataSourceV2.createEmpty(inputPos); memoryDataSource = FullDataSourceV2.createEmpty(inputPos);
pair = new DataSourceSavedTimePair(memoryDataSource); pair = new DataSourceSavedTimePair(memoryDataSource);
this.dataSourceByPosition.put(inputPos, pair); DataSourceSavedTimePair oldPair = this.dataSourceByPosition.put(inputPos, pair);
if (oldPair != null)
{
// shouldn't happen, but just in case
this.handleDataSourceRemoval(oldPair.dataSource);
}
} }
else else
{ {
@@ -28,8 +28,8 @@ import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataUpdatePropag
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue; import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -38,12 +38,12 @@ import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList; import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
@@ -68,7 +68,9 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
* TODO this should be dynamically allocated based on CPU load * TODO this should be dynamically allocated based on CPU load
* and abilities. * 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); private final AtomicReference<IFullDataSourceRetrievalQueue> worldGenQueueRef = new AtomicReference<>(null);
@@ -85,15 +87,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure) throws SQLException, IOException
{ this(level, saveStructure, null); } { this(level, saveStructure, null); }
public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException public GeneratedFullDataSourceProvider(IDhLevel level, ISaveStructure saveStructure, @Nullable File saveDirOverride) throws SQLException, IOException
{ { super(level, saveStructure, saveDirOverride); }
super(level, saveStructure, saveDirOverride);
this.addDataSourceUpdateListener((@NotNull FullDataSourceV2 updatedData) ->
{
this.onWorldGenTaskComplete(WorldGenResult.CreateSuccess(updatedData.getPos()), null);
});
}
@@ -122,32 +116,47 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// events // // events //
//========// //========//
private void onWorldGenTaskComplete(WorldGenResult genTaskResult, Throwable exception) private void onWorldGenTaskComplete(DataSourceRetrievalResult genTaskResult, Throwable exception)
{ {
if (exception != null) try
{ {
// don't log shutdown exceptions if (exception != null)
if (!(exception instanceof CancellationException || exception.getCause() instanceof CancellationException))
{ {
LOGGER.error("Uncaught Gen Task Exception at ["+genTaskResult.pos+"], error: ["+exception.getMessage()+"].", exception); return;
}
if (genTaskResult.state == ERetrievalResultState.FAIL)
{
LodUtil.assertTrue(genTaskResult.dataSource == null, "Errored retrieval object should not have a datasource.");
// don't log shutdown exceptions
if (!ExceptionUtil.isInterruptOrReject(exception))
{
LOGGER.error("Uncaught Gen Task Exception at [" + genTaskResult.pos + "], error: [" + exception.getMessage() + "].", exception);
}
}
else if (genTaskResult.state == ERetrievalResultState.SUCCESS)
{
LodUtil.assertTrue(genTaskResult.dataSource != null, "Successful retrieval object should have a datasource.");
this.dataUpdater.updateDataSource(genTaskResult.dataSource);
this.fireOnGenPosSuccessListeners(genTaskResult.pos);
genTaskResult.dataSource.close();
}
else if (genTaskResult.state == ERetrievalResultState.REQUIRES_SPLITTING)
{
// task was split
LodUtil.assertTrue(genTaskResult.dataSource == null, "Split retrieval object should not have a datasource.");
}
else
{
// shouldn't happen, but just in case
LOGGER.warn("Unexpected gen Task state at: [" + DhSectionPos.toString(genTaskResult.pos) + "], state: [" + genTaskResult.state + "], datasource: NULL, exception: NULL.");
} }
} }
else if (genTaskResult.success) catch (Exception e)
{ {
this.fireOnGenPosSuccessListeners(genTaskResult.pos); LOGGER.error("Unexpected issue during onWorldGenTaskComplete, error: ["+e.getMessage()+"].", e);
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));
} }
} }
@@ -207,10 +216,10 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
} }
@Override @Override
public boolean canQueueRetrieval() { return this.canQueueRetrieval(false); } public boolean canQueueRetrievalNow() { return this.canQueueRetrievalNow(false); }
public boolean canQueueRetrieval(boolean pruneWaitingTasksAboveLimit) public boolean canQueueRetrievalNow(boolean pruneWaitingTasksAboveLimit)
{ {
if (!super.canQueueRetrieval()) if (!super.canQueueRetrievalNow())
{ {
return false; return false;
} }
@@ -270,12 +279,16 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount(); int availableTaskSlots = maxWorldGenQueueCount - worldGenQueue.getWaitingTaskCount();
if (availableTaskSlots <= 0) if (availableTaskSlots == 0)
{
return false;
}
else if (availableTaskSlots < 0)
{ {
if (pruneWaitingTasksAboveLimit) if (pruneWaitingTasksAboveLimit)
{ {
AtomicInteger tasksToCancel = new AtomicInteger(-availableTaskSlots + 1); AtomicInteger tasksToCancel = new AtomicInteger(availableTaskSlots * -1);
worldGenQueue.removeRetrievalRequestIf(x -> tasksToCancel.getAndDecrement() > 0); worldGenQueue.removeRetrievalRequestIf(taskPos -> tasksToCancel.getAndDecrement() > 0);
} }
else else
{ {
@@ -288,7 +301,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
} }
@Override @Override
public CompletableFuture<WorldGenResult> queuePositionForRetrieval(Long genPos) public CompletableFuture<DataSourceRetrievalResult> queuePositionForRetrieval(Long genPos)
{ {
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get(); IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null) if (worldGenQueue == null)
@@ -296,13 +309,8 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
return null; return null;
} }
WorldGenTaskTracker genTaskTracker = new WorldGenTaskTracker(genPos); CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
CompletableFuture<WorldGenResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL), genTaskTracker); worldGenFuture.whenComplete(this::onWorldGenTaskComplete);
worldGenFuture.whenComplete((genTaskResult, ex) ->
{
//LOGGER.info("gen task complete ["+DhSectionPos.toString(genPos)+"]");
//this.onWorldGenTaskComplete(genTaskResult, ex);
});
return worldGenFuture; return worldGenFuture;
} }
@@ -321,22 +329,20 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
public void clearRetrievalQueue() { this.worldGenQueueRef.set(null); } public void clearRetrievalQueue() { this.worldGenQueueRef.set(null); }
public boolean isFullyGenerated(ByteArrayList columnGenerationSteps) public boolean generationStepsAreFullyGenerated(ByteArrayList columnGenerationSteps)
{ {
return IntStream.range(0, columnGenerationSteps.size()) return IntStream.range(0, columnGenerationSteps.size())
.noneMatch(i -> .noneMatch((int intValue) ->
{ {
byte value = columnGenerationSteps.getByte(i); byte value = columnGenerationSteps.getByte(intValue);
return value == EDhApiWorldGenerationStep.EMPTY.value return value == EDhApiWorldGenerationStep.EMPTY.value
|| value == EDhApiWorldGenerationStep.DOWN_SAMPLED.value; || value == EDhApiWorldGenerationStep.DOWN_SAMPLED.value;
}); });
} }
public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Generated Provider");
@Override @Override
public LongArrayList getPositionsToRetrieve(Long pos) public LongArrayList getPositionsToRetrieve(long pos)
{ {
IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get(); IFullDataSourceRetrievalQueue worldGenQueue = this.worldGenQueueRef.get();
if (worldGenQueue == null) if (worldGenQueue == null)
@@ -352,7 +358,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
{ {
ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH); ByteArrayList columnGenStepArray = checkout.getByteArray(0, FullDataSourceV2.WIDTH*FullDataSourceV2.WIDTH);
this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray); this.repo.getColumnGenerationStepForPos(pos, columnGenStepArray);
if (!columnGenStepArray.isEmpty()) if (columnGenStepArray.size() != 0)
{ {
boolean positionFullyGenerated = true; boolean positionFullyGenerated = true;
@@ -378,12 +384,11 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// this section is missing one or more columns, queue the missing ones for generation. // this section is missing one or more columns, queue the missing ones for generation.
// TODO speed up this logic by only checking ungenerated columns
LongArrayList generationList = new LongArrayList(); LongArrayList generationList = new LongArrayList();
byte lowestGeneratorDetailLevel = (byte) Math.min( byte lowestGeneratorDetailLevel = (byte) Math.min(
worldGenQueue.lowestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL, worldGenQueue.lowestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL,
DhSectionPos.getDetailLevel(pos)); DhSectionPos.getDetailLevel(pos));
DhSectionPos.forEachChildAtDetailLevel(pos, lowestGeneratorDetailLevel, (genPos) -> DhSectionPos.forEachChildAtDetailLevel(pos, lowestGeneratorDetailLevel, (genPos) ->
{ {
@@ -471,48 +476,13 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// helper classes // // helper classes //
//================// //================//
// TODO may not be needed
private class WorldGenTaskTracker implements IWorldGenTaskTracker
{
/** just used when debugging/troubleshooting */
private final long pos;
public WorldGenTaskTracker(long pos) { this.pos = pos; }
@Override
public Consumer<FullDataSourceV2> getDataSourceConsumer()
{
return (dataSource) ->
{
GeneratedFullDataSourceProvider.this.delayedFullDataSourceSaveCache.writeDataSourceToMemoryAndQueueSave(dataSource);
};
}
@Override
public CompletableFuture<Boolean> shouldGenerateSplitChild(long pos)
{
return GeneratedFullDataSourceProvider.this.getAsync(pos).thenApply(fullDataSource ->
{
//noinspection TryFinallyCanBeTryWithResources
try
{
return !GeneratedFullDataSourceProvider.this.isFullyGenerated(fullDataSource.columnGenerationSteps);
}
finally
{
fullDataSource.close();
}
});
}
}
private CompletableFuture<Void> onDataSourceSaveAsync(FullDataSourceV2 fullDataSource) private CompletableFuture<Void> onDataSourceSaveAsync(FullDataSourceV2 fullDataSource)
{ {
// block lights should have been populated at the chunkWrapper stage // block lights should have been populated at the chunkWrapper stage
// waiting to populate the data source's skylight at this stage prevents re-lighting and // waiting to populate the data source's skylight at this stage prevents re-lighting and
// allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once // allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, LodUtil.MAX_MC_LIGHT); int skyLight = this.level.getLevelWrapper().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT;
DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, skyLight);
return this.updateDataSourceAsync(fullDataSource); return this.updateDataSourceAsync(fullDataSource);
} }
@@ -524,7 +494,6 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
{ {
boolean shouldDoWorldGen(); boolean shouldDoWorldGen();
@Nullable
DhBlockPos2D getTargetPosForGeneration(); DhBlockPos2D getTargetPosForGeneration();
/** Fired whenever a section has completed generating */ /** Fired whenever a section has completed generating */
@@ -23,9 +23,11 @@ import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue; import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.LodRequestModule; import com.seibel.distanthorizons.core.level.LodRequestModule;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue; import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -74,7 +76,7 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide
//==================// //==================//
@Override @Override
public boolean canQueueRetrieval() { return this.canQueueRetrieval(true); } public boolean canQueueRetrievalNow() { return this.canQueueRetrievalNow(true); }
@Override @Override
@Nullable @Nullable
@@ -102,10 +104,20 @@ public class RemoteFullDataSourceProvider extends GeneratedFullDataSourceProvide
Long timestamp = this.getTimestampForPos(pos); Long timestamp = this.getTimestampForPos(pos);
if (timestamp != null) if (timestamp != null)
{ {
this.syncOnLoadRequestQueue.submitRequest(pos, timestamp, fullDataSource -> this.syncOnLoadRequestQueue.submitRequest(pos, timestamp)
{ .thenAccept((DataSourceRetrievalResult result) ->
this.updateDataSourceAsync(fullDataSource).whenComplete((result, throwable) -> fullDataSource.close()); {
}); if (result.state == ERetrievalResultState.SUCCESS
&& result.dataSource != null)
{
this.updateDataSourceAsync(result.dataSource)
.handle((voidObj, throwable) ->
{
result.dataSource.close();
return null;
});
}
});
} }
return super.get(pos); return super.get(pos);
@@ -25,7 +25,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc; import com.seibel.distanthorizons.core.file.fullDatafile.IDataSourceUpdateListenerFunc;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -48,7 +48,6 @@ import java.sql.SQLException;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
/** /**
* Handles reading/writing {@link FullDataSourceV2} * Handles reading/writing {@link FullDataSourceV2}
@@ -86,9 +85,9 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
protected final String levelId; protected final String levelId;
private final FullDataUpdaterV2 dataUpdater; protected final FullDataUpdaterV2 dataUpdater;
private final FullDataUpdatePropagatorV2 updatePropagator; protected final FullDataUpdatePropagatorV2 updatePropagator;
private final DataMigratorV1 dataMigratorV1; protected final DataMigratorV1 dataMigratorV1;
@@ -201,9 +200,10 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
return FullDataSourceV2.createEmpty(pos); return FullDataSourceV2.createEmpty(pos);
} }
FullDataSourceV2 dataSource = null;
try try
{ {
FullDataSourceV2 dataSource = this.createDataSourceFromDto(dto); dataSource = this.createDataSourceFromDto(dto);
// automatically create and save adjacent data if missing // automatically create and save adjacent data if missing
if (dto.dataFormatVersion == FullDataSourceV2DTO.DATA_FORMAT.V1_NO_ADJACENT_DATA) if (dto.dataFormatVersion == FullDataSourceV2DTO.DATA_FORMAT.V1_NO_ADJACENT_DATA)
@@ -222,6 +222,15 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
this.tryLogCorruptedDataError(DhSectionPos.toString(pos), e); this.tryLogCorruptedDataError(DhSectionPos.toString(pos), e);
this.repo.deleteWithKey(pos); this.repo.deleteWithKey(pos);
} }
catch (Exception e)
{
if (dataSource != null)
{
dataSource.close();
}
throw e;
}
} }
catch (InterruptedException ignore) { } catch (InterruptedException ignore) { }
catch (IOException e) catch (IOException e)
@@ -243,6 +252,11 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
catch (Exception e) catch (Exception e)
{ {
String message = e.getMessage(); String message = e.getMessage();
if (message == null)
{
message = "NULL";
}
if (CORRUPT_DATA_ERRORS_LOGGED.add(message)) if (CORRUPT_DATA_ERRORS_LOGGED.add(message))
{ {
LOGGER.warn("Unexpected error getting: [" + DhSectionPos.toString(pos) + "], this error message will only be logged once, error: [" + message + "].", e); LOGGER.warn("Unexpected error getting: [" + DhSectionPos.toString(pos) + "], this error message will only be logged once, error: [" + message + "].", e);
@@ -360,7 +374,7 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
* to the beginning of your override. * to the beginning of your override.
* Otherwise, parent retrieval limits will be ignored. * Otherwise, parent retrieval limits will be ignored.
*/ */
public boolean canQueueRetrieval() public boolean canQueueRetrievalNow()
{ {
// Retrieval shouldn't happen while an unknown number of // Retrieval shouldn't happen while an unknown number of
// legacy data sources are present. // legacy data sources are present.
@@ -369,15 +383,15 @@ public class FullDataSourceProviderV2 implements IDebugRenderable, AutoCloseable
} }
/** /**
* @return null if this provider can't generate any positions and * @return null if this provider can't generate any positions or
* an empty array if all positions were generated * an empty array if all positions were generated
*/ */
@Nullable @Nullable
public LongArrayList getPositionsToRetrieve(Long pos) { return null; } public LongArrayList getPositionsToRetrieve(long pos) { return null; }
/** @return true if the position was queued, false if not */ /** @return null if the position couldn't be queued */
@Nullable @Nullable
public CompletableFuture<WorldGenResult> queuePositionForRetrieval(Long genPos) { return null; } public CompletableFuture<DataSourceRetrievalResult> queuePositionForRetrieval(Long genPos) { return null; }
/** does nothing if the given position isn't present in the queue */ /** does nothing if the given position isn't present in the queue */
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { } public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) { }
@@ -185,7 +185,7 @@ public class FullDataUpdatePropagatorV2 implements IDebugRenderable, AutoCloseab
parentLocked = true; parentLocked = true;
this.dataUpdater.lockedPosSet.add(parentUpdatePos); this.dataUpdater.lockedPosSet.add(parentUpdatePos);
try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos)) // TODO can we cache anything in memory to speed up the propagation process? Compression/Disk IO is by far the slowest part of this process try (FullDataSourceV2 parentDataSource = this.provider.get(parentUpdatePos))
{ {
// will return null if the file handler is shutting down // will return null if the file handler is shutting down
if (parentDataSource != null) if (parentDataSource != null)
@@ -118,15 +118,19 @@ public class DhLightingEngine
* @param centerChunk the chunk we want to apply lighting to * @param centerChunk the chunk we want to apply lighting to
* @param nearbyChunkList should also contain centerChunk * @param nearbyChunkList should also contain centerChunk
* @param maxSkyLight should be a value between 0 and 15 * @param maxSkyLight should be a value between 0 and 15
*
* @return the number of light positions iterated over, can be used for profiling.
*/ */
private void lightChunk( private int lightChunk(
@NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList, @NotNull IChunkWrapper centerChunk, @NotNull ArrayList<IChunkWrapper> nearbyChunkList,
int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight) int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight)
{ {
DhChunkPos centerChunkPos = centerChunk.getChunkPos(); DhChunkPos centerChunkPos = centerChunk.getChunkPos();
AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk); AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk);
// how many positions we've walked over, can be used for profiling/debugging
int posIterations = 0;
// try-finally to handle the stableArray resources // try-finally to handle the stableArray resources
StableLightPosStack blockLightWorldPosQueue = null; StableLightPosStack blockLightWorldPosQueue = null;
StableLightPosStack skyLightWorldPosQueue = null; StableLightPosStack skyLightWorldPosQueue = null;
@@ -245,13 +249,15 @@ public class DhLightingEngine
} }
} }
// block light // block light
if (updateBlockLight) if (updateBlockLight)
{ {
// done to prevent a rare issue where the light values are incorrectly set to -1 // done to prevent a rare issue where the light values are incorrectly set to -1
centerChunk.clearDhBlockLighting(); centerChunk.clearDhBlockLighting();
this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder, posIterations += this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), (neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
true); true);
@@ -262,7 +268,7 @@ public class DhLightingEngine
{ {
centerChunk.clearDhSkyLighting(); centerChunk.clearDhSkyLighting();
this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder, posIterations += this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), (neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
false); false);
@@ -287,10 +293,12 @@ public class DhLightingEngine
{ {
centerChunk.setIsDhSkyLightCorrect(true); centerChunk.setIsDhSkyLightCorrect(true);
} }
return posIterations;
} }
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */ /** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
private void propagateChunkLightPosList( private int propagateChunkLightPosList(
StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder, StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder,
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc, IGetLightFunc getLightFunc, ISetLightFunc setLightFunc,
boolean propagatingBlockLights) boolean propagatingBlockLights)
@@ -320,66 +328,89 @@ public class DhLightingEngine
IBlockStateWrapper previousBlockState = null; IBlockStateWrapper previousBlockState = null;
// update each light position int iterations = 0;
while (!lightPosQueue.isEmpty())
// 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, // Walking down from the top light level to the bottom can reduce iterating over
// 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) // the same positions multiple times.
lightPosQueue.popMutate(lightPos); // 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;
// update each light position
// propagate the lighting in each cardinal direction, IE: -x, +x, -y, +y, -z, +z while (!lightPosQueue.isLightLevelEmpty(currentLightLevel))
for (EDhDirection direction : EDhDirection.ALL) // since this is an array instead of an ArrayList this advanced for-loop shouldn't cause any GC issues
{ {
lightPos.mutateOffset(direction, neighbourBlockPos); // since we don't care about the order the positions are processed,
neighbourBlockPos.mutateToChunkRelativePos(relNeighbourBlockPos); // 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 // propagate the lighting in each cardinal direction, IE: -x, +x, -y, +y, -z, +z
IChunkWrapper neighbourChunk = adjacentChunkHolder.getByBlockPos(neighbourBlockPos.getX(), neighbourBlockPos.getZ()); for (EDhDirection direction : EDhDirection.ALL) // since this is an array instead of an ArrayList this advanced for-loop shouldn't cause any GC issues
if (neighbourChunk == null)
{ {
// the light pos is outside our generator's range, ignore it lightPos.mutateOffset(direction, neighbourBlockPos);
continue; neighbourBlockPos.mutateToChunkRelativePos(relNeighbourBlockPos);
}
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);
// now that light has been propagated to this blockPos
// we need to queue it up so its neighbours can be propagated as well // only continue if the light position is inside one of our chunks
lightPosQueue.push(neighbourBlockPos.getX(), neighbourBlockPos.getY(), neighbourBlockPos.getZ(), targetLevel); 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 // can be enabled if troubleshooting lighting issues
if (RENDER_BLOCK_LIGHT_WIREFRAME if (RENDER_BLOCK_LIGHT_WIREFRAME
@@ -395,6 +426,7 @@ public class DhLightingEngine
// propagation complete // propagation complete
return iterations;
} }
@@ -748,16 +780,24 @@ public class DhLightingEngine
private static final Queue<StableLightPosStack> lightArrayCache = new ArrayDeque<>(); private static final Queue<StableLightPosStack> lightArrayCache = new ArrayDeque<>();
/** the index of the last item in the array, -1 if empty */ /** the index of the last item in the array, -1 if empty */
private int index = -1; private int[] indexByLightLevel = new int[LodUtil.MAX_MC_LIGHT + 1];
/** x, y, z, and lightValue. */ /** x, y, z */
public static final int INTS_PER_LIGHT_POS = 4; public static final int INTS_PER_LIGHT_POS = 3;
/** private final IntArrayList[] lightPositionsByLightLevel = new IntArrayList[LodUtil.MAX_MC_LIGHT + 1];
* When tested with a normal 1.20 world James saw a maximum of 36,709 block and 2,355 sky lights,
* so 40,000 should be a good starting point that can contain most lighting tasks.
*/ public StableLightPosStack()
private final IntArrayList lightPositions = new IntArrayList(40_000 * INTS_PER_LIGHT_POS); {
for (int i = 0; i < this.lightPositionsByLightLevel.length; i++)
{
// When tested with a normal 1.20 world James saw a maximum of 36,709 block and 2,355 sky lights,
// so 40,000 should be a good starting point that can contain most lighting tasks.
this.lightPositionsByLightLevel[i] = new IntArrayList(40_000 * INTS_PER_LIGHT_POS);
this.indexByLightLevel[i] = -1;
}
}
@@ -804,45 +844,56 @@ public class DhLightingEngine
// stack methods // // stack methods //
//===============// //===============//
public boolean isEmpty() { return this.index == -1; } public boolean isLightLevelEmpty(int lightLevel) { return this.indexByLightLevel[lightLevel] == -1; }
public int size() { return this.index+1; } //public int size() { return this.index+1; }
public void push(int blockX, int blockY, int blockZ, int lightValue) public void push(int blockX, int blockY, int blockZ, int lightLevel)
{ {
this.index++; IntArrayList lightPositions = this.lightPositionsByLightLevel[lightLevel];
int subIndex = this.index * INTS_PER_LIGHT_POS;
if (subIndex < this.lightPositions.size()) this.indexByLightLevel[lightLevel]++;
int subIndex = this.indexByLightLevel[lightLevel] * INTS_PER_LIGHT_POS;
if (subIndex < lightPositions.size())
{ {
this.lightPositions.set(subIndex, blockX); lightPositions.set(subIndex, blockX);
this.lightPositions.set(subIndex + 1, blockY); lightPositions.set(subIndex + 1, blockY);
this.lightPositions.set(subIndex + 2, blockZ); lightPositions.set(subIndex + 2, blockZ);
this.lightPositions.set(subIndex + 3, lightValue);
} }
else else
{ {
// add a new pos // add a new pos
this.lightPositions.add(blockX); lightPositions.add(blockX);
this.lightPositions.add(blockY); lightPositions.add(blockY);
this.lightPositions.add(blockZ); lightPositions.add(blockZ);
this.lightPositions.add(lightValue);
} }
} }
/** mutates the given {@link LightPos} to match the next {@link LightPos} in the queue. */ /** mutates the given {@link LightPos} to match the next {@link LightPos} in the queue. */
public void popMutate(LightPos pos) public void popMutate(LightPos pos, int lightLevel)
{ {
int subIndex = this.index * INTS_PER_LIGHT_POS; int subIndex = this.indexByLightLevel[lightLevel] * INTS_PER_LIGHT_POS;
IntArrayList lightPositions = this.lightPositionsByLightLevel[lightLevel];
pos.setX(this.lightPositions.getInt(subIndex)); pos.setX(lightPositions.getInt(subIndex));
pos.setY(this.lightPositions.getInt(subIndex + 1)); pos.setY(lightPositions.getInt(subIndex + 1));
pos.setZ(this.lightPositions.getInt(subIndex + 2)); pos.setZ(lightPositions.getInt(subIndex + 2));
pos.lightValue = this.lightPositions.getInt(subIndex + 3);
this.index--; this.indexByLightLevel[lightLevel]--;
} }
@Override @Override
public String toString() { return this.index + "/" + (this.lightPositions.size() / INTS_PER_LIGHT_POS); } public String toString()
{
StringBuilder builder = new StringBuilder();
for (int i = 0; i < this.indexByLightLevel.length; i++)
{
builder.append("light: ").append(i)
.append(" size: ").append(this.indexByLightLevel[i]).append("/").append(this.lightPositionsByLightLevel[i].size() / INTS_PER_LIGHT_POS).append("\n");
}
return builder.toString();
}
} }
@@ -19,13 +19,11 @@
package com.seibel.distanthorizons.core.generation; package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.LodQuadTree; import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import org.jetbrains.annotations.Nullable;
import java.io.Closeable; import java.io.Closeable;
import java.util.List; import java.util.List;
@@ -92,7 +90,7 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
*/ */
void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf); void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf);
CompletableFuture<WorldGenResult> submitRetrievalTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker); CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long pos, byte requiredDataDetail);
@@ -7,7 +7,9 @@ import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider; import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.FormatUtil; import com.seibel.distanthorizons.core.util.FormatUtil;
@@ -148,20 +150,22 @@ public class PregenManager
this.fullDataSourceProvider.getAsync(nextSectionPos) this.fullDataSourceProvider.getAsync(nextSectionPos)
.thenAccept(fullDataSource -> .thenAccept(fullDataSource ->
{ {
if (this.fullDataSourceProvider.isFullyGenerated(fullDataSource.columnGenerationSteps)) if (this.fullDataSourceProvider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps))
{ {
this.pendingGenerations.invalidate(fullDataSource.getPos()); this.pendingGenerations.invalidate(fullDataSource.getPos());
} }
else else
{ {
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos()).thenAccept(result -> { this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos())
if (!result.success) .thenAccept((DataSourceRetrievalResult result) ->
{ {
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos)); if (result.state == ERetrievalResultState.FAIL)
} {
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
this.pendingGenerations.invalidate(result.pos); }
});
this.pendingGenerations.invalidate(result.pos);
});
} }
fullDataSource.close(); fullDataSource.close();
@@ -1,8 +1,8 @@
package com.seibel.distanthorizons.core.generation; package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.AbstractFullDataNetworkRequestQueue; import com.seibel.distanthorizons.core.multiplayer.client.AbstractFullDataNetworkRequestQueue;
@@ -14,10 +14,8 @@ import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.WorldGenUtil; import com.seibel.distanthorizons.core.util.WorldGenUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*; import java.util.concurrent.*;
public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
@@ -54,46 +52,28 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; } public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; }
@Override @Override
public CompletableFuture<WorldGenResult> submitRetrievalTask(long sectionPos, byte requiredDataDetail, IWorldGenTaskTracker tracker) public CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long sectionPos, byte requiredDataDetail)
{ {
long generationStartMsTime = System.currentTimeMillis(); long generationStartMsTime = System.currentTimeMillis();
return super.submitRequest(sectionPos, fullDataSource -> { CompletableFuture<DataSourceRetrievalResult> future = super.submitRequest(sectionPos, /* client timestamp */null);
Objects.requireNonNull(tracker.getDataSourceConsumer()).accept(fullDataSource); future.thenAccept((DataSourceRetrievalResult result) ->
fullDataSource.close(); {
}) if (result.state == ERetrievalResultState.SUCCESS)
.thenApply(requestResult -> {
{ long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
int chunkWidth = DhSectionPos.getChunkWidth(sectionPos);
int chunkWidth = DhSectionPos.getChunkWidth(sectionPos); int chunkCount = chunkWidth * chunkWidth;
int chunkCount = chunkWidth * chunkWidth; double timePerChunk = (double) totalGenTimeInMs / (double) chunkCount;
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
this.rollingAverageChunkGenTimeInMs.add(timePerChunk); // only add the time on successes
// it won't be a perfect estimate but fails will often come back faster, skewing the time faster
switch (requestResult) this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
{ }
case SUCCEEDED: });
return WorldGenResult.CreateSuccess(sectionPos); return future;
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();
});
} }
@Override @Override
@@ -109,7 +89,7 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
@Override @Override
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getGenerationRequestRateLimit(); } protected int getRequestRateLimit() { return this.networkState.sessionConfig.getGenerationRequestRateLimit(); }
@Override @Override
protected boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos) protected boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos)
{ {
if (this.networkState.sessionConfig.getGenerationMaxChunkRadius() > 0) if (this.networkState.sessionConfig.getGenerationMaxChunkRadius() > 0)
{ {
@@ -127,12 +107,13 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxGenerationRequestDistance() * 16; return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxGenerationRequestDistance() * 16;
} }
@Override @Override
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<ERequestResult> future) protected boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future)
{ {
if (DhSectionPos.getDetailLevel(sectionPos) > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL // split up large requests if N-sized gen isn't enabled
&& !Config.Server.Experimental.enableNSizedGeneration.get()) if (!Config.Server.Experimental.enableNSizedGeneration.get()
&& DhSectionPos.getDetailLevel(sectionPos) > DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)
{ {
future.complete(ERequestResult.REQUIRES_SPLITTING); future.complete(DataSourceRetrievalResult.CreateSplit());
return false; return false;
} }
@@ -26,11 +26,8 @@ import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource; import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.InProgressWorldGenTaskGroup; import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalTask;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTask;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTaskGroup;
import com.seibel.distanthorizons.core.level.IDhServerLevel; import com.seibel.distanthorizons.core.level.IDhServerLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
@@ -52,14 +49,12 @@ import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import java.awt.*; import java.awt.*;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.Consumer;
public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDebugRenderable
{ {
@@ -71,9 +66,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private final IDhServerLevel level; private final IDhServerLevel level;
/** contains the positions that need to be generated */ /** contains the positions that need to be generated */
private final ConcurrentHashMap<Long, WorldGenTask> waitingTasks = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Long, DataSourceRetrievalTask> waitingTasks = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, DataSourceRetrievalTask> inProgressGenTasksByLodPos = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, InProgressWorldGenTaskGroup> inProgressGenTasksByLodPos = new ConcurrentHashMap<>();
/** largest numerical detail level allowed */ /** largest numerical detail level allowed */
public final byte lowestDataDetail; public final byte lowestDataDetail;
@@ -102,9 +96,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
//==============// //=============//
// constructors // // constructor //
//==============// //=============//
///region constructor
public WorldGenerationQueue(IDhApiWorldGenerator generator, IDhServerLevel level) public WorldGenerationQueue(IDhApiWorldGenerator generator, IDhServerLevel level)
{ {
@@ -118,20 +113,29 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LOGGER.info("Created world gen queue"); LOGGER.info("Created world gen queue");
} }
///endregion constructor
//=================//
// world generator // //===============//
// task handling // // task handling //
//=================// //===============//
///region task handling
@Override @Override
public CompletableFuture<WorldGenResult> submitRetrievalTask(long pos, byte requiredDataDetail, IWorldGenTaskTracker tracker) public CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long pos, byte requiredDataDetail)
{ {
// the generator is shutting down, don't add new tasks // the generator is shutting down, don't add new tasks
if (this.generatorClosingFuture != null) if (this.generatorClosingFuture != null)
{ {
return CompletableFuture.completedFuture(WorldGenResult.CreateFail()); return CompletableFuture.completedFuture(DataSourceRetrievalResult.CreateFail());
}
// use the existing task if present
DataSourceRetrievalTask existingGenTask = this.waitingTasks.get(pos);
if (existingGenTask != null)
{
return existingGenTask.future;
} }
@@ -145,13 +149,12 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
requiredDataDetail = this.lowestDataDetail; requiredDataDetail = this.lowestDataDetail;
} }
// Assert that the data at least can fill in 1 single ChunkSizedFullDataAccessor // the request should be at least chunk-sized
LodUtil.assertTrue(DhSectionPos.getDetailLevel(pos) > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL); LodUtil.assertTrue(DhSectionPos.getDetailLevel(pos) > requiredDataDetail + LodUtil.CHUNK_DETAIL_LEVEL);
DataSourceRetrievalTask genTask = new DataSourceRetrievalTask(pos, requiredDataDetail);
CompletableFuture<WorldGenResult> future = new CompletableFuture<>(); this.waitingTasks.put(pos, genTask);
this.waitingTasks.put(pos, new WorldGenTask(pos, requiredDataDetail, tracker, future)); return genTask.future;
return future;
} }
@Override @Override
@@ -161,11 +164,17 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{ {
if (removeIf.accept(genPos)) if (removeIf.accept(genPos))
{ {
this.waitingTasks.remove(genPos); DataSourceRetrievalTask removedTask = this.waitingTasks.remove(genPos);
if (removedTask != null)
{
// cancel tasks so any waiting future steps can be triggered
removedTask.future.cancel(true);
}
} }
}); });
} }
///endregion task handling
@@ -248,18 +257,27 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
} }
// find the closest task
TaskDistancePair closestTaskPair = this.waitingTasks.reduceEntries(1024, TaskDistancePair closestTaskPair = this.waitingTasks.reduceEntries(1024,
entry -> new TaskDistancePair(entry.getValue(), DhSectionPos.getSectionBBoxPos(entry.getValue().pos).getCenterBlockPos().toPos2D().chebyshevDist(targetPos.toPos2D())), // get the target distance for each task
(TaskDistancePair aTaskPair, TaskDistancePair bTaskPair) -> (aTaskPair.dist < bTaskPair.dist) ? aTaskPair : bTaskPair); (Map.Entry<Long, DataSourceRetrievalTask> entry) ->
{
DataSourceRetrievalTask task = entry.getValue();
int distance = DhSectionPos.getCenterBlockPos(task.pos).chebyshevDist(targetPos);
return new TaskDistancePair(entry.getValue(), distance);
},
// find the closest task
(TaskDistancePair aTaskPair, TaskDistancePair bTaskPair) ->
{
return (aTaskPair.dist < bTaskPair.dist) ? aTaskPair : bTaskPair;
});
if (closestTaskPair == null) if (closestTaskPair == null)
{ {
// FIXME concurrency issue // the waitingTasks was modified while this check was running
return false; return false;
} }
DataSourceRetrievalTask closestTask = closestTaskPair.task;
WorldGenTask closestTask = closestTaskPair.task;
// remove the task we found, we are going to start it and don't want to run it multiple times // remove the task we found, we are going to start it and don't want to run it multiple times
this.waitingTasks.remove(closestTask.pos, closestTask); this.waitingTasks.remove(closestTask.pos, closestTask);
@@ -269,76 +287,69 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
{ {
// detail level is correct for generation, start generation // detail level is correct for generation, start generation
WorldGenTaskGroup closestTaskGroup = new WorldGenTaskGroup(closestTask.pos, (byte)(closestTask.pos - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL)); DataSourceRetrievalTask existingTask = this.inProgressGenTasksByLodPos.get(closestTask.pos);
closestTaskGroup.worldGenTasks.add(closestTask); if (existingTask == null)
if (!this.inProgressGenTasksByLodPos.containsKey(closestTask.pos))
{ {
// no task exists for this position, start one // no task exists for this position, start one
InProgressWorldGenTaskGroup newTaskGroup = new InProgressWorldGenTaskGroup(closestTaskGroup); this.startWorldGenTaskGroup(closestTask);
this.startWorldGenTaskGroup(newTaskGroup);
} }
else else
{ {
// TODO replace the previous inProgress task if one exists // shouldn't normally happen, but if
// Note: Due to concurrency reasons, even if the currently running task is compatible with // we somehow queued the same task twice:
// the newly selected task, we cannot use it, // merge the two futures so they both complete
// as some chunks may have already been written into.
//LOGGER.warn("A task already exists for this position, todo: "+DhSectionPos.toString(closestTask.pos)); existingTask.future.thenApply((DataSourceRetrievalResult result)->
{
closestTask.future.complete(result);
return closestTask.future; // return value ignored
});
existingTask.future.exceptionally((Throwable throwable)->
{
closestTask.future.completeExceptionally(throwable);
return null; // return value ignored
});
} }
// a task has been started
return true;
} }
else else
{ {
// detail level is too high (if the detail level was too low, the generator would've ignored the request), // detail level is too high (if the detail level was too low, the generator would've ignored the request),
// split up the task // split up the task
closestTask.future.complete(DataSourceRetrievalResult.CreateSplit());
// split up the task and add each one to the tree
LinkedList<CompletableFuture<WorldGenResult>> childFutures = new LinkedList<>();
long sectionPos = closestTask.pos;
WorldGenTask finalClosestTask = closestTask;
DhSectionPos.forEachChild(sectionPos, (childDhSectionPos) ->
{
CompletableFuture<WorldGenResult> newFuture = new CompletableFuture<>();
childFutures.add(newFuture);
WorldGenTask newGenTask = new WorldGenTask(childDhSectionPos, DhSectionPos.getDetailLevel(childDhSectionPos), finalClosestTask.taskTracker, newFuture);
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;
} }
}
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(); long generationStartMsTime = System.currentTimeMillis();
CompletableFuture<Void> generationFuture = this.startGenerationEvent(taskPos, taskDetailLevel, generationRequestChunkWidthCount, newTaskGroup.group::consumeDataSource); CompletableFuture<FullDataSourceV2> generationFuture = this.startGenerationEvent(worldGenTask);
// calculate generation speed
generationFuture.thenRun(() -> generationFuture.thenRun(() ->
{ {
long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime; long totalGenTimeInMs = System.currentTimeMillis() - generationStartMsTime;
int chunkCount = generationRequestChunkWidthCount * generationRequestChunkWidthCount; int chunkCount = worldGenTask.widthInChunks * worldGenTask.widthInChunks;
double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount; double timePerChunk = (double)totalGenTimeInMs / (double)chunkCount;
this.rollingAverageChunkGenTimeInMs.add(timePerChunk); this.rollingAverageChunkGenTimeInMs.add(timePerChunk);
}); });
newTaskGroup.genFuture = generationFuture; generationFuture.handle((FullDataSourceV2 fullDataSource, Throwable exception) ->
LodUtil.assertTrue(newTaskGroup.genFuture != null);
newTaskGroup.genFuture.whenComplete((voidObj, exception) ->
{ {
try try
{ {
@@ -350,157 +361,51 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception); LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
} }
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateFail())); LodUtil.assertTrue(fullDataSource == null);
worldGenTask.future.complete(DataSourceRetrievalResult.CreateFail());
} }
else else
{ {
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(taskPos))); boolean taskRemoved = this.inProgressGenTasksByLodPos.remove(taskPos, worldGenTask);
LodUtil.assertTrue(taskRemoved, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]");
worldGenTask.future.complete(DataSourceRetrievalResult.CreateSuccess(taskPos, fullDataSource));
} }
boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, newTaskGroup);
LodUtil.assertTrue(worked, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]");
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e); LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e);
worldGenTask.future.completeExceptionally(e);
} }
finally finally
{ {
this.tryQueueNewWorldGenRequestsAsync(); this.tryQueueNewWorldGenRequestsAsync();
} }
return null;
}); });
this.inProgressGenTasksByLodPos.put(taskPos, newTaskGroup);
} }
private CompletableFuture<Void> startGenerationEvent( private CompletableFuture<FullDataSourceV2> startGenerationEvent(DataSourceRetrievalTask task)
long requestPos,
byte targetDataDetail,
int generationRequestChunkWidthCount,
Consumer<FullDataSourceV2> dataSourceConsumer
)
{ {
DhChunkPos chunkPosMin = new DhChunkPos(DhSectionPos.getSectionBBoxPos(requestPos).getCornerBlockPos()); this.inProgressGenTasksByLodPos.put(task.pos, task);
DhChunkPos chunkPosMin = new DhChunkPos(new DhBlockPos2D(DhSectionPos.getMinCornerBlockX(task.pos), DhSectionPos.getMinCornerBlockZ(task.pos)));
EDhApiDistantGeneratorMode generatorMode = Config.Common.WorldGenerator.distantGeneratorMode.get(); EDhApiDistantGeneratorMode generatorMode = Config.Common.WorldGenerator.distantGeneratorMode.get();
EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType(); EDhApiWorldGeneratorReturnType returnType = this.generator.getReturnType();
switch (returnType) switch (returnType)
{ {
case VANILLA_CHUNKS: case VANILLA_CHUNKS:
{ {
return this.generator.generateChunks( return this.startVanillaChunkGenerationEvent(task, chunkPosMin, generatorMode);
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);
}
}
);
} }
case API_CHUNKS: case API_CHUNKS:
{ {
return this.generator.generateApiChunks( return this.startApiChunkGenerationEvent(task, chunkPosMin, generatorMode);
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);
}
}
);
} }
case API_DATA_SOURCES: case API_DATA_SOURCES:
{ {
// done to reduce GC overhead return this.startApiDataSourceGenerationEvent(task, chunkPosMin, generatorMode);
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);
}
}
);
} }
default: default:
{ {
@@ -509,30 +414,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 // // getters / setters //
//===================// //===================//
///region getters/setters
@Override public int getWaitingTaskCount() { return this.waitingTasks.size(); } @Override public int getWaitingTaskCount() { return this.waitingTasks.size(); }
@Override public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); } @Override public int getInProgressTaskCount() { return this.inProgressGenTasksByLodPos.size(); }
@Override @Override public byte lowestDataDetail() { return this.lowestDataDetail; }
public byte lowestDataDetail() { return this.lowestDataDetail; } @Override public byte highestDataDetail() { return this.highestDataDetail; }
@Override
public byte highestDataDetail() { return this.highestDataDetail; }
@Override @Override public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; }
public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; } @Override public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; }
@Override
public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; }
@Override @Override public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; }
public int getRetrievalEstimatedRemainingChunkCount() { return this.estimatedRemainingChunkCount; } @Override public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; }
@Override
public void setRetrievalEstimatedRemainingChunkCount(int newEstimate) { this.estimatedRemainingChunkCount = newEstimate; }
@Override @Override
public void addDebugMenuStringsToList(List<String> messageList) { } public void addDebugMenuStringsToList(List<String> messageList) { }
@@ -550,13 +606,55 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
return chunkCount; return chunkCount;
} }
///endregion getters/setters
//=======//
// debug //
//=======//
///region debug
@Override
public void debugRender(DebugRenderer renderer)
{
int levelMinY = this.level.getLevelWrapper().getMinHeight();
int levelMaxY = this.level.getLevelWrapper().getMaxHeight();
// show the wireframe a bit lower than world max height,
// since most worlds don't render all the way up to the max height
int levelHeightRange = (levelMaxY - levelMinY);
int maxY = levelMaxY - (levelHeightRange / 2);
// blue - queued
this.waitingTasks.keySet().forEach((Long pos) ->
{
renderer.renderBox(
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue)
);
});
// red - in progress
this.inProgressGenTasksByLodPos.forEach((Long pos, DataSourceRetrievalTask task) ->
{
renderer.renderBox(
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red)
);
});
}
///endregion debug
//==========// //==========//
// shutdown // // shutdown //
//==========// //==========//
///region shutdown
@Override public CompletableFuture<Void> startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning) @Override
public CompletableFuture<Void> startClosingAsync(boolean cancelCurrentGeneration, boolean alsoInterruptRunning)
{ {
LOGGER.info("Closing world gen queue"); LOGGER.info("Closing world gen queue");
this.queueingThread.shutdownNow(); this.queueingThread.shutdownNow();
@@ -564,33 +662,32 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// stop and remove any in progress tasks // stop and remove any in progress tasks
ArrayList<CompletableFuture<Void>> inProgressTasksCancelingFutures = new ArrayList<>(this.inProgressGenTasksByLodPos.size()); ArrayList<CompletableFuture<Void>> inProgressTasksCancelingFutures = new ArrayList<>(this.inProgressGenTasksByLodPos.size());
this.inProgressGenTasksByLodPos.values().forEach(runningTaskGroup -> this.inProgressGenTasksByLodPos.values().forEach((DataSourceRetrievalTask genTask) ->
{ {
CompletableFuture<Void> genFuture = runningTaskGroup.genFuture; // Do this to prevent it getting swapped out CompletableFuture<DataSourceRetrievalResult> genFuture = genTask.future;
if (genFuture == null)
{
// genFuture's shouldn't be null, but sometimes they are...
LOGGER.info("Null gen future: "+runningTaskGroup.group.pos);
return;
}
if (cancelCurrentGeneration) if (cancelCurrentGeneration)
{ {
genFuture.cancel(alsoInterruptRunning); genFuture.cancel(alsoInterruptRunning);
} }
inProgressTasksCancelingFutures.add(genFuture.handle((voidObj, exception) -> inProgressTasksCancelingFutures.add(genFuture.handle((DataSourceRetrievalResult result, Throwable throwable) ->
{ {
if (exception instanceof CompletionException) if (throwable instanceof CompletionException)
{ {
exception = exception.getCause(); throwable = throwable.getCause();
} }
if (!UncheckedInterruptedException.isInterrupt(exception) if (!UncheckedInterruptedException.isInterrupt(throwable)
&& !(exception instanceof CancellationException)) && !(throwable instanceof CancellationException))
{ {
LOGGER.error("Error when terminating data generation for pos: ["+DhSectionPos.toString(runningTaskGroup.group.pos)+"], error: ["+exception.getMessage()+"].", exception); LOGGER.error("Error when terminating data generation for pos: ["+DhSectionPos.toString(genTask.pos)+"], error: ["+throwable.getMessage()+"].", throwable);
}
if (result != null
&& result.dataSource != null)
{
result.dataSource.close();
} }
return null; return null;
@@ -623,7 +720,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LOGGER.info("World generator thread pool shutdown with [" + queueSize + "] incomplete tasks."); LOGGER.info("World generator thread pool shutdown with [" + queueSize + "] incomplete tasks.");
} }
this.inProgressGenTasksByLodPos.values().forEach((inProgressWorldGenTaskGroup) -> inProgressWorldGenTaskGroup.genFuture.cancel(true)); this.inProgressGenTasksByLodPos.values().forEach((inProgressWorldGenTaskGroup) -> inProgressWorldGenTaskGroup.future.cancel(true));
this.waitingTasks.values().forEach((worldGenTask) -> worldGenTask.future.cancel(true)); this.waitingTasks.values().forEach((worldGenTask) -> worldGenTask.future.cancel(true));
@@ -644,63 +741,22 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
LOGGER.info("Finished closing " + WorldGenerationQueue.class.getSimpleName()); LOGGER.info("Finished closing " + WorldGenerationQueue.class.getSimpleName());
} }
///endregion shutdown
//=======//
// debug //
//=======//
@Override
public void debugRender(DebugRenderer renderer)
{
int levelMinY = this.level.getLevelWrapper().getMinHeight();
int levelMaxY = this.level.getLevelWrapper().getMaxHeight();
// show the wireframe a bit lower than world max height,
// since most worlds don't render all the way up to the max height
int levelHeightRange = (levelMaxY - levelMinY);
int maxY = levelMaxY - (levelHeightRange / 2);
// blue - queued
this.waitingTasks.keySet().forEach((pos) ->
{
renderer.renderBox(
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.blue));
});
// red - in progress
this.inProgressGenTasksByLodPos.forEach((pos, t) ->
{
renderer.renderBox(
new DebugRenderer.Box(pos, levelMinY, maxY, 0.05f, Color.red));
});
}
//================//
// helper methods //
//================//
private boolean canGenerateDetailLevel(byte taskDetailLevel)
{
byte requestedDetailLevel = (byte) (taskDetailLevel - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
return (this.highestDataDetail <= requestedDetailLevel && requestedDetailLevel <= this.lowestDataDetail);
}
//================// //================//
// helper classes // // helper classes //
//================// //================//
///region helper classes
/** Used during task starting to determine the closest task */
private static class TaskDistancePair private static class TaskDistancePair
{ {
public final WorldGenTask task; public final DataSourceRetrievalTask task;
public final int dist; public final int dist;
public TaskDistancePair(WorldGenTask task, int dist) public TaskDistancePair(DataSourceRetrievalTask task, int dist)
{ {
this.task = task; this.task = task;
this.dist = dist; this.dist = dist;
@@ -708,4 +764,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
} }
///endregion helper classes
} }
@@ -0,0 +1,53 @@
/*
* 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 org.jetbrains.annotations.Nullable;
/**
* @see DataSourceRetrievalTask
*/
public class DataSourceRetrievalResult
{
public final ERetrievalResultState state;
/** the position that was generated, will be null if nothing was generated */
public final long pos;
@Nullable
public final FullDataSourceV2 dataSource;
//==============//
// constructors //
//==============//
public static DataSourceRetrievalResult CreateSplit() { return new DataSourceRetrievalResult(ERetrievalResultState.REQUIRES_SPLITTING, 0, null); }
public static DataSourceRetrievalResult CreateFail() { return new DataSourceRetrievalResult(ERetrievalResultState.FAIL, 0, null); }
public static DataSourceRetrievalResult CreateSuccess(long pos, FullDataSourceV2 generatedDataSource) { return new DataSourceRetrievalResult(ERetrievalResultState.SUCCESS, pos, generatedDataSource); }
private DataSourceRetrievalResult(ERetrievalResultState state, long pos, @Nullable FullDataSourceV2 dataSource)
{
this.state = state;
this.pos = pos;
this.dataSource = dataSource;
}
}
@@ -19,29 +19,37 @@
package com.seibel.distanthorizons.core.generation.tasks; package com.seibel.distanthorizons.core.generation.tasks;
import com.seibel.distanthorizons.core.file.fullDatafile.GeneratedFullDataSourceProvider; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* @author Leetom * @see DataSourceRetrievalResult
* @version 2022-11-25
*/ */
public final class WorldGenTask public final class DataSourceRetrievalTask
{ {
public final long pos; public final long pos;
public final byte dataDetailLevel; /**
public final IWorldGenTaskTracker taskTracker; * Usually the same as {@link DataSourceRetrievalTask#pos}, but
public final CompletableFuture<WorldGenResult> future; * can differ if the task needs something different.
*/
public final byte requestDetailLevel;
public final int widthInChunks;
public final CompletableFuture<DataSourceRetrievalResult> future = new CompletableFuture<>();
public WorldGenTask(long pos, byte dataDetail, IWorldGenTaskTracker taskTracker, CompletableFuture<WorldGenResult> future) //=============//
// constructor //
//=============//
public DataSourceRetrievalTask(long pos, byte dataDetail)
{ {
this.dataDetailLevel = dataDetail;
this.pos = pos; this.pos = pos;
this.taskTracker = taskTracker; this.requestDetailLevel = dataDetail;
this.future = future; this.widthInChunks = BitShiftUtil.powerOfTwo(DhSectionPos.getDetailLevel(this.pos) - this.requestDetailLevel - 4); // minus 4 is equal to dividing by 16 to convert to chunk scale
} }
} }
@@ -0,0 +1,15 @@
package com.seibel.distanthorizons.core.generation.tasks;
/**
* SUCCESS <br>
* REQUIRES_SPLITTING <br>
* FAIL <br>
*
* @see DataSourceRetrievalResult
*/
public enum ERetrievalResultState
{
SUCCESS,
REQUIRES_SPLITTING,
FAIL,
}
@@ -1,39 +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 javax.annotation.Nullable;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
/**
* @author Leetom
* @version 2022-11-25
*/
public interface IWorldGenTaskTracker
{
@Nullable
Consumer<FullDataSourceV2> getDataSourceConsumer();
CompletableFuture<Boolean> shouldGenerateSplitChild(long pos);
}
@@ -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);
}
}
}
@@ -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);
}
}
}
}
@@ -192,23 +192,30 @@ public abstract class AbstractDhLevel implements IDhLevel
return this.updateDataSourcesAsync(fullDataSource) return this.updateDataSourcesAsync(fullDataSource)
.thenRun(() -> .thenRun(() ->
{ {
HashSet<DhChunkPos> updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos()); try
if (updatedChunkPosSet != null)
{ {
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 for (DhChunkPos chunkPos : updatedChunkPosSet)
Integer chunkHash = this.updatedChunkHashesByChunkPos.remove(chunkPos);
if (this.chunkHashRepo != null && chunkHash != null)
{ {
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash)); // 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)
ApiEventInjector.INSTANCE.fireAllEvents( {
this.chunkHashRepo.save(new ChunkHashDTO(chunkPos, chunkHash));
}
ApiEventInjector.INSTANCE.fireAllEvents(
DhApiChunkModifiedEvent.class, DhApiChunkModifiedEvent.class,
new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ())); new DhApiChunkModifiedEvent.EventParam(this.getLevelWrapper(), chunkPos.getX(), chunkPos.getZ()));
}
} }
} }
catch (Exception e)
{
LOGGER.error("Unexpected issue after onDataSourceSaveAsync, error: ["+e.getMessage()+"].", e);
}
}); });
} }
@@ -102,13 +102,12 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
{ return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); } { return Config.Common.WorldGenerator.enableDistantGeneration.get() && !this.worldGenPlayerCenteringQueue.isEmpty(); }
@Override @Override
@Nullable
public DhBlockPos2D getTargetPosForGeneration() public DhBlockPos2D getTargetPosForGeneration()
{ {
IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek(); IServerPlayerWrapper firstPlayer = this.worldGenPlayerCenteringQueue.peek();
if (firstPlayer == null) if (firstPlayer == null)
{ {
return null; return DhBlockPos2D.ZERO;
} }
// Put first player in back before removing from front, so it can be removed by other thread without blocking // Put first player in back before removing from front, so it can be removed by other thread without blocking
@@ -247,7 +247,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
} }
@Override @Override
@Nullable
public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); } public DhBlockPos2D getTargetPosForGeneration() { return new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()); }
@@ -259,13 +258,6 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Override @Override
public void onWorldGenTaskComplete(long pos) public void onWorldGenTaskComplete(long pos)
{ {
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(pos, 128f, 156f, 0.09f, Color.red.darker()),
0.2, 32f
)
);
this.clientside.reloadPos(pos); this.clientside.reloadPos(pos);
} }
@@ -379,9 +371,9 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
private static class LodRequestState extends LodRequestModule.AbstractLodRequestState private static class LodRequestState extends LodRequestModule.AbstractLodRequestState
{ {
LodRequestState(DhClientLevel level, ClientNetworkState networkState) LodRequestState(DhClientLevel clientLevel, ClientNetworkState networkState)
{ {
this.retrievalQueue = new RemoteWorldRetrievalQueue(networkState, level); this.retrievalQueue = new RemoteWorldRetrievalQueue(networkState, clientLevel);
} }
} }
@@ -132,14 +132,6 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
public void onWorldGenTaskComplete(long pos) public void onWorldGenTaskComplete(long pos)
{ {
super.onWorldGenTaskComplete(pos); super.onWorldGenTaskComplete(pos);
DebugRenderer.makeParticle(
new DebugRenderer.BoxParticle(
new DebugRenderer.Box(pos, 128f, 156f, 0.09f, Color.red.darker()),
0.2, 32f
)
);
this.clientside.reloadPos(pos); this.clientside.reloadPos(pos);
} }
@@ -56,7 +56,7 @@ public class DhServerLevel extends AbstractDhServerLevel
return true; //todo; return true; //todo;
} }
@Override @Override
public @Nullable DhBlockPos2D getTargetPosForGeneration() public DhBlockPos2D getTargetPosForGeneration()
{ {
DhBlockPos2D targetPos = super.getTargetPosForGeneration(); DhBlockPos2D targetPos = super.getTargetPosForGeneration();
if (targetPos == null) if (targetPos == null)
@@ -6,6 +6,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.types.ConfigEntry; import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -23,7 +24,6 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter; import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy; import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -33,9 +33,7 @@ import java.awt.*;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRenderable, AutoCloseable public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRenderable, AutoCloseable
{ {
@@ -58,7 +56,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
private volatile CompletableFuture<Void> closingFuture = null; private volatile CompletableFuture<Void> closingFuture = null;
protected final ConcurrentMap<Long, RequestQueueEntry> waitingTasksBySectionPos = new ConcurrentHashMap<>(); protected final ConcurrentMap<Long, NetRequestTask> waitingTasksBySectionPos = new ConcurrentHashMap<>();
/** /**
* This semaphore prevents a given thread from accidentally locking on the same group * This semaphore prevents a given thread from accidentally locking on the same group
* multiple times, as the semaphore is tied to the given thread. <br> * multiple times, as the semaphore is tied to the given thread. <br>
@@ -74,16 +72,6 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
private final SupplierBasedRateLimiter<Void> rateLimiter = new SupplierBasedRateLimiter<>(this::getRequestRateLimit); private final SupplierBasedRateLimiter<Void> rateLimiter = new SupplierBasedRateLimiter<>(this::getRequestRateLimit);
private final Set<Long> succeededPositions = Collections.newSetFromMap(CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.MINUTES)
.<Long, Boolean>build()
.asMap());
private final Set<Long> requiresSplittingPositions = Collections.newSetFromMap(CacheBuilder.newBuilder()
.expireAfterWrite(20, TimeUnit.MINUTES)
.<Long, Boolean>build()
.asMap());
//=============// //=============//
@@ -108,8 +96,8 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
//==================// //==================//
protected abstract int getRequestRateLimit(); protected abstract int getRequestRateLimit();
protected abstract boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos); protected abstract boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos);
protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<ERequestResult> future); protected abstract boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future);
protected abstract String getQueueName(); protected abstract String getQueueName();
@@ -119,74 +107,60 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
// request submitting // // request submitting //
//====================// //====================//
public CompletableFuture<ERequestResult> submitRequest(long sectionPos, Consumer<FullDataSourceV2> dataSourceConsumer) public CompletableFuture<DataSourceRetrievalResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp)
{ return this.submitRequest(sectionPos, null, dataSourceConsumer); }
public CompletableFuture<ERequestResult> submitRequest(long sectionPos, @Nullable Long clientTimestamp, Consumer<FullDataSourceV2> dataSourceConsumer)
{ {
if (this.succeededPositions.contains(sectionPos)) NetRequestTask requestEntry = this.waitingTasksBySectionPos.compute(sectionPos, (Long pos, NetRequestTask existingNetTask) ->
{ {
return CompletableFuture.completedFuture(ERequestResult.FAILED); // ignore already queued tasks
} if (existingNetTask != null)
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)
{ {
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: if (!(throwable instanceof CancellationException))
this.finishedRequests.incrementAndGet(); {
this.succeededPositions.add(pos); this.failedRequests.incrementAndGet();
return; }
case REQUIRES_SPLITTING: return;
this.requiresSplittingPositions.add(sectionPos); }
return;
case FAILED: switch (requestResult.state)
{
case SUCCESS:
this.finishedRequests.incrementAndGet();
break;
case REQUIRES_SPLITTING:
break;
case FAIL:
this.failedRequests.incrementAndGet(); this.failedRequests.incrementAndGet();
return;
default:
if (throwable != null && !(throwable instanceof CancellationException))
{
this.failedRequests.incrementAndGet();
}
break; break;
} }
}); });
added.set(true); return newRequestEntry;
return newEntry;
}); });
if (!added.get()) return requestEntry.future;
{
return CompletableFuture.completedFuture(ERequestResult.FAILED);
}
return entry.future;
} }
public synchronized boolean tick(DhBlockPos2D targetPos) public synchronized boolean tick(DhBlockPos2D targetPos)
{ {
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly()) if (DhApiWorldProxy.INSTANCE.worldLoaded()
&& DhApiWorldProxy.INSTANCE.getReadOnly())
{ {
return false; return false;
} }
if (this.closingFuture != null || !this.networkState.isReady()) if (this.closingFuture != null
|| !this.networkState.isReady())
{ {
return false; return false;
} }
@@ -209,145 +183,125 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
} }
private void sendNextRequest(DhBlockPos2D targetPos) private void sendNextRequest(DhBlockPos2D targetPos)
{ {
Map.Entry<Long, RequestQueueEntry> mapEntry = this.waitingTasksBySectionPos.entrySet().stream() Map.Entry<Long, NetRequestTask> nearestMapEntry = this.waitingTasksBySectionPos
.filter(task -> task.getValue().networkDataSourceFuture == null) .entrySet().stream()
.min(Comparator.comparingInt(x -> DhSectionPos.getChebyshevSignedBlockDistance(x.getKey(), targetPos))) .filter(task -> task.getValue().networkDataSourceFuture == null)
.orElse(null); .min(Comparator.comparingInt(mapEntry -> DhSectionPos.getChebyshevSignedBlockDistance(mapEntry.getKey(), targetPos)))
.orElse(null);
if (mapEntry == null) if (nearestMapEntry == null)
{ {
this.pendingTasksSemaphore.release(); this.pendingTasksSemaphore.release();
return; return;
} }
long sectionPos = mapEntry.getKey(); long requestPos = nearestMapEntry.getKey();
RequestQueueEntry entry = mapEntry.getValue(); NetRequestTask requestTask = nearestMapEntry.getValue();
if (!this.isSectionAllowedToGenerate(sectionPos, targetPos)) if (!this.sectionInAllowedGenerationRadius(requestPos, targetPos))
{ {
entry.future.cancel(false); requestTask.future.cancel(false);
this.pendingTasksSemaphore.release(); this.pendingTasksSemaphore.release();
return; return;
} }
if (!this.onBeforeRequest(sectionPos, entry.future)) if (!this.onBeforeRequest(requestPos, requestTask.future))
{ {
this.pendingTasksSemaphore.release(); this.pendingTasksSemaphore.release();
return; return;
} }
Long offsetEntryTimestamp = entry.updateTimestamp != null Long offsetEntryTimestamp = requestTask.updateTimestamp != null
? entry.updateTimestamp + this.networkState.getServerTimeOffset() ? requestTask.updateTimestamp + this.networkState.getServerTimeOffset()
: null; : null;
CompletableFuture<FullDataSourceResponseMessage> dataSourceFuture = this.networkState.getSession().sendRequest( CompletableFuture<FullDataSourceResponseMessage> dataSourceNetworkFuture = this.networkState.getSession().sendRequest(
new FullDataSourceRequestMessage(this.level.getLevelWrapper(), sectionPos, offsetEntryTimestamp), new FullDataSourceRequestMessage(this.level.getLevelWrapper(), requestPos, offsetEntryTimestamp),
FullDataSourceResponseMessage.class FullDataSourceResponseMessage.class
); );
entry.networkDataSourceFuture = dataSourceFuture; requestTask.networkDataSourceFuture = dataSourceNetworkFuture;
dataSourceFuture.handle((response, throwable) -> dataSourceNetworkFuture.handle((FullDataSourceResponseMessage response, Throwable throwable) ->
{ {
this.pendingTasksSemaphore.release(); this.handleNetResponse(requestTask, response, throwable);
return null;
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);
}); });
} }
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.complete(DataSourceRetrievalResult.CreateFail());
}
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.complete(DataSourceRetrievalResult.CreateFail());
}
}
}
@@ -357,22 +311,30 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf) public void removeRetrievalRequestIf(DhSectionPos.ICancelablePrimitiveLongConsumer removeIf)
{ {
for (Map.Entry<Long, RequestQueueEntry> mapEntry : (Iterable<? extends Map.Entry<Long, RequestQueueEntry>>) this.waitingTasksBySectionPos.entrySet().stream() // remove tasks furthest
.sorted(Comparator.comparingInt((Map.Entry<Long, RequestQueueEntry> entry) -> DhSectionPos.getChebyshevSignedBlockDistance(entry.getKey(), Objects.requireNonNull(this.level.getTargetPosForGeneration()))).reversed()) Iterator<Map.Entry<Long, NetRequestTask>> farestTaskIterator = this.waitingTasksBySectionPos
::iterator) .entrySet().stream()
.sorted(Comparator.comparingInt((Map.Entry<Long, NetRequestTask> entry) ->
{
Long pos = entry.getKey();
DhBlockPos2D targetPos = this.level.getTargetPosForGeneration();
return DhSectionPos.getChebyshevSignedBlockDistance(pos, targetPos);
}).reversed())
.iterator();
while (farestTaskIterator.hasNext())
{ {
Map.Entry<Long, NetRequestTask> mapEntry = farestTaskIterator.next();
long pos = mapEntry.getKey(); long pos = mapEntry.getKey();
RequestQueueEntry entry = mapEntry.getValue(); NetRequestTask entry = mapEntry.getValue();
if (removeIf.accept(pos)) if (removeIf.accept(pos))
{ {
LOGGER.debug("Removing request [" + mapEntry.getKey() + "]...");
entry.future.cancel(false);
if (entry.networkDataSourceFuture != null) if (entry.networkDataSourceFuture != null)
{ {
entry.networkDataSourceFuture.cancel(false); entry.networkDataSourceFuture.cancel(false);
} }
entry.future.cancel(false);
} }
} }
} }
@@ -400,7 +362,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
do do
{ {
for (RequestQueueEntry entry : this.waitingTasksBySectionPos.values()) for (NetRequestTask entry : this.waitingTasksBySectionPos.values())
{ {
entry.future.cancel(alsoInterruptRunning); entry.future.cancel(alsoInterruptRunning);
if (entry.networkDataSourceFuture != null && entry.networkDataSourceFuture.cancel(alsoInterruptRunning)) if (entry.networkDataSourceFuture != null && entry.networkDataSourceFuture.cancel(alsoInterruptRunning))
@@ -438,13 +400,31 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
return; return;
} }
for (Map.Entry<Long, RequestQueueEntry> mapEntry : this.waitingTasksBySectionPos.entrySet()) DhBlockPos2D targetPos = this.level.getTargetPosForGeneration();
for (Map.Entry<Long, NetRequestTask> mapEntry : this.waitingTasksBySectionPos.entrySet())
{ {
renderer.renderBox(new DebugRenderer.Box(mapEntry.getKey(), -32f, 64f, 0.05f, long pos = mapEntry.getKey();
mapEntry.getValue().networkDataSourceFuture != null ? Color.red NetRequestTask task = mapEntry.getValue();
: this.isSectionAllowedToGenerate(mapEntry.getKey(), Objects.requireNonNull(this.level.getTargetPosForGeneration())) ? Color.gray
: Color.darkGray Color color;
)); if (task.networkDataSourceFuture != null)
{
color = Color.RED;
}
else
{
boolean taskInAllowedGenRadius = this.sectionInAllowedGenerationRadius(pos, targetPos);
if (taskInAllowedGenRadius)
{
color = Color.GRAY;
}
else
{
color = Color.DARK_GRAY;
}
}
renderer.renderBox(new DebugRenderer.Box(pos, -32f, 64f, 0.05f, color));
} }
} }
@@ -454,11 +434,12 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
// helper classes // // helper classes //
//================// //================//
protected static class RequestQueueEntry protected static class NetRequestTask
{ {
public final long pos;
/** encapsulates the entire request, including client side queuing and the actual server request */ /** encapsulates the entire request, including client side queuing and the actual server request */
public final CompletableFuture<ERequestResult> future = new CompletableFuture<>(); public final CompletableFuture<DataSourceRetrievalResult> future = new CompletableFuture<>();
public final Consumer<FullDataSourceV2> dataSourceConsumer;
/** will be null if we want to retrieve the LOD regardless of when it was last updated */ /** will be null if we want to retrieve the LOD regardless of when it was last updated */
@Nullable @Nullable
public final Long updateTimestamp; public final Long updateTimestamp;
@@ -477,23 +458,14 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
// constructor // // constructor //
//=============// //=============//
public RequestQueueEntry( public NetRequestTask(long pos, @Nullable Long updateTimestamp)
Consumer<FullDataSourceV2> dataSourceConsumer,
@Nullable Long updateTimestamp)
{ {
this.dataSourceConsumer = dataSourceConsumer; this.pos = pos;
this.updateTimestamp = updateTimestamp; this.updateTimestamp = updateTimestamp;
} }
} }
public enum ERequestResult
{
SUCCEEDED,
REQUIRES_SPLITTING,
FAILED,
}
} }
@@ -18,6 +18,7 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartial
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.Closeable; import java.io.Closeable;
@@ -29,6 +30,10 @@ public class ClientNetworkState implements Closeable
.fileLevelConfig(Config.Common.Logging.logNetworkEventToFile) .fileLevelConfig(Config.Common.Logging.logNetworkEventToFile)
.build(); .build();
protected static final DhLogger CONFIG_CHANGE_LOGGER = new DhLoggerBuilder()
.fileLevelConfig(Config.Common.Logging.logConnectionConfigChangesToFile)
.build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@@ -44,6 +49,7 @@ public class ClientNetworkState implements Closeable
*/ */
public NetworkSession getSession() { return this.networkSession; } public NetworkSession getSession() { return this.networkSession; }
@NotNull
public SessionConfig sessionConfig = new SessionConfig(); public SessionConfig sessionConfig = new SessionConfig();
private volatile boolean configReceived = false; private volatile boolean configReceived = false;
@@ -129,7 +135,9 @@ public class ClientNetworkState implements Closeable
{ {
this.serverSupportStatus = EServerSupportStatus.FULL; this.serverSupportStatus = EServerSupportStatus.FULL;
LOGGER.info("Connection config has been changed: [" + message.config + "]."); String configChanges = this.sessionConfig.getDifferencesAsString(message.config);
CONFIG_CHANGE_LOGGER.info("Connection config has been changed: [" + configChanges + "].");
this.sessionConfig = message.config; this.sessionConfig = message.config;
this.configReceived = true; this.configReceived = true;
}); });
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue; import com.seibel.distanthorizons.core.generation.RemoteWorldRetrievalQueue;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
@@ -35,12 +36,12 @@ public class SyncOnLoadRequestQueue extends AbstractFullDataNetworkRequestQueue
@Override @Override
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getSyncOnLoginRateLimit(); } protected int getRequestRateLimit() { return this.networkState.sessionConfig.getSyncOnLoginRateLimit(); }
@Override @Override
protected boolean isSectionAllowedToGenerate(long sectionPos, DhBlockPos2D targetPos) protected boolean sectionInAllowedGenerationRadius(long sectionPos, DhBlockPos2D targetPos)
{ {
return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxSyncOnLoadDistance() * 16; return DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) <= this.networkState.sessionConfig.getMaxSyncOnLoadDistance() * 16;
} }
@Override @Override
protected boolean onBeforeRequest(long sectionPos, CompletableFuture<ERequestResult> future) { return true; } protected boolean onBeforeRequest(long sectionPos, CompletableFuture<DataSourceRetrievalResult> future) { return true; }
@Override @Override
protected String getQueueName() { return "Sync On Login Queue"; } protected String getQueueName() { return "Sync On Login Queue"; }
@@ -162,6 +162,34 @@ public class SessionConfig implements INetworkObject
//=========//
// logging //
//=========//
/**
* example: "common.playerBandwidthLimit:[497], " <br>
* Useful to see what was changed when receiving a new config from the server.
*/
public String getDifferencesAsString(SessionConfig that)
{
StringBuilder stringBuilder = new StringBuilder();
for (String key : this.values.keySet())
{
String thisFieldString = this.values.get(key) + "";
String thatFieldString = that.values.get(key) + "";
if (!thisFieldString.equals(thatFieldString))
{
stringBuilder.append(key+":["+thisFieldString+"], ");
}
}
return stringBuilder.toString();
}
//================// //================//
// base overrides // // base overrides //
//================// //================//
@@ -9,7 +9,7 @@ import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMe
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO; import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.buffer.Unpooled;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -36,7 +36,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
{ {
if (message.isFirst) if (message.isFirst)
{ {
composite = UnpooledByteBufAllocator.DEFAULT.compositeBuffer(); composite = Unpooled.compositeBuffer();
LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]"); LOGGER.debug("Created new full data buffer [" + message.bufferId + "]: [" + composite + "]");
} }
else if (composite == null) else if (composite == null)
@@ -55,12 +55,11 @@ public class FullDataPayloadReceiver implements AutoCloseable
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload) public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
{ {
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId); CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
LodUtil.assertTrue(compositeByteBuffer != null); LodUtil.assertTrue(compositeByteBuffer != null, "decoded data source missing byte buffer");
try try
{ {
FullDataSourceV2DTO dataSourceDto = INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer); FullDataSourceV2DTO dataSourceDto = INetworkObject.decodeToInstance(FullDataSourceV2DTO.CreateEmptyDataSourceForDecoding(), compositeByteBuffer);
LOGGER.debug("Buffer {} DTO: {}", payload.dtoBufferId, dataSourceDto);
return dataSourceDto; return dataSourceDto;
} }
finally finally
@@ -174,12 +174,21 @@ public class FullDataSourceRequestHandler implements AutoCloseable
DataSourceRequestGroup requestGroup = this.requestGroupsByPos.computeIfAbsent(requestData.sectionPos(), pos -> DataSourceRequestGroup requestGroup = this.requestGroupsByPos.computeIfAbsent(requestData.sectionPos(), pos ->
{ {
DataSourceRequestGroup newGroup = new DataSourceRequestGroup(pos); DataSourceRequestGroup newGroup = new DataSourceRequestGroup(pos);
newGroup.tryAddRequest(requestData); try
createdNewGroup.set(true); {
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; return newGroup;
}); });
@@ -229,10 +238,14 @@ public class FullDataSourceRequestHandler implements AutoCloseable
private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos) private void tryFulfillDataSourceRequestGroup(DataSourceRequestGroup requestGroup, long pos)
{ {
this.fullDataSourceProvider().getAsync(pos).thenAccept(fullDataSource -> final GeneratedFullDataSourceProvider provider = this.fullDataSourceProvider();
provider.getAsync(pos)
.thenAccept((FullDataSourceV2 fullDataSource) ->
{ {
if (this.fullDataSourceProvider().isFullyGenerated(fullDataSource.columnGenerationSteps)) if (provider.generationStepsAreFullyGenerated(fullDataSource.columnGenerationSteps))
{ {
//LOGGER.info("sending - complete [" + DhSectionPos.toString(pos) + "]");
requestGroup.fullDataSource = fullDataSource; requestGroup.fullDataSource = fullDataSource;
return; return;
} }
@@ -247,11 +260,14 @@ public class FullDataSourceRequestHandler implements AutoCloseable
this.requestGroupsByPos.remove(pos); this.requestGroupsByPos.remove(pos);
if (!requestGroup.tryClose()) if (!requestGroup.tryClose())
{ {
//LOGGER.info("closing [" + DhSectionPos.toString(pos) + "]");
return; return;
} }
for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values()) for (DataSourceRequestGroup.RequestData requestData : requestGroup.requestMessages.values())
{ {
//LOGGER.info("sending [" + DhSectionPos.toString(pos) + "] - ["+DhSectionPos.toString(requestData.sectionPos())+"]");
this.requestGroupsByFutureId.remove(requestData.futureId()); this.requestGroupsByFutureId.remove(requestData.futureId());
requestData.rateLimiterSet.generationRequestRateLimiter.release(); requestData.rateLimiterSet.generationRequestRateLimiter.release();
requestData.message.sendResponse(new SectionRequiresSplittingException()); requestData.message.sendResponse(new SectionRequiresSplittingException());
@@ -264,7 +280,7 @@ public class FullDataSourceRequestHandler implements AutoCloseable
} }
else else
{ {
//LOGGER.info("sending - queueing [" + DhSectionPos.toString(pos) + "]"); //LOGGER.info("queueing incomplete world gen [" + DhSectionPos.toString(pos) + "]");
this.fullDataSourceProvider().queuePositionForRetrieval(pos); this.fullDataSourceProvider().queuePositionForRetrieval(pos);
} }
}); });
@@ -92,7 +92,8 @@ public class NetworkSession extends AbstractNetworkEventSource
{ {
LOGGER.error("Failed to handle the message. New messages will be ignored.", e); LOGGER.error("Failed to handle the message. New messages will be ignored.", e);
LOGGER.error("Message: ["+message+"]"); LOGGER.error("Message: ["+message+"]");
this.close();
this.close(e);
} }
} }
@@ -40,7 +40,9 @@ public abstract class AbstractPhantomArrayList implements AutoCloseable
/** The Array counts can be 0 or greater. */ /** The Array counts can be 0 or greater. */
public AbstractPhantomArrayList(PhantomArrayListPool phantomArrayListPool, int byteArrayCount, int shortArrayCount, int longArrayCount) public AbstractPhantomArrayList(PhantomArrayListPool phantomArrayListPool, int byteArrayCount, int shortArrayCount, int longArrayCount)
{ {
if (byteArrayCount < 0 || shortArrayCount < 0 || longArrayCount < 0) if (byteArrayCount < 0
|| shortArrayCount < 0
|| longArrayCount < 0)
{ {
throw new IllegalArgumentException("Can't get a negative number of pooled arrays."); throw new IllegalArgumentException("Can't get a negative number of pooled arrays.");
} }
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
/** /**
* This keeps track of all the poolable * This keeps track of all the poolable
@@ -33,7 +34,7 @@ public class PhantomArrayListCheckout implements AutoCloseable
/** Will be null if the parent pool doesn't want leak stack tracing */ /** Will be null if the parent pool doesn't want leak stack tracing */
@Nullable @Nullable
public final String allocationStackTrace; public String allocationStackTrace = null;
private final ArrayList<ByteArrayList> byteArrayLists = new ArrayList<>(); private final ArrayList<ByteArrayList> byteArrayLists = new ArrayList<>();
private final ArrayList<ShortArrayList> shortArrayLists = new ArrayList<>(); private final ArrayList<ShortArrayList> shortArrayLists = new ArrayList<>();
@@ -47,21 +48,20 @@ public class PhantomArrayListCheckout implements AutoCloseable
public PhantomArrayListCheckout(@NotNull PhantomArrayListPool owningPool) public PhantomArrayListCheckout(@NotNull PhantomArrayListPool owningPool)
{ {
if (owningPool.logGarbageCollectedStacks)
{
// TODO remove the top 4 or so lines since those will always be the same (relating to the phantom allocations)
// and aren't helpful when debugging
this.allocationStackTrace = StringUtil.join("\n", Thread.currentThread().getStackTrace());
}
else
{
this.allocationStackTrace = null;
}
this.owningPool = owningPool; this.owningPool = owningPool;
this.ownerSoftReference = new SoftReference<>(this); this.ownerSoftReference = new SoftReference<>(this);
} }
public void onCheckout()
{
if (this.owningPool.logGarbageCollectedStacks)
{
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement[] trimmedElements = Arrays.copyOfRange(stackTraceElements, 4, stackTraceElements.length);
this.allocationStackTrace = StringUtil.join("\n", trimmedElements).intern();
}
}
//=========// //=========//
@@ -159,6 +159,7 @@ public class PhantomArrayListPool
{ {
// pool is empty, create new checkout // pool is empty, create new checkout
checkout = new PhantomArrayListCheckout(this); checkout = new PhantomArrayListCheckout(this);
checkout.onCheckout();
} }
else else
{ {
@@ -166,6 +167,7 @@ public class PhantomArrayListPool
if (checkout != null) if (checkout != null)
{ {
// use pooled checkout // use pooled checkout
checkout.onCheckout();
} }
else else
{ {
@@ -23,7 +23,9 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
import java.util.Objects; import java.util.Objects;
/** immutable */ /** immutable */
@Deprecated // TODO why does this exist vs blockpos2d?
public class Pos2D public class Pos2D
{ {
public static final Pos2D ZERO = new Pos2D(0, 0); public static final Pos2D ZERO = new Pos2D(0, 0);
@@ -75,6 +75,30 @@ public class DhBlockPos2D
public long distSquared(DhBlockPos2D other) { return this.distSquared(other.x, other.z); } public long distSquared(DhBlockPos2D other) { return this.distSquared(other.x, other.z); }
public long distSquared(int x, int z) { return MathUtil.pow2((long) this.x - x) + MathUtil.pow2((long) this.z - z); } public long distSquared(int x, int z) { return MathUtil.pow2((long) this.x - x) + MathUtil.pow2((long) this.z - z); }
/**
* Returns the maximum distance along either the X or Z axis <br><br>
*
* Example chebyshev distance between X and every point around it: <br>
* <code>
* 2 2 2 2 2 <br>
* 2 1 1 1 2 <br>
* 2 1 X 1 2 <br>
* 2 1 1 1 2 <br>
* 2 2 2 2 2 <br>
* </code>
*/
public int chebyshevDist(DhBlockPos2D other) { return Math.max(Math.abs(this.x - other.x), Math.abs(this.z - other.z)); }
/**
* Can be used to quickly determine the rough distance between two points<Br>
* or determine the taxi cab (manhattan) distance between two points. <Br><Br>
*
* Manhattan distance is equivalent to determining the distance between two street intersections,
* where you can only drive along each street, instead of directly to the other point.
*/
public int manhattanDist(DhBlockPos2D other) { return Math.abs(this.x - other.x) + Math.abs(this.z - other.z); }
//===========// //===========//
@@ -20,10 +20,12 @@
package com.seibel.distanthorizons.core.render; package com.seibel.distanthorizons.core.render;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer; import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2; import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -34,12 +36,13 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler; import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer; import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.PerfRecorder;
import com.seibel.distanthorizons.core.util.ThreadUtil; import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.WorldGenUtil;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode; import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree; import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongIterator;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -47,6 +50,7 @@ import javax.annotation.WillNotClose;
import java.awt.*; import java.awt.*;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -57,11 +61,11 @@ import java.util.concurrent.locks.ReentrantLock;
* This quadTree structure is our core data structure and holds * This quadTree structure is our core data structure and holds
* all rendering data. * all rendering data.
*/ */
public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRenderable, AutoCloseable public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRenderable, IConfigListener, AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */ /** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */
private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleThreadPool("QuadTree Full Data Retrieval Queue Populator"); private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleThreadPool("LodQuadTree Data Retrieval Queue");
public final int blockRenderDistanceDiameter; public final int blockRenderDistanceDiameter;
@@ -73,9 +77,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
* This is a {@link ConcurrentLinkedQueue} because new sections can be added to this list via the world generator threads. * This is a {@link ConcurrentLinkedQueue} because new sections can be added to this list via the world generator threads.
*/ */
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>(); private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
private final IDhClientLevel level; //FIXME: Proper hierarchy to remove this reference! private final IDhClientLevel level;
private final ReentrantLock treeReadWriteLock = new ReentrantLock(); private final ReentrantLock treeLock = new ReentrantLock();
private final AtomicBoolean fullDataRetrievalQueueRunning = new AtomicBoolean(false);
private ArrayList<LodRenderSection> debugRenderSections = new ArrayList<>(); private ArrayList<LodRenderSection> debugRenderSections = new ArrayList<>();
private ArrayList<LodRenderSection> altDebugRenderSections = new ArrayList<>(); private ArrayList<LodRenderSection> altDebugRenderSections = new ArrayList<>();
@@ -88,7 +91,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
* as further sections are loaded before closer ones. * as further sections are loaded before closer ones.
* Only queuing a few of the sections at a time solves this problem. * Only queuing a few of the sections at a time solves this problem.
*/ */
public final AtomicInteger uploadTaskCountRef = new AtomicInteger(0); private final AtomicInteger uploadTaskCountRef = new AtomicInteger(0);
private final AtomicBoolean requeueAllRetrievalTasksRef = new AtomicBoolean(false);
private final AtomicBoolean queueThreadRunningRef = new AtomicBoolean(false);
@Nullable @Nullable
@@ -104,11 +109,18 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
/** used to calculate when a detail drop will occur */ /** used to calculate when a detail drop will occur */
private double detailDropOffLogBase; private double detailDropOffLogBase;
/** the {@link DhSectionPos} that need to be retrieved/generated */
private final Set<Long> missingGenerationPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); // concurrency is annoying but required due to needing to add/remove items in the world gen future
private final Set<Long> queuedGenerationPosSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
/** cached array to prevent having to re-allocate it each tick */
private final ArrayList<Long> sortedMissingPosList = new ArrayList<>();
//==============//
// constructors // //=============//
//==============// // constructor //
//=============//
//region constructor
public LodQuadTree( public LodQuadTree(
IDhClientLevel level, int viewDiameterInBlocks, IDhClientLevel level, int viewDiameterInBlocks,
@@ -126,13 +138,18 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
GenericObjectRenderer genericObjectRenderer = this.level.getGenericRenderer(); GenericObjectRenderer genericObjectRenderer = this.level.getGenericRenderer();
this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null; this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null;
Config.Common.WorldGenerator.enableDistantGeneration.addListener(this);
} }
//endregion constructor
//=============// //=============//
// tick update // // tick update //
//=============// //=============//
//region tick update
/** /**
* This function updates the quadTree based on the playerPos and the current game configs (static and global) * This function updates the quadTree based on the playerPos and the current game configs (static and global)
@@ -143,23 +160,32 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
{ {
if (this.level == null) if (this.level == null)
{ {
// the level hasn't finished loading yet // the quad tree was created before a level reference was created
// TODO sometimes null pointers still happen, when logging back into a world (maybe the old level isn't null but isn't valid either?)
return; return;
} }
// this shouldn't be updated while the tree is being iterated through
this.updateDetailLevelVariables();
// don't traverse the tree if it is being modified // don't traverse the tree if it is being modified
if (this.treeReadWriteLock.tryLock()) if (this.treeLock.tryLock())
{ {
// this shouldn't be updated while the tree is being iterated through
this.updateDetailLevelVariables();
try try
{ {
// recenter if necessary, removing out of bounds sections // recenter if necessary...
this.setCenterBlockPos(playerPos, LodRenderSection::close); this.setCenterBlockPos(playerPos, (renderSection) ->
{
//...removing out of bounds sections
if (renderSection != null)
{
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
this.missingGenerationPosSet.remove(renderSection.pos);
this.queuedGenerationPosSet.remove(renderSection.pos);
renderSection.close();
}
});
this.updateAllRenderSections(playerPos); this.updateAllRenderSections(playerPos);
} }
@@ -169,7 +195,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
} }
finally finally
{ {
this.treeReadWriteLock.unlock(); this.treeLock.unlock();
} }
} }
} }
@@ -197,7 +223,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// walk through each root node // walk through each root node
HashSet<LodRenderSection> nodesNeedingRetrieval = new HashSet<>();
HashSet<LodRenderSection> nodesNeedingLoading = new HashSet<>(); HashSet<LodRenderSection> nodesNeedingLoading = new HashSet<>();
LongIterator rootPosIterator = this.rootNodePosIterator(); LongIterator rootPosIterator = this.rootNodePosIterator();
while (rootPosIterator.hasNext()) while (rootPosIterator.hasNext())
@@ -210,17 +235,61 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
} }
QuadNode<LodRenderSection> rootNode = this.getNode(rootPos); QuadNode<LodRenderSection> rootNode = this.getNode(rootPos);
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, rootNode, rootNode.sectionPos, false, nodesNeedingRetrieval, nodesNeedingLoading); LodUtil.assertTrue(rootNode != null, "All root nodes should have been created by this point.");
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, rootNode, rootNode.sectionPos, false, nodesNeedingLoading);
} }
// queue full data retrieval (world gen) requests if needed // requeue everything if needed
if (nodesNeedingRetrieval.size() != 0 if (this.requeueAllRetrievalTasksRef.get()
&& !this.fullDataRetrievalQueueRunning.get() && !this.queueThreadRunningRef.get())
&& this.fullDataSourceProvider.canQueueRetrieval())
{ {
this.fullDataRetrievalQueueRunning.set(true); this.queueThreadRunningRef.set(true);
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() -> this.queueFullDataRetrievalTasks(playerPos, nodesNeedingRetrieval)); this.requeueAllRetrievalTasksRef.set(false);
// running on a separate thread allows for faster loading
// of finished LODs
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() ->
{
try
{
this.checkAllNodesForRetrievalRequests();
}
catch (Exception e)
{
LOGGER.error("Unexpected error getting new queued retrieval tasks, error: [" + e.getMessage() + "].", e);
}
finally
{
this.queueThreadRunningRef.set(false);
}
});
}
// queue full data retrieval (world gen) requests if needed
if (this.missingGenerationPosSet.size() != 0 //
&& this.fullDataSourceProvider.canQueueRetrievalNow()
&& !this.queueThreadRunningRef.get())
{
this.queueThreadRunningRef.set(true);
// running on a separate thread allows for faster loading
// of finished LODs
FULL_DATA_RETRIEVAL_QUEUE_THREAD.execute(() ->
{
try
{
this.startQueuedRetrievalTasks(playerPos);
}
catch (Exception e)
{
LOGGER.error("Unexpected error starting queued retrieval tasks, error: [" + e.getMessage() + "].", e);
}
finally
{
this.queueThreadRunningRef.set(false);
}
});
} }
@@ -236,7 +305,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
DhBlockPos2D playerPos, DhBlockPos2D playerPos,
QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> quadNode, long sectionPos, QuadNode<LodRenderSection> rootNode, QuadNode<LodRenderSection> quadNode, long sectionPos,
boolean parentSectionIsRendering, boolean parentSectionIsRendering,
HashSet<LodRenderSection> nodesNeedingRetrieval,
HashSet<LodRenderSection> nodesNeedingLoading) HashSet<LodRenderSection> nodesNeedingLoading)
{ {
//=====================// //=====================//
@@ -245,7 +313,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
//=====================// //=====================//
// create the node // create the node
if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance if (quadNode == null
&& this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance
{ {
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider, this.uploadTaskCountRef)); rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider, this.uploadTaskCountRef));
quadNode = rootNode.getNode(sectionPos); quadNode = rootNode.getNode(sectionPos);
@@ -288,7 +357,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i); QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), thisPosIsRendering || parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading); boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), thisPosIsRendering || parentSectionIsRendering, nodesNeedingLoading);
allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded; allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded;
} }
@@ -347,7 +416,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i); QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading); this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingLoading);
} }
// disabling rendering must be done after the children are enabled // disabling rendering must be done after the children are enabled
@@ -368,23 +437,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// prepare this section for rendering // prepare this section for rendering
if (!renderSection.gpuUploadInProgress() if (!renderSection.gpuUploadInProgress()
&& renderSection.bufferContainer == null && renderSection.bufferContainer == null)
// TODO this is commented out since some users reported LODs refusing to
// load at their expected higher-detail levels
// this check is specifically for N-sized world generators where the higher quality
// data source may not exist yet, this is done to prevent holes while waiting for said generator
//&& renderSection.getFullDataSourceExists()
)
{ {
nodesNeedingLoading.add(renderSection); nodesNeedingLoading.add(renderSection);
} }
// queue world gen if needed
if (!renderSection.isFullyGenerated())
{
nodesNeedingRetrieval.add(renderSection);
}
// update debug if needed // update debug if needed
if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get()) if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get())
{ {
@@ -394,7 +451,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// wait for the parent to disable before enabling this section, so we don't have a hole // wait for the parent to disable before enabling this section, so we don't have a hole
if (!parentSectionIsRendering && renderSection.canRender()) if (!parentSectionIsRendering
&& renderSection.canRender())
{ {
// if rendering is already enabled we don't have to re-enable it // if rendering is already enabled we don't have to re-enable it
if (!renderSection.getRenderingEnabled()) if (!renderSection.getRenderingEnabled())
@@ -425,6 +483,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// needs to be fired after the children are disabled so beacons render correctly // needs to be fired after the children are disabled so beacons render correctly
renderSection.onRenderingEnabled(); renderSection.onRenderingEnabled();
// since this section wants to render
// check if it needs any generation to do so
this.tryQueuePosForRetrieval(renderSection.pos);
} }
} }
@@ -453,9 +514,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
LodRenderSection renderSection = this.getValue(pos); LodRenderSection renderSection = this.getValue(pos);
if (renderSection != null) if (renderSection != null)
{ {
// this data source may now exist
renderSection.updateFullDataSourceExists();
if (renderSection.canRender()) if (renderSection.canRender())
{ {
if (renderSection.gpuUploadInProgress() if (renderSection.gpuUploadInProgress()
@@ -488,18 +546,193 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
for (int i = 0; i < loadSectionList.size(); i++) for (int i = 0; i < loadSectionList.size(); i++)
{ {
LodRenderSection renderSection = loadSectionList.get(i); LodRenderSection renderSection = loadSectionList.get(i);
if (!renderSection.gpuUploadInProgress() && renderSection.bufferContainer == null) if (!renderSection.gpuUploadInProgress()
&& renderSection.bufferContainer == null)
{ {
renderSection.uploadRenderDataToGpuAsync(); renderSection.uploadRenderDataToGpuAsync();
} }
} }
} }
//endregion tick update
//=================================//
// full data retrieval (world gen) //
//=================================//
//region world gen
private void startQueuedRetrievalTasks(DhBlockPos2D playerPos)
{
// sort the nodes from nearest to farthest
this.sortedMissingPosList.clear();
this.sortedMissingPosList.addAll(this.missingGenerationPosSet);
this.sortedMissingPosList.sort((posA, posB) ->
{
int aDist = DhSectionPos.getManhattanBlockDistance(posA, playerPos);
int bDist = DhSectionPos.getManhattanBlockDistance(posB, playerPos);
return Integer.compare(aDist, bDist);
});
//==================================//
// add retrieval tasks to the queue //
//==================================//
for (int i = 0; i < this.sortedMissingPosList.size(); i++)
{
if (!this.fullDataSourceProvider.canQueueRetrievalNow())
{
break;
}
long missingPos = this.sortedMissingPosList.get(i);
// is this position within acceptable generator range?
boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
missingPos,
Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(),
Config.Common.WorldGenerator.generationMaxChunkRadius.get()
);
if (!posInRange)
{
continue;
}
CompletableFuture<DataSourceRetrievalResult> genFuture = this.fullDataSourceProvider.queuePositionForRetrieval(missingPos);
boolean positionQueued = (genFuture != null && !genFuture.isCompletedExceptionally());
if (positionQueued)
{
this.queuedGenerationPosSet.add(missingPos);
this.missingGenerationPosSet.remove(missingPos);
genFuture.exceptionally((Throwable throwable) ->
{
// gen task failed,
// requeue so we can try again in the future
this.queuedGenerationPosSet.remove(missingPos);
this.missingGenerationPosSet.add(missingPos);
return null;
});
genFuture.thenAccept((DataSourceRetrievalResult result) ->
{
// task finished
this.queuedGenerationPosSet.remove(missingPos);
// if the task failed re-queue so we can try again
if (result.state == ERetrievalResultState.FAIL)
{
this.missingGenerationPosSet.add(missingPos);
}
else if (result.state == ERetrievalResultState.REQUIRES_SPLITTING)
{
DhSectionPos.forEachChild(missingPos, (long childPos) ->
{
this.tryQueuePosForRetrieval(childPos);
});
}
});
}
}
//==========================//
// calc task count estimate //
//==========================//
// calculate an estimate for the max number of chunks for the queue
int totalWorldGenChunkCount = 0;
int totalWorldGenTaskCount = 0;
for (int i = 0; i < this.sortedMissingPosList.size(); i++)
{
long missingPos = this.sortedMissingPosList.get(i);
// chunk count
int sectionWidthInChunks = DhSectionPos.getChunkWidth(missingPos);
totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks;
// task count
totalWorldGenTaskCount++;
}
this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount);
this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount);
}
@Override
public void onConfigValueSet()
{
boolean generatorEnabled = Config.Common.WorldGenerator.enableDistantGeneration.get();
if (generatorEnabled)
{
// world gen tasks will need to be re-queued
// since all the render sections will already have been loaded
this.requeueAllRetrievalTasksRef.set(true);
}
else
{
// generation is disabled, clear the queues
this.missingGenerationPosSet.clear();
this.queuedGenerationPosSet.clear();
this.requeueAllRetrievalTasksRef.set(false);
}
}
/**
* Needed to get all necessary retrieval requests
* after the quad tree has already been loaded.
*/
private void checkAllNodesForRetrievalRequests()
{
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
while (nodeIterator.hasNext())
{
QuadNode<LodRenderSection> node = nodeIterator.next();
if (node != null)
{
LodRenderSection renderSection = node.value;
if (renderSection != null
&& renderSection.getRenderingEnabled())
{
this.tryQueuePosForRetrieval(renderSection.pos);
}
}
}
}
/** Does nothing if the missing positions are already queued. */
private void tryQueuePosForRetrieval(long pos)
{
LongArrayList missingPosList = this.fullDataSourceProvider.getPositionsToRetrieve(pos);
if (missingPosList == null)
{
return;
}
for (int i = 0; i < missingPosList.size(); i++)
{
long missingPos = missingPosList.getLong(i);
if (!this.queuedGenerationPosSet.contains(missingPos))
{
this.missingGenerationPosSet.add(missingPos);
}
}
}
//endregion world gen
//====================// //====================//
// detail level logic // // detail level logic //
//====================// //====================//
//region detail level logic
/** /**
* This method will compute the detail level based on player position and section pos * This method will compute the detail level based on player position and section pos
@@ -553,11 +786,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) this.minRootRenderDetailLevel = (byte) Math.max(minSectionDetailLevel, this.maxLeafRenderDetailLevel); // respect the user's selected max resolution if it is lower detail (IE they want 2x2 block, but minSectionDetailLevel is specifically for 1x1 block render resolution)
} }
//endregion detail level logic
//=============//
// render data // //==========================//
//=============// // external render requests //
//==========================//
//region external render requests
/** /**
* Re-creates the color, render data. * Re-creates the color, render data.
@@ -565,34 +801,32 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
*/ */
public void clearRenderDataCache() public void clearRenderDataCache()
{ {
if (this.treeReadWriteLock.tryLock()) // TODO make async, can lock render thread try
{ {
try this.treeLock.lock();
LOGGER.info("Disposing render data...");
// clear the tree
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
while (nodeIterator.hasNext())
{ {
LOGGER.info("Disposing render data..."); QuadNode<LodRenderSection> quadNode = nodeIterator.next();
if (quadNode.value != null)
// clear the tree
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.nodeIterator();
while (nodeIterator.hasNext())
{ {
QuadNode<LodRenderSection> quadNode = nodeIterator.next(); quadNode.value.close();
if (quadNode.value != null) 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 +854,14 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
} }
} }
//endregion external render requests
//=================================//
// full data retrieval (world gen) //
//=================================//
private void queueFullDataRetrievalTasks(DhBlockPos2D playerPos, HashSet<LodRenderSection> nodesNeedingRetrieval)
{
try
{
// sort the nodes from nearest to farthest
ArrayList<LodRenderSection> nodeList = new ArrayList<>(nodesNeedingRetrieval);
nodeList.sort((a, b) ->
{
int aDist = DhSectionPos.getManhattanBlockDistance(a.pos, playerPos);
int bDist = DhSectionPos.getManhattanBlockDistance(b.pos, playerPos);
return Integer.compare(aDist, bDist);
});
// add retrieval tasks to the queue
for (int i = 0; i < nodeList.size(); i++)
{
LodRenderSection renderSection = nodeList.get(i);
if (!this.fullDataSourceProvider.canQueueRetrieval())
{
break;
}
renderSection.tryQueuingMissingLodRetrieval();
}
// calculate an estimate for the max number of chunks for the queue
int totalWorldGenChunkCount = 0;
int totalWorldGenTaskCount = 0;
for (int i = 0; i < nodeList.size(); i++)
{
LodRenderSection renderSection = nodeList.get(i);
if (!renderSection.missingPositionsCalculated())
{
// chunk count
int sectionWidthInChunks = DhSectionPos.getChunkWidth(renderSection.pos);
totalWorldGenChunkCount += sectionWidthInChunks * sectionWidthInChunks;
// task count
totalWorldGenTaskCount += renderSection.ungeneratedPositionCount();
}
else
{
totalWorldGenChunkCount += renderSection.ungeneratedChunkCount();
// 1 since we assume the position can be generated in a single go
// TODO this is a bad assumption, can we determine what the world gen supports and determine it from that?
totalWorldGenTaskCount += 1;
}
}
this.fullDataSourceProvider.setEstimatedRemainingRetrievalChunkCount(totalWorldGenChunkCount);
this.fullDataSourceProvider.setTotalRetrievalPositionCount(totalWorldGenTaskCount);
}
catch (Exception e)
{
LOGGER.error("Unexpected error: "+e.getMessage(), e);
}
finally
{
this.fullDataRetrievalQueueRunning.set(false);
}
}
//===========// //===========//
// debugging // // debugging //
//===========// //===========//
//region debugging
@Override @Override
public void debugRender(DebugRenderer debugRenderer) public void debugRender(DebugRenderer debugRenderer)
@@ -739,11 +908,14 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
} }
} }
//endregion debugging
//==============// //==============//
// base methods // // base methods //
//==============// //==============//
//region base methods
@Override @Override
public void close() public void close()
@@ -751,13 +923,15 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
LOGGER.info("Shutting down LodQuadTree..."); LOGGER.info("Shutting down LodQuadTree...");
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus); DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
ThreadPoolExecutor mainCleanupExecutor = ThreadPoolUtil.getCleanupExecutor(); ThreadPoolExecutor mainCleanupExecutor = ThreadPoolUtil.getCleanupExecutor();
// closing every node may take a few moments // closing every node may take a few moments
// so this is run on a separate thread to prevent lagging the render thread // so this is run on a separate thread to prevent lagging the render thread
mainCleanupExecutor.execute(() -> mainCleanupExecutor.execute(() ->
{ {
this.treeReadWriteLock.lock(); this.treeLock.lock();
try try
{ {
// walk through each node // walk through each node
@@ -775,7 +949,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
} }
finally finally
{ {
this.treeReadWriteLock.unlock(); this.treeLock.unlock();
} }
}); });
@@ -783,6 +957,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
LOGGER.info("Finished shutting down LodQuadTree"); LOGGER.info("Finished shutting down LodQuadTree");
} }
//endregion base methods
} }
@@ -19,8 +19,6 @@
package com.seibel.distanthorizons.core.render; package com.seibel.distanthorizons.core.render;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
@@ -42,12 +40,10 @@ import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler; import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo; import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.util.WorldGenUtil;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.annotation.WillNotClose; import javax.annotation.WillNotClose;
@@ -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. * different threads (buffer uploading is on the MC render thread) and need to be canceled separately.
*/ */
private CompletableFuture<LodBufferContainer> bufferUploadFuture = null; private CompletableFuture<LodBufferContainer> bufferUploadFuture = null;
/**
* should be an empty array if no positions need to be generated
*
* @deprecated see the comment where this variable is set
*/
@Nullable
@Deprecated
private Supplier<LongArrayList> missingGenerationPosFunc;
private LongArrayList getMissingGenerationPos() { return this.missingGenerationPosFunc != null ? this.missingGenerationPosFunc.get() : null; }
private boolean checkedIfFullDataSourceExists = false;
private boolean fullDataSourceExists = false;
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region constructor
public LodRenderSection( public LodRenderSection(
long pos, long pos,
@@ -149,15 +133,18 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.beaconRenderHandler = this.quadTree.beaconRenderHandler; this.beaconRenderHandler = this.quadTree.beaconRenderHandler;
this.beaconBeamRepo = this.level.getBeaconBeamRepo(); this.beaconBeamRepo = this.level.getBeaconBeamRepo();
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus); DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
} }
//endregion constructor
//======================================// //======================================//
// render data generation and uploading // // render data generation and uploading //
//======================================// //======================================//
//region render data uploading
/** @return true if the upload started, false if it wasn't able to for any reason */ /** @return true if the upload started, false if it wasn't able to for any reason */
public synchronized boolean uploadRenderDataToGpuAsync() public synchronized boolean uploadRenderDataToGpuAsync()
@@ -254,24 +241,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// get the adjacent positions // get the adjacent positions
// needs to be done async to prevent threads waiting on the same positions to be processed // needs to be done async to prevent threads waiting on the same positions to be processed
final CompletableFuture<ColumnRenderSource>[] adjacentLoadFutures = new CompletableFuture[4]; final CompletableFuture<ColumnRenderSource>[] adjacentLoadFutures = new CompletableFuture[4];
adjacentLoadFutures[0] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.NORTH);
if (Config.Client.Advanced.Graphics.Experimental.onlyLoadCenterLods.get()) adjacentLoadFutures[1] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.SOUTH);
{ adjacentLoadFutures[2] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.EAST);
// TODO temporary test, long term something else should be done to so we can get adjacent lighting data adjacentLoadFutures[3] = this.getRenderSourceForPosAsync(this.pos, EDhDirection.WEST);
// 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);
}
return CompletableFuture.allOf(adjacentLoadFutures).thenRun(() -> return CompletableFuture.allOf(adjacentLoadFutures).thenRun(() ->
{ {
try (ColumnRenderSource northRenderSource = adjacentLoadFutures[0].get(); try (ColumnRenderSource northRenderSource = adjacentLoadFutures[0].get();
@@ -314,7 +287,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
} }
}); });
} }
/** async is done so each thread can run without waiting on others */ /**
* async is done so each thread can run without waiting on others
* @param direction the direction to load relative to the given position, null will return the given position
*/
private CompletableFuture<ColumnRenderSource> getRenderSourceForPosAsync(long pos, @Nullable EDhDirection direction) private CompletableFuture<ColumnRenderSource> getRenderSourceForPosAsync(long pos, @Nullable EDhDirection direction)
{ {
if (direction != null) if (direction != null)
@@ -400,11 +376,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
}); });
} }
//endregion render data uploading
//========================//
// getters and properties // //====================//
//========================// // enabling rendering //
//====================//
//region enabling rendering
public boolean canRender() { return this.bufferContainer != null; } public boolean canRender() { return this.bufferContainer != null; }
@@ -439,123 +418,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public boolean gpuUploadInProgress() { return this.getAndBuildRenderDataFuture != null; } public boolean gpuUploadInProgress() { return this.getAndBuildRenderDataFuture != null; }
//endregion enabling rendering
//=================================//
// full data retrieval (world gen) //
//=================================//
public boolean isFullyGenerated()
{
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
return missingGenerationPos != null && missingGenerationPos.isEmpty();
}
/** Returns true if an LOD exists, regardless of what data is in it */
public boolean getFullDataSourceExists()
{
if (!this.checkedIfFullDataSourceExists)
{
this.fullDataSourceExists = this.fullDataSourceProvider.repo.existsWithKey(this.pos);
this.checkedIfFullDataSourceExists = true;
}
return this.fullDataSourceExists;
}
public void updateFullDataSourceExists()
{
// we don't have any ability to remove LODs so we only
// need to check if an LOD was previously missing
if (!this.fullDataSourceExists)
{
this.checkedIfFullDataSourceExists = false;
this.getFullDataSourceExists();
}
}
public boolean missingPositionsCalculated() { return this.getMissingGenerationPos() != null; }
public int ungeneratedPositionCount()
{
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
return missingGenerationPos != null ? missingGenerationPos.size() : 0;
}
public int ungeneratedChunkCount()
{
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
if (missingGenerationPos == null)
{
return 0;
}
int chunkCount = 0;
// get the number of chunks each position contains
for (int i = 0; i < missingGenerationPos.size(); i++)
{
int chunkWidth = DhSectionPos.getChunkWidth(missingGenerationPos.getLong(i));
chunkCount += (chunkWidth * chunkWidth);
}
return chunkCount;
}
public void tryQueuingMissingLodRetrieval()
{
if (this.fullDataSourceProvider.canRetrieveMissingDataSources()
&& this.fullDataSourceProvider.canQueueRetrieval())
{
// calculate the missing positions if not already done
if (this.missingGenerationPosFunc == null)
{
// TODO memoization is needed for multiplayer, otherwise
// new retrieval requests won't be submitted.
// TODO why is that the case? Shouldn't the missing positions be un-changing?
// TODO setting this value to low can cause world gen to slow down significantly
// due to a race condition where the world gen thinks it is finished, but the results
// haven't been saved to file yet, causing the gen to fire again
this.missingGenerationPosFunc = Suppliers.memoizeWithExpiration(
() -> this.fullDataSourceProvider.getPositionsToRetrieve(this.pos),
10, TimeUnit.MINUTES);
}
LongArrayList missingGenerationPos = this.getMissingGenerationPos();
if (missingGenerationPos != null)
{
// queue from last to first to prevent shifting the array unnecessarily
for (int i = missingGenerationPos.size() - 1; i >= 0; i--)
{
if (!this.fullDataSourceProvider.canQueueRetrieval())
{
// the data source provider isn't accepting any more jobs
break;
}
long pos = missingGenerationPos.removeLong(i);
boolean posInRange = WorldGenUtil.isPosInWorldGenRange(
pos,
Config.Common.WorldGenerator.generationCenterChunkX.get(), Config.Common.WorldGenerator.generationCenterChunkZ.get(),
Config.Common.WorldGenerator.generationMaxChunkRadius.get()
);
if (!posInRange)
{
continue;
}
boolean positionQueued = (this.fullDataSourceProvider.queuePositionForRetrieval(pos) != null);
if (!positionQueued)
{
// shouldn't normally happen, but just in case
missingGenerationPos.add(pos);
}
}
}
}
}
//=================// //=================//
// beacon handling // // beacon handling //
//=================// //=================//
//region beacon handling
/** gets the active beacon list and stops/starts beacon rendering as necessary */ /** gets the active beacon list and stops/starts beacon rendering as necessary */
private void getAndRefreshRenderingBeacons() private void getAndRefreshRenderingBeacons()
@@ -629,11 +499,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
} }
} }
//endregion beacon handling
//==============// //==============//
// base methods // // base methods //
//==============// //==============//
//region base methods
@Override @Override
public void debugRender(DebugRenderer debugRenderer) public void debugRender(DebugRenderer debugRenderer)
@@ -693,13 +566,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{ {
// remove the task from our executor if present // remove the task from our executor if present
// note: don't cancel the task since that prevents cleanup, we just don't want it to run // note: don't cancel the task since that prevents cleanup, we just don't want it to run
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor(); PriorityTaskPicker.Executor renderLoaderExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
if (executor != null && !executor.isTerminated()) if (renderLoaderExecutor != null
&& !renderLoaderExecutor.isTerminated())
{ {
Runnable runnable = this.getAndBuildRenderDataRunnable; Runnable runnable = this.getAndBuildRenderDataRunnable;
if (runnable != null) if (runnable != null)
{ {
executor.remove(runnable); renderLoaderExecutor.remove(runnable);
} }
} }
} }
@@ -710,16 +584,10 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
uploadFuture.cancel(true); uploadFuture.cancel(true);
} }
// remove any active world gen requests that may be for this position
ThreadPoolExecutor executor = ThreadPoolUtil.getCleanupExecutor();
// while this should generally be a fast operation
// this is run on a separate thread to prevent lag on the render thread
executor.execute(() -> this.fullDataSourceProvider.removeRetrievalRequestIf((genPos) -> DhSectionPos.contains(this.pos, genPos)));
} }
//endregion base methods
} }
@@ -56,13 +56,13 @@ public class GLProxy
public static final Set<String> LOGGED_GL_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); public static final Set<String> LOGGED_GL_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
private static final ConcurrentLinkedQueue<Runnable> RENDER_THREAD_RUNNABLE_QUEUE = new ConcurrentLinkedQueue<>();
private static GLProxy instance = null; private static GLProxy instance = null;
private final ConcurrentLinkedQueue<Runnable> renderThreadRunnableQueue = new ConcurrentLinkedQueue<>();
/** Minecraft's GL capabilities */ /** Minecraft's GL capabilities */
public final GLCapabilities glCapabilities; public final GLCapabilities glCapabilities;
@@ -231,7 +231,7 @@ public class GLProxy
return uploadOverride; return uploadOverride;
} }
public boolean runningOnRenderThread() public static boolean runningOnRenderThread()
{ {
long currentContext = GLFW.glfwGetCurrentContext(); long currentContext = GLFW.glfwGetCurrentContext();
return currentContext != 0L; // if the context isn't null, it's the MC context return currentContext != 0L; // if the context isn't null, it's the MC context
@@ -243,12 +243,12 @@ public class GLProxy
// Worker Thread Runnables // // Worker Thread Runnables //
//=========================// //=========================//
public void queueRunningOnRenderThread(Runnable renderCall) public static void queueRunningOnRenderThread(Runnable renderCall)
{ {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
this.renderThreadRunnableQueue.add(() -> this.runOpenGlCall(renderCall, stackTrace)); RENDER_THREAD_RUNNABLE_QUEUE.add(() -> runOpenGlCall(renderCall, stackTrace));
} }
private void runOpenGlCall(Runnable renderCall, StackTraceElement[] stackTrace) private static void runOpenGlCall(Runnable renderCall, StackTraceElement[] stackTrace)
{ {
try try
{ {
@@ -266,11 +266,11 @@ public class GLProxy
* Doesn't do any thread/GL Context validation. * Doesn't do any thread/GL Context validation.
* Running this outside of the render thread may cause crashes or other issues. * Running this outside of the render thread may cause crashes or other issues.
*/ */
public void runRenderThreadTasks() public static void runRenderThreadTasks()
{ {
long startTime = System.nanoTime(); long startTime = System.nanoTime();
Runnable runnable = this.renderThreadRunnableQueue.poll(); Runnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
while(runnable != null) while(runnable != null)
{ {
runnable.run(); runnable.run();
@@ -283,7 +283,7 @@ public class GLProxy
break; break;
} }
runnable = this.renderThreadRunnableQueue.poll(); runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
} }
} }
@@ -100,7 +100,7 @@ public class GLBuffer implements AutoCloseable
protected void create(boolean asBufferStorage) protected void create(boolean asBufferStorage)
{ {
if (!GLProxy.getInstance().runningOnRenderThread()) if (!GLProxy.runningOnRenderThread())
{ {
LodUtil.assertNotReach("Thread ["+Thread.currentThread()+"] tried to create a GLBuffer outside the MC render thread."); LodUtil.assertNotReach("Thread ["+Thread.currentThread()+"] tried to create a GLBuffer outside the MC render thread.");
} }
@@ -151,7 +151,7 @@ public class GLBuffer implements AutoCloseable
BUFFER_ID_TO_PHANTOM.remove(id); BUFFER_ID_TO_PHANTOM.remove(id);
} }
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
// destroy the buffer if it exists, // destroy the buffer if it exists,
// the buffer may not exist if the destroy method is called twice // the buffer may not exist if the destroy method is called twice
@@ -44,7 +44,7 @@ public class QuadElementBuffer extends GLElementBuffer
public int getCapacity() public int getCapacity()
{ {
return super.getSize() / GLEnums.getTypeSize(getType()); return super.getSize() / GLEnums.getTypeSize(this.getType());
} }
private static void buildBufferByte(int quadCount, ByteBuffer buffer) private static void buildBufferByte(int quadCount, ByteBuffer buffer)
@@ -140,7 +140,6 @@ public class QuadElementBuffer extends GLElementBuffer
return; return;
} }
int vertexCount = quadCount * 4; // 4 vertices per quad int vertexCount = quadCount * 4; // 4 vertices per quad
GLProxy gl = GLProxy.getInstance();
if (vertexCount < 255) if (vertexCount < 255)
{ // Reserve 1 for the reset index { // Reserve 1 for the reset index
@@ -158,7 +157,7 @@ public class QuadElementBuffer extends GLElementBuffer
ByteBuffer buffer = MemoryUtil.memAlloc(this.indicesCount * GLEnums.getTypeSize(this.type)); ByteBuffer buffer = MemoryUtil.memAlloc(this.indicesCount * GLEnums.getTypeSize(this.type));
buildBuffer(quadCount, buffer, this.type); buildBuffer(quadCount, buffer, this.type);
if (!gl.bufferStorageSupported) if (!GLProxy.getInstance().bufferStorageSupported)
{ {
this.bind(); this.bind();
@@ -95,7 +95,7 @@ public class ShaderProgram
for (int i = 0; i < attributes.length; i++) for (int i = 0; i < attributes.length; i++)
{ {
GL32.glBindAttribLocation(id, i, attributes[i]); GL32.glBindAttribLocation(this.id, i, attributes[i]);
} }
GL32.glLinkProgram(this.id); GL32.glLinkProgram(this.id);
@@ -59,8 +59,10 @@ public final class VertexPointer
/** Always aligned to 4 bytes */ /** Always aligned to 4 bytes */
public static VertexPointer addUnsignedBytePointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4, useInteger); } public static VertexPointer addUnsignedBytePointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4, useInteger); }
/** aligned to 4 bytes */ /** aligned to 4 bytes */
public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, _align(elementCount), useInteger); } public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized, boolean useInteger)
public static VertexPointer addUnsignedShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount * 2), useInteger); } { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, _align(elementCount), useInteger); }
public static VertexPointer addUnsignedShortsPointer(int elementCount, boolean normalized, boolean useInteger)
{ return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount * 2), useInteger); }
public static VertexPointer addShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_SHORT, normalized, _align(elementCount * 2), useInteger); } public static VertexPointer addShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_SHORT, normalized, _align(elementCount * 2), useInteger); }
public static VertexPointer addIntPointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_INT, normalized, 4, useInteger); } public static VertexPointer addIntPointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_INT, normalized, 4, useInteger); }
public static VertexPointer addIVec2Pointer(boolean normalized, boolean useInteger) { return new VertexPointer(2, GL32.GL_INT, normalized, 8, useInteger); } public static VertexPointer addIVec2Pointer(boolean normalized, boolean useInteger) { return new VertexPointer(2, GL32.GL_INT, normalized, 8, useInteger); }
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShader
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder;
import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.shader.Shader; import com.seibel.distanthorizons.core.render.glObject.shader.Shader;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram; import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
@@ -37,6 +38,7 @@ import com.seibel.distanthorizons.core.util.math.Vec3f;
/** /**
* Handles rendering the normal LOD terrain. * Handles rendering the normal LOD terrain.
* @see LodQuadBuilder
*/ */
public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShaderProgram public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShaderProgram
{ {
@@ -46,16 +48,14 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
public int uCombinedMatrix = -1; public int uCombinedMatrix = -1;
public int uModelOffset = -1; public int uModelOffset = -1;
public int uWorldYOffset = -1; public int uWorldYOffset = -1;
public int uDitherDhRendering = -1;
public int uMircoOffset = -1; public int uMircoOffset = -1;
public int uEarthRadius = -1; public int uEarthRadius = -1;
public int uLightMap = -1; public int uLightMap = -1;
// Fog/Clip Uniforms // fragment shader uniforms
public int uClipDistance = -1; public int uClipDistance = -1;
public int uDitherDhRendering = -1;
// Noise Uniforms // Noise Uniforms
public int uNoiseEnabled = -1; public int uNoiseEnabled = -1;
@@ -64,7 +64,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
public int uNoiseDropoff = -1; public int uNoiseDropoff = -1;
// Debug Uniform // Debug Uniform
public int uWhiteWorld = -1; public int uIsWhiteWorld = -1;
@@ -76,19 +76,16 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
public DhTerrainShaderProgram() public DhTerrainShaderProgram()
{ {
super( super(
() -> Shader.loadFile(Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get() != 0 () -> Shader.loadFile("shaders/standard.vert", false, new StringBuilder()).toString(),
? "shaders/curve.vert" () -> Shader.loadFile("shaders/flat_shaded.frag", false, new StringBuilder()).toString(),
: "shaders/standard.vert", "fragColor", new String[]{"vPosition", "color"});
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.uCombinedMatrix = this.getUniformLocation("uCombinedMatrix");
this.uModelOffset = this.getUniformLocation("uModelOffset"); this.uModelOffset = this.getUniformLocation("uModelOffset");
this.uWorldYOffset = this.tryGetUniformLocation("uWorldYOffset"); this.uWorldYOffset = this.getUniformLocation("uWorldYOffset");
this.uDitherDhRendering = this.tryGetUniformLocation("uDitherDhRendering"); this.uDitherDhRendering = this.getUniformLocation("uDitherDhRendering");
this.uMircoOffset = this.getUniformLocation("uMircoOffset"); this.uMircoOffset = this.getUniformLocation("uMircoOffset");
this.uEarthRadius = this.tryGetUniformLocation("uEarthRadius"); this.uEarthRadius = this.getUniformLocation("uEarthRadius");
this.uLightMap = this.getUniformLocation("uLightMap"); this.uLightMap = this.getUniformLocation("uLightMap");
@@ -102,7 +99,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
this.uNoiseDropoff = this.getUniformLocation("uNoiseDropoff"); this.uNoiseDropoff = this.getUniformLocation("uNoiseDropoff");
// Debug Uniform // Debug Uniform
this.uWhiteWorld = this.getUniformLocation("uWhiteWorld"); this.uIsWhiteWorld = this.getUniformLocation("uIsWhiteWorld");
// TODO: Add better use of the LODFormat thing // TODO: Add better use of the LODFormat thing
@@ -117,10 +114,13 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
} }
this.vao.bind(); this.vao.bind();
// TODO comment what each attribute represents // short: x, y, z, meta
this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true)); // 2+2+2+2 // TODO probably color, blockpos // meta: byte skylight, byte blocklight, byte microOffset
this.vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false)); // +4 // TODO ? this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true));
this.vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true)); // +4 // TODO probably normal index and Iris block ID // byte: r, g, b, a
this.vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false));
// byte: iris material ID, normal index, 2 spacers
this.vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true));
try try
{ {
@@ -178,12 +178,21 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
// setUniform(skyLightUniform, skyLight); // setUniform(skyLightUniform, skyLight);
this.setUniform(this.uLightMap, 0); // TODO this should probably be passed in this.setUniform(this.uLightMap, 0); // TODO this should probably be passed in
if (this.uWorldYOffset != -1) this.setUniform(this.uWorldYOffset, (float) renderParameters.worldYOffset); this.setUniform(this.uWorldYOffset, (float) renderParameters.worldYOffset);
if (this.uDitherDhRendering != -1) this.setUniform(this.uDitherDhRendering, Config.Client.Advanced.Graphics.Quality.ditherDhFade.get()); this.setUniform(this.uDitherDhRendering, Config.Client.Advanced.Graphics.Quality.ditherDhFade.get());
if (this.uEarthRadius != -1) this.setUniform(this.uEarthRadius, float curveRatio = Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get();
/*6371KM*/ 6371000.0f / Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get()); if (curveRatio < -1.0f || curveRatio > 1.0f)
{
curveRatio = /*6371KM*/ 6371000.0f / curveRatio;
}
else
{
// disable curvature if the config value is between -1 and 1
curveRatio = 0.0f;
}
this.setUniform(this.uEarthRadius, curveRatio);
// Noise Uniforms // Noise Uniforms
this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get()); this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get());
@@ -192,7 +201,7 @@ public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShade
this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get()); this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get());
// Debug // Debug
this.setUniform(this.uWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get()); this.setUniform(this.uIsWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
// Clip Uniform // Clip Uniform
float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks); float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocksForFading(renderParameters.partialTicks);
@@ -493,12 +493,8 @@ public class LodRenderer
return false; return false;
} }
if (!GLProxy.hasInstance()) // GLProxy should have already been created by this point, but just in case create it now
{ GLProxy.getInstance();
// shouldn't normally happen, but just in case
LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
return false;
}
@@ -30,6 +30,7 @@ public class RenderParams extends DhApiRenderParam
public IDhClientWorld dhClientWorld; public IDhClientWorld dhClientWorld;
public IDhClientLevel dhClientLevel; public IDhClientLevel dhClientLevel;
/** more specific override of the API value {@link DhApiRenderParam#clientLevelWrapper} */
public IClientLevelWrapper clientLevelWrapper; public IClientLevelWrapper clientLevelWrapper;
public ILightMapWrapper lightmap; public ILightMapWrapper lightmap;
public RenderBufferHandler renderBufferHandler; public RenderBufferHandler renderBufferHandler;
@@ -56,7 +57,8 @@ public class RenderParams extends DhApiRenderParam
RenderUtil.getNearClipPlaneDistanceInBlocks(newPartialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(), RenderUtil.getNearClipPlaneDistanceInBlocks(newPartialTicks), RenderUtil.getFarClipPlaneDistanceInBlocks(),
newMcProjectionMatrix, newMcModelViewMatrix, newMcProjectionMatrix, newMcModelViewMatrix,
RenderUtil.createLodProjectionMatrix(newMcProjectionMatrix, newPartialTicks), RenderUtil.createLodModelViewMatrix(newMcModelViewMatrix), RenderUtil.createLodProjectionMatrix(newMcProjectionMatrix, newPartialTicks), RenderUtil.createLodModelViewMatrix(newMcModelViewMatrix),
clientLevelWrapper.getMinHeight()); clientLevelWrapper.getMinHeight(),
clientLevelWrapper);
this.dhClientWorld = SharedApi.tryGetDhClientWorld(); this.dhClientWorld = SharedApi.tryGetDhClientWorld();
@@ -337,7 +337,7 @@ public class RenderableBoxGroup
@Override @Override
public void close() public void close()
{ {
GLProxy.getInstance().queueRunningOnRenderThread(() -> GLProxy.queueRunningOnRenderThread(() ->
{ {
if (this.instanceChunkPosVbo != 0) if (this.instanceChunkPosVbo != 0)
{ {
@@ -25,8 +25,10 @@ import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.SSAORenderer; import com.seibel.distanthorizons.core.render.renderer.SSAORenderer;
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad; import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
import com.seibel.distanthorizons.core.util.NumberUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
/** /**
@@ -58,6 +60,7 @@ public class SSAOShader extends AbstractShaderRenderer
public int uMinLight; public int uMinLight;
public int uBias; public int uBias;
public int uDepthMap; public int uDepthMap;
public int uFadeDistanceInBlocks;
@@ -81,6 +84,7 @@ public class SSAOShader extends AbstractShaderRenderer
this.uMinLight = this.shader.getUniformLocation("uMinLight"); this.uMinLight = this.shader.getUniformLocation("uMinLight");
this.uBias = this.shader.getUniformLocation("uBias"); this.uBias = this.shader.getUniformLocation("uBias");
this.uDepthMap = this.shader.getUniformLocation("uDepthMap"); this.uDepthMap = this.shader.getUniformLocation("uDepthMap");
this.uFadeDistanceInBlocks = this.shader.getUniformLocation("uFadeDistanceInBlocks");
} }
@@ -120,6 +124,10 @@ public class SSAOShader extends AbstractShaderRenderer
this.shader.setUniform(this.uBias, bias.floatValue()); this.shader.setUniform(this.uBias, bias.floatValue());
GL32.glUniform1i(this.uDepthMap, 0); GL32.glUniform1i(this.uDepthMap, 0);
float fadeDistanceInBlocks = Config.Client.Advanced.Graphics.Ssao.fadeDistanceInBlocks.get().floatValue();
fadeDistanceInBlocks = MathUtil.clamp(0.0f, fadeDistanceInBlocks, Float.MAX_VALUE); // clamp to prevent accidentally setting a negative number
this.shader.setUniform(this.uFadeDistanceInBlocks, fadeDistanceInBlocks);
} }
@@ -63,6 +63,8 @@ public interface IChunkWrapper extends IBindable
*/ */
int getMaxNonEmptyHeight(); int getMaxNonEmptyHeight();
void createDhHeightMaps();
/** @return The highest y position of a solid block at the given relative chunk position. */ /** @return The highest y position of a solid block at the given relative chunk position. */
int getSolidHeightMapValue(int xRel, int zRel); int getSolidHeightMapValue(int xRel, int zRel);
/** /**
@@ -126,8 +128,6 @@ public interface IChunkWrapper extends IBindable
IBiomeWrapper getBiome(int relX, int relY, int relZ); IBiomeWrapper getBiome(int relX, int relY, int relZ);
boolean isStillValid();
//========================// //========================//
@@ -406,5 +406,8 @@ public interface IChunkWrapper extends IBindable
return beaconTintBlockFound ? new Color(red, green, blue) : Color.WHITE; return beaconTintBlockFound ? new Color(red, green, blue) : Color.WHITE;
} }
IChunkWrapper copy();
} }
@@ -54,7 +54,7 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
/** /**
* Includes both the namespace and name. <br> * Includes both the namespace and name. <br>
* example: "minecraft@@overworld" * example: "minecraft:overworld"
*/ */
@Override @Override
String getDimensionName(); String getDimensionName();
@@ -85,8 +85,6 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
@Override @Override
int getMinHeight(); int getMinHeight();
default IChunkWrapper tryGetChunk(DhChunkPos pos) { return null; }
/** Fired when the level is being unloaded. Doesn't unload the level. */ /** Fired when the level is being unloaded. Doesn't unload the level. */
void onUnload(); void onUnload();
@@ -208,6 +208,10 @@
"Blur Radius", "Blur Radius",
"distanthorizons.config.client.advanced.graphics.ssao.blurRadius.@tooltip": "distanthorizons.config.client.advanced.graphics.ssao.blurRadius.@tooltip":
"The radius, measured in pixels, that blurring is calculated for the SSAO. \nHigher numbers will reduce banding at the cost of GPU performance.", "The radius, measured in pixels, that blurring is calculated for the SSAO. \nHigher numbers will reduce banding at the cost of GPU performance.",
"distanthorizons.config.client.advanced.graphics.ssao.fadeDistanceInBlocks":
"Fade Distance",
"distanthorizons.config.client.advanced.graphics.ssao.fadeDistanceInBlocks.@tooltip":
"The distance in blocks from the camera where the SSAO will fade out to. \nThis is done to prevent banding and noise at extreme distances.",
@@ -396,14 +400,13 @@
"Experimental", "Experimental",
"distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio": "distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio":
"Earth Curve Ratio §6(EXPERIMENTAL)§r", "Earth Curve Ratio",
"distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio.@tooltip": "distanthorizons.config.client.advanced.graphics.experimental.earthCurveRatio.@tooltip":
"A value of 1 is equivalent to the curvature of Earth in real life. \nThe minimum accepted value is 50 and the maximum value is 5000. \nEverything between 1 and 49 will be rounded up to 50.", "A value of 1 is equivalent to the curvature of Earth in real life. \nThe minimum accepted value is 50 and the maximum value is 5000. \nEverything between 1 and 49 will be rounded up to 50.",
"distanthorizons.config.client.advanced.graphics.experimental.ignoredDimensionCsv":
"distanthorizons.config.client.advanced.graphics.experimental.onlyLoadCenterLods": "Ignored Dimension CSV",
"Only load center LODs", "distanthorizons.config.client.advanced.graphics.experimental.ignoredDimensionCsv.@tooltip":
"distanthorizons.config.client.advanced.graphics.experimental.onlyLoadCenterLods.@tooltip": "A comma separated list of dimension resource locations where DH won't render. Example: \"minecraft:the_nether,minecraft:the_end\" \n\nNote: \nSome DH settings will be disabled and/or changed to improve \nvisuals when DH rendering is disabled.",
"Skips loading adjacent LODs to significantly reduce load times (~5x)\nbut causes lighting on LOD borders to appear as full-bright\nand other graphical bugs.\n",
@@ -714,6 +717,8 @@
"OpenGL Events - Chat", "OpenGL Events - Chat",
"distanthorizons.config.common.logging.logNetworkEventToFile": "distanthorizons.config.common.logging.logNetworkEventToFile":
"Network Events - File", "Network Events - File",
"distanthorizons.config.common.logging.logConnectionConfigChangesToFile":
"Network Connection Config Changes - File",
"distanthorizons.config.common.logging.warning": "distanthorizons.config.common.logging.warning":
"Warnings", "Warnings",
@@ -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);
}
+24 -9
View File
@@ -17,6 +17,7 @@ uniform float uMinLight;
uniform float uBias; uniform float uBias;
uniform mat4 uInvProj; uniform mat4 uInvProj;
uniform mat4 uProj; uniform mat4 uProj;
uniform float uFadeDistanceInBlocks;
const float EPSILON = 1.e-6; const float EPSILON = 1.e-6;
const float GOLDEN_ANGLE = 2.39996323; const float GOLDEN_ANGLE = 2.39996323;
@@ -99,16 +100,30 @@ void main()
{ {
vec3 viewPos = calcViewPosition(vec3(TexCoord, fragmentDepth)); vec3 viewPos = calcViewPosition(vec3(TexCoord, fragmentDepth));
#ifdef GL_ARB_derivative_control // fading is done to prevent banding/noise
// Get higher precision derivatives when available // at super far distance
vec3 viewNormal = cross(dFdxFine(viewPos.xyz), dFdyFine(viewPos.xyz)); float distanceFromCamera = length(viewPos);
#else float fadeDistance = uFadeDistanceInBlocks;
vec3 viewNormal = cross(dFdx(viewPos.xyz), dFdy(viewPos.xyz)); if (distanceFromCamera < fadeDistance)
#endif {
#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); viewNormal = normalize(viewNormal);
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);
occlusion = GetSpiralOcclusion(TexCoord, viewPos, viewNormal);
// linearly fade with distance
occlusion *= (fadeDistance - distanceFromCamera) / fadeDistance;
}
else
{
// we're out of range, no need to do any SSAO calculations
occlusion = 0.0;
}
} }
fragColor = vec4(vec3(1.0 - occlusion), 1.0); fragColor = vec4(vec3(1.0 - occlusion), 1.0);
+34 -21
View File
@@ -8,37 +8,37 @@ out vec4 vertexColor;
out vec3 vertexWorldPos; out vec3 vertexWorldPos;
out float vertexYPos; out float vertexYPos;
uniform bool uWhiteWorld; uniform bool uIsWhiteWorld;
uniform mat4 uCombinedMatrix; uniform mat4 uCombinedMatrix;
uniform vec3 uModelOffset; uniform vec3 uModelOffset;
uniform float uWorldYOffset; uniform float uWorldYOffset;
uniform int uWorldSkyLight;
uniform sampler2D uLightMap; uniform sampler2D uLightMap;
uniform float uMircoOffset; uniform float uMircoOffset;
uniform float uEarthRadius;
/** /**
* TODO in the future this and curve.vert should be merged together to prevent inconsistencies between the two
*
* Vertex Shader * Vertex Shader
* *
* author: James Seibel * author: James Seibel
* updated: TomTheFurry * author: TomTheFurry
* author: stduhpf
* updated: coolGi * updated: coolGi
* version: 2023-6-25 *
* version: 2025-12-22
*/ */
void main() void main()
{ {
vPos = vPosition; // This is so it can be passed to the fragment shader vPos = vPosition; // This is so it can be passed to the fragment shader
vertexWorldPos = vPosition.xyz + uModelOffset; vertexWorldPos = vPosition.xyz + uModelOffset;
vertexYPos = vPosition.y + uWorldYOffset; vertexYPos = vPosition.y + uWorldYOffset;
uint meta = vPosition.a; uint meta = vPosition.a;
uint mirco = (meta & 0xFF00u) >> 8u; // mirco offset which is a xyz 2bit value uint mirco = (meta & 0xFF00u) >> 8u; // mirco offset which is a xyz 2bit value
// 0b00 = no offset // 0b00 = no offset
// 0b01 = positive offset // 0b01 = positive offset
@@ -46,21 +46,34 @@ void main()
// format is: 0b00zzyyxx // format is: 0b00zzyyxx
float mx = (mirco & 1u)!=0u ? uMircoOffset : 0.0; float mx = (mirco & 1u)!=0u ? uMircoOffset : 0.0;
mx = (mirco & 2u)!=0u ? -mx : mx; mx = (mirco & 2u)!=0u ? -mx : mx;
float my = (mirco & 4u)!=0u ? uMircoOffset : 0.0; //float my = (mirco & 4u)!=0u ? uMircoOffset : 0.0;
my = (mirco & 8u)!=0u ? -my : my; //my = (mirco & 8u)!=0u ? -my : my;
float mz = (mirco & 16u)!=0u ? uMircoOffset : 0.0; float mz = (mirco & 16u)!=0u ? uMircoOffset : 0.0;
mz = (mirco & 32u)!=0u ? -mz : mz; mz = (mirco & 32u)!=0u ? -mz : mz;
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; vertexColor *= color;
} }
gl_Position = uCombinedMatrix * vec4(vertexWorldPos + vec3(mx, 0, mz), 1.0); gl_Position = uCombinedMatrix * vec4(vertexWorldPos, 1.0);
} }