aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0688-Execute-chunk-tasks-mid-tick.patch
blob: f4272a051257ffb62657aa4bc5af3ebaaed3a078 (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
177
178
179
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 678a31fb85947ee8807d02d0fe4e11a73d2dafaa..0fc4feb00940079c4eb9554a3a48d19cbbeeb7bc 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 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 7e4f3b7a5790292b3b8439fb4696d9d6cfec0167..6c02d5233c782b0e5049afdf84250687ae71dcde 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1285,6 +1285,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
 
     private boolean pollTaskInternal() {
         if (super.pollTask()) {
+            this.executeMidTickTasks(); // Paper - execute chunk tasks mid tick
             return true;
         } else {
             if (this.haveTime()) {
@@ -2704,4 +2705,74 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
         }
     }
     // Paper end
+
+    // 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
 }
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 7d7dc8246f44d6ffd46b05f917ffbdb9cc24372b..dbedc73039f53ddf042999524eec62f032a20d5d 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -571,6 +571,7 @@ public class ServerChunkCache extends ChunkSource {
             // Paper end
             Iterator iterator1 = list.iterator();
 
+            int chunksTicked = 0; // Paper
             while (iterator1.hasNext()) {
                 ServerChunkCache.ChunkAndHolder chunkproviderserver_a = (ServerChunkCache.ChunkAndHolder) iterator1.next();
                 LevelChunk chunk1 = chunkproviderserver_a.chunk;
@@ -584,6 +585,7 @@ public class ServerChunkCache extends ChunkSource {
 
                     if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
                         this.level.tickChunk(chunk1, k);
+                        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 80f2a1204bee79daf0cae9310ce8d552c59e66fe..5c39bf8a3975e632a4f41938306d9b2eb77f335d 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -215,6 +215,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;
@@ -1187,6 +1188,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
 
     }
 
@@ -1196,6 +1198,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 0f16a962082cf8a213cb348b87683692f818c80a..a21b2a100503b1fcceb700da11bef5866f3d178b 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -918,6 +918,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
                 // Spigot end
             } else if (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);
@@ -932,6 +937,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