PaperMC/patches/server/1024-Execute-chunk-tasks-mid-tick.patch
2024-01-23 14:39:23 +01:00

176 lines
9.1 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 6 Apr 2020 04:20:44 -0700
Subject: [PATCH] Execute chunk tasks mid-tick
This will help the server load chunks if tick times are high.
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
index 7a4234abf620b8394307fa01913bc00215a64e23..4b467f1af93452d13829f756d55dee18b8889d40 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -49,6 +49,8 @@ public final class MinecraftTimings {
public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Paper - add timings for scoreboard search
public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager
+ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
+
private static final Map<Class<?>, String> taskNameCache = new MapMaker().weakKeys().makeMap();
private MinecraftTimings() {}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index e2e37b75475c7c243245ca3ff78d29c51ff0ad39..399cb6d50b5a49ab873fab9b5f86c986364d49c3 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1353,8 +1353,79 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return flag;
}
+ // Paper start - execute chunk tasks mid tick
+ static final long CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME = 25L * 1000L; // 25us
+ static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us
+
+ static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us
+
+ private static long lastMidTickExecute;
+ private static long lastMidTickExecuteFailure;
+
+ private boolean tickMidTickTasks() {
+ // give all worlds a fair chance at by targetting them all.
+ // if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time.
+ boolean executed = false;
+ for (ServerLevel world : this.getAllLevels()) {
+ long currTime = System.nanoTime();
+ if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) {
+ continue;
+ }
+ if (!world.getChunkSource().pollTask()) {
+ // we need to back off if this fails
+ world.lastMidTickExecuteFailure = currTime;
+ } else {
+ executed = true;
+ }
+ }
+
+ return executed;
+ }
+
+ public final void executeMidTickTasks() {
+ org.spigotmc.AsyncCatcher.catchOp("mid tick chunk task execution");
+ long startTime = System.nanoTime();
+ if ((startTime - lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) {
+ // it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed.
+ // so, backoff to prevent this
+ return;
+ }
+
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming();
+ try {
+ for (;;) {
+ boolean moreTasks = this.tickMidTickTasks();
+ long currTime = System.nanoTime();
+ long diff = currTime - startTime;
+
+ if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) {
+ if (!moreTasks) {
+ lastMidTickExecuteFailure = currTime;
+ }
+
+ // note: negative values reduce the time
+ long overuse = diff - MAX_CHUNK_EXEC_TIME;
+ if (overuse >= (10L * 1000L * 1000L)) { // 10ms
+ // make sure something like a GC or dumb plugin doesn't screw us over...
+ overuse = 10L * 1000L * 1000L; // 10ms
+ }
+
+ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME;
+ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME);
+
+ lastMidTickExecute = currTime + extraSleep;
+ return;
+ }
+ }
+ } finally {
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming();
+ }
+ }
+ // Paper end - execute chunk tasks mid tick
+
private boolean pollTaskInternal() {
if (super.pollTask()) {
+ this.executeMidTickTasks(); // Paper - execute chunk tasks mid tick
return true;
} else {
boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 3b0fd733e8fd9639362f251876bea814a2515618..c2973fcfde402b5432db37468e4a40a1a119cca1 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -583,6 +583,7 @@ public class ServerChunkCache extends ChunkSource {
boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
Iterator iterator1 = list.iterator();
+ int chunksTicked = 0; // Paper
while (iterator1.hasNext()) {
ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next();
LevelChunk chunk1 = chunkproviderserver_a.chunk;
@@ -596,6 +597,7 @@ public class ServerChunkCache extends ChunkSource {
if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
this.level.tickChunk(chunk1, l);
+ if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
}
}
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index 715f6fb68d832545f36184a77f2229c2a1a79f75..ac5c505a9d280fb128bbc59526bb8bbe7aaaf307 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -216,6 +216,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
private final StructureCheck structureCheck;
private final boolean tickTime;
private final RandomSequences randomSequences;
+ public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick
// CraftBukkit start
public final LevelStorageSource.LevelStorageAccess convertable;
@@ -1205,6 +1206,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (fluid1.is(fluid)) {
fluid1.tick(this, pos);
}
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick
}
@@ -1214,6 +1216,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (iblockdata.is(block)) {
iblockdata.tick(this, pos, this.random);
}
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick
}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index ec6b607f6564acceddd694d65ab6014f129250d6..b47e9d029c72f1ce397fcfaa3d6da8bddb2cb4a8 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -929,6 +929,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
// Spigot end
} else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) {
tickingblockentity.tick();
+ // Paper start - execute chunk tasks during tick
+ if ((this.tileTickPosition & 7) == 0) {
+ MinecraftServer.getServer().executeMidTickTasks();
+ }
+ // Paper end - execute chunk tasks during tick
}
}
this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075
@@ -943,6 +948,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
try {
tickConsumer.accept(entity);
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick
} catch (Throwable throwable) {
if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent tile entity and entity crashes