diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index f1054c103..192110a5b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -291,8 +291,8 @@ public class SharedApi { UPDATE_POS_MANAGER.removeItem(chunkWrapper.getChunkPos()); } - boolean queueHasRemainingCapacity = UPDATE_POS_MANAGER.addItem(chunkWrapper.getChunkPos(), updateData); - if (!queueHasRemainingCapacity) + int remainingCapacity = UPDATE_POS_MANAGER.addItem(chunkWrapper.getChunkPos(), updateData); + if (remainingCapacity <= 0) { // limit how often an overloaded message can be sent long msBetweenLastLog = System.currentTimeMillis() - lastOverloadedLogMessageMsTime; @@ -542,36 +542,38 @@ public class SharedApi } } - /** @return true if the queue has remaining slots, false if the queue is full */ - public boolean addItem(DhChunkPos pos, UpdateChunkData updateData) + /** + * Adds an item to the queue of chunks that need to be updated. + * If there are no more slots, replaces the item furthest from the center. + * + * @return The number of remaining slots available in the queue. + */ + public int addItem(DhChunkPos pos, UpdateChunkData updateData) { try { this.lock.lock(); + int remainingSlots = this.maxSize - this.positionMap.size(); if (this.positionMap.containsKey(pos)) { - // assume the queue has additional slots - return true; + // Chunk is already present in queue, no need to insert + return remainingSlots; } - - boolean queueFull = false; - if (this.positionMap.size() >= this.maxSize) + // If no slots are left, get one by removing the item furthest from the center + if (remainingSlots <= 0) { - // Remove item furthest from the center DhChunkPos furthest = this.furthestQueue.poll(); this.closestQueue.remove(furthest); this.positionMap.remove(furthest); - - queueFull = true; } this.positionMap.put(pos, updateData); this.closestQueue.add(pos); this.furthestQueue.add(pos); - return queueFull; + return remainingSlots; } finally { 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 d3d47f9ae..c8440cd9d 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 @@ -1574,9 +1574,10 @@ public class Config public static ConfigEntry maxDataTransferSpeed = new ConfigEntry.Builder() .setServersideShortName("maxDataTransferSpeed") - .setMinDefaultMax(1, 500, 1000000 /* 1 GB/s */) + .setMinDefaultMax(0, 500, 1000000 /* 1 GB/s */) .comment("" - + "Maximum speed for uploading LODs to the clients, in KB/s." + + "Maximum 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/config/SessionConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/multiplayer/config/SessionConfig.java index f0b7bd82c..790bffccc 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 @@ -29,7 +29,7 @@ public class SessionConfig implements INetworkObject static { - // Note: config values are ordered by serversideShortName when transmitted + // Note: config values are transmitted in the insertion order registerConfigEntry(Config.Server.realTimeUpdateDistanceRadiusInChunks, Math::min); @@ -41,7 +41,17 @@ public class SessionConfig implements INetworkObject registerConfigEntry(Config.Server.synchronizeOnLoad, (x, y) -> x && y); registerConfigEntry(Config.Server.syncOnLoadRateLimit, Math::min); - registerConfigEntry(Config.Server.maxDataTransferSpeed, Math::min); + registerConfigEntry(Config.Server.maxDataTransferSpeed, (x, y) -> { + if (x == 0 && y == 0) + { + return 0; + } + + return Math.min( + x > 0 ? x : Integer.MAX_VALUE, + y > 0 ? y : Integer.MAX_VALUE + ); + }); } public SessionConfig() {} 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 d2e89190e..c969e34d9 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 @@ -15,6 +15,10 @@ public class FullDataPayloadSender implements AutoCloseable { private static final int TICK_RATE = 4; + /** 1 Mebibyte minus 576 bytes for other info */ + public static final int FULL_DATA_SPLIT_SIZE_IN_BYTES = 1_048_000; + + private static final Timer UPLOAD_TIMER = TimerUtil.CreateTimer("FullDataPayloadSender"); private final TimerTask tickTimerTask = TimerUtil.createTimerTask(this::tick); @@ -22,6 +26,7 @@ public class FullDataPayloadSender implements AutoCloseable private final IntSupplier maxKBpsSupplier; private final ConcurrentLinkedQueue transferQueue = new ConcurrentLinkedQueue<>(); + public FullDataPayloadSender(NetworkSession session, IntSupplier maxKBpsSupplier) { this.session = session; @@ -49,7 +54,11 @@ public class FullDataPayloadSender implements AutoCloseable private void tick() { - int bytesToSend = (this.maxKBpsSupplier.getAsInt() * 1000) / TICK_RATE; + int convertedMaxRate = this.maxKBpsSupplier.getAsInt(); + convertedMaxRate = convertedMaxRate > 0 ? convertedMaxRate : Integer.MAX_VALUE / 1000; + + // + 1 to account for rounding errors on values of < 4 + int bytesToSend = (convertedMaxRate * 1000) / TICK_RATE + 1; while (bytesToSend > 0) { PendingTransfer pendingTransfer = this.transferQueue.peek(); @@ -58,7 +67,7 @@ public class FullDataPayloadSender implements AutoCloseable return; } - int chunkSize = Math.min(bytesToSend, pendingTransfer.buffer.readableBytes()); + int chunkSize = Math.min(Math.min(bytesToSend, FULL_DATA_SPLIT_SIZE_IN_BYTES), pendingTransfer.buffer.readableBytes()); boolean isFirstChunk = pendingTransfer.buffer.readerIndex() == 0; FullDataSplitMessage chunkMessage = new FullDataSplitMessage(pendingTransfer.bufferId, pendingTransfer.buffer.readRetainedSlice(chunkSize), isFirstChunk); 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 3be0ebb7b..10236c0ca 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -692,7 +692,7 @@ "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", + "Maximum speed for uploading LODs to the clients, in KB/s.\nValue of 0 disables the limit.",