WIP generation distance limiting
This commit is contained in:
@@ -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>
|
||||
@@ -1512,23 +1510,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)
|
||||
@@ -1549,6 +1533,8 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
|
||||
// Generation
|
||||
public static ConfigEntry<Integer> generationRequestRateLimit = new ConfigEntry.Builder<Integer>()
|
||||
.setServersideShortName("generationRequestRateLimit")
|
||||
.setMinDefaultMax(1, 20, 100)
|
||||
@@ -1558,6 +1544,23 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Integer> maxGenerationRequestDistance = new ConfigEntry.Builder<Integer>()
|
||||
.setServersideShortName("maxGenerationRequestDistance")
|
||||
.setMinDefaultMax(32, 1024, 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();
|
||||
|
||||
|
||||
// Real-time updates
|
||||
public static ConfigEntry<Boolean> enableRealTimeUpdates = new ConfigEntry.Builder<Boolean>()
|
||||
.setServersideShortName("enableRealTimeUpdates")
|
||||
.set(true)
|
||||
@@ -1566,7 +1569,23 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Integer> realTimeUpdateDistanceRadiusInChunks = new ConfigEntry.Builder<Integer>()
|
||||
.setServersideShortName("realTimeUpdateDistanceRadius")
|
||||
.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();
|
||||
|
||||
|
||||
// Sync on load
|
||||
public static ConfigEntry<Boolean> synchronizeOnLoad = new ConfigEntry.Builder<Boolean>()
|
||||
.setServersideShortName("synchronizeOnLoad")
|
||||
.set(true)
|
||||
@@ -1584,6 +1603,23 @@ public class Config
|
||||
+ "")
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<Integer> maxSyncOnLoadRequestDistance = new ConfigEntry.Builder<Integer>()
|
||||
.setServersideShortName("maxSyncOnLoadRequestDistance")
|
||||
.setMinDefaultMax(32, 1024, 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();
|
||||
|
||||
|
||||
// Common
|
||||
public static ConfigEntry<Integer> maxDataTransferSpeed = new ConfigEntry.Builder<Integer>()
|
||||
.setServersideShortName("maxDataTransferSpeed")
|
||||
.setMinDefaultMax(0, 500, 1000000 /* 1 GB/s */)
|
||||
|
||||
+2
@@ -64,6 +64,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"; }
|
||||
|
||||
+12
-3
@@ -9,7 +9,7 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
|
||||
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;
|
||||
@@ -194,6 +194,13 @@ 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;
|
||||
if (distanceFromPlayer > Config.Server.maxGenerationRequestDistance.get())
|
||||
{
|
||||
message.sendResponse(new RequestOutOfRangeException("Distance too large: " + distanceFromPlayer + " > " + Config.Server.maxGenerationRequestDistance.get()));
|
||||
return;
|
||||
}
|
||||
|
||||
ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this);
|
||||
|
||||
@@ -241,6 +248,8 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
|
||||
}
|
||||
private void queueLodSyncForRequestMessage(ServerPlayerState serverPlayerState, FullDataSourceRequestMessage message, ServerPlayerState.RateLimiterSet rateLimiterSet)
|
||||
{
|
||||
|
||||
|
||||
if (!serverPlayerState.sessionConfig.getSynchronizeOnLoad())
|
||||
{
|
||||
message.sendResponse(new RequestRejectedException("Operation is disabled in config."));
|
||||
@@ -350,7 +359,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() + "], " +
|
||||
@@ -446,7 +455,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())
|
||||
{
|
||||
|
||||
+23
-18
@@ -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.session.SessionClosedException;
|
||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
|
||||
@@ -71,6 +71,8 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
|
||||
private final SupplierBasedRateLimiter<Void> rateLimiter = new SupplierBasedRateLimiter<>(this::getRequestRateLimit);
|
||||
|
||||
private DhBlockPos2D lastTargetPos = new DhBlockPos2D(0, 0);
|
||||
|
||||
|
||||
|
||||
//=============//
|
||||
@@ -95,6 +97,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
//==================//
|
||||
|
||||
protected abstract int getRequestRateLimit();
|
||||
protected abstract int getMaxRequestDistance();
|
||||
|
||||
protected abstract String getQueueName();
|
||||
|
||||
@@ -138,6 +141,8 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
return false;
|
||||
}
|
||||
|
||||
this.lastTargetPos = targetPos;
|
||||
|
||||
// queue requests until the queue is full
|
||||
while (this.getInProgressTaskCount() < this.getWaitingTaskCount()
|
||||
&& this.getInProgressTaskCount() < this.getRequestRateLimit()
|
||||
@@ -158,7 +163,8 @@ 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 -> posDistanceSquared(targetPos, x.getKey())))
|
||||
.filter(task -> DhSectionPos.getChebyshevSignedBlockDistance(task.getKey(), targetPos) <= this.getMaxRequestDistance())
|
||||
.min(Comparator.comparingInt(task -> DhSectionPos.getChebyshevSignedBlockDistance(task.getKey(), targetPos)))
|
||||
.orElse(null);
|
||||
|
||||
if (mapEntry == null)
|
||||
@@ -220,16 +226,15 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
LodUtil.assertTrue(this.changedOnly, "Received empty data source response for not changes-only request");
|
||||
}
|
||||
}
|
||||
catch (InvalidLevelException | RequestRejectedException ignored)
|
||||
{
|
||||
// We're too late / some cases might trigger a bunch of expected rejections
|
||||
return entry.future.complete(false);
|
||||
}
|
||||
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(false);
|
||||
}
|
||||
catch (RateLimitedException e)
|
||||
{
|
||||
LOGGER.warn("Rate limited by server, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage());
|
||||
@@ -240,6 +245,13 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
||||
entry.networkDataSourceFuture = null;
|
||||
return null;
|
||||
}
|
||||
catch (RequestOutOfRangeException e)
|
||||
{
|
||||
LOGGER.warn("Out of range, re-queueing task [" + DhSectionPos.toString(sectionPos) + "]: " + e.getMessage());
|
||||
|
||||
entry.networkDataSourceFuture = null;
|
||||
return null;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
entry.retryAttempts--;
|
||||
@@ -352,22 +364,15 @@ 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(), this.lastTargetPos) <= this.getMaxRequestDistance() ? Color.gray
|
||||
: Color.lightGray
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
protected static int posDistanceSquared(DhBlockPos2D targetPos, long pos)
|
||||
{ return (int) DhSectionPos.getCenterBlockPos(pos).distSquared(targetPos); }
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
+2
@@ -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"; }
|
||||
|
||||
+20
-7
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
+4
-3
@@ -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); }
|
||||
|
||||
}
|
||||
+2
-2
@@ -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.messages.AbstractTrackableMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -36,7 +36,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);
|
||||
}};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user