Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into NSizedMultiplayerTest

This commit is contained in:
s809
2024-12-02 21:17:46 +05:00
30 changed files with 329 additions and 155 deletions
@@ -25,6 +25,7 @@ import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.renderer.FadeRenderer;
@@ -355,6 +356,11 @@ public class ClientApi
// networking //
//============//
/**
* Forwards a decoded message into the registered handlers.
*
* @see MessageRegistry
*/
public void pluginMessageReceived(@NotNull AbstractNetworkMessage message)
{
NetworkSession networkSession = this.pluginChannelApi.networkSession;
@@ -430,7 +436,9 @@ public class ClientApi
try
{
if (!RenderUtil.shouldLodsRender(levelWrapper))
// TODO write this message to the F3 menu so people can see when a different mod screws with the lightmap
String reasonLodsCannotRender = RenderUtil.shouldLodsRender(levelWrapper, renderEventParam);
if (reasonLodsCannotRender != null)
{
return;
}
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.world.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
@@ -186,6 +187,11 @@ public class ServerApi
}
}
/**
* Forwards a decoded message into the registered handlers.
*
* @see MessageRegistry
*/
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
{
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
@@ -33,7 +33,6 @@ 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.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
@@ -43,7 +42,6 @@ import java.awt.*;
import java.io.File;
import java.util.*;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
/**
* This handles any configuration the user has access to. <br><br>
@@ -1514,23 +1512,9 @@ public class Config
}
public static class Server
public static class Server
{
public static ConfigEntry<Integer> realTimeUpdateDistanceRadiusInChunks = new ConfigEntry.Builder<Integer>()
.setServersideShortName("renderDistanceRadius")
.setMinDefaultMax(32, 256, 4096)
.comment("" +
"Defines the distance players will receive real-time updates for if enabled. \n" +
"\n" +
"Note: \n" +
"This setting does not prevent players from generating farther out. \n" +
"If you want to limit performance impact, change rate limits \n" +
"and thread count/runtime ratio settings instead. \n" +
"It also does not affect the visuals on clients. \n" +
"")
.setPerformance(EConfigEntryPerformance.HIGH)
.build();
// Level keys
public static ConfigEntry<Boolean> sendLevelKeys = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("sendLevelKeys")
.setAppearance(EConfigEntryAppearance.ONLY_IN_FILE)
@@ -1551,6 +1535,8 @@ public class Config
+ "")
.build();
// Generation
public static ConfigEntry<Integer> generationRequestRateLimit = new ConfigEntry.Builder<Integer>()
.setServersideShortName("generationRequestRateLimit")
.setMinDefaultMax(1, 20, 100)
@@ -1560,6 +1546,17 @@ public class Config
+ "")
.build();
public static ConfigEntry<Integer> maxGenerationRequestDistance = new ConfigEntry.Builder<Integer>()
.setServersideShortName("maxGenerationRequestDistance")
.setMinDefaultMax(256, 4096, 4096)
.comment("" +
"Defines the distance allowed to generate around the player." +
"")
.setPerformance(EConfigEntryPerformance.HIGH)
.build();
// Real-time updates
public static ConfigEntry<Boolean> enableRealTimeUpdates = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("enableRealTimeUpdates")
.set(true)
@@ -1568,7 +1565,17 @@ public class Config
+ "")
.build();
public static ConfigEntry<Integer> realTimeUpdateDistanceRadiusInChunks = new ConfigEntry.Builder<Integer>()
.setServersideShortName("realTimeUpdateDistanceRadius")
.setMinDefaultMax(32, 256, 4096)
.comment("" +
"Defines the distance the player will receive updates around." +
"")
.setPerformance(EConfigEntryPerformance.HIGH)
.build();
// Sync on load
public static ConfigEntry<Boolean> synchronizeOnLoad = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("synchronizeOnLoad")
.set(true)
@@ -1586,6 +1593,18 @@ public class Config
+ "")
.build();
public static ConfigEntry<Integer> maxSyncOnLoadRequestDistance = new ConfigEntry.Builder<Integer>()
.setServersideShortName("maxSyncOnLoadRequestDistance")
.setMinDefaultMax(256, 4096, 4096)
.comment("" +
"Defines the distance allowed to be synchronized around the player. \n" +
"Should be the same or larger than maxGenerationRequestDistance in most cases." +
"")
.setPerformance(EConfigEntryPerformance.HIGH)
.build();
// Common
public static ConfigEntry<Integer> maxDataTransferSpeed = new ConfigEntry.Builder<Integer>()
.setServersideShortName("maxDataTransferSpeed")
.setMinDefaultMax(0, 500, 1000000 /* 1 GB/s */)
@@ -54,9 +54,9 @@ public class ConfigChangeListener<T> implements IConfigListener, Closeable
public void onConfigValueSet()
{
T newValue = this.configEntry.get();
if (newValue != previousValue)
if (newValue != this.previousValue)
{
previousValue = newValue;
this.previousValue = newValue;
this.onValueChangeFunc.accept(newValue);
}
}
@@ -33,6 +33,7 @@ public abstract class AbstractConfigType<T, S>
public String category = ""; // This should only be set once in the init
public String name; // This should only be set once in the init
protected final T defaultValue;
protected final boolean isFloatingPointNumber;
protected T value;
public ConfigBase configBase;
@@ -40,54 +41,45 @@ public abstract class AbstractConfigType<T, S>
protected EConfigEntryAppearance appearance;
protected AbstractConfigType(EConfigEntryAppearance appearance, T value)
//=============//
// constructor //
//=============//
protected AbstractConfigType(EConfigEntryAppearance appearance, T defaultValue)
{
this.defaultValue = value;
this.value = value;
this.defaultValue = defaultValue;
this.value = defaultValue;
this.appearance = appearance;
Class<?> defaultValueClass = defaultValue.getClass();
this.isFloatingPointNumber = (defaultValueClass == Double.class || defaultValueClass == Float.class);
}
//=========//
// methods //
//=========//
/** Gets the value */
public T get()
{
return this.value;
}
public T get() { return this.value; }
/** Sets the value */
public void set(T newValue)
{
this.value = newValue;
}
public void set(T newValue) { this.value = newValue; }
public EConfigEntryAppearance getAppearance()
{
return appearance;
}
public void setAppearance(EConfigEntryAppearance newAppearance)
{
this.appearance = newAppearance;
}
public EConfigEntryAppearance getAppearance() { return this.appearance; }
public void setAppearance(EConfigEntryAppearance newAppearance) { this.appearance = newAppearance; }
public String getCategory()
{
return this.category;
}
public String getName()
{
return this.name;
}
public String getNameWCategory()
{
return (this.category.isEmpty() ? "" : this.category + ".") + this.name;
}
public String getCategory() { return this.category; }
public String getName() { return this.name; }
public String getNameWCategory() { return (this.category.isEmpty() ? "" : this.category + ".") + this.name; }
// Gets the class of T
public Class<?> getType()
{
return this.defaultValue.getClass();
}
/** Gets the class of T */
public Class<?> getType() { return this.defaultValue.getClass(); }
public boolean typeIsFloatingPointNumber() { return this.isFloatingPointNumber; }
protected static abstract class Builder<T, S>
{
@@ -275,7 +275,11 @@ public class ColumnRenderBufferBuilder
// the old logic handled additional cases, but they never appeared to fire,
// so just these two cases should be fine
LodUtil.assertTrue(adjDetailLevel == thisDetailLevel || adjDetailLevel > thisDetailLevel);
boolean expectedDetailLevels = (adjDetailLevel == thisDetailLevel) || (adjDetailLevel > thisDetailLevel);
if (!expectedDetailLevels)
{
LodUtil.assertNotReach("Mismatch between adjacent detail level ["+adjDetailLevel+"] and this render source's detail level ["+thisDetailLevel+"]. Detail levels should be adj >= this.");
}
adjColumnViews[lodDirection.ordinal() - 2] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj);
}
@@ -246,6 +246,10 @@ public class DhLightingEngine
// block light
if (updateBlockLight)
{
// done to prevent a rare issue where the light values are incorrectly set to -1
// TODO why could that happen?
centerChunk.clearDhBlockLighting();
this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
@@ -255,6 +259,8 @@ public class DhLightingEngine
// sky light
if (updateSkyLight)
{
centerChunk.clearDhSkyLighting();
this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder,
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()),
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue),
@@ -81,6 +81,8 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
@Override
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getGenerationRequestRateLimit(); }
@Override
protected int getMaxRequestDistance() { return this.networkState.sessionConfig.getMaxGenerationRequestDistance(); }
@Override
protected String getQueueName() { return "World Remote Generation Queue"; }
@@ -9,7 +9,8 @@ import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.multiplayer.server.FullDataSourceRequestHandler;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
import com.seibel.distanthorizons.core.network.messages.ILevelRelatedMessage;
@@ -138,16 +139,29 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
return;
}
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this);
LOGGER.info("received message ["+DhSectionPos.toString(message.sectionPos)+"]");
if (message.clientTimestamp == null)
{
if (distanceFromPlayer > Config.Server.maxGenerationRequestDistance.get())
{
message.sendResponse(new RequestOutOfRangeException("Distance too large: " + distanceFromPlayer + " > " + Config.Server.maxGenerationRequestDistance.get()));
return;
}
this.requestHandler.queueWorldGenForRequestMessage(serverPlayerState, message, rateLimiterSet);
}
else
{
if (distanceFromPlayer > Config.Server.maxSyncOnLoadRequestDistance.get())
{
message.sendResponse(new RequestOutOfRangeException("Distance too large: " + distanceFromPlayer + " > " + Config.Server.maxSyncOnLoadRequestDistance.get()));
return;
}
this.requestHandler.queueLodSyncForRequestMessage(serverPlayerState, message, rateLimiterSet);
}
});
@@ -184,7 +198,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
if (message instanceof AbstractTrackableMessage)
{
((AbstractTrackableMessage) message).sendResponse(
new InvalidLevelException(
new RequestRejectedException(
"Generation not allowed. " +
"Requested dimension: ["+((ILevelRelatedMessage) message).getLevelName()+"], " +
"player dimension: [" + message.getSession().serverPlayer.getLevel().getDhIdentifier() + "], " +
@@ -257,7 +271,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
}
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer >= serverPlayerState.getServerPlayer().getViewDistance()
&& distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
{
@@ -191,7 +191,7 @@ public class WorldGenModule implements Closeable
String waitingCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getWaitingTaskCount());
String inProgressCountStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getInProgressTaskCount());
String totalCountEstimateStr = F3Screen.NUMBER_FORMAT.format(worldGenState.worldGenerationQueue.getEstimatedTotalTaskCount());
messageList.add("World Gen Tasks: ${waitingCountStr}/${totalCountEstimateStr} (in progress: ${inProgressCountStr})");
messageList.add("World Gen Tasks: "+waitingCountStr+"/"+totalCountEstimateStr+" (in progress "+inProgressCountStr+")");
worldGenState.worldGenerationQueue.addDebugMenuStringsToList(messageList);
}
@@ -20,12 +20,16 @@
package com.seibel.distanthorizons.core.logging.f3;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.jar.ModJarInfo;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.DependencyInjector;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import org.apache.logging.log4j.LogManager;
@@ -38,6 +42,7 @@ import java.util.concurrent.ThreadPoolExecutor;
public class F3Screen
{
private static final Logger LOGGER = LogManager.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final NumberFormat NUMBER_FORMAT = NumberFormat.getIntegerInstance();
@@ -88,6 +93,11 @@ public class F3Screen
{
messageList.add("Build: " + StringUtil.shortenString(ModJarInfo.Git_Commit, 8) + " (" + ModJarInfo.Git_Branch + ")");
}
if (MC_CLIENT != null)
{
// player pos
messageList.add("LOD Pos: " + DhSectionPos.toString(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, MC_CLIENT.getPlayerChunkPos())) );
}
messageList.add("");
// thread pools
messageList.add(getThreadPoolStatString("World Gen", worldGenPool));//"World Gen Tasks: 40/5304, (in progress: 7)");
@@ -7,8 +7,8 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException;
import com.seibel.distanthorizons.core.network.session.SessionClosedException;
@@ -97,6 +97,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
//==================//
protected abstract int getRequestRateLimit();
protected abstract int getMaxRequestDistance();
protected abstract String getQueueName();
@@ -182,7 +183,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
Map.Entry<Long, RequestQueueEntry> mapEntry = this.waitingTasksBySectionPos
.entrySet().stream()
.filter(task -> task.getValue().networkDataSourceFuture == null)
.min(Comparator.comparingInt(x -> posDistance(targetPos, x.getKey())))
.min(Comparator.comparingInt(x -> posDistanceSquared(targetPos, x.getKey())))
.orElse(null);
if (mapEntry == null)
@@ -194,6 +195,13 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
long sectionPos = mapEntry.getKey();
RequestQueueEntry entry = mapEntry.getValue();
if (DhSectionPos.getChebyshevSignedBlockDistance(sectionPos, targetPos) > this.getMaxRequestDistance() * 16)
{
entry.future.cancel(false);
this.pendingTasksSemaphore.release();
return;
}
Long offsetEntryTimestamp = entry.updateTimestamp != null
? entry.updateTimestamp + this.networkState.getServerTimeOffset()
: null;
@@ -249,16 +257,15 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
{
return entry.future.complete(RequestResult.REQUIRES_SPLITTING);
}
catch (InvalidLevelException | RequestRejectedException ignored)
{
// We're too late / some cases might trigger a bunch of expected rejections
return entry.future.complete(RequestResult.FAILED);
}
catch (SessionClosedException | CancellationException ignored)
{
// Triggered when level is unloaded
return entry.future.cancel(false);
}
catch (RequestRejectedException e)
{
LOGGER.info("Request rejected by the server: " + e.getMessage());
return entry.future.complete(RequestResult.FAILED);
}
catch (RateLimitedException e)
{
LOGGER.warn("Rate limited by server, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage());
@@ -269,6 +276,13 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
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--;
@@ -381,7 +395,9 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
for (Map.Entry<Long, RequestQueueEntry> mapEntry : this.waitingTasksBySectionPos.entrySet())
{
renderer.renderBox(new DebugRenderer.Box(mapEntry.getKey(), -32f, 64f, 0.05f,
mapEntry.getValue().networkDataSourceFuture != null ? Color.red : Color.gray
mapEntry.getValue().networkDataSourceFuture != null ? Color.red
: DhSectionPos.getChebyshevSignedBlockDistance(mapEntry.getKey(), Objects.requireNonNull(this.level.getTargetPosForGeneration())) <= this.getMaxRequestDistance() * 16 ? Color.gray
: Color.darkGray
));
}
}
@@ -392,10 +408,8 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
// helper methods //
//================//
protected static int posDistance(DhBlockPos2D targetPos, long pos)
{
return DhSectionPos.signedDistance(pos, targetPos);
}
protected static int posDistanceSquared(DhBlockPos2D targetPos, long pos)
{ return (int) DhSectionPos.getCenterBlockPos(pos).distSquared(targetPos); }
@@ -82,7 +82,7 @@ public class ClientNetworkState implements Closeable
}
this.serverTimeOffset = message.serverTime - System.currentTimeMillis();
LOGGER.info("Server time offset: [${this.serverTimeOffset}] ms");
LOGGER.info("Server time offset: ["+this.serverTimeOffset+"] ms");
});
this.networkSession.registerHandler(CloseInternalEvent.class, message ->
@@ -31,6 +31,8 @@ public class SyncOnLoadRequestQueue extends AbstractFullDataNetworkRequestQueue
@Override
protected int getRequestRateLimit() { return this.networkState.sessionConfig.getSyncOnLoginRateLimit(); }
@Override
protected int getMaxRequestDistance() { return this.networkState.sessionConfig.getMaxSyncOnLoadDistance(); }
@Override
protected String getQueueName() { return "Sync On Login Queue"; }
@@ -31,14 +31,15 @@ public class SessionConfig implements INetworkObject
{
// Note: config values are transmitted in the insertion order
registerConfigEntry(Config.Server.realTimeUpdateDistanceRadiusInChunks, Math::min);
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, (x, y) -> x && y);
registerConfigEntry(Config.Common.WorldGenerator.enableDistantGeneration, Boolean::logicalAnd);
registerConfigEntry(Config.Server.maxGenerationRequestDistance, Math::min);
registerConfigEntry(Config.Server.generationRequestRateLimit, Math::min);
registerConfigEntry(Config.Server.enableRealTimeUpdates, (x, y) -> x && y);
registerConfigEntry(Config.Server.enableRealTimeUpdates, Boolean::logicalAnd);
registerConfigEntry(Config.Server.realTimeUpdateDistanceRadiusInChunks, Math::min);
registerConfigEntry(Config.Server.synchronizeOnLoad, (x, y) -> x && y);
registerConfigEntry(Config.Server.synchronizeOnLoad, Boolean::logicalAnd);
registerConfigEntry(Config.Server.maxSyncOnLoadRequestDistance, Math::min);
registerConfigEntry(Config.Server.syncOnLoadRateLimit, Math::min);
registerConfigEntry(Config.Server.maxDataTransferSpeed, (x, y) -> {
@@ -62,12 +63,17 @@ public class SessionConfig implements INetworkObject
// public values //
//===============//
public int getMaxUpdateDistanceRadius() { return this.getValue(Config.Server.realTimeUpdateDistanceRadiusInChunks); }
public boolean isDistantGenerationEnabled() { return this.getValue(Config.Common.WorldGenerator.enableDistantGeneration); }
public int getMaxGenerationRequestDistance() { return this.getValue(Config.Server.maxGenerationRequestDistance); }
public int getGenerationRequestRateLimit() { return this.getValue(Config.Server.generationRequestRateLimit); }
public boolean isRealTimeUpdatesEnabled() { return this.getValue(Config.Server.enableRealTimeUpdates); }
public int getMaxUpdateDistanceRadius() { return this.getValue(Config.Server.realTimeUpdateDistanceRadiusInChunks); }
public boolean getSynchronizeOnLoad() { return this.getValue(Config.Server.synchronizeOnLoad); }
public int getMaxSyncOnLoadDistance() { return this.getValue(Config.Server.maxSyncOnLoadRequestDistance); }
public int getSyncOnLoginRateLimit() { return this.getValue(Config.Server.syncOnLoadRateLimit); }
public int getMaxDataTransferSpeed() { return this.getValue(Config.Server.maxDataTransferSpeed); }
@@ -78,7 +84,14 @@ public class SessionConfig implements INetworkObject
private static <T> void registerConfigEntry(ConfigEntry<T> configEntry, BiFunction<T, T, T> valueConstrainer)
{
CONFIG_ENTRIES.put(Objects.requireNonNull(configEntry.getServersideShortName()), new Entry(configEntry, valueConstrainer));
CONFIG_ENTRIES.compute(Objects.requireNonNull(configEntry.getServersideShortName()), (key, existingEntry) -> {
if (existingEntry != null)
{
throw new IllegalArgumentException("Attempted to register config entry with duplicate serversideShortName: " + key);
}
return new Entry(configEntry, valueConstrainer);
});
}
@@ -19,8 +19,9 @@
package com.seibel.distanthorizons.core.network.exceptions;
/** Fired if a user attempts to run an operation in a level they aren't currently in. */
public class InvalidLevelException extends Exception
/** Fired if the client attempts to request an LOD out of allowed range. */
public class RequestOutOfRangeException extends Exception
{
public InvalidLevelException(String message) { super(message); }
public RequestOutOfRangeException(String message) { super(message); }
}
@@ -12,7 +12,8 @@ public class LevelInitMessage extends AbstractNetworkMessage
// prefix@namespace:path
// 1-150 characters in total, all parts except namespace can be omitted
public static final String VALIDATION_REGEX = "^(?=.{1,$MAX_LENGTH}$)([$PART_ALLOWED_CHARS_REGEX]+@)?[$PART_ALLOWED_CHARS_REGEX]+(:[$PART_ALLOWED_CHARS_REGEX]+)?$";
public static final String VALIDATION_REGEX = String.format("^(?=.{1,%s}$)([%s]+@)?[%s]+(:[%s]+)?$",
+ MAX_LENGTH, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX, PART_ALLOWED_CHARS_REGEX);
public String levelKey;
@@ -20,8 +20,8 @@
package com.seibel.distanthorizons.core.network.messages.requests;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.exceptions.InvalidLevelException;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException;
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
@@ -37,7 +37,7 @@ public class ExceptionMessage extends AbstractTrackableMessage
{{
// All exceptions here must include constructor: (String)
this.add(RateLimitedException.class);
this.add(InvalidLevelException.class);
this.add(RequestOutOfRangeException.class);
this.add(RequestRejectedException.class);
this.add(SectionRequiresSplittingException.class);
}};
@@ -261,12 +261,18 @@ public class DhSectionPos
+ Math.abs(getCenterBlockPosZ(pos) - blockPos.z);
}
public static int getChebyshevBlockDistance(long pos, DhBlockPos2D blockPos)
/**
* Returns the signed distance from a given block to a given section. <br>
* Essentially acts like a distance from the block to the nearest edge of the section,
* except inside the section it's negative. <br>
* Useful for detail level insensitive distance comparisons.
*/
public static int getChebyshevSignedBlockDistance(long pos, DhBlockPos2D blockPos)
{
return Math.max(
Math.abs(getCenterBlockPosX(pos) - blockPos.x),
Math.abs(getCenterBlockPosZ(pos) - blockPos.z)
);
) - getBlockWidth(pos) / 2;
}
public static int signedDistance(long pos, DhBlockPos2D blockPos)
@@ -345,9 +345,15 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
// prepare this section for rendering
if (!renderSection.gpuUploadInProgress()
&& renderSection.renderBuffer == null
// this check is specifically for N-sized world generators where the higher quality
// data source may not exist yet
&& renderSection.getFullDataSourceExists())
&&
(
// 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()
// if we can't request generation we don't want to check for full data existing
// since that will prevent server LODs from loading high-detail LODs where quadrants haven't been generated.
|| !this.fullDataSourceProvider.canQueueRetrieval())
)
{
nodesNeedingLoading.add(renderSection);
}
@@ -424,7 +430,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
if (renderSection != null)
{
// this data source may now exist
renderSection.updateFullDataSourceExists();
renderSection.updateFullDataSourceExists();
if (renderSection.canRender())
{
@@ -272,7 +272,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
}
this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, this.pos, lodQuadBuilder);
return this.bufferUploadFuture.thenCompose((buffer) ->
return this.bufferUploadFuture.thenAccept((buffer) ->
{
// needed to clean up the old data
ColumnRenderBuffer previousBuffer = this.renderBuffer;
@@ -286,8 +286,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
previousBuffer.close();
}
return null;
});
}
@@ -336,9 +334,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// use the already loading future if one is present
ReferencedRenderSourceFutureWrapper oldFuture = this.renderSourceLoadingRefFuture;
if (oldFuture != null)
if (oldFuture != null && oldFuture.tryIncrementRefCount())
{
oldFuture.incrementRefCount();
return oldFuture;
}
@@ -490,7 +487,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{
// shouldn't normally happen, but just in case
this.missingGenerationPos.add(pos);
break;
}
}
}
@@ -644,23 +640,49 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public ReferencedRenderSourceFutureWrapper(CompletableFuture<ColumnRenderSource> future) { this.future = future; }
public void incrementRefCount() { this.refCount.incrementAndGet(); }
/** @return true if this {@link ReferencedRenderSourceFutureWrapper} can be acquired, false if it has already been freed */
public boolean tryIncrementRefCount()
{
// synchronized to prevent incrementing/decrementing at the same time
synchronized (this.refCount)
{
int refCount = this.refCount.get();
if (refCount <= 0)
{
// there are no references to this data source and it has been returned to the pool
// a new reference will be needed
return false;
}
else
{
// at least one other
this.refCount.getAndIncrement();
return true;
}
}
}
public void decrementRefCount()
{
int refCount = this.refCount.decrementAndGet();
if (refCount <= 0)
// synchronized to prevent incrementing/decrementing at the same time
synchronized (this.refCount)
{
// this render section should only be released once
if (refCount < 0)
int refCount = this.refCount.decrementAndGet();
if (refCount <= 0)
{
LodUtil.assertNotReach("ReferencedRenderSourceFutureWrapper was released more than once! Ref Count ["+refCount+"].");
}
// return data source to the pool
ColumnRenderSource source = this.future.getNow(null);
if (source != null)
{
ColumnRenderSource.DATA_SOURCE_POOL.returnPooledDataSource(source);
// this should only be released once
if (refCount < 0)
{
LodUtil.assertNotReach("ReferencedRenderSourceFutureWrapper was released more than once! Ref Count [" + refCount + "].");
}
// return data source to the pool
ColumnRenderSource source = this.future.getNow(null);
if (source != null)
{
ColumnRenderSource.DATA_SOURCE_POOL.returnPooledDataSource(source);
}
}
}
}
@@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiDataCompressionMode;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
import com.seibel.distanthorizons.core.util.objects.dataStreams.DhDataInputStream;
import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -34,6 +35,7 @@ import java.io.File;
import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -311,6 +313,10 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
List<Map<String, Object>> row = this.queryDictionary(preparedStatement);
return !row.isEmpty() ? (Long) row.get(0).get("LastModifiedUnixDateTime") : null;
}
catch (DbConnectionClosedException e)
{
return null;
}
catch (SQLException e)
{
throw new RuntimeException(e);
@@ -342,6 +348,10 @@ public class FullDataSourceV2Repo extends AbstractDhRepo<Long, FullDataSourceV2D
row -> (long) row.get("LastModifiedUnixDateTime"))
);
}
catch (DbConnectionClosedException e)
{
return new HashMap<>();
}
catch (SQLException e)
{
throw new RuntimeException(e);
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.util;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
@@ -164,37 +165,43 @@ public class RenderUtil
return -1.0f;
}
/** @return false if LODs shouldn't be rendered for any reason */
public static boolean shouldLodsRender(ILevelWrapper levelWrapper)
/** @return a message if LODs shouldn't be rendered, null if the LODs can render */
public static String shouldLodsRender(ILevelWrapper levelWrapper, DhApiRenderParam renderEventParam)
{
if (!MC.playerExists())
{
return false;
return "No Player Exists";
}
if (levelWrapper == null)
{
return false;
return "No Level Given";
}
IDhClientWorld clientWorld = SharedApi.getIDhClientWorld();
if (clientWorld == null)
{
return false;
return "No Client World Loaded";
}
IDhClientLevel level = clientWorld.getClientLevel(levelWrapper);
if (level == null)
{
return false; //Level is not ready yet.
return "No Client Level Loaded"; //Level is not ready yet.
}
if (MC_RENDER.getLightmapWrapper(levelWrapper) == null)
{
return false;
return "No Lightmap loaded";
}
return true;
if (renderEventParam.dhModelViewMatrix == null
|| renderEventParam.mcModelViewMatrix == null)
{
return "No MVM or Proj Matrix Given";
}
return null;
}
}
@@ -69,7 +69,7 @@ public abstract class AbstractDhWorld implements IDhWorld, Closeable
readOnlyStr += " - ReadOnly";
}
String message = "${environment} World with ${levelCountStr} levels${readOnlyStr}";
String message = environment+" World with "+levelCountStr+" levels"+readOnlyStr;
messageList.add(message);
}
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.world;
import com.google.common.io.BaseEncoding;
import com.google.common.primitives.Longs;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
@@ -30,7 +32,6 @@ import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindab
/** Can be either a Server world or a Client world. */
public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
{
@Override
IDimensionTypeWrapper getDimensionType();
@@ -38,12 +39,21 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
String getDimensionName();
long getHashedSeed();
/**
* Returns the result of {@link #getHashedSeed()}, encoded into a short string. <br>
* Prefer using this method over stringifying the number directly.
*/
default String getHashedSeedEncoded()
{
String encoded = BaseEncoding.base32Hex().encode(Longs.toByteArray(this.getHashedSeed()));
return encoded.substring(0, 13).toLowerCase(); // Remaining 3 chars are padding
}
/**
* A string intended to uniquely identify this level.
*/
@Override
default String getDhIdentifier() { return this.getDimensionName() + "_" + this.getHashedSeed(); }
@Override
String getDhIdentifier();
@Override
boolean hasCeiling();
@@ -45,12 +45,12 @@ public interface IServerLevelWrapper extends ILevelWrapper
.replaceAll(" ", "_");
levelKeyPrefix += (!levelKeyPrefix.isEmpty() ? "_" : "") + cleanWorldFolderName
+ "_" + this.getHashedSeed();
+ "_" + this.getHashedSeedEncoded();
}
if (levelKeyPrefix.isEmpty())
{
levelKeyPrefix = String.valueOf(this.getHashedSeed());
levelKeyPrefix = this.getHashedSeedEncoded();
}
String mainPart = "@" + dimensionName;
@@ -669,30 +669,51 @@
"Server",
"distanthorizons.config.server.realTimeUpdateDistanceRadiusInChunks":
"Realtime update Radius In Chunks",
"distanthorizons.config.server.realTimeUpdateDistanceRadiusInChunks.@tooltip":
"Defines the how far away players will receive real-time updates for if enabled.",
"distanthorizons.config.server.sendLevelKeys":
"Send Level Keys",
"distanthorizons.config.server.sendLevelKeys.@tooltip":
"Makes the server send level keys for each world.\nDisable this if you use alternative ways to send level keys.",
"distanthorizons.config.server.levelKeyPrefix":
"Level Key Prefix",
"distanthorizons.config.server.levelKeyPrefix.@tooltip":
"Prefix of the level keys sent to the clients.",
"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.generationRequestRateLimit":
"Rate Limit for Generation Requests",
"distanthorizons.config.server.generationRequestRateLimit.@tooltip":
"How many LOD generation requests per second should a client send? \nAlso limits the amount of player's requests allowed to stay in the server's queue.",
"How many LOD generation requests per second should a client send?\nAlso limits the number of client requests allowed to stay in the server's queue.",
"distanthorizons.config.server.maxGenerationRequestDistance":
"Max Generation Request Distance",
"distanthorizons.config.server.maxGenerationRequestDistance.@tooltip":
"Defines the distance allowed to generate around the player.",
"distanthorizons.config.server.enableRealTimeUpdates":
"Enable Real-time Updates",
"distanthorizons.config.server.enableRealTimeUpdates.@tooltip":
"If true, clients will receive real-time LOD updates for chunks outside the client's render distance.",
"distanthorizons.config.server.realTimeUpdateDistanceRadiusInChunks":
"Real-time Update Radius in Chunks",
"distanthorizons.config.server.realTimeUpdateDistanceRadiusInChunks.@tooltip":
"Defines the distance the player will receive updates around.",
"distanthorizons.config.server.synchronizeOnLoad":
"Synchronize LODs on Load",
"distanthorizons.config.server.synchronizeOnLoad.@tooltip":
"If true, clients will receive updated LODs on join if any changes occurred since last join.",
"If true, clients will receive updated LODs when joining or loading new LODs.",
"distanthorizons.config.server.syncOnLoadRateLimit":
"Rate Limit for Sync on Load",
"distanthorizons.config.server.syncOnLoadRateLimit.@tooltip":
"How many LOD sync requests per second should a client send? \nAlso limits the amount of player's requests allowed to stay in the server's queue.",
"How many LOD sync requests per second should a client send?\nAlso limits the number of client's requests allowed to stay in the server's queue.",
"distanthorizons.config.server.maxSyncOnLoadRequestDistance":
"Max Sync on Load Request Distance",
"distanthorizons.config.server.maxSyncOnLoadRequestDistance.@tooltip":
"Defines the distance allowed to be synchronized around the player.\nShould be the same or larger than maxGenerationRequestDistance in most cases.",
"distanthorizons.config.server.maxDataTransferSpeed":
"Maximum Data Transfer Speed, KB/s",
"distanthorizons.config.server.maxDataTransferSpeed.@tooltip":
+1 -1
View File
@@ -255,7 +255,7 @@ float calculateHeightFogDepth(float worldYPos)
else
{
// shouldn't happen,
return 0;
return 0.0;
}
}