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 7f47b9b7f8
commit 7bf9446d9e
6 changed files with 133 additions and 28 deletions

View File

@ -2890,23 +2890,25 @@ index 0000000000000000000000000000000000000000..a1bc1d1d0c86217ef18883d281195bc6
+} +}
diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java diff --git a/src/main/java/io/papermc/paper/util/IntervalledCounter.java b/src/main/java/io/papermc/paper/util/IntervalledCounter.java
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..059e8c61108cb78a80895cae36f2f8ac644e704c index 0000000000000000000000000000000000000000..cea9c098ade00ee87b8efc8164ab72f5279758f0
--- /dev/null --- /dev/null
+++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java +++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java
@@ -0,0 +1,100 @@ @@ -0,0 +1,115 @@
+package io.papermc.paper.util; +package io.papermc.paper.util;
+ +
+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..059e8c61108cb78a80895cae36f2f8ac
+ } + }
+ +
+ 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..059e8c61108cb78a80895cae36f2f8ac
+ +
+ // 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..059e8c61108cb78a80895cae36f2f8ac
+ } + }
+ +
+ 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..059e8c61108cb78a80895cae36f2f8ac
+ } + }
+ +
+ 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..059e8c61108cb78a80895cae36f2f8ac
+ +
+ 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..059e8c61108cb78a80895cae36f2f8ac
+ 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

@ -84,10 +84,10 @@ index 309dbf5fce3ce940d5e1b57d267b9d6b2c5ff5b6..5ba64e1083b7cb1eec64d1925095c6ca
})); }));
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 2e68fe31fc788a3ff1fe4ac52140e5e518f660b1..9bff729df7156b071b08913549838024bb17c3c9 100644 index 2e68fe31fc788a3ff1fe4ac52140e5e518f660b1..b7cb07e36c0056dea64dea220a465337f0d5c843 100644
--- 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
@@ -652,4 +652,33 @@ public class PaperConfig { @@ -652,4 +652,35 @@ public class PaperConfig {
private static void timeCommandAffectsAllWorlds() { private static void timeCommandAffectsAllWorlds() {
timeCommandAffectsAllWorlds = getBoolean("settings.time-command-affects-all-worlds", timeCommandAffectsAllWorlds); timeCommandAffectsAllWorlds = getBoolean("settings.time-command-affects-all-worlds", timeCommandAffectsAllWorlds);
} }
@ -102,6 +102,7 @@ index 2e68fe31fc788a3ff1fe4ac52140e5e518f660b1..9bff729df7156b071b08913549838024
+ 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,14 +120,15 @@ index 2e68fe31fc788a3ff1fe4ac52140e5e518f660b1..9bff729df7156b071b08913549838024
+ 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
new file mode 100644 new file mode 100644
index 0000000000000000000000000000000000000000..bdd4cc040111d18b82d3ebeb5dbe2537cf148090 index 0000000000000000000000000000000000000000..0f62a766a3249d8651a11dce6e9051b162693716
--- /dev/null --- /dev/null
+++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java +++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
@@ -0,0 +1,1108 @@ @@ -0,0 +1,1128 @@
+package io.papermc.paper.chunk; +package io.papermc.paper.chunk;
+ +
+import com.destroystokyo.paper.PaperConfig; +import com.destroystokyo.paper.PaperConfig;
@ -218,10 +220,16 @@ index 0000000000000000000000000000000000000000..bdd4cc040111d18b82d3ebeb5dbe2537
+ +
+ 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..bdd4cc040111d18b82d3ebeb5dbe2537
+ 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..bdd4cc040111d18b82d3ebeb5dbe2537
+ 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..bdd4cc040111d18b82d3ebeb5dbe2537
+ // 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..bdd4cc040111d18b82d3ebeb5dbe2537
+ ++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..bdd4cc040111d18b82d3ebeb5dbe2537
+ +
+ 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;

View File

@ -10,12 +10,12 @@ just looking at the LevelStem key, look at the DimensionType key which
is one level below that. Defaults to off to keep vanilla behavior. is one level below that. Defaults to off to keep vanilla behavior.
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 9bff729df7156b071b08913549838024bb17c3c9..88a4dda44e59fbe6215d7ac2e5af0c54527a2fc7 100644 index b7cb07e36c0056dea64dea220a465337f0d5c843..c9c08e20f729b40b1d90f3ac144f5245f4f35230 100644
--- 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
@@ -681,4 +681,9 @@ public class PaperConfig { @@ -683,4 +683,9 @@ 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

@ -7,7 +7,7 @@ Bring the vehicle move packet behavior in line with the
regular player move packet. regular player move packet.
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 952debf2fb4abf675e226224909b14c866528e62..68e1ab0057452228817ecbc6556338a8906c2538 100644 index 952debf2fb4abf675e226224909b14c866528e62..e4d54fdc28b6161e74626f25299b1081e6605e98 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -517,6 +517,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser @@ -517,6 +517,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
@ -15,7 +15,7 @@ index 952debf2fb4abf675e226224909b14c866528e62..68e1ab0057452228817ecbc6556338a8
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 8b57a24d4e8469dfbfb4eb2d11ca616e1db98598..26911884384d5e8afd1b43360494b793374f505f 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -2590,6 +2590,11 @@ 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 26911884384d5e8afd1b43360494b793374f505f..49cf3601df7b145d49b1fe9a71ba0bc60c5394b3 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -4090,11 +4090,33 @@ 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) {