Compare commits

...

22 Commits

Author SHA1 Message Date
s809 2a4bfef7a6 Add correct descriptions 2026-01-18 22:38:01 +05:00
s809 7be65a2258 Split off server generation into a separate toggle 2026-01-17 01:43:05 +05:00
s809 1a540cf2bc Make sure payload chunk is readable 2026-01-14 22:17:46 +05:00
James Seibel 20fc2efb46 Improve concurrent iterating in QuadTree 2026-01-10 17:03:43 -06:00
James Seibel d8beba2498 minor cleanup in LodBufferContainer cleanup 2026-01-10 17:02:56 -06:00
James Seibel 9f0cb5a394 Add forge specific icon/logo
Done to fix a forge limitation where logos can't contain a file pathhttps://github.com/MinecraftForge/MinecraftForge/issues/7348
2026-01-10 11:56:08 -06:00
James Seibel df63401d11 DB updater use correct classloader 2026-01-10 08:21:09 -06:00
James Seibel db95951ade minor reformat and comment 2026-01-10 08:20:44 -06:00
s809 1e020f93a6 Reapply "Run plugin messages on a DH thread"
This reverts commit ff3145336d.
2026-01-09 20:29:23 +05:00
James Seibel 7aee6dfb44 Merge branch 'main' of gitlab.com:distant-horizons-team/distant-horizons-core 2026-01-07 07:50:25 -06:00
James Seibel 546a51a295 expand distant beacon beams for visiblity 2026-01-07 07:50:22 -06:00
James Seibel ec7e791e9f Change EMinecraftColor -> MinecraftTextFormat
No need for an enum when all the values are strings
2026-01-06 07:10:40 -06:00
James Seibel d60dec3d82 Merge branch 'main' into 'main'
Fix typo in high vanilla render distance warning

