Add per player chunk loading limits

Configurable under "settings.chunk-loading.player-max-chunk-load-rate",
defaults to -1. This commit also changes the chunk loading to be
distributed equally for all players, rather than distance based. This is
to ensure players flying around do not take priority over everyone else.
The exception to this new rule is the min-load-radius, which still has
priority over everything else.
This commit is contained in:
Spottedleaf 2022-03-31 06:04:23 -07:00
parent 5c7b445c76
commit a096540d94
6 changed files with 124 additions and 19 deletions

View file

@ -15,7 +15,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
Entity entity = this.player.getRootVehicle(); Entity entity = this.player.getRootVehicle();
+ // Paper start + // Paper start
+ if (this.awaitingPositionFromClient != null) { + if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) {
+ return; + return;
+ } + }
+ // Paper end + // Paper end

View file

@ -0,0 +1,23 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Thu, 31 Mar 2022 05:11:37 -0700
Subject: [PATCH] Ensure entity passenger world matches ridden entity
Bad plugins doing this would cause some obvious problems...
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
protected boolean addPassenger(Entity entity) { // CraftBukkit
+ // Paper start
+ if (entity.level != this.level) {
+ throw new IllegalArgumentException("Entity passenger world must match");
+ }
+ // Paper end
if (entity == this) throw new IllegalArgumentException("Entities cannot become a passenger of themselves"); // Paper - issue 572
if (entity.getVehicle() != this) {
throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)");

View file

@ -0,0 +1,45 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Thu, 31 Mar 2022 05:18:28 -0700
Subject: [PATCH] Guard against invalid entity positions
Anything not finite should be blocked and logged
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -0,0 +0,0 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale);
}
+ // Paper start - block invalid positions
+ public static boolean checkPosition(Entity entity, double newX, double newY, double newZ) {
+ if (Double.isFinite(newX) && Double.isFinite(newY) && Double.isFinite(newZ)) {
+ return true;
+ }
+
+ String entityInfo = null;
+ try {
+ entityInfo = entity.toString();
+ } catch (Exception ex) {
+ entityInfo = "[Entity info unavailable] ";
+ }
+ LOGGER.error("New entity position is invalid! Tried to set invalid position (" + newX + "," + newY + "," + newZ + ") for entity " + entity.getClass().getName() + " located at " + entity.position + ", entity info: " + entityInfo, new Throwable());
+ return false;
+ }
+ // Paper end - block invalid positions
+
public final void setPosRaw(double x, double y, double z) {
// Paper start
this.setPosRaw(x, y, z, false);
}
public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) {
+ // Paper start - block invalid positions
+ if (!checkPosition(this, x, y, z)) {
+ return;
+ }
+ // Paper end - block invalid positions
// Paper end
// Paper start - fix MC-4
if (this instanceof ItemEntity) {

View file

@ -2899,14 +2899,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+public final class IntervalledCounter { +public final class IntervalledCounter {
+ +
+ protected long[] times; + protected long[] times;
+ protected long[] counts;
+ protected final long interval; + protected final long interval;
+ protected long minTime; + protected long minTime;
+ protected int sum; + protected long sum;
+ protected int head; // inclusive + protected int head; // inclusive
+ protected int tail; // exclusive + protected int tail; // exclusive
+ +
+ public IntervalledCounter(final long interval) { + public IntervalledCounter(final long interval) {
+ this.times = new long[8]; + this.times = new long[8];
+ this.counts = new long[8];
+ this.interval = interval; + this.interval = interval;
+ } + }
+ +
@ -2915,7 +2917,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ +
+ public void updateCurrentTime(final long currentTime) { + public void updateCurrentTime(final long currentTime) {
+ int sum = this.sum; + long sum = this.sum;
+ int head = this.head; + int head = this.head;
+ final int tail = this.tail; + final int tail = this.tail;
+ final long minTime = currentTime - this.interval; + final long minTime = currentTime - this.interval;
@ -2924,8 +2926,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ // guard against overflow by using subtraction + // guard against overflow by using subtraction
+ while (head != tail && this.times[head] - minTime < 0) { + while (head != tail && this.times[head] - minTime < 0) {
+ head = (head + 1) % arrayLen; + sum -= this.counts[head];
+ --sum; + // there are two ways we can do this:
+ // 1. free the count when adding
+ // 2. free it now
+ // option #2
+ this.counts[head] = 0;
+ if (++head >= arrayLen) {
+ head = 0;
+ }
+ } + }
+ +
+ this.sum = sum; + this.sum = sum;
@ -2934,6 +2943,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ +
+ public void addTime(final long currTime) { + public void addTime(final long currTime) {
+ this.addTime(currTime, 1L);
+ }
+
+ public void addTime(final long currTime, final long count) {
+ // guard against overflow by using subtraction + // guard against overflow by using subtraction
+ if (currTime - this.minTime < 0) { + if (currTime - this.minTime < 0) {
+ return; + return;
@ -2945,28 +2958,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ +
+ this.times[this.tail] = currTime; + this.times[this.tail] = currTime;
+ this.counts[this.tail] += count;
+ this.sum += count;
+ this.tail = nextTail; + this.tail = nextTail;
+ } + }
+ +
+ public void updateAndAdd(final int count) { + public void updateAndAdd(final int count) {
+ final long currTime = System.nanoTime(); + final long currTime = System.nanoTime();
+ this.updateCurrentTime(currTime); + this.updateCurrentTime(currTime);
+ for (int i = 0; i < count; ++i) { + this.addTime(currTime, count);
+ this.addTime(currTime);
+ }
+ } + }
+ +
+ public void updateAndAdd(final int count, final long currTime) { + public void updateAndAdd(final int count, final long currTime) {
+ this.updateCurrentTime(currTime); + this.updateCurrentTime(currTime);
+ for (int i = 0; i < count; ++i) { + this.addTime(currTime, count);
+ this.addTime(currTime);
+ }
+ } + }
+ +
+ private void resize() { + private void resize() {
+ final long[] oldElements = this.times; + final long[] oldElements = this.times;
+ final long[] oldCounts = this.counts;
+ final long[] newElements = new long[this.times.length * 2]; + final long[] newElements = new long[this.times.length * 2];
+ final long[] newCounts = new long[this.times.length * 2];
+ this.times = newElements; + this.times = newElements;
+ this.counts = newCounts;
+ +
+ final int head = this.head; + final int head = this.head;
+ final int tail = this.tail; + final int tail = this.tail;
@ -2976,9 +2990,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ if (tail >= head) { + if (tail >= head) {
+ System.arraycopy(oldElements, head, newElements, 0, size); + System.arraycopy(oldElements, head, newElements, 0, size);
+ System.arraycopy(oldCounts, head, newCounts, 0, size);
+ } else { + } else {
+ System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); + System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head);
+ System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail); + System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail);
+
+ System.arraycopy(oldCounts, head, newCounts, 0, oldCounts.length - head);
+ System.arraycopy(oldCounts, 0, newCounts, oldCounts.length - head, tail);
+ } + }
+ } + }
+ +
@ -2987,11 +3005,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return this.size() / (this.interval * 1.0e-9); + return this.size() / (this.interval * 1.0e-9);
+ } + }
+ +
+ public int size() { + public long size() {
+ final int head = this.head; + return this.sum;
+ final int tail = this.tail;
+
+ return tail >= head ? (tail - head) : (tail + (this.times.length - head));
+ } + }
+} +}
diff --git a/src/main/java/io/papermc/paper/util/WorldUtil.java b/src/main/java/io/papermc/paper/util/WorldUtil.java diff --git a/src/main/java/io/papermc/paper/util/WorldUtil.java b/src/main/java/io/papermc/paper/util/WorldUtil.java

