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 63e56fc79..855fbdf04 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 @@ -1713,6 +1713,15 @@ public class Config + "Value of 0 disables the limit." + "") .build(); + public static ConfigEntry enableAdaptiveTransferSpeed = new ConfigEntry.Builder() + .set(true) + .comment("" + + "Enables adaptive transfer speed based on client performance.\n" + + "If true, DH will automatically adjust transfer rate to minimize connection lag.\n" + + "If false, transfer speed will remain fixed.\n" + + "") + .build(); + public static ConfigCategory experimental = new ConfigCategory.Builder().set(Experimental.class).build(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientCongestionControl.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientCongestionControl.java index 6c80f91b0..5c985cf0d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientCongestionControl.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/client/ClientCongestionControl.java @@ -16,65 +16,63 @@ public class ClientCongestionControl private final AtomicLong bytesReceived = new AtomicLong(0); private volatile double lastIntervalThroughput = 0; - private double currentRate = 50000; + private double desiredRate = 50000; private long lastAdjustTime = System.currentTimeMillis(); - private final IntSupplier currentMaxRateSupplier; - private final BooleanSupplier hasIncompleteBuffersSupplier; - private final IntConsumer rateUpdateConsumer; + private final IntSupplier currentRateSupplier; + private final Runnable rateUpdateHandler; public ClientCongestionControl( - IntSupplier currentMaxRateSupplier, - BooleanSupplier hasIncompleteBuffersSupplier, - IntConsumer rateUpdateConsumer + IntSupplier currentRateSupplier, + Runnable rateUpdateHandler ) { - this.currentMaxRateSupplier = currentMaxRateSupplier; - this.hasIncompleteBuffersSupplier = hasIncompleteBuffersSupplier; - this.rateUpdateConsumer = rateUpdateConsumer; + this.currentRateSupplier = currentRateSupplier; + this.rateUpdateHandler = rateUpdateHandler; } public void onPayloadReceived(FullDataSplitMessage message) { - this.bytesReceived.addAndGet(message.buffer.readableBytes()); - long now = System.currentTimeMillis(); if (now - this.lastAdjustTime >= INTERVAL_MS) { this.adjustRate(now); } + + this.bytesReceived.addAndGet(message.buffer.readableBytes()); } private void adjustRate(long now) { + this.desiredRate = this.currentRateSupplier.getAsInt() * 1000; double throughput = this.bytesReceived.getAndSet(0); if (throughput != 0 && throughput >= this.lastIntervalThroughput) { - this.currentRate = Math.min(this.currentRate, this.currentMaxRateSupplier.getAsInt() * 1000) + ADDITIVE_INCREASE; + this.desiredRate += ADDITIVE_INCREASE; } - else if (this.hasIncompleteBuffersSupplier.getAsBoolean()) + else { - this.currentRate *= MULTIPLICATIVE_DECREASE; + this.desiredRate *= MULTIPLICATIVE_DECREASE; throughput *= MULTIPLICATIVE_DECREASE; - if (this.currentRate < 1) + if (this.desiredRate < 1) { - this.currentRate = 1; + this.desiredRate = 1; } } this.lastIntervalThroughput = throughput; this.lastAdjustTime = now; - this.rateUpdateConsumer.accept((int) this.currentRate / 1000); + this.rateUpdateHandler.run(); } - public double getCurrentRate() + public int getDesiredRate() { - return this.currentRate; + return (int) (this.desiredRate / 1000); } } 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 705420e88..c02e535bc 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 @@ -1,6 +1,7 @@ package com.seibel.distanthorizons.core.multiplayer.client; 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.logging.ConfigBasedLogger; import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig; @@ -58,9 +59,14 @@ public class ClientNetworkState implements Closeable private final ClientCongestionControl congestionControl = new ClientCongestionControl( () -> this.sessionConfig.getMaxDataTransferSpeed(), - this.fullDataPayloadReceiver::hasIncompleteBuffers, - x -> this.sendConfigMessage(false) + () -> { + if (Config.Server.enableAdaptiveTransferSpeed.get()) + { + this.sendConfigMessage(false); + } + } ); + private final ConfigChangeListener adaptiveTransferSpeedListener = new ConfigChangeListener<>(Config.Server.enableAdaptiveTransferSpeed, this::sendConfigMessage); @@ -138,7 +144,11 @@ public class ClientNetworkState implements Closeable public void sendConfigMessage(boolean blocking) { SessionConfig sessionConfig = new SessionConfig(); - sessionConfig.overrideValue(Config.Server.maxDataTransferSpeed, (int) this.congestionControl.getCurrentRate() / 1000); + + if (Config.Server.enableAdaptiveTransferSpeed.get()) + { + sessionConfig.constrainValue(Config.Server.maxDataTransferSpeed, this.congestionControl.getDesiredRate()); + } if (blocking) { @@ -181,6 +191,7 @@ public class ClientNetworkState implements Closeable public void close() { this.fullDataPayloadReceiver.close(); + this.adaptiveTransferSpeedListener.close(); this.configAnyChangeListener.close(); this.networkSession.close(); } 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 b8e3f1058..9d991a8a7 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 @@ -106,7 +106,7 @@ public class SessionConfig implements INetworkObject // internal getters // //==================// - public T getValue(ConfigEntry configEntry) { return this.getValue(configEntry.getChatCommandName()); } + private T getValue(ConfigEntry configEntry) { return this.getValue(configEntry.getChatCommandName()); } @SuppressWarnings("unchecked") private T getValue(String name) { @@ -123,10 +123,11 @@ public class SessionConfig implements INetworkObject : value); } - public void overrideValue(ConfigEntry configEntry, T value) { this.overrideValue(configEntry.getChatCommandName(), value); } - private void overrideValue(String name, Object value) + public void constrainValue(ConfigEntry configEntry, T value) { this.constrainValue(configEntry.getChatCommandName(), value); } + private void constrainValue(String name, Object value) { - this.values.put(name, value); + Entry entry = CONFIG_ENTRIES.get(name); + this.values.put(name, entry.valueConstrainer.apply(this.getValue(name), value)); } private Map getValues() diff --git a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadReceiver.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadReceiver.java index 0445d4d30..5674b3299 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadReceiver.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/fullData/FullDataPayloadReceiver.java @@ -34,9 +34,6 @@ public class FullDataPayloadReceiver implements AutoCloseable }) .build().asMap(); - - public boolean hasIncompleteBuffers() { return !this.buffersById.isEmpty(); } - @Override public void close() { 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 0e03caf3d..dce6664e0 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -746,6 +746,10 @@ "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.enableAdaptiveTransferSpeed": + "Enable Adaptive Transfer Speed", + "distanthorizons.config.server.enableAdaptiveTransferSpeed.@tooltip": + "Enables adaptive transfer speed based on client performance.\nIf true, DH will automatically adjust transfer rate to minimize connection lag.\nIf false, transfer speed will remain fixed.", "distanthorizons.config.server.experimental":