diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index bae7689e6..d661ac736 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -907,6 +907,14 @@ public class Config + "") .build(); + public static ConfigEntry genTaskPriorityRequestRateLimit = new ConfigEntry.Builder() + .setServersideShortName("genTaskPriorityRequestRateLimit") + .setMinDefaultMax(1, 50, 200) + .comment("" + + "Limits the amount of LOD sections that the client can request states for, per second. \n" + + "") + .build(); + /** * Intentionally disabled. * @see #enablePostRelogUpdate diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java index 0fafb7e49..f6ccd4056 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldRemoteGenerationQueue.java @@ -112,7 +112,7 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug List posList = waitingTasks.entrySet().stream() .filter(task -> task.getValue().request == null && task.getValue().priority == 0) .sorted((x, y) -> posDistanceSquared(targetPos, x.getKey()) - posDistanceSquared(targetPos, y.getKey())) - .limit(this.networkState.config.fullDataRequestConcurrencyLimit) + .limit(this.networkState.config.genTaskPriorityRequestRateLimit) .map(Map.Entry::getKey) .collect(Collectors.toList()); if (posList.isEmpty()) { @@ -135,7 +135,7 @@ public class WorldRemoteGenerationQueue implements IWorldGenerationQueue, IDebug entry.priority = mapEntry.getValue(); } } - catch (ChannelException | CancellationException ignored) + catch (ChannelException | CancellationException | RateLimitedException ignored) { } catch (Throwable e) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java index 60b45d10e..f7d135db4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhServerLevel.java @@ -24,22 +24,18 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedF import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource; -import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile; import com.seibel.distanthorizons.core.file.fullDatafile.IFullDataSourceProvider; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState; import com.seibel.distanthorizons.core.multiplayer.server.RemotePlayerConnectionHandler; import com.seibel.distanthorizons.core.network.ScopedNetworkEventSource; import com.seibel.distanthorizons.core.network.NetworkServer; -import com.seibel.distanthorizons.core.network.exceptions.InvalidSectionPosException; import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException; import com.seibel.distanthorizons.core.network.messages.base.CancelMessage; import com.seibel.distanthorizons.core.network.messages.fullData.generation.FullDataSourceRequestMessage; import com.seibel.distanthorizons.core.network.messages.fullData.generation.FullDataSourceResponseMessage; import com.seibel.distanthorizons.core.network.messages.fullData.generation.priority.GenTaskPriorityRequestMessage; import com.seibel.distanthorizons.core.network.messages.fullData.generation.priority.GenTaskPriorityResponseMessage; -import com.seibel.distanthorizons.core.network.messages.fullData.updates.FullDataChangeSummaryRequestMessage; -import com.seibel.distanthorizons.core.network.messages.fullData.updates.FullDataChangeSummaryResponseMessage; import com.seibel.distanthorizons.core.network.messages.fullData.updates.FullDataPartialUpdateMessage; import com.seibel.distanthorizons.core.pos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.DhChunkPos; @@ -54,7 +50,6 @@ import com.seibel.distanthorizons.coreapi.util.math.Vec3d; import org.apache.logging.log4j.Logger; import javax.annotation.CheckForNull; -import java.util.HashSet; import java.util.Map; import java.util.concurrent.*; @@ -128,7 +123,9 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel this.eventSource.registerHandler(GenTaskPriorityRequestMessage.class, remotePlayerConnectionHandler.currentLevelOnly(this, (msg, serverPlayerState) -> { msg.sendResponse(new GenTaskPriorityResponseMessage( - this.serverside.dataFileHandler.getLoadStates(msg.posList) + this.serverside.dataFileHandler.getLoadStates(msg.posList.stream() + .limit(serverPlayerState.genTaskPriorityRequestRateLimiter.acquireOrDrain(msg.posList.size())) + ::iterator) )); })); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/AbstractMultiplayerConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/AbstractMultiplayerConfig.java index 006e90e2b..f1ad36d78 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/AbstractMultiplayerConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/AbstractMultiplayerConfig.java @@ -8,6 +8,7 @@ public abstract class AbstractMultiplayerConfig implements INetworkObject public abstract int getRenderDistanceRadius(); public abstract boolean isDistantGenerationEnabled(); public abstract int getFullDataRequestConcurrencyLimit(); + public abstract int getGenTaskPriorityRequestRateLimit(); public abstract boolean isRealTimeUpdatesEnabled(); public abstract boolean isPostRelogUpdateEnabled(); @@ -17,6 +18,7 @@ public abstract class AbstractMultiplayerConfig implements INetworkObject out.writeInt(this.getRenderDistanceRadius()); out.writeBoolean(this.isDistantGenerationEnabled()); out.writeInt(this.getFullDataRequestConcurrencyLimit()); + out.writeInt(this.getGenTaskPriorityRequestRateLimit()); out.writeBoolean(this.isRealTimeUpdatesEnabled()); out.writeBoolean(this.isPostRelogUpdateEnabled()); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfig.java index 9b7d60cac..631a3ce03 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfig.java @@ -16,6 +16,9 @@ public class MultiplayerConfig extends AbstractMultiplayerConfig public int fullDataRequestConcurrencyLimit = Config.Client.Advanced.Multiplayer.ServerNetworking.fullDataRequestConcurrencyLimit.get(); @Override public int getFullDataRequestConcurrencyLimit() { return fullDataRequestConcurrencyLimit; } + public int genTaskPriorityRequestRateLimit = Config.Client.Advanced.Multiplayer.ServerNetworking.genTaskPriorityRequestRateLimit.get(); + @Override public int getGenTaskPriorityRequestRateLimit() { return genTaskPriorityRequestRateLimit; } + public boolean realTimeUpdatesEnabled = Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get(); @Override public boolean isRealTimeUpdatesEnabled() { return realTimeUpdatesEnabled; } @@ -28,6 +31,7 @@ public class MultiplayerConfig extends AbstractMultiplayerConfig this.renderDistanceRadius = in.readInt(); this.distantGenerationEnabled = in.readBoolean(); this.fullDataRequestConcurrencyLimit = in.readInt(); + this.genTaskPriorityRequestRateLimit = in.readInt(); this.realTimeUpdatesEnabled = in.readBoolean(); this.postRelogUpdateEnabled = in.readBoolean(); } @@ -38,6 +42,7 @@ public class MultiplayerConfig extends AbstractMultiplayerConfig "renderDistance=" + renderDistanceRadius + ", distantGenerationEnabled=" + distantGenerationEnabled + ", fullDataRequestConcurrencyLimit=" + fullDataRequestConcurrencyLimit + + ", genTaskPriorityRequestRateLimit=" + genTaskPriorityRequestRateLimit + ", realTimeUpdatesEnabled=" + realTimeUpdatesEnabled + ", postRelogUpdatesEnabled=" + postRelogUpdateEnabled + '}'; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfigChangeListener.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfigChangeListener.java index 1dc0dfe2c..3a4b8ce50 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfigChangeListener.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/MultiplayerConfigChangeListener.java @@ -14,6 +14,7 @@ public class MultiplayerConfigChangeListener implements Closeable Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius, Config.Client.Advanced.WorldGenerator.enableDistantGeneration, Config.Client.Advanced.Multiplayer.ServerNetworking.fullDataRequestConcurrencyLimit, + Config.Client.Advanced.Multiplayer.ServerNetworking.genTaskPriorityRequestRateLimit, Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates, //Config.Client.Advanced.Multiplayer.ServerNetworking.enablePostRelogUpdate }; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java index f22a15567..f8e1ac49b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerState.java @@ -3,7 +3,7 @@ package com.seibel.distanthorizons.core.multiplayer.server; import com.seibel.distanthorizons.core.network.IConnection; import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; import com.seibel.distanthorizons.core.network.messages.fullData.generation.FullDataSourceRequestMessage; -import com.seibel.distanthorizons.core.network.messages.fullData.updates.FullDataChangeSummaryRequestMessage; +import com.seibel.distanthorizons.core.network.messages.fullData.generation.priority.GenTaskPriorityRequestMessage; import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedConcurrencyLimiter; import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; @@ -27,11 +27,20 @@ public class ServerPlayerState public final SupplierBasedConcurrencyLimiter fullDataRequestConcurrencyLimiter = new SupplierBasedConcurrencyLimiter<>( () -> ServerNetworking.fullDataRequestConcurrencyLimit.get(), msg -> { - msg.sendResponse(new RateLimitedException("Max concurrent requests: " + config.getFullDataRequestConcurrencyLimit())); + msg.sendResponse(new RateLimitedException("Max concurrent full data requests: " + config.getFullDataRequestConcurrencyLimit())); this.rateLimitKickTrigger.tryAcquire(null); } ); + public final SupplierBasedRateLimiter genTaskPriorityRequestRateLimiter = new SupplierBasedRateLimiter<>( + () -> ServerNetworking.genTaskPriorityRequestRateLimit.get(), + msg -> { + // Shouldn't be called, but it's here just in case + msg.sendResponse(new RateLimitedException("Max section checks per second: " + config.getFullDataRequestConcurrencyLimit())); + this.rateLimitKickTrigger.tryAcquire(null); + } + ); + public ServerPlayerState(IServerPlayerWrapper serverPlayer) { this.serverPlayer = serverPlayer; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServersideMultiplayerConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServersideMultiplayerConfig.java index 4f9cb47d8..03a3547a8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServersideMultiplayerConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServersideMultiplayerConfig.java @@ -30,6 +30,12 @@ public class ServersideMultiplayerConfig extends AbstractMultiplayerConfig return Math.min(clientConfig.fullDataRequestConcurrencyLimit, Config.Client.Advanced.Multiplayer.ServerNetworking.fullDataRequestConcurrencyLimit.get()); } + @Override + public int getGenTaskPriorityRequestRateLimit() + { + return Math.min(clientConfig.genTaskPriorityRequestRateLimit, Config.Client.Advanced.Multiplayer.ServerNetworking.genTaskPriorityRequestRateLimit.get()); + } + @Override public boolean isRealTimeUpdatesEnabled() { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkChannelInitializer.java b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkChannelInitializer.java index c72d8a1d4..d17209a7e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkChannelInitializer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/network/protocol/NetworkChannelInitializer.java @@ -43,7 +43,7 @@ public class NetworkChannelInitializer extends ChannelInitializer pipeline.addLast(new NetworkOutboundExceptionRouter()); // Decoder - pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, Integer.BYTES, 0, Integer.BYTES)); + pipeline.addLast(new LengthFieldBasedFrameDecoder(4194304 /* 4 MiB */, 0, Integer.BYTES, 0, Integer.BYTES)); pipeline.addLast(new MessageDecoder()); // Handler diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/ratelimiting/SupplierBasedRateLimiter.java b/core/src/main/java/com/seibel/distanthorizons/core/util/ratelimiting/SupplierBasedRateLimiter.java index efa3813f2..99ec05b58 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/ratelimiting/SupplierBasedRateLimiter.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/ratelimiting/SupplierBasedRateLimiter.java @@ -27,12 +27,29 @@ public class SupplierBasedRateLimiter @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean tryAcquire(T context) { - return tryAcquire(1, context); + return tryAcquire(context, 1); } - public boolean tryAcquire(int permits, T context) + public int acquireOrDrain(int permits) { rateLimiter.setRate(maxRateSupplier.get()); + + if (rateLimiter.tryAcquire(permits)) + return permits; + + int acquired = 0; + while ((permits /= 2) > 0) + { + if (rateLimiter.tryAcquire(permits)) + acquired += permits; + } + return acquired; + } + + public boolean tryAcquire(T context, int permits) + { + rateLimiter.setRate(maxRateSupplier.get()); + if (!rateLimiter.tryAcquire(permits)) { this.onFailureConsumer.accept(context); diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index 8e553e697..bf73ce835 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -347,7 +347,8 @@ "distanthorizons.config.client.advanced.multiplayer.serverNetworking": "Server Networking", "distanthorizons.config.client.advanced.multiplayer.serverNetworking.enableServerNetworking": "Enable Server Networking", - "distanthorizons.config.client.advanced.multiplayer.serverNetworking.fullDataRequestConcurrencyLimit": "Full data request concurrency limit", + "distanthorizons.config.client.advanced.multiplayer.serverNetworking.fullDataRequestConcurrencyLimit": "Sections - request concurrency limit", + "distanthorizons.config.client.advanced.multiplayer.serverNetworking.genTaskPriorityRequestRateLimit": "Generation task priority - request rate limit", "distanthorizons.config.client.advanced.multiplayer.serverNetworking.enableRealTimeUpdates": "Enable Real Time Updates", "distanthorizons.config.client.advanced.multiplayer.serverNetworking.enablePostRelogUpdates": "Enable Post Relog Updates", "distanthorizons.config.client.advanced.multiplayer.serverNetworking.serverPort": "Server Port",