aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/1003-Execute-chunk-tasks-mid-tick.patch
blob: 6ee2d94ec969e9809061e97d0c8c53840352f180 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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 6b3cde6d4d1e63bec01f502f2027ee9fddac08aa..46449728f69ee7d4f78470f8da23c055acd53a3b 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -48,6 +48,8 @@ public final class MinecraftTimings {
     public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
     public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Paper - add timings for scoreboard search
 
+    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 478d238c8d8d41a1d27f305849bd216ec2e894f5..4f076eae3a9c597e41f4520dae8378ec429d9f69 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1411,8 +1411,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 c7b7f153895a4b95b2071a31db00c9c4b69fa094..7fbeebe63f755624b967374072aa2e0565ce8c35 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -559,6 +559,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;
@@ -572,6 +573,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 1ec7865e2e2bd23607e9b3041d77bd4badf39a4a..3a49f8933bc6ca1862994b7e0b3006f5236dd94a 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -221,6 +221,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;
@@ -1211,6 +1212,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
 
     }
 
@@ -1220,6 +1222,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 d3d7abb2d31e8ce9f9c53eca66a83a1c28fec792..dab55ab08665f2b5ae0c899a4ab07c18460552ae 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -915,6 +915,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
@@ -929,6 +934,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 block entity and entity crashes