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 6116a2e88..0784ea9d0 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 @@ -1722,11 +1722,19 @@ public class Config // Common - public static ConfigEntry maxDataTransferSpeed = new ConfigEntry.Builder() - .setChatCommandName("common.maxDataTransferSpeed") + public static ConfigEntry playerBandwidthLimit = new ConfigEntry.Builder() + .setChatCommandName("common.playerBandwidthLimit") .setMinDefaultMax(0, 500, 1000000 /* 1 GB/s */) .comment("" - + "Maximum speed for uploading LODs to the clients, in KB/s.\n" + + "Maximum per-player speed for uploading LODs to the clients, in KB/s.\n" + + "Value of 0 disables the limit." + + "") + .build(); + public static ConfigEntry globalBandwidthLimit = new ConfigEntry.Builder() + .setChatCommandName("common.globalBandwidthLimit") + .setMinDefaultMax(0, 0, 10000000 /* 10 GB/s */) + .comment("" + + "Maximum global speed for uploading LODs to the clients, in KB/s.\n" + "Value of 0 disables the limit." + "") .build(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java index 5664b20f3..58ac15d0b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientNetworkState.java @@ -153,7 +153,7 @@ public class ClientNetworkState implements Closeable if (Config.Server.enableAdaptiveTransferSpeed.get()) { - sessionConfig.constrainValue(Config.Server.maxDataTransferSpeed, this.congestionControl.getDesiredRate()); + sessionConfig.constrainValue(Config.Server.playerBandwidthLimit, this.congestionControl.getDesiredRate()); } if (blocking) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/SessionConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/SessionConfig.java index 5fd59ccc4..f73f0e7e4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/SessionConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/SessionConfig.java @@ -45,7 +45,7 @@ public class SessionConfig implements INetworkObject registerConfigEntry(Config.Server.maxSyncOnLoadRequestDistance, Math::min); registerConfigEntry(Config.Server.syncOnLoadRateLimit, Math::min); - registerConfigEntry(Config.Server.maxDataTransferSpeed, (x, y) -> { + registerConfigEntry(Config.Server.playerBandwidthLimit, (x, y) -> { if (x == 0 && y == 0) { return 0; @@ -80,7 +80,7 @@ public class SessionConfig implements INetworkObject 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); } + public int getPlayerBandwidthLimit() { return this.getValue(Config.Server.playerBandwidthLimit); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadSender.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadSender.java index 8fa332402..2676a724f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadSender.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadSender.java @@ -8,7 +8,6 @@ import io.netty.buffer.ByteBuf; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.*; public class FullDataPayloadSender implements AutoCloseable @@ -26,11 +25,14 @@ public class FullDataPayloadSender implements AutoCloseable private final IntSupplier maxKBpsSupplier; private final ConcurrentLinkedQueue transferQueue = new ConcurrentLinkedQueue<>(); + private final SharedBandwidthLimit sharedBandwidthLimit; - public FullDataPayloadSender(NetworkSession session, IntSupplier maxKBpsSupplier) + + public FullDataPayloadSender(NetworkSession session, IntSupplier maxKBpsSupplier, SharedBandwidthLimit sharedBandwidthLimit) { this.session = session; this.maxKBpsSupplier = maxKBpsSupplier; + this.sharedBandwidthLimit = sharedBandwidthLimit; UPLOAD_TIMER.scheduleAtFixedRate(this.tickTimerTask, 0, 1000 / TICK_RATE); } @@ -48,11 +50,16 @@ public class FullDataPayloadSender implements AutoCloseable private void tick() { - int convertedMaxRate = this.maxKBpsSupplier.getAsInt(); - convertedMaxRate = convertedMaxRate > 0 ? convertedMaxRate : Integer.MAX_VALUE / 1000; + int bandwidthShare = this.sharedBandwidthLimit.getBandwidthShare(); + int maxPlayerRate = Math.min(this.maxKBpsSupplier.getAsInt(), bandwidthShare); // + 1 to account for rounding errors on values of < 4 - int bytesToSend = (convertedMaxRate * 1000) / TICK_RATE + 1; + int bytesToSend = maxPlayerRate > 0 + ? (maxPlayerRate * 1000) / TICK_RATE + 1 + : Integer.MAX_VALUE; + + this.sharedBandwidthLimit.setSenderActive(this, bytesToSend > 0); + while (bytesToSend > 0) { PendingTransfer pendingTransfer = this.transferQueue.peek(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/SharedBandwidthLimit.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/SharedBandwidthLimit.java new file mode 100644 index 000000000..526822ea1 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/SharedBandwidthLimit.java @@ -0,0 +1,36 @@ +package com.seibel.distanthorizons.core.multiplayer.fullData; + +import com.seibel.distanthorizons.core.config.Config; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class SharedBandwidthLimit +{ + private final Set senders = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public void setSenderActive(FullDataPayloadSender sender, boolean active) + { + if (active) + { + this.senders.add(sender); + } + else + { + this.senders.remove(sender); + } + } + + public int getBandwidthShare() + { + int globalBandwidthLimit = Config.Server.globalBandwidthLimit.get(); + if (globalBandwidthLimit == 0) + { + globalBandwidthLimit = Integer.MAX_VALUE; + } + + return globalBandwidthLimit / Math.max(this.senders.size(), 1); + } + +} 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 857f4cc75..55cb43d6e 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 @@ -5,6 +5,7 @@ import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.level.AbstractDhServerLevel; import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig; import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadSender; +import com.seibel.distanthorizons.core.multiplayer.fullData.SharedBandwidthLimit; import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent; import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; @@ -48,10 +49,10 @@ public class ServerPlayerState implements Closeable // constructors // //==============// - public ServerPlayerState(IServerPlayerWrapper serverPlayer) + public ServerPlayerState(IServerPlayerWrapper serverPlayer, SharedBandwidthLimit sharedBandwidthLimit) { this.networkSession = new NetworkSession(serverPlayer); - this.fullDataPayloadSender = new FullDataPayloadSender(this.networkSession, this.sessionConfig::getMaxDataTransferSpeed); + this.fullDataPayloadSender = new FullDataPayloadSender(this.networkSession, this.sessionConfig::getPlayerBandwidthLimit, sharedBandwidthLimit); this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) -> { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerStateManager.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerStateManager.java index 4af86f0e1..f74ce93d5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerStateManager.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/server/ServerPlayerStateManager.java @@ -1,5 +1,6 @@ package com.seibel.distanthorizons.core.multiplayer.server; +import com.seibel.distanthorizons.core.multiplayer.fullData.SharedBandwidthLimit; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import org.jetbrains.annotations.Nullable; @@ -16,6 +17,7 @@ public class ServerPlayerStateManager private final ConcurrentMap connectedPlayerStateByPlayerWrapper = new ConcurrentHashMap<>(); private final ConcurrentMap messageQueueByPlayerWrapper = new ConcurrentHashMap<>(); + private final SharedBandwidthLimit sharedBandwidthLimit = new SharedBandwidthLimit(); //========================// @@ -24,7 +26,7 @@ public class ServerPlayerStateManager public ServerPlayerState registerJoinedPlayer(IServerPlayerWrapper serverPlayer) { - ServerPlayerState playerState = new ServerPlayerState(serverPlayer); + ServerPlayerState playerState = new ServerPlayerState(serverPlayer, this.sharedBandwidthLimit); this.connectedPlayerStateByPlayerWrapper.put(serverPlayer, playerState); return playerState; } 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 8a17c7e1c..6cd9460a7 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -750,10 +750,14 @@ "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": - "Maximum speed for uploading LODs to the clients, in KB/s.\nValue of 0 disables the limit.", + "distanthorizons.config.server.playerBandwidthLimit": + "Per-player Bandwidth Limit, KB/s", + "distanthorizons.config.server.playerBandwidthLimit.@tooltip": + "Maximum per-player speed for uploading LODs to the clients, in KB/s.\nValue of 0 disables the limit.", + "distanthorizons.config.server.globalBandwidthLimit": + "Global Bandwidth Limit, KB/s", + "distanthorizons.config.server.globalBandwidthLimit.@tooltip": + "Maximum global speed for uploading LODs to the clients, in KB/s.\nValue of 0 disables the limit.", "distanthorizons.config.server.enableAdaptiveTransferSpeed": "Enable Adaptive Transfer Speed", "distanthorizons.config.server.enableAdaptiveTransferSpeed.@tooltip":