From 7bf9446d9e620f6177635ecfb712f75c1944210d Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Thu, 31 Mar 2022 06:04:23 -0700 Subject: [PATCH] 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. --- patches/server/0007-MC-Utils.patch | 49 ++++++++++++------- ...7-Replace-player-chunk-loader-system.patch | 34 ++++++++++--- ...efault-CustomSpawners-in-custom-worl.patch | 6 +-- ...cle-movement-from-players-while-tele.patch | 4 +- ...assenger-world-matches-ridden-entity.patch | 23 +++++++++ ...ard-against-invalid-entity-positions.patch | 45 +++++++++++++++++ 6 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 patches/server/0886-Ensure-entity-passenger-world-matches-ridden-entity.patch create mode 100644 patches/server/0887-Guard-against-invalid-entity-positions.patch diff --git a/patches/server/0007-MC-Utils.patch b/patches/server/0007-MC-Utils.patch index f8b5ec815..0928d4a02 100644 --- a/patches/server/0007-MC-Utils.patch +++ b/patches/server/0007-MC-Utils.patch @@ -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 new file mode 100644 -index 0000000000000000000000000000000000000000..059e8c61108cb78a80895cae36f2f8ac644e704c +index 0000000000000000000000000000000000000000..cea9c098ade00ee87b8efc8164ab72f5279758f0 --- /dev/null +++ b/src/main/java/io/papermc/paper/util/IntervalledCounter.java -@@ -0,0 +1,100 @@ +@@ -0,0 +1,115 @@ +package io.papermc.paper.util; + +public final class IntervalledCounter { + + protected long[] times; ++ protected long[] counts; + protected final long interval; + protected long minTime; -+ protected int sum; ++ protected long sum; + protected int head; // inclusive + protected int tail; // exclusive + + public IntervalledCounter(final long interval) { + this.times = new long[8]; ++ this.counts = new long[8]; + this.interval = interval; + } + @@ -2915,7 +2917,7 @@ index 0000000000000000000000000000000000000000..059e8c61108cb78a80895cae36f2f8ac + } + + public void updateCurrentTime(final long currentTime) { -+ int sum = this.sum; ++ long sum = this.sum; + int head = this.head; + final int tail = this.tail; + final long minTime = currentTime - this.interval; @@ -2924,8 +2926,15 @@ index 0000000000000000000000000000000000000000..059e8c61108cb78a80895cae36f2f8ac + + // guard against overflow by using subtraction + while (head != tail && this.times[head] - minTime < 0) { -+ head = (head + 1) % arrayLen; -+ --sum; ++ sum -= this.counts[head]; ++ // 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; @@ -2934,6 +2943,10 @@ index 0000000000000000000000000000000000000000..059e8c61108cb78a80895cae36f2f8ac + } + + 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 + if (currTime - this.minTime < 0) { + return; @@ -2945,28 +2958,29 @@ index 0000000000000000000000000000000000000000..059e8c61108cb78a80895cae36f2f8ac + } + + this.times[this.tail] = currTime; ++ this.counts[this.tail] += count; ++ this.sum += count; + this.tail = nextTail; + } + + public void updateAndAdd(final int count) { + final long currTime = System.nanoTime(); + this.updateCurrentTime(currTime); -+ for (int i = 0; i < count; ++i) { -+ this.addTime(currTime); -+ } ++ this.addTime(currTime, count); + } + + public void updateAndAdd(final int count, final long currTime) { + this.updateCurrentTime(currTime); -+ for (int i = 0; i < count; ++i) { -+ this.addTime(currTime); -+ } ++ this.addTime(currTime, count); + } + + private void resize() { + final long[] oldElements = this.times; ++ final long[] oldCounts = this.counts; + final long[] newElements = new long[this.times.length * 2]; ++ final long[] newCounts = new long[this.times.length * 2]; + this.times = newElements; ++ this.counts = newCounts; + + final int head = this.head; + final int tail = this.tail; @@ -2976,9 +2990,13 @@ index 0000000000000000000000000000000000000000..059e8c61108cb78a80895cae36f2f8ac + + if (tail >= head) { + System.arraycopy(oldElements, head, newElements, 0, size); ++ System.arraycopy(oldCounts, head, newCounts, 0, size); + } else { + System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); + 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); + } + -+ public int size() { -+ final int head = this.head; -+ final int tail = this.tail; -+ -+ return tail >= head ? (tail - head) : (tail + (this.times.length - head)); ++ public long size() { ++ return this.sum; + } +} 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/patches/server/0857-Replace-player-chunk-loader-system.patch b/patches/server/0857-Replace-player-chunk-loader-system.patch index 46d7fd489..9dac047cd 100644 --- a/patches/server/0857-Replace-player-chunk-loader-system.patch +++ b/patches/server/0857-Replace-player-chunk-loader-system.patch @@ -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 -index 2e68fe31fc788a3ff1fe4ac52140e5e518f660b1..9bff729df7156b071b08913549838024bb17c3c9 100644 +index 2e68fe31fc788a3ff1fe4ac52140e5e518f660b1..b7cb07e36c0056dea64dea220a465337f0d5c843 100644 --- a/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() { timeCommandAffectsAllWorlds = getBoolean("settings.time-command-affects-all-worlds", timeCommandAffectsAllWorlds); } @@ -102,6 +102,7 @@ index 2e68fe31fc788a3ff1fe4ac52140e5e518f660b1..9bff729df7156b071b08913549838024 + public static double globalMaxChunkLoadRate; + public static double playerMaxConcurrentChunkLoads; + public static double globalMaxConcurrentChunkLoads; ++ public static double playerMaxChunkLoadRate; + + private static void newPlayerChunkManagement() { + 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); + } + 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 new file mode 100644 -index 0000000000000000000000000000000000000000..bdd4cc040111d18b82d3ebeb5dbe2537cf148090 +index 0000000000000000000000000000000000000000..0f62a766a3249d8651a11dce6e9051b162693716 --- /dev/null +++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java -@@ -0,0 +1,1108 @@ +@@ -0,0 +1,1128 @@ +package io.papermc.paper.chunk; + +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); + -+ 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; + } + ++ if (lastLoadTimeCompare != 0) { ++ return lastLoadTimeCompare; ++ } ++ + final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); + + if (idCompare != 0) { @@ -744,6 +752,8 @@ index 0000000000000000000000000000000000000000..bdd4cc040111d18b82d3ebeb5dbe2537 + for (;;) { + final PlayerLoaderData data = this.chunkLoadQueue.pollFirst(); + ++ data.lastChunkLoad = time; ++ + final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst(); + if (queuedLoad == null) { + if (this.chunkLoadQueue.isEmpty()) { @@ -756,6 +766,8 @@ index 0000000000000000000000000000000000000000..bdd4cc040111d18b82d3ebeb5dbe2537 + updatedCounters = true; + TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time); + TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time); ++ data.ticketAdditionCounterShort.updateCurrentTime(time); ++ data.ticketAdditionCounterLong.updateCurrentTime(time); + } + + if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { @@ -791,7 +803,8 @@ index 0000000000000000000000000000000000000000..bdd4cc040111d18b82d3ebeb5dbe2537 + // priority >= 0.0 implies rate limited chunks + + 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 + this.chunkLoadQueue.add(data); + break; @@ -821,6 +834,8 @@ index 0000000000000000000000000000000000000000..bdd4cc040111d18b82d3ebeb5dbe2537 + ++this.concurrentChunkLoads; + TICKET_ADDITION_COUNTER_SHORT.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; + ++ // 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) { + this.player = player; + this.loader = loader; diff --git a/patches/server/0863-Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/0863-Option-to-have-default-CustomSpawners-in-custom-worl.patch index e4ea20bd1..5711927a4 100644 --- a/patches/server/0863-Option-to-have-default-CustomSpawners-in-custom-worl.patch +++ b/patches/server/0863-Option-to-have-default-CustomSpawners-in-custom-worl.patch @@ -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. 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 +++ 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); + playerMaxChunkLoadRate = getDouble("settings.chunk-loading.player-max-chunk-load-rate", -1.0); } + + public static boolean useDimensionTypeForCustomSpawners; diff --git a/patches/server/0876-Don-t-allow-vehicle-movement-from-players-while-tele.patch b/patches/server/0876-Don-t-allow-vehicle-movement-from-players-while-tele.patch index bd0b39982..d7c1d836f 100644 --- a/patches/server/0876-Don-t-allow-vehicle-movement-from-players-while-tele.patch +++ b/patches/server/0876-Don-t-allow-vehicle-movement-from-players-while-tele.patch @@ -7,7 +7,7 @@ Bring the vehicle move packet behavior in line with the 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 -index 952debf2fb4abf675e226224909b14c866528e62..68e1ab0057452228817ecbc6556338a8906c2538 100644 +index 952debf2fb4abf675e226224909b14c866528e62..e4d54fdc28b6161e74626f25299b1081e6605e98 100644 --- a/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 @@ -15,7 +15,7 @@ index 952debf2fb4abf675e226224909b14c866528e62..68e1ab0057452228817ecbc6556338a8 Entity entity = this.player.getRootVehicle(); + // Paper start -+ if (this.awaitingPositionFromClient != null) { ++ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { + return; + } + // Paper end diff --git a/patches/server/0886-Ensure-entity-passenger-world-matches-ridden-entity.patch b/patches/server/0886-Ensure-entity-passenger-world-matches-ridden-entity.patch new file mode 100644 index 000000000..7de0352db --- /dev/null +++ b/patches/server/0886-Ensure-entity-passenger-world-matches-ridden-entity.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +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)"); diff --git a/patches/server/0887-Guard-against-invalid-entity-positions.patch b/patches/server/0887-Guard-against-invalid-entity-positions.patch new file mode 100644 index 000000000..a9bce4333 --- /dev/null +++ b/patches/server/0887-Guard-against-invalid-entity-positions.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +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) {