PaperMC/Spigot-Server-Patches/0028-Lighting-Queue.patch
Zach Brown 34731dd04e
Restructure lighting queue runnable handling
Instead of overriding add within the queue, never add runnables to the
queue if the light queue is disabled.

This change is made to make timings reports and stacktraces less
confusing for administrators, who prior to this change, would have seen
the lighting queue referenced in both, regardless of whether or not it
was enabled.

This change should not affect performance, nor is it made with the
intent to.
2017-12-22 15:25:01 -06:00

249 lines
10 KiB
Diff

From 1564084393d4718d099082ea434dd8f2b1a0b53a Mon Sep 17 00:00:00 2001
From: Byteflux <byte@byteflux.net>
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 e0ad559b..4eebd9fa 100644
--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
@@ -49,6 +49,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() +" - ";
@@ -95,5 +97,7 @@ 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");
}
}
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index e524a464..fd606ee1 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -143,4 +143,10 @@ public class PaperWorldConfig {
netherVoidTopDamage = getBoolean( "nether-ceiling-void-damage", false );
log("Top of the nether void damage: " + netherVoidTopDamage);
}
+
+ public boolean queueLightUpdates;
+ private void queueLightUpdates() {
+ queueLightUpdates = getBoolean("queue-light-updates", false);
+ log("Lighting Queue enabled: " + queueLightUpdates);
+ }
}
diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
index 66c78083..c1f25e8d 100644
--- a/src/main/java/net/minecraft/server/Chunk.java
+++ b/src/main/java/net/minecraft/server/Chunk.java
@@ -33,6 +33,7 @@ public class Chunk {
private boolean m;
public final Map<BlockPosition, TileEntity> tileEntities;
public final List<Entity>[] entitySlices; // Spigot
+ final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this); // Paper
private boolean done;
private boolean lit;
private boolean r;
@@ -229,6 +230,13 @@ public class Chunk {
private void h(boolean flag) {
this.world.methodProfiler.a("recheckGaps");
if (this.world.areChunksLoaded(new BlockPosition(this.locX * 16 + 8, 0, this.locZ * 16 + 8), 16)) {
+ this.runOrQueueLightUpdate(() -> recheckGaps(flag)); // Paper - Queue light update
+ }
+ }
+
+ private void recheckGaps(boolean flag) {
+ if (true) {
+ // Paper end
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 16; ++j) {
if (this.i[i + j * 16]) {
@@ -482,6 +490,7 @@ public class Chunk {
if (flag) {
this.initLighting();
} else {
+ this.runOrQueueLightUpdate(() -> { // Paper - Queue light update
int j1 = iblockdata.c();
int k1 = iblockdata1.c();
@@ -496,6 +505,7 @@ public class Chunk {
if (j1 != k1 && (j1 < k1 || this.getBrightness(EnumSkyBlock.SKY, blockposition) > 0 || this.getBrightness(EnumSkyBlock.BLOCK, blockposition) > 0)) {
this.d(i, k);
}
+ }); // Paper
}
TileEntity tileentity;
@@ -1340,6 +1350,16 @@ public class Chunk {
this.w = i;
}
+ // 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 69ded6aa..e9bc23a6 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -295,6 +295,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 47fe9262..d63a243f 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -722,7 +722,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs
protected void C() throws ExceptionWorldConflict { // CraftBukkit - added throws
co.aikar.timings.TimingsManager.FULL_SERVER_TICK.startTiming(); // Paper
this.slackActivityAccountant.tickStarted(); // Spigot
- long i = System.nanoTime();
+ long i = System.nanoTime(); long startTime = i; // Paper
++this.ticks;
if (this.T) {
@@ -783,6 +783,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs
this.methodProfiler.b();
org.spigotmc.WatchdogThread.tick(); // Spigot
+ PaperLightingQueue.processQueue(startTime); // Paper
this.slackActivityAccountant.tickEnded(tickNanos); // 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 00000000..345cd582
--- /dev/null
+++ b/src/main/java/net/minecraft/server/PaperLightingQueue.java
@@ -0,0 +1,92 @@
+package net.minecraft.server;
+
+import co.aikar.timings.Timing;
+import it.unimi.dsi.fastutil.objects.ObjectCollection;
+
+import java.util.ArrayDeque;
+
+class PaperLightingQueue {
+ private static final long MAX_TIME = (long) (1000000000 / 20 * .95);
+ private static int updatesThisTick;
+
+
+ static void processQueue(long curTime) {
+ updatesThisTick = 0;
+
+ final long startTime = System.nanoTime();
+ final long maxTickTime = MAX_TIME - (startTime - curTime);
+
+ START:
+ for (World world : MinecraftServer.getServer().worlds) {
+ if (!world.paperConfig.queueLightUpdates) {
+ continue;
+ }
+
+ ObjectCollection<Chunk> loadedChunks = ((WorldServer) world).getChunkProviderServer().chunks.values();
+ for (Chunk chunk : loadedChunks.toArray(new Chunk[loadedChunks.size()])) {
+ 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;
+ }
+ try (Timing ignored = chunk.world.timings.lightingQueueTimer.startTiming()) {
+ Runnable lightUpdate;
+ while ((lightUpdate = this.poll()) != null) {
+ lightUpdate.run();
+ if (startTime > 0 && ++PaperLightingQueue.updatesThisTick % 10 == 0 && PaperLightingQueue.updatesThisTick > 10) {
+ if (System.nanoTime() - startTime > maxTickTime) {
+ 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; // TODO: bitflip, why should this ever be 2?
+ 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 = MCUtil.getLoadedChunkWithoutMarkingActive(chunk.world, x, z);
+ if (neighbor != null) {
+ neighbor.lightingQueue.processQueue(0, 0); // No timeout
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index fd5f8102..77ed2d24 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -391,7 +391,7 @@ public abstract class World implements IBlockAccess {
} else {
if (iblockdata.c() != iblockdata1.c() || iblockdata.d() != iblockdata1.d()) {
this.methodProfiler.a("checkLight");
- this.w(blockposition);
+ chunk.runOrQueueLightUpdate(() -> this.w(blockposition)); // Paper - Queue light update
this.methodProfiler.b();
}
--
2.15.1.windows.2