Limit rate+concurrency instead of only concurrency

Rename post-relog update to Login sync
This commit is contained in:
s809
2024-01-29 22:36:13 +05:00
parent c9d426e581
commit 3932ea21c2
14 changed files with 146 additions and 100 deletions
+1 -1
View File
@@ -537,7 +537,7 @@ ij_groovy_wrap_chain_calls_after_dot = false
ij_groovy_wrap_long_lines = false
[{*.har,*.json,*.png.mcmeta,mcmod.info,pack.mcmeta}]
indent_size = 4
indent_size = 2
ij_json_array_wrapping = split_into_lines
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
@@ -845,7 +845,6 @@ public class Config
+ "")
.build();
// TODO Write strings
public static class ServerNetworking
{
public static ConfigEntry<Boolean> enableServerNetworking = new ConfigEntry.Builder<Boolean>()
@@ -862,23 +861,6 @@ public class Config
+ "Note: This requires DH to be installed on the server in order to function. \n"
+ "")
.build();
public static ConfigEntry<Boolean> enableRealTimeUpdates = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("enableRealTimeUpdates")
.set(false)
.comment(""
+ "Enables real time updates from server."
+ "")
.build();
public static ConfigEntry<Boolean> enablePostRelogUpdate = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("enablePostRelogUpdate")
.set(false)
.comment(""
+ "Enables updating of LODs after relog."
+ "")
.build();
public static ConfigEntry<Integer> serverPort = new ConfigEntry.Builder<Integer>()
.setServersideShortName("serverPort")
.setMinDefaultMax(1, 25049, 65535)
@@ -893,16 +875,16 @@ public class Config
.setMinDefaultMax(1, 10, 100)
.comment(""
+ "Amount of rate/concurrency limit hits in one second before disconnecting the offending clients. \n"
+ "Warning: too low values can cause slower clients to disconnect prematurely.\n"
+ "This setting is server-only; it does not have effect on client.\n"
+ "This setting is server-only; it does not have effect on the client.\n"
+ "")
.build();
public static ConfigEntry<Integer> fullDataRequestConcurrencyLimit = new ConfigEntry.Builder<Integer>()
.setServersideShortName("fullDataRequestConcurrencyLimit")
public static ConfigEntry<Integer> generationRequestRCLimit = new ConfigEntry.Builder<Integer>()
.setServersideShortName("generationRequestRCLimit")
.setMinDefaultMax(1, 20, 100)
.comment(""
+ "Limits the amount of sent/processed LOD *generation* requests concurrently on server, per player. \n"
+ "Limits the amount of generation requests sent by client and processed by server. \n"
+ "")
.build();
@@ -914,7 +896,7 @@ public class Config
+ "")
.build();
public static ConfigEntry<Integer> fullDataRequestBeginDelay = new ConfigEntry.Builder<Integer>()
public static ConfigEntry<Integer> generationRequestBeginDelay = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(0, 3, 10)
.comment(""
+ "Adds a delay in seconds before sending LOD requests, when generation is enabled. \n"
@@ -922,25 +904,31 @@ public class Config
+ "")
.build();
public static ConfigEntry<Integer> postRelogUpdateConcurrencyLimit = new ConfigEntry.Builder<Integer>()
.setServersideShortName("postRelogUpdateConcurrencyLimit")
.setMinDefaultMax(1, 50, 100)
public static ConfigEntry<Boolean> enableRealTimeUpdates = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("enableRealTimeUpdates")
.set(false)
.comment(""
+ "Limits the amount of sent/processed LOD *update* requests concurrently on server, per player. \n"
+ "Enables real time updates from server."
+ "")
.build();
/**
* Intentionally disabled.
* @see #enablePostRelogUpdate
*/
private static final ConfigEntry<Integer> fullDataChangeSummaryRequestRateLimit = new ConfigEntry.Builder<Integer>()
.setServersideShortName("fullDataChangeSummaryRequestRateLimit")
.setMinDefaultMax(1, 20, 100)
public static ConfigEntry<Boolean> enableLoginDataSync = new ConfigEntry.Builder<Boolean>()
.setServersideShortName("enableLoginDataSync")
.set(false)
.comment(""
+ "Limits the amount of LOD updates the client can check within a second. \n"
+ "Enables updating of saved LODs after login."
+ "")
.build();
.build();
public static ConfigEntry<Integer> loginDataSyncRCLimit = new ConfigEntry.Builder<Integer>()
.setServersideShortName("loginDataSyncRCLimit")
.setMinDefaultMax(1, 50, 100)
.comment(""
+ "Limits the amount of sent/processed LOD *update* requests concurrently, per player. \n"
+ "")
.build();
}
}
@@ -30,7 +30,7 @@ public class WorldRemoteGenerationQueue extends AbstractFullDataRequestQueue imp
// Used to prevent requests for section very far away, as result of request list not completely filled.
// Kinda a hack, since queue is not notified when file handler is done with feeding sections to generate
private static final ConfigEntry<Integer> REQUEST_BEGIN_DELAY = Config.Client.Advanced.Multiplayer.ServerNetworking.fullDataRequestBeginDelay;
private static final ConfigEntry<Integer> REQUEST_BEGIN_DELAY = Config.Client.Advanced.Multiplayer.ServerNetworking.generationRequestBeginDelay;
private final Stopwatch requestBeginStopwatch = Stopwatch.createStarted();
private CompletableFuture<?> genTaskPriorityRequest = CompletableFuture.completedFuture(null);
@@ -127,15 +127,15 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel
}
else
{
// Post-relog update
// Sync only
if (!serverPlayerState.config.isPostRelogUpdateEnabled())
if (!serverPlayerState.config.isLoginDataSyncEnabled())
{
msg.sendResponse(new RequestRejectedException("Operation is disabled from config."));
return;
}
if (!serverPlayerState.postRelogUpdateRequestConcurrencyLimiter.tryAcquire(msg))
if (!serverPlayerState.loginDataSyncRCLimiter.tryAcquire(msg))
{
return;
}
@@ -143,14 +143,14 @@ public class DhServerLevel extends DhLevel implements IDhServerLevel
Integer serverChecksum = this.serverside.dataFileHandler.repo.getChecksumForSection(msg.sectionPos);
if (serverChecksum == null || serverChecksum.equals(msg.checksum))
{
serverPlayerState.postRelogUpdateRequestConcurrencyLimiter.release();
serverPlayerState.loginDataSyncRCLimiter.release();
msg.sendResponse(new FullDataSourceResponseMessage(null, this));
return;
}
this.serverside.dataFileHandler.getAsync(msg.sectionPos).thenAccept(fullDataSource ->
{
serverPlayerState.postRelogUpdateRequestConcurrencyLimiter.release();
serverPlayerState.loginDataSyncRCLimiter.release();
msg.sendResponse(new FullDataSourceResponseMessage((CompleteFullDataSource) fullDataSource, this));
});
}
@@ -16,6 +16,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter;
import io.netty.channel.ChannelException;
import org.apache.logging.log4j.Logger;
@@ -51,6 +52,8 @@ public abstract class AbstractFullDataRequestQueue implements IDebugRenderable,
private final Set<DhSectionPos> alreadyRequestedPositions = ConcurrentHashMap.newKeySet();
private final SupplierBasedRateLimiter<Void> rateLimiter = new SupplierBasedRateLimiter<>(this::getRequestConcurrencyLimit);
protected abstract int getRequestConcurrencyLimit();
@@ -104,6 +107,12 @@ public abstract class AbstractFullDataRequestQueue implements IDebugRenderable,
&& this.getInProgressTaskCount() < this.getRequestConcurrencyLimit()
&& this.pendingTasksSemaphore.tryAcquire())
{
if (!this.rateLimiter.tryAcquire())
{
this.pendingTasksSemaphore.release();
break;
}
this.sendNewRequest(targetPos);
}
@@ -277,8 +286,7 @@ public abstract class AbstractFullDataRequestQueue implements IDebugRenderable,
public RequestQueueEntry(
Consumer<ChunkSizedFullDataAccessor> chunkDataConsumer,
@Nullable
Integer currentChecksum)
@Nullable Integer currentChecksum)
{
this.chunkDataConsumer = chunkDataConsumer;
this.currentChecksum = currentChecksum;
@@ -12,7 +12,7 @@ public class FullDataRefreshQueue extends AbstractFullDataRequestQueue
}
@Override
protected int getRequestConcurrencyLimit() { return this.networkState.config.postRelogUpdateConcurrencyLimit; }
protected int getRequestConcurrencyLimit() { return this.networkState.config.loginDataSyncRCLimit; }
@Override
protected String getQueueName() { return "Data Refresh Queue"; }
@@ -20,7 +20,7 @@ public class FullDataRefreshQueue extends AbstractFullDataRequestQueue
@Override
public boolean tick(DhBlockPos2D targetPos)
{
if (!this.networkState.config.postRelogUpdateEnabled)
if (!this.networkState.config.loginDataSyncEnabled)
{
return false;
}
@@ -10,8 +10,8 @@ public abstract class AbstractMultiplayerConfig implements INetworkObject
public abstract int getFullDataRequestConcurrencyLimit();
public abstract int getGenTaskPriorityRequestRateLimit();
public abstract boolean isRealTimeUpdatesEnabled();
public abstract boolean isPostRelogUpdateEnabled();
public abstract int getPostRelogUpdateConcurrencyLimit();
public abstract boolean isLoginDataSyncEnabled();
public abstract int getLoginDataSyncRCLimit();
@Override
public void encode(ByteBuf out)
@@ -21,8 +21,8 @@ public abstract class AbstractMultiplayerConfig implements INetworkObject
out.writeInt(this.getFullDataRequestConcurrencyLimit());
out.writeInt(this.getGenTaskPriorityRequestRateLimit());
out.writeBoolean(this.isRealTimeUpdatesEnabled());
out.writeBoolean(this.isPostRelogUpdateEnabled());
out.writeInt(this.getPostRelogUpdateConcurrencyLimit());
out.writeBoolean(this.isLoginDataSyncEnabled());
out.writeInt(this.getLoginDataSyncRCLimit());
}
}
@@ -13,7 +13,7 @@ public class MultiplayerConfig extends AbstractMultiplayerConfig
public boolean distantGenerationEnabled = Config.Client.Advanced.WorldGenerator.enableDistantGeneration.get();
@Override public boolean isDistantGenerationEnabled() { return this.distantGenerationEnabled; }
public int fullDataRequestConcurrencyLimit = Config.Client.Advanced.Multiplayer.ServerNetworking.fullDataRequestConcurrencyLimit.get();
public int fullDataRequestConcurrencyLimit = Config.Client.Advanced.Multiplayer.ServerNetworking.generationRequestRCLimit.get();
@Override public int getFullDataRequestConcurrencyLimit() { return this.fullDataRequestConcurrencyLimit; }
public int genTaskPriorityRequestRateLimit = Config.Client.Advanced.Multiplayer.ServerNetworking.genTaskPriorityRequestRateLimit.get();
@@ -22,11 +22,11 @@ public class MultiplayerConfig extends AbstractMultiplayerConfig
public boolean realTimeUpdatesEnabled = Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates.get();
@Override public boolean isRealTimeUpdatesEnabled() { return this.realTimeUpdatesEnabled; }
public boolean postRelogUpdateEnabled = Config.Client.Advanced.Multiplayer.ServerNetworking.enablePostRelogUpdate.get();
@Override public boolean isPostRelogUpdateEnabled() { return this.postRelogUpdateEnabled; }
public boolean loginDataSyncEnabled = Config.Client.Advanced.Multiplayer.ServerNetworking.enableLoginDataSync.get();
@Override public boolean isLoginDataSyncEnabled() { return this.loginDataSyncEnabled; }
public int postRelogUpdateConcurrencyLimit = Config.Client.Advanced.Multiplayer.ServerNetworking.postRelogUpdateConcurrencyLimit.get();
@Override public int getPostRelogUpdateConcurrencyLimit() { return this.postRelogUpdateConcurrencyLimit; }
public int loginDataSyncRCLimit = Config.Client.Advanced.Multiplayer.ServerNetworking.loginDataSyncRCLimit.get();
@Override public int getLoginDataSyncRCLimit() { return this.loginDataSyncRCLimit; }
@Override
public void decode(ByteBuf in)
@@ -36,20 +36,20 @@ public class MultiplayerConfig extends AbstractMultiplayerConfig
this.fullDataRequestConcurrencyLimit = in.readInt();
this.genTaskPriorityRequestRateLimit = in.readInt();
this.realTimeUpdatesEnabled = in.readBoolean();
this.postRelogUpdateEnabled = in.readBoolean();
this.postRelogUpdateConcurrencyLimit = in.readInt();
this.loginDataSyncEnabled = in.readBoolean();
this.loginDataSyncRCLimit = in.readInt();
}
@Override public String toString()
{
return "MultiplayerConfig{" +
"renderDistance=" + this.renderDistanceRadius +
"renderDistanceRadius=" + this.renderDistanceRadius +
", distantGenerationEnabled=" + this.distantGenerationEnabled +
", fullDataRequestConcurrencyLimit=" + this.fullDataRequestConcurrencyLimit +
", genTaskPriorityRequestRateLimit=" + this.genTaskPriorityRequestRateLimit +
", realTimeUpdatesEnabled=" + this.realTimeUpdatesEnabled +
", postRelogUpdatesEnabled=" + this.postRelogUpdateEnabled +
", postRelogUpdateConcurrencyLimit=" + this.postRelogUpdateConcurrencyLimit +
", loginDataSyncEnabled=" + this.loginDataSyncEnabled +
", loginDataSyncRCLimit=" + this.loginDataSyncRCLimit +
'}';
}
@@ -13,11 +13,11 @@ public class MultiplayerConfigChangeListener implements Closeable
private static final ConfigEntry[] CONFIG_ENTRIES = new ConfigEntry[] {
Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius,
Config.Client.Advanced.WorldGenerator.enableDistantGeneration,
Config.Client.Advanced.Multiplayer.ServerNetworking.fullDataRequestConcurrencyLimit,
Config.Client.Advanced.Multiplayer.ServerNetworking.generationRequestRCLimit,
Config.Client.Advanced.Multiplayer.ServerNetworking.genTaskPriorityRequestRateLimit,
Config.Client.Advanced.Multiplayer.ServerNetworking.enableRealTimeUpdates,
Config.Client.Advanced.Multiplayer.ServerNetworking.enablePostRelogUpdate,
Config.Client.Advanced.Multiplayer.ServerNetworking.postRelogUpdateConcurrencyLimit,
Config.Client.Advanced.Multiplayer.ServerNetworking.enableLoginDataSync,
Config.Client.Advanced.Multiplayer.ServerNetworking.loginDataSyncRCLimit,
};
private final ArrayList<ConfigChangeListener> changeListeners = new ArrayList<>();
@@ -4,7 +4,7 @@ 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.generation.priority.GenTaskPriorityRequestMessage;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedConcurrencyLimiter;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateAndConcurrencyLimiter;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateLimiter;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import org.jetbrains.annotations.NotNull;
@@ -24,10 +24,10 @@ public class ServerPlayerState
ignored -> this.connection.disconnect("You have been repeatedly exceeding rate/concurrency limits.")
);
public final SupplierBasedConcurrencyLimiter<FullDataSourceRequestMessage> fullDataRequestConcurrencyLimiter = new SupplierBasedConcurrencyLimiter<>(
() -> ServerNetworking.fullDataRequestConcurrencyLimit.get(),
public final SupplierBasedRateAndConcurrencyLimiter<FullDataSourceRequestMessage> fullDataRequestConcurrencyLimiter = new SupplierBasedRateAndConcurrencyLimiter<>(
() -> ServerNetworking.generationRequestRCLimit.get(),
msg -> {
msg.sendResponse(new RateLimitedException("Max concurrent full data requests: " + this.config.getFullDataRequestConcurrencyLimit()));
msg.sendResponse(new RateLimitedException("Full data request rate/concurrency limit: " + this.config.getFullDataRequestConcurrencyLimit()));
this.rateLimitKickTrigger.tryAcquire(null);
}
);
@@ -35,15 +35,15 @@ public class ServerPlayerState
public final SupplierBasedRateLimiter<GenTaskPriorityRequestMessage> genTaskPriorityRequestRateLimiter = new SupplierBasedRateLimiter<>(
() -> ServerNetworking.genTaskPriorityRequestRateLimit.get(),
msg -> {
msg.sendResponse(new RateLimitedException("Max section checks per second: " + this.config.getFullDataRequestConcurrencyLimit()));
msg.sendResponse(new RateLimitedException("Generation task priority check rate limit: " + this.config.getFullDataRequestConcurrencyLimit()));
this.rateLimitKickTrigger.tryAcquire(null);
}
);
public final SupplierBasedConcurrencyLimiter<FullDataSourceRequestMessage> postRelogUpdateRequestConcurrencyLimiter = new SupplierBasedConcurrencyLimiter<>(
() -> ServerNetworking.postRelogUpdateConcurrencyLimit.get(),
public final SupplierBasedRateAndConcurrencyLimiter<FullDataSourceRequestMessage> loginDataSyncRCLimiter = new SupplierBasedRateAndConcurrencyLimiter<>(
() -> ServerNetworking.loginDataSyncRCLimit.get(),
msg -> {
msg.sendResponse(new RateLimitedException("Max concurrent post-relog update requests: " + this.config.getPostRelogUpdateConcurrencyLimit()));
msg.sendResponse(new RateLimitedException("Data sync rate/concurrency limit: " + this.config.getLoginDataSyncRCLimit()));
this.rateLimitKickTrigger.tryAcquire(null);
}
);
@@ -27,7 +27,7 @@ public class ServersideMultiplayerConfig extends AbstractMultiplayerConfig
@Override
public int getFullDataRequestConcurrencyLimit()
{
return Math.min(this.clientConfig.fullDataRequestConcurrencyLimit, Config.Client.Advanced.Multiplayer.ServerNetworking.fullDataRequestConcurrencyLimit.get());
return Math.min(this.clientConfig.fullDataRequestConcurrencyLimit, Config.Client.Advanced.Multiplayer.ServerNetworking.generationRequestRCLimit.get());
}
@Override
@@ -43,14 +43,15 @@ public class ServersideMultiplayerConfig extends AbstractMultiplayerConfig
}
@Override
public boolean isPostRelogUpdateEnabled() {
return this.clientConfig.postRelogUpdateEnabled && Config.Client.Advanced.Multiplayer.ServerNetworking.enablePostRelogUpdate.get();
public boolean isLoginDataSyncEnabled()
{
return this.clientConfig.loginDataSyncEnabled && Config.Client.Advanced.Multiplayer.ServerNetworking.enableLoginDataSync.get();
}
@Override
public int getPostRelogUpdateConcurrencyLimit()
public int getLoginDataSyncRCLimit()
{
return Math.min(this.clientConfig.postRelogUpdateConcurrencyLimit, Config.Client.Advanced.Multiplayer.ServerNetworking.postRelogUpdateConcurrencyLimit.get());
return Math.min(this.clientConfig.loginDataSyncRCLimit, Config.Client.Advanced.Multiplayer.ServerNetworking.loginDataSyncRCLimit.get());
}
@Override
@@ -0,0 +1,39 @@
package com.seibel.distanthorizons.core.util.ratelimiting;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class SupplierBasedRateAndConcurrencyLimiter<T>
{
private final SupplierBasedRateLimiter<T> rateLimiter;
private final SupplierBasedConcurrencyLimiter<T> concurrencyLimiter;
public SupplierBasedRateAndConcurrencyLimiter(Supplier<Integer> maxRateSupplier, Consumer<T> onFailureConsumer)
{
this.rateLimiter = new SupplierBasedRateLimiter<>(maxRateSupplier, onFailureConsumer);
this.concurrencyLimiter = new SupplierBasedConcurrencyLimiter<>(maxRateSupplier, onFailureConsumer);
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean tryAcquire(T context)
{
if (!this.concurrencyLimiter.tryAcquire(context))
{
return false;
}
if (!this.rateLimiter.tryAcquire(context))
{
this.concurrencyLimiter.release();
return false;
}
return true;
}
public void release()
{
this.concurrencyLimiter.release();
}
}
@@ -18,6 +18,7 @@ public class SupplierBasedRateLimiter<T>
private final RateLimiter rateLimiter = RateLimiter.create(1);
public SupplierBasedRateLimiter(Supplier<Integer> maxRateSupplier) { this(maxRateSupplier, ignored -> { }); }
public SupplierBasedRateLimiter(Supplier<Integer> maxRateSupplier, Consumer<T> onFailureConsumer)
{
this.maxRateSupplier = maxRateSupplier;
@@ -27,30 +28,40 @@ public class SupplierBasedRateLimiter<T>
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean tryAcquire(T context)
{
return tryAcquire(context, 1);
return this.tryAcquire(context, 1);
}
public boolean tryAcquire()
{
return this.tryAcquire(null, 1);
}
public int acquireOrDrain(int permits)
{
rateLimiter.setRate(maxRateSupplier.get());
this.rateLimiter.setRate(this.maxRateSupplier.get());
if (rateLimiter.tryAcquire(permits))
if (this.rateLimiter.tryAcquire(permits))
{
return permits;
}
int acquired = 0;
while ((permits /= 2) > 0)
{
if (rateLimiter.tryAcquire(permits))
if (this.rateLimiter.tryAcquire(permits))
{
acquired += permits;
}
}
return acquired;
}
public boolean tryAcquire(T context, int permits)
{
rateLimiter.setRate(maxRateSupplier.get());
this.rateLimiter.setRate(this.maxRateSupplier.get());
if (!rateLimiter.tryAcquire(permits))
if (!this.rateLimiter.tryAcquire(permits))
{
this.onFailureConsumer.accept(context);
return false;
@@ -345,15 +345,23 @@
"Multiplayer",
"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": "Sections - request concurrency limit",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.genTaskPriorityRequestRateLimit": "Generation task priority - request rate limit",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.fullDataRequestBeginDelay": "Generation request begin delay",
"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.enableServerNetworking.@tooltip": "§6Attention:§r this feature is not fully implemented. \n\nIf true Distant Horizons will attempt to communicate with the connected \nserver in order to load LODs outside your vanilla render distance. \n\nNote: This requires DH to be installed on the server in order to function.",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.serverPort": "Server Port",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.serverPort.@tooltip": "The port on the server that's used for sending LOD data.",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.generationRequestRCLimit": "Gen task rate/concurrency limit",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.generationRequestRCLimit.@tooltip": "Limits the amount of generation requests sent by client and processed by server.",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.genTaskPriorityRequestRateLimit": "Gen task priority check rate limit",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.genTaskPriorityRequestRateLimit.@tooltip": "Limits the amount of LOD sections that the client can request states for, per second.",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.generationRequestBeginDelay": "Generation request begin delay",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.generationRequestBeginDelay.@tooltip": "Adds a delay in seconds before sending LOD requests, when generation is enabled. \nIncrease this value if initial generation starts too far away.",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.enableRealTimeUpdates": "Enable Real Time Updates",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.enableRealTimeUpdates.@tooltip": "Enables real time updates from server.",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.enableLoginDataSync": "Synchronize LODs on Login",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.enableLoginDataSync.@tooltip": "Enables updating of saved LODs after login.",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.loginDataSyncRCLimit": "Login sync rate/concurrency limit",
"distanthorizons.config.client.advanced.multiplayer.serverNetworking.loginDataSyncRCLimit.@tooltip": "Limits the amount of sent/processed LOD *update* requests concurrently, per player.",
"distanthorizons.config.client.advanced.multiplayer.serverFolderNameMode":
"Server Folder Mode",
"distanthorizons.config.client.advanced.multiplayer.serverFolderNameMode.@tooltip":
@@ -366,15 +374,6 @@
"Networked Multiverse Support",
"distanthorizons.config.client.advanced.multiplayer.enableMultiverseNetworking.@tooltip":
"If true Distant Horizons will attempt to communicate with the connected \nserver in order to improve multiverse support.",
"distanthorizons.config.client.advanced.multiplayer.enableServerNetworking":
"§4Partially implemented§r - Server Support",
"distanthorizons.config.client.advanced.multiplayer.enableServerNetworking.@tooltip":
"§6Attention:§r this feature is not fully implemented. \n\nIf true Distant Horizons will attempt to communicate with the connected \nserver in order to load LODs outside your vanilla render distance. \n\nNote: This requires DH to be installed on the server in order to function.",
"distanthorizons.config.client.advanced.multiplayer.serverNetworkingRateLimit":
"Server Support - Request Rate Limit",
"distanthorizons.config.client.advanced.multiplayer.serverNetworkingRateLimit.@tooltip":
"Limits the amount of sent/processed LOD requests concurrently. \n\nNote: Server can set its own rate limit.",
"distanthorizons.config.client.advanced.multiThreading":