From dda988688e1cc0e88e958f3d2efb47a23df5cc01 Mon Sep 17 00:00:00 2001 From: Byteflux 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 11dd692..da3787d 100644 --- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java +++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java @@ -35,6 +35,8 @@ public class WorldTimingsHandler { public final Timing syncChunkLoadTileTicksTimer; public final Timing syncChunkLoadPostTimer; + public final Timing lightingQueueTimer; + public WorldTimingsHandler(World server) { String name = server.worldData.getName() +" - "; @@ -67,5 +69,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 7c0e61f..8e3a0f3 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -155,4 +155,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 5690e81..d55beb9 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 tileEntities; public final List[] entitySlices; // Spigot + final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this); // Paper private boolean done; private boolean lit; private boolean r; @@ -227,6 +228,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)) { + lightingQueue.add(() -> 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]) { @@ -478,7 +486,7 @@ public class Chunk { } else { if (flag) { this.initLighting(); - } else { + } else { lightingQueue.add(() -> { // Paper - Queue light update int j1 = iblockdata.c(); int k1 = iblockdata1.c(); @@ -493,6 +501,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; diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java index de85bd5..50f0345 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -275,6 +275,7 @@ public class ChunkProviderServer implements IChunkProvider { if (event.isCancelled()) { continue; } + 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 ebf6c48..89a40a8 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -706,7 +706,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs protected void C() throws ExceptionWorldConflict { // CraftBukkit - added throws co.aikar.timings.TimingsManager.FULL_SERVER_TICK.startTiming(); // Paper - long i = System.nanoTime(); + long i = System.nanoTime(); long startTime = i; // Paper ++this.ticks; if (this.S) { @@ -766,6 +766,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs this.methodProfiler.b(); org.spigotmc.WatchdogThread.tick(); // Spigot + PaperLightingQueue.processQueue(startTime); // Paper 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 0000000..2350fe3 --- /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 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; + } + + for (Chunk chunk : ((WorldServer) world).getChunkProviderServer().chunks.values()) { + if (chunk.lightingQueue.processQueue(startTime, maxTickTime)) { + break START; + } + } + } + } + + static class LightingQueue extends ArrayDeque { + final private Chunk chunk; + + LightingQueue(Chunk chunk) { + super(); + this.chunk = chunk; + } + + @Override + public boolean add(Runnable runnable) { + if (chunk.world.paperConfig.queueLightUpdates) { + return super.add(runnable); + } + runnable.run(); + return true; + } + + /** + * 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 362df8f..dc51a79 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -384,7 +384,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.lightingQueue.add(() -> this.w(blockposition)); // Paper - Queue light update this.methodProfiler.b(); } -- 2.8.2