aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0750-Execute-chunk-tasks-mid-tick.patch
blob: 3705f009e8c46f9745c85367b7eb6287dabb45db (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
180
181
182
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
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 b27021a42cbed3f0648a8d0903d00d03922ae221..eada966d7f108a6081be7a848f5c1dfcb1eed676 100644
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
@@ -45,6 +45,8 @@ public final class MinecraftTimings {
     public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
     public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
 
+    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 ce40b5999e97b1bc56342876861b5bdad18d6cd6..ee33fd892e97e1ffea259a4f7974675923377d6b 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1346,6 +1346,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()) {
@@ -2669,4 +2670,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 d5eb5a365c8e8cdcd8e9cf54918cc2fb383c6625..b73f49b9bf49dc2a08aa8ffece675df41c077412 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -1019,6 +1019,7 @@ public class ServerChunkCache extends ChunkSource {
                 iterator1 = shuffled.iterator();
             }
 
+            int chunksTicked = 0; // Paper
             try {
             while (iterator1.hasNext()) {
                 LevelChunk chunk1 = iterator1.next();
@@ -1036,6 +1037,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
                     }
                 }
                 // Paper start - optimise chunk tick iteration
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index eace9e9c97fff7d02326805ce77cc75a650f9b6d..381a4283715290162f445265e61294256e6b3075 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -202,7 +202,9 @@ public class ServerLevel extends Level implements WorldGenLevel {
     private final StructureFeatureManager structureFeatureManager;
     private final StructureCheck structureCheck;
     private final boolean tickTime;
-
+    // Paper start - execute chunk tasks mid tick
+    public long lastMidTickExecuteFailure;
+    // Paper end - execute chunk tasks mid tick
 
     // CraftBukkit start
     private int tickPosition;
@@ -965,6 +967,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
 
     }
 
@@ -974,6 +977,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 944519c4433710610ac5015d3d3de380d9ec39c9..06035b728a66a63582c34c85096bada1588bfaa6 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -838,6 +838,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
                 // Spigot end
             } else if (this.shouldTickBlocksAt(ChunkPos.asLong(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);
@@ -852,6 +857,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