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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
|
From b85bbfb42e8b12aa8e73894b0ad478c734d12f1d Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 2 Mar 2016 00:52:31 -0600
Subject: [PATCH] Lighting Queue
This provides option to queue lighting updates to ensure they do not cause the server lag
diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
index 145cb274b0..eff9dcf54f 100644
--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
@@ -50,6 +50,8 @@ public class WorldTimingsHandler {
public final Timing worldSaveLevel;
public final Timing chunkSaveData;
+ public final Timing lightingQueueTimer;
+
public WorldTimingsHandler(World server) {
String name = server.worldData.getName() +" - ";
@@ -96,6 +98,8 @@ public class WorldTimingsHandler {
tracker2 = Timings.ofSafe(name + "tracker stage 2");
doTick = Timings.ofSafe(name + "doTick");
tickEntities = Timings.ofSafe(name + "tickEntities");
+
+ lightingQueueTimer = Timings.ofSafe(name + "Lighting Queue");
}
public static Timing getTickList(WorldServer worldserver, String timingsType) {
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
index 7691409f6c..cfcc244672 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -191,6 +191,13 @@ public class PaperConfig {
return config.getString(path, config.getString(path));
}
+ public static int maxTickMsLostLightQueue;
+ private static void lightQueue() {
+ int badSetting = config.getInt("queue-light-updates-max-loss", 10);
+ config.set("queue-light-updates-max-loss", null);
+ maxTickMsLostLightQueue = getInt("settings.queue-light-updates-max-loss", badSetting);
+ }
+
private static void timings() {
boolean timings = getBoolean("timings.enabled", true);
boolean verboseTimings = getBoolean("timings.verbose", true);
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index a797a57671..59d82fa4fb 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -139,4 +139,12 @@ public class PaperWorldConfig {
}
}
}
+
+ public boolean queueLightUpdates;
+ private void queueLightUpdates() {
+ queueLightUpdates = getBoolean("queue-light-updates", false);
+ log("Lighting Queue enabled: " + queueLightUpdates);
+ log("Warning: This feature may help reduce TPS loss from light, but comes at the cost of buggy light data");
+ log("We are working to improve this feature.");
+ }
}
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index e15ed21f67..a9aa13fbf8 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -94,6 +94,7 @@ public class Chunk implements IChunkAccess {
return removed;
}
}
+ final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this);
// Paper end
public boolean areNeighborsLoaded(final int radius) {
switch (radius) {
@@ -284,7 +285,7 @@ public class Chunk implements IChunkAccess {
private void g(boolean flag) {
this.world.methodProfiler.enter("recheckGaps");
- if (this.world.areChunksLoaded(new BlockPosition(this.locX * 16 + 8, 0, this.locZ * 16 + 8), 16)) {
+ if (this.areNeighborsLoaded(1)) { // Paper
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 16; ++j) {
if (this.g[i + j * 16]) {
@@ -335,7 +336,7 @@ public class Chunk implements IChunkAccess {
}
private void a(int i, int j, int k, int l) {
- if (l > k && this.world.areChunksLoaded(new BlockPosition(i, 0, j), 16)) {
+ if (l > k && this.areNeighborsLoaded(1)) { // Paper
for (int i1 = k; i1 < l; ++i1) {
this.world.c(EnumSkyBlock.SKY, new BlockPosition(i, i1, j));
}
@@ -535,6 +536,7 @@ public class Chunk implements IChunkAccess {
if (flag1) {
this.initLighting();
} else {
+ this.runOrQueueLightUpdate(() -> { // Paper - Queue light update
int i1 = iblockdata.b(this.world, blockposition);
int j1 = iblockdata1.b(this.world, blockposition);
@@ -542,6 +544,7 @@ public class Chunk implements IChunkAccess {
if (i1 != j1 && (i1 < j1 || this.getBrightness(EnumSkyBlock.SKY, blockposition) > 0 || this.getBrightness(EnumSkyBlock.BLOCK, blockposition) > 0)) {
this.c(i, k);
}
+ }); // Paper
}
TileEntity tileentity;
@@ -1358,6 +1361,16 @@ public class Chunk implements IChunkAccess {
return this.D == 8;
}
+ // Paper start
+ public void runOrQueueLightUpdate(Runnable runnable) {
+ if (this.world.paperConfig.queueLightUpdates) {
+ lightingQueue.add(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+ // Paper end
+
public static enum EnumTileEntityState {
IMMEDIATE, QUEUED, CHECK;
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index eb83e20d50..c2ecc034e8 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -313,6 +313,7 @@ public class ChunkProviderServer implements IChunkProvider {
return false;
}
save = event.isSaveChunk();
+ chunk.lightingQueue.processUnload(); // Paper
// Update neighbor counts
for (int x = -2; x < 3; x++) {
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 7ca7b9f3ac..00f1c36e99 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -890,7 +890,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
protected void a(BooleanSupplier booleansupplier) {
co.aikar.timings.TimingsManager.FULL_SERVER_TICK.startTiming(); // Paper
this.slackActivityAccountant.tickStarted(); // Spigot
- long i = SystemUtils.getMonotonicNanos();
+ long i = SystemUtils.getMonotonicNanos(); long startTime = i; // Paper
++this.ticks;
if (this.S) {
@@ -948,6 +948,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
this.methodProfiler.exit();
this.methodProfiler.exit();
org.spigotmc.WatchdogThread.tick(); // Spigot
+ PaperLightingQueue.processQueue(startTime); // Paper
this.slackActivityAccountant.tickEnded(l); // Spigot
co.aikar.timings.TimingsManager.FULL_SERVER_TICK.stopTiming(); // Paper
}
diff --git a/src/main/java/net/minecraft/server/PaperLightingQueue.java b/src/main/java/net/minecraft/server/PaperLightingQueue.java
new file mode 100644
index 0000000000..9783f3a0d9
--- /dev/null
+++ b/src/main/java/net/minecraft/server/PaperLightingQueue.java
@@ -0,0 +1,98 @@
+package net.minecraft.server;
+
+import co.aikar.timings.Timing;
+import com.destroystokyo.paper.PaperConfig;
+import it.unimi.dsi.fastutil.objects.ObjectCollection;
+
+import java.util.ArrayDeque;
+
+class PaperLightingQueue {
+ private static final long MAX_TIME = (long) (1000000 * (50 + PaperConfig.maxTickMsLostLightQueue));
+
+ static void processQueue(long curTime) {
+ final long startTime = System.nanoTime();
+ final long maxTickTime = MAX_TIME - (startTime - curTime);
+
+ if (maxTickTime <= 0) {
+ return;
+ }
+
+ START:
+ for (World world : MinecraftServer.getServer().getWorlds()) {
+ if (!world.paperConfig.queueLightUpdates) {
+ continue;
+ }
+
+ ObjectCollection<Chunk> loadedChunks = ((WorldServer) world).getChunkProvider().chunks.values();
+ for (Chunk chunk : loadedChunks.toArray(new Chunk[0])) {
+ if (chunk.lightingQueue.processQueue(startTime, maxTickTime)) {
+ break START;
+ }
+ }
+ }
+ }
+
+ static class LightingQueue extends ArrayDeque<Runnable> {
+ final private Chunk chunk;
+
+ LightingQueue(Chunk chunk) {
+ super();
+ this.chunk = chunk;
+ }
+
+ /**
+ * Processes the lighting queue for this chunk
+ *
+ * @param startTime If start Time is 0, we will not limit execution time
+ * @param maxTickTime Maximum time to spend processing lighting updates
+ * @return true to abort processing furthur lighting updates
+ */
+ private boolean processQueue(long startTime, long maxTickTime) {
+ if (this.isEmpty()) {
+ return false;
+ }
+ if (isOutOfTime(maxTickTime, startTime)) {
+ return true;
+ }
+ try (Timing ignored = chunk.world.timings.lightingQueueTimer.startTiming()) {
+ Runnable lightUpdate;
+ while ((lightUpdate = this.poll()) != null) {
+ lightUpdate.run();
+ if (isOutOfTime(maxTickTime, startTime)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Flushes lighting updates to unload the chunk
+ */
+ void processUnload() {
+ if (!chunk.world.paperConfig.queueLightUpdates) {
+ return;
+ }
+ processQueue(0, 0); // No timeout
+
+ final int radius = 1;
+ for (int x = chunk.locX - radius; x <= chunk.locX + radius; ++x) {
+ for (int z = chunk.locZ - radius; z <= chunk.locZ + radius; ++z) {
+ if (x == chunk.locX && z == chunk.locZ) {
+ continue;
+ }
+
+ Chunk neighbor = chunk.world.getChunkIfLoaded(x, z);
+ if (neighbor != null) {
+ neighbor.lightingQueue.processQueue(0, 0); // No timeout
+ }
+ }
+ }
+ }
+ }
+
+ private static boolean isOutOfTime(long maxTickTime, long startTime) {
+ return startTime > 0 && System.nanoTime() - startTime > maxTickTime;
+ }
+}
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index b10e4c409d..f3755fcfbe 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -329,7 +329,7 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
if (iblockdata2.b(this, blockposition) != iblockdata1.b(this, blockposition) || iblockdata2.e() != iblockdata1.e()) {
this.methodProfiler.enter("checkLight");
- this.r(blockposition);
+ chunk.runOrQueueLightUpdate(() -> this.r(blockposition)); // Paper - Queue light update
this.methodProfiler.exit();
}
--
2.21.0
|