From 6c6649b7448fdcb1a1d3235b41294da22a232603 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 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 6ab2eaa169..27775476f9 100644 --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java @@ -184,6 +184,11 @@ public class PaperConfig { return config.getString(path, config.getString(path)); } + public static int maxTickMsLostLightQueue; + private static void lightQueue() { + maxTickMsLostLightQueue = getInt("queue-light-updates-max-loss", 10); + } + 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 39d565db1f..8f6f0288be 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -130,4 +130,12 @@ 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); + 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 af181d4bd7..4ec5fe739f 100644 --- a/src/main/java/net/minecraft/server/Chunk.java +++ b/src/main/java/net/minecraft/server/Chunk.java @@ -38,7 +38,7 @@ public class Chunk implements IChunkAccess { public final Map heightMap; public final int locX; public final int locZ; - private boolean l; + private boolean l; public boolean needsGapCheck() { return l; } // Paper - OBFHELPER private final ChunkConverter m; public final Map tileEntities; public final List[] entitySlices; // Spigot @@ -90,6 +90,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) { @@ -277,9 +278,10 @@ public class Chunk implements IChunkAccess { this.l = true; } + private void recheckGaps(boolean flag) { g(flag); } // Paper - OBFHELPER private void g(boolean flag) { this.world.methodProfiler.a("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]) { @@ -330,7 +332,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)); } @@ -530,6 +532,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); @@ -537,6 +540,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; @@ -1011,10 +1015,16 @@ public class Chunk implements IChunkAccess { return false; } - public void d(boolean flag) { - if (this.l && this.world.worldProvider.g() && !flag) { - this.g(this.world.isClientSide); + // Paper start + private boolean shouldRecheckGaps = false; + public void doGapCheck() { + if (shouldRecheckGaps) { + this.recheckGaps(false); + shouldRecheckGaps = false; } + } + public void d(boolean flag) { + shouldRecheckGaps = this.needsGapCheck() && this.world.worldProvider.hasNaturalLight() && !flag; // Paper this.u = true; @@ -1374,6 +1384,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 31ed3e43a5..0b03c6266a 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -314,6 +314,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 d6ea4ae532..5086fe4027 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -894,7 +894,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.c(); + long i = SystemUtils.c(); long startTime = i; // Paper ++this.ticks; if (this.S) { @@ -952,6 +952,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati this.methodProfiler.e(); this.methodProfiler.e(); 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..d207266db3 --- /dev/null +++ b/src/main/java/net/minecraft/server/PaperLightingQueue.java @@ -0,0 +1,99 @@ +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 loadedChunks = ((WorldServer) world).getChunkProviderServer().chunks.values(); + for (Chunk chunk : loadedChunks.toArray(new Chunk[0])) { + if (chunk.lightingQueue.processQueue(startTime, maxTickTime)) { + break START; + } + chunk.doGapCheck(); + } + } + } + + static class LightingQueue extends ArrayDeque { + 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 499d64ea2c..e06da6bef9 100644 --- a/src/main/java/net/minecraft/server/World.java +++ b/src/main/java/net/minecraft/server/World.java @@ -335,7 +335,7 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc if (iblockdata2.b(this, blockposition) != iblockdata1.b(this, blockposition) || iblockdata2.e() != iblockdata1.e()) { this.methodProfiler.a("checkLight"); - this.r(blockposition); + chunk.runOrQueueLightUpdate(() -> this.r(blockposition)); // Paper - Queue light update this.methodProfiler.e(); } diff --git a/src/main/java/net/minecraft/server/WorldProvider.java b/src/main/java/net/minecraft/server/WorldProvider.java index 517b1e7124..53ce7d5e11 100644 --- a/src/main/java/net/minecraft/server/WorldProvider.java +++ b/src/main/java/net/minecraft/server/WorldProvider.java @@ -7,7 +7,7 @@ public abstract class WorldProvider { protected World b; protected boolean c; protected boolean d; - protected boolean e; + protected boolean e; public boolean hasNaturalLight() { return e; } // Paper - OBFHELPER protected final float[] f = new float[16]; private final float[] g = new float[4]; -- 2.19.0