Add server flag for ChunkUpdateLimitChecker history size (#2596)

* Add server flag for ChunkUpdateLimitChecker history size

* Fixup server flag for ChunkUpdateLimitChecker history size
This commit is contained in:
Oleg Ivanov 2025-01-19 16:16:29 +03:00 committed by GitHub
parent 807a887fbd
commit 6f107c7f88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 88 additions and 6 deletions

View File

@ -27,6 +27,7 @@ public final class ServerFlag {
public static final int PLAYER_PACKET_QUEUE_SIZE = intProperty("minestom.packet-queue-size", 1000);
public static final long KEEP_ALIVE_DELAY = longProperty("minestom.keep-alive-delay", 10_000);
public static final long KEEP_ALIVE_KICK = longProperty("minestom.keep-alive-kick", 15_000);
public static final int PLAYER_CHUNK_UPDATE_LIMITER_HISTORY_SIZE = intProperty("minestom.player.chunk-update-limiter-history-size", 5, 0, Integer.MAX_VALUE);
// Network buffers
public static final int MAX_PACKET_SIZE = intProperty("minestom.max-packet-size", 2_097_151); // 3 bytes var-int
@ -98,8 +99,19 @@ public final class ServerFlag {
return System.getProperty(name);
}
private static int intProperty(String name, int defaultValue, int minValue, int maxValue) {
int value = Integer.getInteger(name, defaultValue);
if (value < minValue || value > maxValue) {
throw new IllegalArgumentException(String.format(
"Property '%s' value must be in range [%d..%d] but was %d",
name, minValue, maxValue, value
));
}
return value;
}
private static int intProperty(String name, int defaultValue) {
return Integer.getInteger(name, defaultValue);
return intProperty(name, defaultValue, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
private static long longProperty(String name, long defaultValue) {

View File

@ -191,7 +191,7 @@ public class Player extends LivingEntity implements CommandSender, HoverEventSou
// Game state (https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol#Game_Event)
private boolean enableRespawnScreen;
private final ChunkUpdateLimitChecker chunkUpdateLimitChecker = new ChunkUpdateLimitChecker(6);
private final ChunkUpdateLimitChecker chunkUpdateLimitChecker = new ChunkUpdateLimitChecker(ServerFlag.PLAYER_CHUNK_UPDATE_LIMITER_HISTORY_SIZE);
// Experience orb pickup
protected Cooldown experiencePickupCooldown = new Cooldown(Duration.of(10, TimeUnit.SERVER_TICK));

View File

@ -6,17 +6,28 @@ import org.jetbrains.annotations.ApiStatus;
import java.util.Arrays;
/**
* Allows to limit operations with recently operated chunks
* <p>
* {@link ChunkUpdateLimitChecker#historySize} defines how many last chunks will be remembered
* to skip operations with them via {@link ChunkUpdateLimitChecker#addToHistory(Chunk)} returning {@code false}
*/
@ApiStatus.Internal
public final class ChunkUpdateLimitChecker {
private final int historySize;
private final long[] chunkHistory;
public ChunkUpdateLimitChecker(int historySize) {
this.historySize = historySize;
this.chunkHistory = new long[historySize];
this.historySize = Math.max(0, historySize);
this.chunkHistory = new long[this.historySize];
this.clearHistory();
}
public boolean isEnabled() {
return historySize > 0;
}
/**
* Adds the chunk to the history
*
@ -24,14 +35,19 @@ public final class ChunkUpdateLimitChecker {
* @return {@code true} if it's a new chunk in the history
*/
public boolean addToHistory(Chunk chunk) {
if (!isEnabled()) {
return true;
}
final long index = CoordConversion.chunkIndex(chunk.getChunkX(), chunk.getChunkZ());
boolean result = true;
final int lastIndex = historySize - 1;
for (int i = 0; i < lastIndex; i++) {
for (int i = 0; i <= lastIndex; i++) {
if (chunkHistory[i] == index) {
result = false;
}
chunkHistory[i] = chunkHistory[i + 1];
if (i != lastIndex) {
chunkHistory[i] = chunkHistory[i + 1];
}
}
chunkHistory[lastIndex] = index;
return result;

View File

@ -0,0 +1,54 @@
package net.minestom.server.utils.chunk;
import net.minestom.server.instance.DynamicChunk;
import net.minestom.testing.Env;
import net.minestom.testing.EnvTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@EnvTest
public class ChunkUpdateLimitCheckerTest {
@Test
public void testHistory(Env env) {
var instance = env.createFlatInstance();
var limiter = new ChunkUpdateLimitChecker(3);
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 1)));
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 2)));
// history : 0, 1, 2
assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
// history : 1, 2, 0
assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 1)));
// history : 2, 0, 1
assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 2)));
// history : 0, 1, 2
assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 2)));
// history : 1, 2, 2
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
}
@Test
public void testOneSlotHistory(Env env) {
var instance = env.createFlatInstance();
var limiter = new ChunkUpdateLimitChecker(1);
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
assertFalse(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 1)));
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
}
@Test
public void testDisabling(Env env) {
var instance = env.createFlatInstance();
var limiter = new ChunkUpdateLimitChecker(0);
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 0)));
assertTrue(limiter.addToHistory(new DynamicChunk(instance, 0, 1)));
}
}