See merge request distant-horizons-team/distant-horizons-core!94
2026-01-05 13:00:17 +00:00
s809 89a80103f0 Wrong message target 2026-01-04 20:04:30 +05:00
s809 8e14a7223c Add a chat message for incompatible messages 2026-01-04 19:36:24 +05:00
meanwhile131 7cf1e901f5 Fix typo in high vanilla render distance warning 2026-01-01 15:06:30 +04:00
James Seibel ba923fa829 Fix neoforge thread causing resource loading to fail 2025-12-26 14:13:27 -06:00
s809 505dbe2f62 Replace the failure state with future exceptions 2025-12-27 00:51:30 +05:00
James Seibel 48c5828e8f up version number 2.4.5 -> 2.4.6-dev 2025-12-24 22:41:27 -06:00
James Seibel eb2317934f up version number 2.4.4 -> 2.4.5 2025-12-24 22:06:53 -06:00
James Seibel 60537cda1b Replace MC color code strings with an enum 2025-12-24 22:04:50 -06:00
James Seibel 508ff2b776 Fix null pointer in ChunkUpdateQueueManager 2025-12-24 21:53:39 -06:00
36 changed files with 421 additions and 227 deletions
@@ -38,7 +38,7 @@ public final class ModInfo
public static final String NAME = "DistantHorizons";
/** Human-readable version of NAME */
public static final String READABLE_NAME = "Distant Horizons";
public static final String VERSION = "2.4.4-b";
public static final String VERSION = "2.4.6-b-dev";
/** Returns true if the current build is an unstable developer build, false otherwise. */
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
@@ -32,6 +33,8 @@ import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.renderer.*;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.config.Config;
@@ -57,6 +60,8 @@ import org.lwjgl.glfw.GLFW;
import java.io.File;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
/**
* This holds the methods that should be called
@@ -152,10 +157,10 @@ public class ClientApi
if (Config.Common.Logging.Warning.showReplayWarningOnStartup.get())
{
MC_CLIENT.sendChatMessage("\u00A76" + "Distant Horizons: Replay detected." + "\u00A7r"); // gold color
MC_CLIENT.sendChatMessage(MinecraftTextFormat.ORANGE + "Distant Horizons: Replay detected." + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage("DH may behave strangely or have missing functionality.");
MC_CLIENT.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:");
MC_CLIENT.sendChatMessage("\u00A77"+".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+"\u00A7r"); // light gray color
MC_CLIENT.sendChatMessage(MinecraftTextFormat.GRAY +".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+ MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
MC_CLIENT.sendChatMessage("");
}
@@ -328,10 +333,27 @@ public class ClientApi
*/
public void pluginMessageReceived(@NotNull AbstractNetworkMessage message)
{
NetworkSession networkSession = this.pluginChannelApi.networkSession;
if (networkSession != null)
@Nullable ThreadPoolExecutor executor = ThreadPoolUtil.networkClientHandlerExecutor();
if (executor == null)
{
networkSession.tryHandleMessage(message);
LOGGER.warn("warn");
return;
}
try
{
executor.execute(() ->
{
NetworkSession networkSession = this.pluginChannelApi.networkSession;
if (networkSession != null)
{
networkSession.tryHandleMessage(message);
}
});
}
catch (RejectedExecutionException e)
{
LOGGER.warn("Plugin message executor rejected");
}
}
@@ -507,10 +529,10 @@ public class ClientApi
this.rendererDisabledBecauseOfExceptions = true;
LOGGER.error("Unexpected Renderer error in render pass [" + renderPass + "]. Error: " + e.getMessage(), e);
MC_CLIENT.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
MC_CLIENT.sendChatMessage("\u00A74Renderer disabled to try preventing GL state corruption.");
MC_CLIENT.sendChatMessage("\u00A74Toggle DH rendering via the config UI to re-activate DH rendering.");
MC_CLIENT.sendChatMessage("\u00A74Error: " + e);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "" + MinecraftTextFormat.BOLD + "ERROR: Distant Horizons renderer has encountered an exception!" + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Renderer disabled to try preventing GL state corruption." + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Toggle DH rendering via the config UI to re-activate DH rendering." + MinecraftTextFormat.CLEAR_FORMATTING);
MC_CLIENT.sendChatMessage(MinecraftTextFormat.DARK_RED + "Error: " + MinecraftTextFormat.CLEAR_FORMATTING + e);
}
@@ -655,8 +677,7 @@ public class ClientApi
// remind the user that this is a development build
String message =
// green text
"\u00A72" + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + "\u00A7r\n" +
MinecraftTextFormat.DARK_GREEN + "Distant Horizons: nightly/unstable build, version: [" + ModInfo.VERSION+"]." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Issues may occur with this version.\n" +
"Here be dragons!\n";
MC_CLIENT.sendChatMessage(message);
@@ -680,7 +701,7 @@ public class ClientApi
{
String message =
// orange text
"\u00A76" + "Distant Horizons: Low memory detected." + "\u00A7r \n" +
MinecraftTextFormat.ORANGE + "Distant Horizons: Low memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Stuttering or low FPS may occur. \n" +
"Please increase Minecraft's available memory to 4 GB or more. \n" +
"This warning can be disabled in DH's config under Advanced -> Logging. \n";
@@ -702,15 +723,13 @@ public class ClientApi
this.lastStaticWarningMessageSentMsTime = System.currentTimeMillis();
String message =
// yellow text
"\u00A7e" + "Distant Horizons: High vanilla render distance detected." + "\u00A7r \n" +
"Using a high vanilla render distance uses a lot of CPU power \n" +
"and doesn't improve graphics much after about 12.\n" +
"Lowing your vanilla render distance will give you better FPS\n" +
"and reduce stuttering at a similar visual quality.\n" +
// gray text
"\u00A77" + "A vanilla render distance of 8 is recommended." + "\u00A7r \n" +
"This message can be disabled in DH's config under Advanced -> Logging.\n";
MinecraftTextFormat.YELLOW + "Distant Horizons: High vanilla render distance detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Using a high vanilla render distance uses a lot of CPU power \n" +
"and doesn't improve graphics much after about 12.\n" +
"Lowering your vanilla render distance will give you better FPS\n" +
"and reduce stuttering at a similar visual quality.\n" +
MinecraftTextFormat.GRAY + "A vanilla render distance of 8 is recommended." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"This message can be disabled in DH's config under Advanced -> Logging.\n";
MC_CLIENT.sendChatMessage(message);
}
}
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import org.jetbrains.annotations.Nullable;
import java.util.Comparator;
import java.util.concurrent.ConcurrentHashMap;
@@ -105,6 +106,7 @@ public class ChunkPosQueue
this.furthestQueue.remove(closest);
return this.updateDataByChunkPos.remove(closest);
}
@Nullable
public ChunkUpdateData popFurthest()
{
if (this.furthestQueue.isEmpty())
@@ -1,12 +1,11 @@
package com.seibel.distanthorizons.core.api.internal.chunkUpdating;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.client.SyncOnLoadRequestQueue;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.world.EWorldEnvironment;
import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -94,7 +93,10 @@ public class ChunkUpdateQueueManager
if (remainingSlots <= 0)
{
ChunkUpdateData removedData = queue.popFurthest();
this.queuedChunkWrapperByChunkPos.remove(removedData.chunkWrapper.getChunkPos());
if (removedData != null)
{
this.queuedChunkWrapperByChunkPos.remove(removedData.chunkWrapper.getChunkPos());
}
}
queue.addItem(pos,updateData);
@@ -116,7 +118,7 @@ public class ChunkUpdateQueueManager
{
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
String message = "\u00A76" + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + "\u00A7r" +
String message = MinecraftTextFormat.ORANGE + "Distant Horizons overloaded, too many chunks queued for LOD processing. " + MinecraftTextFormat.CLEAR_FORMATTING +
"\nThis may result in holes in your LODs. " +
"\nFix: move through the world slower, decrease your vanilla render distance, slow down your world pre-generator (IE Chunky), or increase the Distant Horizons' CPU thread counts. " +
"\nMax queue count [" + SharedApi.CHUNK_UPDATE_QUEUE_MANAGER.maxSize + "] ([" + SharedApi.MAX_UPDATING_CHUNK_COUNT_PER_THREAD_AND_PLAYER + "] per thread+players).";
@@ -101,6 +101,7 @@ public class Config
.build();
public static ConfigUiLinkedEntry quickEnableWorldGenerator = new ConfigUiLinkedEntry(Common.WorldGenerator.enableDistantGeneration);
public static ConfigUiLinkedEntry quickEnableServerGeneration = new ConfigUiLinkedEntry(Server.enableServerGeneration);
public static ConfigUiLinkedEntry quickShowWorldGenProgress = new ConfigUiLinkedEntry(Common.WorldGenerator.showGenerationProgress);
@@ -457,6 +458,15 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> expandDistantBeacons = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If true LOD beacon beams will be rendered wider at extreme distances, \n"
+ "making them easier to see. \n"
+ "If false all LOD beacon beams will only ever be 1 block wide. \n"
+ "")
.build();
public static ConfigEntry<Boolean> enableCloudRendering = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
@@ -1725,6 +1735,15 @@ public class Config
// Generation
public static ConfigEntry<Boolean> enableServerGeneration = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "When enabled, Distant Horizons will attempt to download missing LODs from the server.\n"
+ "\n"
+ "Note: the server must have Distant Generation enabled for it to work."
+ "")
.build();
public static ConfigEntry<Integer> generationRequestRateLimit = new ConfigEntry.Builder<Integer>()
.setChatCommandName("generation.requestRateLimit")
.setMinDefaultMax(1, 20, 100)
@@ -295,24 +295,21 @@ public class LodBufferContainer implements AutoCloseable
{
this.buffersUploaded = false;
GLProxy.queueRunningOnRenderThread(() ->
for (GLVertexBuffer buffer : this.vbos)
{
for (GLVertexBuffer buffer : this.vbos)
if (buffer != null)
{
if (buffer != null)
{
buffer.destroyAsync();
}
buffer.destroyAsync();
}
for (GLVertexBuffer buffer : this.vbosTransparent)
}
for (GLVertexBuffer buffer : this.vbosTransparent)
{
if (buffer != null)
{
if (buffer != null)
{
buffer.destroyAsync();
}
buffer.destroyAsync();
}
});
}
}
}
@@ -1,12 +0,0 @@
package com.seibel.distanthorizons.core.enums;
/**
* TODO
* might be deprecated in the future? in that case we'll probably want a wrapper
* function to handle colors for new MC versions
*
* source: https://minecraft.wiki/w/Formatting_codes
*/
public class EMinecraftColor
{
}
@@ -0,0 +1,35 @@
package com.seibel.distanthorizons.core.enums;
/**
* might be deprecated in the future? in that case we'll probably want a wrapper
* function to handle colors for new MC versions
* <br><br>
* source: https://minecraft.wiki/w/Formatting_codes
*/
public class MinecraftTextFormat
{
public static final String BLACK = "\u00A70";
public static final String DARK_BLUE = "\u00A71";
public static final String DARK_GREEN = "\u00A72";
public static final String DARK_AQUA = "\u00A73";
public static final String DARK_RED = "\u00A74";
public static final String DARK_PURPLE = "\u00A75";
public static final String ORANGE = "\u00A76";
public static final String GRAY = "\u00A77";
public static final String DARK_GRAY = "\u00A78";
public static final String BLUE = "\u00A79";
public static final String GREEN = "\u00A7a";
public static final String AQUA = "\u00A7b";
public static final String RED = "\u00A7c";
public static final String LIGHT_PURPLE = "\u00A7d";
public static final String YELLOW = "\u00A7e";
public static final String WHITE = "\u00A7f";
public static final String OBFUSCATED = "\u00A7k";
public static final String BOLD = "\u00A7l";
public static final String STRIKETHROUGH = "\u00A7m";
public static final String UNDERLINE = "\u00A7n";
public static final String ITALIC = "\u00A7o";
public static final String CLEAR_FORMATTING = "\u00A7r";
}
@@ -44,6 +44,7 @@ import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
@@ -116,26 +117,22 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
// events //
//========//
private void onWorldGenTaskComplete(DataSourceRetrievalResult genTaskResult, Throwable exception)
private void onWorldGenTaskComplete(@NotNull Long genPos, @Nullable DataSourceRetrievalResult genTaskResult, @Nullable Throwable exception)
{
try
{
if (exception != null)
{
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);
LOGGER.error("Uncaught Gen Task Exception at [" + genPos + "], error: [" + exception.getMessage() + "].", exception);
}
return;
}
else if (genTaskResult.state == ERetrievalResultState.SUCCESS)
Objects.requireNonNull(genTaskResult);
if (genTaskResult.state == ERetrievalResultState.SUCCESS)
{
LodUtil.assertTrue(genTaskResult.dataSource != null, "Successful retrieval object should have a datasource.");
@@ -310,7 +307,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
}
CompletableFuture<DataSourceRetrievalResult> worldGenFuture = worldGenQueue.submitRetrievalTask(genPos, (byte) (DhSectionPos.getDetailLevel(genPos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL));
worldGenFuture.whenComplete(this::onWorldGenTaskComplete);
worldGenFuture.whenComplete((r, e) -> this.onWorldGenTaskComplete(genPos, r, e));
return worldGenFuture;
}
@@ -157,9 +157,9 @@ public class PregenManager
else
{
this.fullDataSourceProvider.queuePositionForRetrieval(fullDataSource.getPos())
.thenAccept((DataSourceRetrievalResult result) ->
.whenComplete((DataSourceRetrievalResult result, Throwable throwable) ->
{
if (result.state == ERetrievalResultState.FAIL)
if (throwable != null)
{
LOGGER.warn("Failed to generate section " + DhSectionPos.toString(result.pos));
}
@@ -92,7 +92,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
private int estimatedRemainingChunkCount = 0;
private final RollingAverage rollingAverageChunkGenTimeInMs = new RollingAverage(Runtime.getRuntime().availableProcessors() * 500);
public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
@Override public RollingAverage getRollingAverageChunkGenTimeInMs() { return this.rollingAverageChunkGenTimeInMs; }
@@ -128,7 +128,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// the generator is shutting down, don't add new tasks
if (this.generatorClosingFuture != null)
{
return CompletableFuture.completedFuture(DataSourceRetrievalResult.CreateFail());
CompletableFuture<DataSourceRetrievalResult> f = new CompletableFuture<>();
f.completeExceptionally(new CancellationException());
return f;
}
// use the existing task if present
@@ -362,7 +364,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
}
LodUtil.assertTrue(fullDataSource == null);
worldGenTask.future.complete(DataSourceRetrievalResult.CreateFail());
worldGenTask.future.completeExceptionally(exception);
}
else
{
@@ -40,7 +40,6 @@ public class DataSourceRetrievalResult
//==============//
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)
{
@@ -3,7 +3,6 @@ package com.seibel.distanthorizons.core.generation.tasks;
/**
* SUCCESS <br>
* REQUIRES_SPLITTING <br>
* FAIL <br>
*
* @see DataSourceRetrievalResult
*/
@@ -11,5 +10,4 @@ public enum ERetrievalResultState
{
SUCCESS,
REQUIRES_SPLITTING,
FAIL,
}
@@ -104,7 +104,7 @@ public class JarUtils
*/
public static InputStream accessFile(String resource)
{
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
final ClassLoader loader = JarUtils.class.getClassLoader();
// this is the path within the jar file
InputStream input = loader.getResourceAsStream(resource);
if (input == null)
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
import com.seibel.distanthorizons.core.config.types.ConfigEntry;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
@@ -302,32 +303,32 @@ public class DhLogger implements IConfigListener
String prefix = "[" + ModInfo.READABLE_NAME + "] ";
if (logLevel == Level.ERROR)
{
prefix += "\u00A74";
prefix += MinecraftTextFormat.DARK_RED;
}
else if (logLevel == Level.WARN)
{
prefix += "\u00A76";
prefix += MinecraftTextFormat.ORANGE;
}
else if (logLevel == Level.INFO)
{
prefix += "\u00A7f";
prefix += MinecraftTextFormat.AQUA;
}
else if (logLevel == Level.DEBUG)
{
prefix += "\u00A77";
prefix += MinecraftTextFormat.GREEN;
}
else if (logLevel == Level.TRACE)
{
prefix += "\u00A78";
prefix += MinecraftTextFormat.DARK_GRAY;
}
else
{
prefix += "\u00A7f";
prefix += MinecraftTextFormat.WHITE;
}
prefix += "\u00A7l\u00A7u";
prefix += MinecraftTextFormat.BOLD + "" + MinecraftTextFormat.WHITE;
prefix += logLevel.name();
prefix += ":\u00A7r ";
prefix += MinecraftTextFormat.CLEAR_FORMATTING + " ";
mc_client.sendChatMessage(prefix + message);
}
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.DhApiWorldProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -139,9 +140,6 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
break;
case REQUIRES_SPLITTING:
break;
case FAIL:
this.failedRequests.incrementAndGet();
break;
}
});
@@ -220,11 +218,18 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
FullDataSourceResponseMessage.class
);
requestTask.networkDataSourceFuture = dataSourceNetworkFuture;
dataSourceNetworkFuture.handle((FullDataSourceResponseMessage response, Throwable throwable) ->
Executor networkCompressionExecutor = ThreadPoolUtil.getNetworkCompressionExecutor();
if (networkCompressionExecutor == null)
{
return;
}
dataSourceNetworkFuture.handleAsync((FullDataSourceResponseMessage response, Throwable throwable) ->
{
this.handleNetResponse(requestTask, response, throwable);
return null;
});
}, networkCompressionExecutor);
}
private void handleNetResponse(NetRequestTask requestTask, FullDataSourceResponseMessage response, Throwable throwable)
{
@@ -269,7 +274,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
catch (RequestRejectedException e)
{
LOGGER.info("Request rejected by the server, message: [" + e.getMessage() + "].");
requestTask.future.complete(DataSourceRetrievalResult.CreateFail());
requestTask.future.completeExceptionally(e);
}
catch (RateLimitedException e)
{
@@ -298,7 +303,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
}
else
{
requestTask.future.complete(DataSourceRetrievalResult.CreateFail());
requestTask.future.completeExceptionally(e);
}
}
}
@@ -1,8 +1,10 @@
package com.seibel.distanthorizons.core.multiplayer.client;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
@@ -95,6 +97,15 @@ public class ClientNetworkState implements Closeable
|| Math.abs(event.protocolVersion - ModInfo.PROTOCOL_VERSION) < this.closestProtocolVersion)
{
this.closestProtocolVersion = event.protocolVersion;
if (ModInfo.PROTOCOL_VERSION < event.protocolVersion)
{
ClientApi.INSTANCE.showChatMessageNextFrame(MinecraftTextFormat.ORANGE + "Distant Horizons: Your mod is outdated. Update to receive LODs on this server.");
}
else
{
ClientApi.INSTANCE.showChatMessageNextFrame(MinecraftTextFormat.ORANGE + "Distant Horizons: The server's mod is outdated. Ask the server's owner to update.");
}
}
});
@@ -11,6 +11,7 @@ import java.io.Closeable;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class SessionConfig implements INetworkObject
@@ -31,7 +32,23 @@ public class SessionConfig implements INetworkObject
{
// Note: config values are transmitted in the insertion order
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd);
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration.getChatCommandName(), new Entry(
Config.Server.enableServerGeneration::get,
runnable -> new Closeable()
{
private final ConfigChangeListener<Boolean> distantGenerationChanges = new ConfigChangeListener<>(Config.Common.WorldGenerator.enableDistantGeneration, ignored -> runnable.run());
private final ConfigChangeListener<Boolean> serverGenerationChanges = new ConfigChangeListener<>(Config.Server.enableServerGeneration, ignored -> runnable.run());
@Override
public void close()
{
this.serverGenerationChanges.close();
this.distantGenerationChanges.close();
}
},
(Boolean client, Boolean server) -> client && Config.Common.WorldGenerator.enableDistantGeneration.get()
));
registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min);
registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkX, (x, y) -> y);
registerConfigEntry(Config.Common.WorldGenerator.generationCenterChunkZ, (x, y) -> y);
@@ -90,14 +107,24 @@ public class SessionConfig implements INetworkObject
private static <T> void registerConfigEntry(ConfigEntry<T> configEntry, BinaryOperator<T> valueConstrainer)
{
CONFIG_ENTRIES.compute(Objects.requireNonNull(configEntry.getChatCommandName()), (key, existingEntry) -> {
if (existingEntry != null)
{
throw new IllegalArgumentException("Attempted to register config entry with duplicate chatCommandName: " + key);
}
return new Entry(configEntry, valueConstrainer);
});
registerConfigEntry(
Objects.requireNonNull(configEntry.getChatCommandName()),
new Entry(
configEntry::get,
runnable -> new ConfigChangeListener<>(configEntry, ignored -> runnable.run()),
valueConstrainer
)
);
}
private static void registerConfigEntry(String key, Entry entry)
{
if (CONFIG_ENTRIES.containsKey(key))
{
throw new IllegalArgumentException("Attempted to register config entry with duplicate key: " + key);
}
CONFIG_ENTRIES.put(key, entry);
}
@@ -115,7 +142,7 @@ public class SessionConfig implements INetworkObject
T value = (T) this.values.get(name);
if (value == null)
{
value = (T) entry.supplier.get();
value = (T) entry.valueSupplier.get();
}
return (this.constrainingConfig != null
@@ -210,13 +237,15 @@ public class SessionConfig implements INetworkObject
private static class Entry
{
public final ConfigEntry<Object> supplier;
public final Supplier<Object> valueSupplier;
public final Function<Runnable, Closeable> changeListenerFactory;
public final BinaryOperator<Object> valueConstrainer;
@SuppressWarnings("unchecked")
private <T> Entry(ConfigEntry<T> supplier, BinaryOperator<T> valueConstrainer)
private <T> Entry(Supplier<Object> valueSupplier, Function<Runnable, Closeable> changeListenerFactory, BinaryOperator<T> valueConstrainer)
{
this.supplier = (ConfigEntry<Object>) supplier;
this.valueSupplier = valueSupplier;
this.changeListenerFactory = changeListenerFactory;
this.valueConstrainer = (BinaryOperator<Object>) valueConstrainer;
}
@@ -225,23 +254,29 @@ public class SessionConfig implements INetworkObject
/** fires if any config value was changed */
public static class AnyChangeListener implements Closeable
{
private final ArrayList<ConfigChangeListener<?>> changeListeners;
private final ArrayList<Closeable> changeListeners;
public AnyChangeListener(Runnable runnable)
{
this.changeListeners = new ArrayList<>(CONFIG_ENTRIES.size());
for (Entry entry : CONFIG_ENTRIES.values())
{
this.changeListeners.add(new ConfigChangeListener<>(entry.supplier, ignored -> runnable.run()));
this.changeListeners.add(entry.changeListenerFactory.apply(runnable));
}
}
@Override
public void close()
{
for (ConfigChangeListener<?> changeListener : this.changeListeners)
for (Closeable changeListener : this.changeListeners)
{
changeListener.close();
try
{
changeListener.close();
}
catch (Exception ignored)
{
}
}
this.changeListeners.clear();
}
@@ -45,6 +45,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
return null;
}
message.buffer.readerIndex(0);
composite.addComponent(message.buffer);
composite.writerIndex(composite.writerIndex() + message.buffer.writerIndex());
LOGGER.debug("Updated full data buffer [" + message.bufferId + "]: [" + composite + "].");
@@ -2,6 +2,7 @@ package com.seibel.distanthorizons.core.pooling;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
@@ -12,7 +13,6 @@ import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -178,8 +178,7 @@ public class PhantomArrayListPool
{
lowMemoryWarningLogged = true;
// orange text
String message = "\u00A76" + "Distant Horizons: Insufficient memory detected." + "\u00A7r \n" +
String message = MinecraftTextFormat.ORANGE + "Distant Horizons: Insufficient memory detected." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"This may cause stuttering or crashing. \n" +
"Potential causes: \n" +
"1. your allocated memory isn't high enough \n" +
@@ -26,6 +26,7 @@ import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.generation.tasks.DataSourceRetrievalResult;
import com.seibel.distanthorizons.core.generation.tasks.ERetrievalResultState;
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -78,6 +79,11 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
*/
private final ConcurrentLinkedQueue<Long> sectionsToReload = new ConcurrentLinkedQueue<>();
private final IDhClientLevel level;
/**
* Note: this doesn't lock all operations as some other threads/operations
* that may traverse the tree while it's being modified.
* IE {@link RenderBufferHandler} will walk through the tree each frame.
*/
private final ReentrantLock treeLock = new ReentrantLock();
private ArrayList<LodRenderSection> debugRenderSections = new ArrayList<>();
@@ -139,6 +145,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null;
Config.Common.WorldGenerator.enableDistantGeneration.addListener(this);
Config.Server.enableServerGeneration.addListener(this);
}
@@ -166,7 +173,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// don't traverse the tree if it is being modified
// don't tick the tree if a modification is still going
// TODO is this lock necessary for anything beyond this tick method?
if (this.treeLock.tryLock())
{
// this shouldn't be updated while the tree is being iterated through
@@ -508,28 +516,23 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
continue;
}
try
// the section only needs to be updated if a buffer is currently present
LodRenderSection renderSection = this.tryGetValue(pos);
if (renderSection != null)
{
// the section only needs to be updated if a buffer is currently present
LodRenderSection renderSection = this.getValue(pos);
if (renderSection != null)
if (renderSection.canRender())
{
if (renderSection.canRender())
if (renderSection.gpuUploadInProgress()
|| !renderSection.uploadRenderDataToGpuAsync())
{
if (renderSection.gpuUploadInProgress()
|| !renderSection.uploadRenderDataToGpuAsync())
{
// if a section is already loading or failed to start upload
// we need to wait to trigger it again
// if we don't trigger it again the LOD will be out of date
// and may be invisible/missing
positionsToRequeue.add(pos);
}
// if a section is already loading or failed to start upload
// we need to wait to trigger it again
// if we don't trigger it again the LOD will be out of date
// and may be invisible/missing
positionsToRequeue.add(pos);
}
}
}
catch (IndexOutOfBoundsException e)
{ /* the section is now out of bounds, it doesn't need to be reloaded */ }
}
this.sectionsToReload.addAll(positionsToRequeue);
}
@@ -622,12 +625,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// 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)
if (result.state == ERetrievalResultState.REQUIRES_SPLITTING)
{
DhSectionPos.forEachChild(missingPos, (long childPos) ->
{
@@ -666,7 +664,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
@Override
public void onConfigValueSet()
{
boolean generatorEnabled = Config.Common.WorldGenerator.enableDistantGeneration.get();
boolean generatorEnabled = this.level instanceof DhClientServerLevel
? Config.Common.WorldGenerator.enableDistantGeneration.get()
: Config.Server.enableServerGeneration.get();
if (generatorEnabled)
{
// world gen tasks will need to be re-queued
@@ -924,6 +924,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
DebugRenderer.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
Config.Server.enableServerGeneration.removeListener(this);
ThreadPoolExecutor mainCleanupExecutor = ThreadPoolUtil.getCleanupExecutor();
@@ -77,7 +77,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
* contains the list of beacons currently being rendered in this section
* if this list is modified the {@link LodRenderSection#beaconRenderHandler} should be updated to match.
*/
private final List<BeaconBeamDTO> activeBeaconList = new ArrayList<>();
private final ArrayList<BeaconBeamDTO> activeBeaconList = new ArrayList<>();
@Nullable
public final BeaconRenderHandler beaconRenderHandler;
@Nullable
@@ -446,20 +446,15 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// stop rendering current beacons
for (BeaconBeamDTO beam : this.activeBeaconList)
{
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
}
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
// swap old and new active beacon list
this.activeBeaconList.clear();
this.activeBeaconList.addAll(activeBeacons);
// start rendering new beacon list
for (BeaconBeamDTO beam : this.activeBeaconList)
{
this.beaconRenderHandler.startRenderingBeacon(beam);
}
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
}
}
@@ -474,10 +469,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
synchronized (this.activeBeaconList)
{
for (BeaconBeamDTO beam : this.activeBeaconList)
{
this.beaconRenderHandler.stopRenderingBeaconAtPos(beam.blockPos);
}
this.beaconRenderHandler.stopRenderingBeacons(this.activeBeaconList);
}
}
@@ -492,10 +484,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
synchronized (this.activeBeaconList)
{
for (BeaconBeamDTO beam : this.activeBeaconList)
{
this.beaconRenderHandler.startRenderingBeacon(beam);
}
byte absoluteDetailLevel = (byte)(DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
this.beaconRenderHandler.startRenderingBeacons(this.activeBeaconList, absoluteDetailLevel);
}
}
@@ -434,7 +434,7 @@ public class LodRenderer
// resize the textures if needed
if (MC_RENDER.getTargetFramebufferViewportWidth() != this.textureWidth
|| MC_RENDER.getTargetFramebufferViewportHeight() != this.textureHeight)
|| MC_RENDER.getTargetFramebufferViewportHeight() != this.textureHeight)
{
// just resizing the textures doesn't work when Optifine is present,
// so recreate the textures with the new size instead
@@ -536,7 +536,7 @@ public class LodRenderer
return true;
}
@SuppressWarnings( "deprecation" )
@SuppressWarnings( "deprecation" ) // done to ignore DhApiColorDepthTextureCreatedEvent
private void createAndBindTextures()
{
int oldWidth = this.textureWidth;
@@ -28,6 +28,7 @@ import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShad
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -39,8 +40,7 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.*;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock;
@@ -54,6 +54,8 @@ public class BeaconRenderHandler
/** how often should we check if a beacon should be culled? */
private static final int MAX_CULLING_FREQUENCY_IN_MS = 1_000;
private static final Comparator<BeaconBeamDTO> NEGATIVE_BLOCKPOS_COMPARATOR = new NegativeInfiniteBlockPosComparator();
private final ReentrantLock updateLock = new ReentrantLock();
@@ -89,20 +91,74 @@ public class BeaconRenderHandler
// render handling //
//=================//
public void startRenderingBeacon(BeaconBeamDTO beacon)
public void startRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList, byte detailLevel)
{
try
{
this.updateLock.lock();
if (this.beaconBlockPosSet.add(beacon.blockPos))
// how wide should each beacon be?
int beaconBlockWidth = 1;
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
{
beaconBlockWidth = DhSectionPos.getBlockWidth(detailLevel);
}
ArrayList<BeaconBeamDTO> sortedBeaconList = new ArrayList<>(beaconList);
// merge distant beams if requested
if (Config.Client.Advanced.Graphics.GenericRendering.expandDistantBeacons.get())
{
// sort beacons from neg inf -> pos inf
// so we can consistently merge adjacent beacons
sortedBeaconList.sort(NEGATIVE_BLOCKPOS_COMPARATOR);
// go through each beacon...
for (int outerIndex = 0; outerIndex < sortedBeaconList.size(); outerIndex++)
{
BeaconBeamDTO outerBeacon = sortedBeaconList.get(outerIndex);
DhBlockPos outerBlockPos = outerBeacon.blockPos;
// ...and remove any beacons that are within the block width to prevent overlaps
for (int mergeIndex = outerIndex + 1; mergeIndex < sortedBeaconList.size(); mergeIndex++)
{
BeaconBeamDTO beaconToMerge = sortedBeaconList.get(mergeIndex);
DhBlockPos mergeBlockPos = beaconToMerge.blockPos;
int xDiff = mergeBlockPos.getX() - outerBlockPos.getX();
int zDiff = mergeBlockPos.getZ() - outerBlockPos.getZ();
// merge (remove) this beacon if
// it's close to the outer beacon
if (xDiff < beaconBlockWidth
&& zDiff < beaconBlockWidth)
{
sortedBeaconList.remove(mergeIndex);
mergeIndex--; // minus 1 so we don't go past the end of the array when incrementing in the for loop up top
}
}
}
}
// add each beacon to the renderer
for (int i = 0; i < sortedBeaconList.size(); i++)
{
BeaconBeamDTO beacon = sortedBeaconList.get(i);
if (!this.beaconBlockPosSet.add(beacon.blockPos))
{
continue;
}
int maxBeaconBeamHeight = Config.Client.Advanced.Graphics.GenericRendering.beaconRenderHeight.get();
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
new DhApiVec3d(beacon.blockPos.getX() + 1, maxBeaconBeamHeight, beacon.blockPos.getZ() + 1),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
new DhApiVec3d(beacon.blockPos.getX(), beacon.blockPos.getY() + 1, beacon.blockPos.getZ()),
new DhApiVec3d(beacon.blockPos.getX() + beaconBlockWidth, maxBeaconBeamHeight, beacon.blockPos.getZ() + beaconBlockWidth),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
);
this.beaconBoxGroup.add(beaconBox);
@@ -116,19 +172,26 @@ public class BeaconRenderHandler
}
}
public void stopRenderingBeaconAtPos(DhBlockPos beaconPos)
public void stopRenderingBeacons(ArrayList<BeaconBeamDTO> beaconList)
{
try
{
this.updateLock.lock();
if (this.beaconBlockPosSet.remove(beaconPos))
for (int i = 0; i < beaconList.size(); i++)
{
BeaconBeamDTO beacon = beaconList.get(i);
DhBlockPos beaconPos = beacon.blockPos;
if (!this.beaconBlockPosSet.remove(beaconPos))
{
continue;
}
Predicate<DhApiRenderableBox> removePredicate = (DhApiRenderableBox box) ->
{
return box.minPos.x == beaconPos.getX()
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.getZ();
&& box.minPos.y == beaconPos.getY() + 1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.getZ();
};
this.beaconBoxGroup.removeIf(removePredicate);
this.fullBeaconBoxList.removeIf(removePredicate);
@@ -255,4 +318,28 @@ public class BeaconRenderHandler
}
//================//
// helper classes //
//================//
private static class NegativeInfiniteBlockPosComparator implements Comparator<BeaconBeamDTO>
{
@Override
public int compare(BeaconBeamDTO beacon1, BeaconBeamDTO beacon2)
{
DhBlockPos blockPos1 = beacon1.blockPos;
DhBlockPos blockPos2 = beacon2.blockPos;
// sort by X, then by Z
if (blockPos1.getX() != blockPos2.getX())
{
return Integer.compare(blockPos1.getX(), blockPos2.getX());
}
return Integer.compare(blockPos1.getZ(), blockPos2.getZ());
}
}
}
@@ -493,7 +493,7 @@ public class CloudRenderHandler
private static boolean[][] getCloudsFromTexture() throws FileNotFoundException, IOException
{
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
final ClassLoader loader = CloudRenderHandler.class.getClassLoader();
boolean[][] whitePixels = null;
try(InputStream imageInputStream = loader.getResourceAsStream(CLOUD_RESOURCE_TEXTURE_PATH))
@@ -173,7 +173,7 @@ public class DatabaseUpdater
/** @throws NullPointerException if any of the script files failed to be read. */
private static ArrayList<SqlScript> getAutoUpdateScripts() throws NullPointerException, IOException
{
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
final ClassLoader loader = DatabaseUpdater.class.getClassLoader();
// get the script list
@@ -76,6 +76,9 @@ public class BeaconBeamDTO implements IBaseDTO<DhBlockPos>, INetworkObject
public void close()
{ /* no closing needed */ }
@Override
public String toString() { return this.blockPos + " " + this.color; }
}
@@ -31,12 +31,10 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
import com.seibel.distanthorizons.core.util.gridList.MovableGridRingList;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongIterator;
import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.LongConsumer;
/**
@@ -98,9 +96,27 @@ public class QuadTree<T>
// getters and setters //
//=====================//
/** @return the value at the given section position. Null will be returned if the value is missing or the position is out of bounds. */
@Nullable
public final T tryGetValue(long pos)
{
QuadNode<T> node = this.tryGetNode(pos);
if (node != null)
{
return node.value;
}
return null;
}
/** @return the node at the given section position, null if out of bounds */
@Nullable
public final QuadNode<T> tryGetNode(long pos) { return this.getOrSetNode(pos, false, null, false); }
/** @return the node at the given section position */
@Nullable
public final QuadNode<T> getNode(long pos) throws IndexOutOfBoundsException { return this.getOrSetNode(pos, false, null, true); }
/** @return the value at the given section position */
@Nullable
public final T getValue(long pos) throws IndexOutOfBoundsException
@@ -122,16 +138,24 @@ public class QuadTree<T>
return previousValue;
}
/** @param runBoundaryChecks should only ever be set to true internally for removing out of bound nodes */
/** @param throwIfOutOfBounds if false returns null */
@Nullable
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean runBoundaryChecks) throws IndexOutOfBoundsException
protected final QuadNode<T> getOrSetNode(long pos, boolean setNewValue, T newValue, boolean throwIfOutOfBounds) throws IndexOutOfBoundsException
{
if (runBoundaryChecks && !this.isSectionPosInBounds(pos))
if (!this.isSectionPosInBounds(pos))
{
int radius = this.diameterInBlocks() / 2;
DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
DhBlockPos2D maxPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeLeafDetailLevel + ", max detail level: " + this.treeRootDetailLevel + ". Given Position: [" + DhSectionPos.toString(pos) + "] = block pos: " + DhSectionPos.convertToDetailLevel(pos, LodUtil.BLOCK_DETAIL_LEVEL));
// how should out-of-bounds positions be handled?
if (throwIfOutOfBounds)
{
int radius = this.diameterInBlocks() / 2;
DhBlockPos2D minBlockPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius));
DhBlockPos2D maxBlockPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius));
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min block pos: [" + minBlockPos + "], max block pos: [" + maxBlockPos + "], leaf detail level: [" + this.treeLeafDetailLevel + "], root detail level: [" + this.treeRootDetailLevel + "]. Requested section pos: [" + DhSectionPos.toString(pos) + "].");
}
else
{
return null;
}
}
@@ -278,46 +302,6 @@ public class QuadTree<T>
removedItemConsumer.accept(quadNode.value);
}
});
// // remove out of bound nodes and clean up empty nodes
// // Note: this will iterate over a lot of unnecessary nodes, hopefully speed won't be an issue
// Iterator<DhSectionPos> rootNodePosIterator = this.rootNodePosIterator();
// while (rootNodePosIterator.hasNext())
// {
// // get the root node (regular nodeIterators won't return them if they are out of bounds)
// DhSectionPos rootPos = rootNodePosIterator.next();
// QuadNode<T> rootNode = this.getOrSetNode(rootPos, false, null, false);
// if (rootNode == null)
// {
// continue;
// }
//
// // remove any child nodes that are out of bounds
// Iterator<QuadNode<T>> nodeIterator = this.nodeIterator();
// while (nodeIterator.hasNext())
// {
// QuadNode<T> node = nodeIterator.next();
// if(!this.isSectionPosInBounds(node.sectionPos))
// {
// // node is out of bounds
//
// // FIXME(?) this appears to potentially return large nodes that are partially or entirely in bounds
//
// if (node.getNonNullChildCount() == 0)
// {
// // no child nodes, can be safely removed
// nodeIterator.remove();
// }
// else
// {
// // node can't be removed, but its value can be set to null
// node.value = null;
// }
// }
// }
// }
}
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
@@ -544,7 +528,9 @@ public class QuadTree<T>
&& this.rootNodeIterator.hasNext())
{
long sectionPos = this.rootNodeIterator.nextLong();
QuadNode<T> rootNode = QuadTree.this.getNode(sectionPos);
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
if (rootNode != null)
{
nodeIterator = this.onlyReturnLeaves ? rootNode.getLeafNodeIterator() : rootNode.getNodeIterator(this.stopIteratingFunc);
@@ -30,7 +30,7 @@ import java.util.function.Consumer;
public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
{
/** lowest numerical value, inclusive */
private final byte highestDetailLevel;
private final byte leafDetailLevel;
private final Queue<QuadNode<T>> validNodesForDetailLevel = new ArrayDeque<>();
@@ -48,8 +48,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
{
this.onlyReturnLeafValues = onlyReturnLeafValues;
this.stopIteratingFunc = stopIteratingFunc;
// TODO the naming conversion for these are flipped in a lot of places
this.highestDetailLevel = rootNode.parentTreeLeafDetailLevel;
this.leafDetailLevel = rootNode.parentTreeLeafDetailLevel;
this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos);
@@ -110,9 +109,9 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
@Override
public QuadNode<T> next()
{
if (this.iteratorDetailLevel < this.highestDetailLevel)
if (this.iteratorDetailLevel < this.leafDetailLevel)
{
throw new NoSuchElementException("Highest detail level reached [" + this.highestDetailLevel + "].");
throw new NoSuchElementException("Leaf detail level reached [" + this.leafDetailLevel + "].");
}
if (this.iteratorNodeQueue.size() == 0)
{
@@ -133,7 +132,7 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
this.iteratorDetailLevel--;
// only continue if we can go down farther
if (this.iteratorDetailLevel >= this.highestDetailLevel)
if (this.iteratorDetailLevel >= this.leafDetailLevel)
{
Queue<QuadNode<T>> parentNodes = new LinkedList<>(this.validNodesForDetailLevel);
this.validNodesForDetailLevel.clear();
@@ -69,10 +69,14 @@ public class ThreadPoolUtil
@Nullable
public static ThreadPoolExecutor getBeaconCullingExecutor() { return beaconCullingThreadPool; }
// The main distinction between these thread pools is that one for compression has multiple threads and client handler is single-threaded
private static PriorityTaskPicker.Executor networkCompressionThreadPool;
@Nullable
public static PriorityTaskPicker.Executor getNetworkCompressionExecutor() { return networkCompressionThreadPool; }
private static ThreadPoolExecutor networkClientHandlerThreadPool;
@Nullable
public static ThreadPoolExecutor networkClientHandlerExecutor() { return networkClientHandlerThreadPool; }
public static final String FULL_DATA_MIGRATION_THREAD_NAME = "Full Data Migration";
@@ -103,7 +107,8 @@ public class ThreadPoolUtil
}
taskPicker = new PriorityTaskPicker();
networkCompressionThreadPool = taskPicker.createExecutor("Network");
networkCompressionThreadPool = taskPicker.createExecutor("Network Compression");
networkClientHandlerThreadPool = ThreadUtil.makeSingleThreadPool("Network Client Handler");
fileHandlerThreadPool = taskPicker.createExecutor("IO");
renderSectionLoadThreadPool = taskPicker.createExecutor("Render Loader");
chunkToLodBuilderThreadPool = taskPicker.createExecutor("LOD Builder");
@@ -133,6 +138,7 @@ public class ThreadPoolUtil
public static void shutdownThreadPools()
{
// standalone threads
networkClientHandlerThreadPool.shutdownNow();
taskPicker.shutdownNow();
beaconCullingThreadPool.shutdown();
fullDataMigrationThreadPool.shutdown();
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.level.DhClientServerLevel;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -82,7 +83,7 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
LOGGER.fatal("Failed to load client-server level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
"\u00A7c" + "Distant Horizons: ClientServer level loading failed." + "\u00A7r \n" +
MinecraftTextFormat.RED + "Distant Horizons: ClientServer level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Unable to load level ["+levelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
@@ -94,9 +95,9 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
"\u00A7c" + "Distant Horizons: Client level loading failed." + "\u00A7r \n" +
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.level.DhServerLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
@@ -62,9 +63,9 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
{
LOGGER.fatal("Failed to load server level, error: ["+e.getMessage()+"].", e);
ClientApi.INSTANCE.showChatMessageNextFrame(// red text
"\u00A7c" + "Distant Horizons: Server level loading failed." + "\u00A7r \n" +
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.RED + "Distant Horizons: Server level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information.");
return null;
}
@@ -227,6 +227,10 @@
"Beacon render height",
"distanthorizons.config.client.advanced.graphics.genericRendering.beaconRenderHeight.@tooltip":
"Sets the maximum height at which beacons will render. \nThis will only affect new beacons coming into LOD render distance. \nBeacons currently visible in LOD chunks will not be affected.",
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons":
"Expand Distant Beacons",
"distanthorizons.config.client.advanced.graphics.genericRendering.expandDistantBeacons.@tooltip":
"If true LOD beacon beams will be rendered wider at extreme distances, \nmaking them easier to see. \nIf false all LOD beacon beams will only ever be 1 block wide.",
"distanthorizons.config.client.advanced.graphics.genericRendering.enableBeaconRendering.@tooltip":
"If true LOD beacon beams will be rendered.",
"distanthorizons.config.client.advanced.graphics.genericRendering.enableCloudRendering":
@@ -759,6 +763,11 @@
"distanthorizons.config.server.levelKeyPrefix.@tooltip":
"Prefix of the level keys sent to the clients.\nIf the mod is running behind a proxy, each backend should use a unique value.\nIf this value is empty, level key will be based on the server's seed hash.",
"distanthorizons.config.server.enableServerGeneration":
"Enable Server Generation",
"distanthorizons.config.server.enableServerGeneration.@tooltip":
"When enabled, Distant Horizons will attempt to download missing LODs from the server.\n\nNote: the server must have Distant Generation enabled for it to work.",
"distanthorizons.config.server.generationRequestRateLimit":
"Rate Limit for Generation Requests",
"distanthorizons.config.server.generationRequestRateLimit.@tooltip":
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB