Yatopia/patches/Tuinity/patches/server/0009-Delay-chunk-unloads.patch

261 lines
14 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Fri, 25 Oct 2019 02:11:30 -0700
Subject: [PATCH] Delay chunk unloads
Chunk unloads are now delayed by 1s. Specifically, ticket level
reduction is delayed by 1s. This is done to allow players to
teleport and have their pets follow them, as the chunks will no longer
unload or have entity ticking status removed.
It's also targetted to reduce performance regressions when
plugins or edge cases in code do not spam sync loads since chunks
without tickets get unloaded immediately.
Configurable under `delay-chunkunloads-by` in config.
This patch replaces the paper patch as the paper patch only
affects player loaded chunks, when we want to target all
loads.
diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
index f10fa659680f8a574f77d260bbc52be349c244e8..182f419fde8eb3646a79cc0ba689ee486cb53338 100644
--- a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
+++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java
@@ -1,6 +1,7 @@
package com.tuinity.tuinity.config;
import com.destroystokyo.paper.util.SneakyThrow;
+import net.minecraft.server.level.TicketType;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
@@ -123,6 +124,15 @@ public final class TuinityConfig {
tickThreads = TuinityConfig.getInt("server-tick-threads", 1); // will be 4 in the future
}*/
+ public static int delayChunkUnloadsBy;
+
+ private static void delayChunkUnloadsBy() {
+ delayChunkUnloadsBy = TuinityConfig.getInt("delay-chunkunloads-by", 5) * 20;
+ if (delayChunkUnloadsBy >= 0) {
+ TicketType.DELAYED_UNLOAD.loadPeriod = delayChunkUnloadsBy;
+ }
+ }
+
public static final class WorldConfig {
public final String worldName;
diff --git a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java
index 4dfc1101d5fc78630ef8b816e8f84f5541683f0b..c474ee61d98772a2852c44dec1c4a1037472ed2c 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMapDistance.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMapDistance.java
@@ -40,7 +40,7 @@ public abstract class ChunkMapDistance {
private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2;
private final Long2ObjectMap<ObjectSet<EntityPlayer>> c = new Long2ObjectOpenHashMap();
public final Long2ObjectOpenHashMap<ArraySetSorted<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
- private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a();
+ private final ChunkMapDistance.a ticketLevelTracker = new ChunkMapDistance.a(); final ChunkMapDistance.a getTicketTracker() { return this.ticketLevelTracker; } // Tuinity - OBFHELPER
public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
private final ChunkMapDistance.c g = new ChunkMapDistance.c(33);
// Paper start use a queue, but still keep unique requirement
@@ -62,6 +62,47 @@ public abstract class ChunkMapDistance {
PlayerChunkMap chunkMap; // Paper
+ // Tuinity start - delay chunk unloads
+ private long nextUnloadId; // delay chunk unloads
+ private final Long2ObjectOpenHashMap<Ticket<Long>> delayedChunks = new Long2ObjectOpenHashMap<>();
+ public final void removeTickets(long chunk, TicketType<?> type) {
+ ArraySetSorted<Ticket<?>> tickets = this.tickets.get(chunk);
+ if (tickets == null) {
+ return;
+ }
+ if (type == TicketType.DELAYED_UNLOAD) {
+ this.delayedChunks.remove(chunk);
+ }
+ boolean changed = tickets.removeIf((Ticket<?> ticket) -> {
+ return ticket.getTicketType() == type;
+ });
+ if (changed) {
+ this.getTicketTracker().update(chunk, getLowestTicketLevel(tickets), false);
+ }
+ }
+
+ private final java.util.function.LongFunction<Ticket<Long>> computeFuntion = (long key) -> {
+ Ticket<Long> ret = new Ticket<>(TicketType.DELAYED_UNLOAD, -1, ++ChunkMapDistance.this.nextUnloadId);
+ ret.isCached = true;
+ return ret;
+ };
+
+ private void computeDelayedTicketFor(long chunk, int removedLevel, ArraySetSorted<Ticket<?>> tickets) {
+ int lowestLevel = getLowestTicketLevel(tickets);
+ if (removedLevel > lowestLevel) {
+ return;
+ }
+ final Ticket<Long> ticket = this.delayedChunks.computeIfAbsent(chunk, this.computeFuntion);
+ if (ticket.getTicketLevel() != -1) {
+ // since we modify data used in sorting, we need to remove before
+ tickets.remove(ticket);
+ }
+ ticket.setCreationTick(this.currentTick);
+ ticket.setTicketLevel(removedLevel);
+ tickets.add(ticket); // re-add with new expire time and ticket level
+ }
+ // Tuinity end - delay chunk unloads
+
protected ChunkMapDistance(Executor executor, Executor executor1) {
executor1.getClass();
Mailbox<Runnable> mailbox = Mailbox.a("player ticket throttler", executor1::execute);
@@ -78,18 +119,41 @@ public abstract class ChunkMapDistance {
++this.currentTick;
ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
+ // Tuinity start - delay chunk unloads
+ int[] tempLevel = new int[] { PlayerChunkMap.GOLDEN_TICKET + 1 };
+ Entry<ArraySetSorted<Ticket<?>>>[] entryPass = new Entry[1];
+ java.util.function.Predicate<Ticket<?>> isExpired = (ticket) -> { // CraftBukkit - decompile error
+ // Tuinity start - delay chunk unloads
+ boolean ret = ticket.isExpired(this.currentTick);
+ if (com.tuinity.tuinity.config.TuinityConfig.delayChunkUnloadsBy <= 0) {
+ return ret;
+ }
+ if (ret && ticket.getTicketType().delayUnloadViable && ticket.getTicketLevel() < tempLevel[0]) {
+ tempLevel[0] = ticket.getTicketLevel();
+ }
+ if (ret && ticket.getTicketType() == TicketType.DELAYED_UNLOAD && ticket.isCached) {
+ this.delayedChunks.remove(entryPass[0].getLongKey(), ticket); // clean up ticket...
+ }
+ return ret;
+ };
+ // Tuinity end - delay chunk unloads
while (objectiterator.hasNext()) {
- Entry<ArraySetSorted<Ticket<?>>> entry = (Entry) objectiterator.next();
+ Entry<ArraySetSorted<Ticket<?>>> entry = (Entry) objectiterator.next(); entryPass[0] = entry; // Tuinity - only allocate lambda once
- if ((entry.getValue()).removeIf((ticket) -> { // CraftBukkit - decompile error
- return ticket.b(this.currentTick);
- })) {
+ if ((entry.getValue()).removeIf(isExpired)) { // Tuinity - move above - only allocate once
+ // Tuinity start - delay chunk unloads
+ if (tempLevel[0] < (PlayerChunkMap.GOLDEN_TICKET + 1)) {
+ this.computeDelayedTicketFor(entry.getLongKey(), tempLevel[0], entry.getValue());
+ }
+ // Tuinity end - delay chunk unloads
this.ticketLevelTracker.update(entry.getLongKey(), getLowestTicketLevel((ArraySetSorted) entry.getValue()), false);
}
if (((ArraySetSorted) entry.getValue()).isEmpty()) {
objectiterator.remove();
}
+
+ tempLevel[0] = PlayerChunkMap.GOLDEN_TICKET + 1; // Tuinity - reset
}
}
@@ -187,27 +251,11 @@ public abstract class ChunkMapDistance {
boolean removed = false; // CraftBukkit
if (arraysetsorted.remove(ticket)) {
removed = true; // CraftBukkit
- // Paper start - delay chunk unloads for player tickets
- long delayChunkUnloadsBy = chunkMap.world.paperConfig.delayChunkUnloadsBy;
- if (ticket.getTicketType() == TicketType.PLAYER && delayChunkUnloadsBy > 0) {
- boolean hasPlayer = false;
- for (Ticket<?> ticket1 : arraysetsorted) {
- if (ticket1.getTicketType() == TicketType.PLAYER) {
- hasPlayer = true;
- break;
- }
- }
- PlayerChunk playerChunk = chunkMap.getUpdatingChunk(i);
- if (!hasPlayer && playerChunk != null && playerChunk.isFullChunkReady()) {
- Ticket<Long> delayUnload = new Ticket<Long>(TicketType.DELAY_UNLOAD, 33, i);
- delayUnload.delayUnloadBy = delayChunkUnloadsBy;
- delayUnload.setCurrentTick(this.currentTick);
- arraysetsorted.remove(delayUnload);
- // refresh ticket
- arraysetsorted.add(delayUnload);
- }
+ // Tuinity start - delay chunk unloads
+ if (com.tuinity.tuinity.config.TuinityConfig.delayChunkUnloadsBy > 0 && ticket.getTicketType().delayUnloadViable) {
+ this.computeDelayedTicketFor(i, ticket.getTicketLevel(), arraysetsorted);
}
- // Paper end
+ // Tuinity end - delay chunk unloads
}
if (arraysetsorted.isEmpty()) {
diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java
index 6e5ae954c6eb40590bf8c83f592c22088d489be8..c9b395dd0e4881a2e2e409bf782491b42ff087e6 100644
--- a/src/main/java/net/minecraft/server/level/Ticket.java
+++ b/src/main/java/net/minecraft/server/level/Ticket.java
@@ -5,17 +5,17 @@ import java.util.Objects;
public final class Ticket<T> implements Comparable<Ticket<?>> {
private final TicketType<T> a;
- private final int b;
+ private int b; public final void setTicketLevel(final int value) { this.b = value; } // Tuinity - remove final, add set OBFHELPER
public final T identifier; public final T getObjectReason() { return this.identifier; } // Paper - OBFHELPER
- private long d; public final long getCreationTick() { return this.d; } // Paper - OBFHELPER
+ private long d; public final long getCreationTick() { return this.d; } public final void setCreationTick(final long value) { this.d = value; } // Paper - OBFHELPER // Tuinity - OBFHELPER
public int priority = 0; // Paper
- public long delayUnloadBy; // Paper
+ boolean isCached; // Tuinity - delay chunk unloads, this defends against really stupid plugins
protected Ticket(TicketType<T> tickettype, int i, T t0) {
this.a = tickettype;
this.b = i;
this.identifier = t0;
- this.delayUnloadBy = tickettype.loadPeriod; // Paper
+ // Tuinity - delay chunk unloads
}
public int compareTo(Ticket<?> ticket) {
@@ -64,8 +64,9 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
this.d = i;
}
+ protected final boolean isExpired(long time) { return this.b(time); } // Tuinity - OBFHELPER
protected boolean b(long i) {
- long j = delayUnloadBy; // Paper
+ long j = this.a.b(); // Tuinity - delay chunk unloads
return j != 0L && i - this.d > j;
}
diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
index 3c804c7b20a14ea6e510810e2be10c1cc89ff5c1..47da7efffde2e6d63c1a064b950abf8135705622 100644
--- a/src/main/java/net/minecraft/server/level/TicketType.java
+++ b/src/main/java/net/minecraft/server/level/TicketType.java
@@ -30,8 +30,18 @@ public class TicketType<T> {
public static final TicketType<Long> ASYNC_LOAD = a("async_load", Long::compareTo); // Paper
public static final TicketType<ChunkCoordIntPair> PRIORITY = a("priority", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
public static final TicketType<ChunkCoordIntPair> URGENT = a("urgent", Comparator.comparingLong(ChunkCoordIntPair::pair), 300); // Paper
- public static final TicketType<Long> DELAY_UNLOAD = a("delay_unload", Long::compareTo, 300); // Paper
+ public static final TicketType<Long> DELAYED_UNLOAD = a("delayed_unload", Long::compareTo); // Tuinity - delay chunk unloads
+ // Tuinity start - delay chunk unloads
+ boolean delayUnloadViable = true;
+ static {
+ TicketType.LIGHT.delayUnloadViable = false;
+ TicketType.PLUGIN.delayUnloadViable = false;
+ TicketType.PRIORITY.delayUnloadViable = false;
+ TicketType.URGENT.delayUnloadViable = false;
+ TicketType.DELAYED_UNLOAD.delayUnloadViable = false;
+ }
+ // Tuinity end - delay chunk unloads
public static <T> TicketType<T> a(String s, Comparator<T> comparator) {
return new TicketType<>(s, comparator, 0L);
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 05098332d83b1abfaa0a6d3bd4a9e801ea90d2ad..8484542c108b69a310b19806db5e32a04e3ee991 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -505,6 +505,7 @@ public class CraftWorld implements World {
org.spigotmc.AsyncCatcher.catchOp("chunk unload"); // Spigot
if (isChunkLoaded(x, z)) {
world.getChunkProvider().removeTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 0, Unit.INSTANCE); // Paper
+ ((ChunkMapDistance)world.getChunkProvider().playerChunkMap.chunkDistanceManager).removeTickets(ChunkCoordIntPair.pair(x, z), TicketType.DELAYED_UNLOAD); // Tuinity - delay chunk unloads - let plugins override
}
return true;