View file

@ -14,8 +14,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig { @@ -0,0 +0,0 @@ public class PaperConfig {
}
globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0); globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0);
playerMaxChunkLoadRate = getDouble("settings.chunk-loading.player-max-chunk-load-rate", -1.0);
} }
+ +
+ public static boolean useDimensionTypeForCustomSpawners; + public static boolean useDimensionTypeForCustomSpawners;

View file

@ -102,6 +102,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ public static double globalMaxChunkLoadRate; + public static double globalMaxChunkLoadRate;
+ public static double playerMaxConcurrentChunkLoads; + public static double playerMaxConcurrentChunkLoads;
+ public static double globalMaxConcurrentChunkLoads; + public static double globalMaxConcurrentChunkLoads;
+ public static double playerMaxChunkLoadRate;
+ +
+ private static void newPlayerChunkManagement() { + private static void newPlayerChunkManagement() {
+ playerMinChunkLoadRadius = getInt("settings.chunk-loading.min-load-radius", 2); + playerMinChunkLoadRadius = getInt("settings.chunk-loading.min-load-radius", 2);
@ -119,6 +120,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ set("settings.chunk-loading.player-max-concurrent-loads", playerMaxConcurrentChunkLoads = 20.0); + set("settings.chunk-loading.player-max-concurrent-loads", playerMaxConcurrentChunkLoads = 20.0);
+ } + }
+ globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0); + globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0);
+ playerMaxChunkLoadRate = getDouble("settings.chunk-loading.player-max-chunk-load-rate", -1.0);
+ } + }
} }
diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
@ -218,10 +220,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority); + final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority);
+ +
+ if (priorityCompare != 0) { + final int lastLoadTimeCompare = Long.compare(p1.lastChunkLoad, p2.lastChunkLoad);
+
+ if ((holder1 == null || holder2 == null || lastLoadTimeCompare == 0 || holder1.priority < 0.0 || holder2.priority < 0.0) && priorityCompare != 0) {
+ return priorityCompare; + return priorityCompare;
+ } + }
+ +
+ if (lastLoadTimeCompare != 0) {
+ return lastLoadTimeCompare;
+ }
+
+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); + final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId());
+ +
+ if (idCompare != 0) { + if (idCompare != 0) {
@ -744,6 +752,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ for (;;) { + for (;;) {
+ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst(); + final PlayerLoaderData data = this.chunkLoadQueue.pollFirst();
+ +
+ data.lastChunkLoad = time;
+
+ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst(); + final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst();
+ if (queuedLoad == null) { + if (queuedLoad == null) {
+ if (this.chunkLoadQueue.isEmpty()) { + if (this.chunkLoadQueue.isEmpty()) {
@ -756,6 +766,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ updatedCounters = true; + updatedCounters = true;
+ TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time); + TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time);
+ TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time); + TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time);
+ data.ticketAdditionCounterShort.updateCurrentTime(time);
+ data.ticketAdditionCounterLong.updateCurrentTime(time);
+ } + }
+ +
+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { + if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
@ -791,7 +803,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ // priority >= 0.0 implies rate limited chunks + // priority >= 0.0 implies rate limited chunks
+ +
+ final int currentChunkLoads = this.concurrentChunkLoads; + final int currentChunkLoads = this.concurrentChunkLoads;
+ if (currentChunkLoads >= maxLoads || (PaperConfig.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= PaperConfig.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= PaperConfig.globalMaxChunkLoadRate))) { + if (currentChunkLoads >= maxLoads || (PaperConfig.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= PaperConfig.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= PaperConfig.globalMaxChunkLoadRate))
+ || (PaperConfig.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= PaperConfig.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= PaperConfig.playerMaxChunkLoadRate))) {
+ // don't poll, we didn't load it + // don't poll, we didn't load it
+ this.chunkLoadQueue.add(data); + this.chunkLoadQueue.add(data);
+ break; + break;
@ -821,6 +834,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ ++this.concurrentChunkLoads; + ++this.concurrentChunkLoads;
+ TICKET_ADDITION_COUNTER_SHORT.addTime(time); + TICKET_ADDITION_COUNTER_SHORT.addTime(time);
+ TICKET_ADDITION_COUNTER_LONG.addTime(time); + TICKET_ADDITION_COUNTER_LONG.addTime(time);
+ data.ticketAdditionCounterShort.addTime(time);
+ data.ticketAdditionCounterLong.addTime(time);
+ } + }
+ } + }
+ } + }
@ -926,6 +941,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ protected long nextChunkSendTarget; + protected long nextChunkSendTarget;
+ +
+ // this interval prevents bursting a lot of chunk loads
+ protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms
+ // this ensures the rate is kept between ticks correctly
+ protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms
+
+ public long lastChunkLoad;
+
+ public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) { + public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) {
+ this.player = player; + this.player = player;
+ this.loader = loader; + this.loader = loader;