|
|
|
|
@@ -3,19 +3,29 @@ 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/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
|
|
|
diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
|
|
|
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
|
|
|
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
|
|
|
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
|
|
|
@@ -0,0 +0,0 @@ public final class MinecraftTimings {
|
|
|
|
|
public static final Timing timeUpdateTimer = Timings.ofSafe("Time Update");
|
|
|
|
|
public static final Timing serverCommandTimer = Timings.ofSafe("Server Command");
|
|
|
|
|
public static final Timing worldSaveTimer = Timings.ofSafe("World Save");
|
|
|
|
|
+ public static final Timing lightingQueueTimer = Timings.ofSafe("Lighting Queue");
|
|
|
|
|
--- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
|
|
|
|
|
+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
|
|
|
|
|
@@ -0,0 +0,0 @@ public class WorldTimingsHandler {
|
|
|
|
|
public final Timing syncChunkLoadTileTicksTimer;
|
|
|
|
|
public final Timing syncChunkLoadPostTimer;
|
|
|
|
|
|
|
|
|
|
public static final Timing tickEntityTimer = Timings.ofSafe("## tickEntity");
|
|
|
|
|
public static final Timing tickTileEntityTimer = Timings.ofSafe("## tickTileEntity");
|
|
|
|
|
+ public final Timing lightingQueueTimer;
|
|
|
|
|
+
|
|
|
|
|
public WorldTimingsHandler(World server) {
|
|
|
|
|
String name = server.worldData.getName() +" - ";
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
|
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
|
|
|
|
@@ -36,27 +46,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|
|
|
|
--- a/src/main/java/net/minecraft/server/Chunk.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/server/Chunk.java
|
|
|
|
|
@@ -0,0 +0,0 @@ public class Chunk {
|
|
|
|
|
private ConcurrentLinkedQueue<BlockPosition> y;
|
|
|
|
|
public boolean d;
|
|
|
|
|
protected gnu.trove.map.hash.TObjectIntHashMap<Class> entityCount = new gnu.trove.map.hash.TObjectIntHashMap<Class>(); // Spigot
|
|
|
|
|
+ public int lightUpdates; // Paper - Number of queued light updates for this chunk
|
|
|
|
|
|
|
|
|
|
// CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
|
|
|
|
|
private int neighbors = 0x1 << 12;
|
|
|
|
|
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;
|
|
|
|
|
@@ -0,0 +0,0 @@ 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)) {
|
|
|
|
|
+ // Paper start - Queue light update
|
|
|
|
|
+ if (!world.paperConfig.queueLightUpdates) {
|
|
|
|
|
+ recheckGaps(flag);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ++lightUpdates;
|
|
|
|
|
+ world.getServer().getServer().lightingQueue.add(() -> {
|
|
|
|
|
+ recheckGaps(flag);
|
|
|
|
|
+ --lightUpdates;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ lightingQueue.add(() -> recheckGaps(flag)); // Paper - Queue light update
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
@@ -71,7 +72,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|
|
|
|
if (flag) {
|
|
|
|
|
this.initLighting();
|
|
|
|
|
- } else {
|
|
|
|
|
+ } else if (!world.paperConfig.queueLightUpdates) { // Paper
|
|
|
|
|
+ } else { lightingQueue.add(() -> { // Paper - Queue light update
|
|
|
|
|
int j1 = iblockdata.c();
|
|
|
|
|
int k1 = iblockdata1.c();
|
|
|
|
|
|
|
|
|
|
@@ -79,132 +80,147 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|
|
|
|
if (j1 != k1 && (j1 < k1 || this.getBrightness(EnumSkyBlock.SKY, blockposition) > 0 || this.getBrightness(EnumSkyBlock.BLOCK, blockposition) > 0)) {
|
|
|
|
|
this.d(i, k);
|
|
|
|
|
}
|
|
|
|
|
+ // Paper start - Queue light update
|
|
|
|
|
+ } else {
|
|
|
|
|
+ int j1 = iblockdata.c();
|
|
|
|
|
+ int k1 = iblockdata1.c();
|
|
|
|
|
+
|
|
|
|
|
+ ++lightUpdates;
|
|
|
|
|
+ world.getServer().getServer().lightingQueue.add(() -> {
|
|
|
|
|
+ if (j1 > 0) {
|
|
|
|
|
+ if (j >= i1) {
|
|
|
|
|
+ this.c(i, j + 1, k);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (j == i1 - 1) {
|
|
|
|
|
+ this.c(i, j, k);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (j1 != k1 && (j1 < k1 || this.getBrightness(EnumSkyBlock.SKY, blockposition) > 0 || this.getBrightness(EnumSkyBlock.BLOCK, blockposition) > 0)) {
|
|
|
|
|
+ this.d(i, k);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ --lightUpdates;
|
|
|
|
|
+ });
|
|
|
|
|
+ // Paper end
|
|
|
|
|
+ }); // Paper
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TileEntity tileentity;
|
|
|
|
|
@@ -0,0 +0,0 @@ public class Chunk {
|
|
|
|
|
|
|
|
|
|
private EnumTileEntityState() {}
|
|
|
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // Paper start
|
|
|
|
|
+ public boolean hasLightUpdates() {
|
|
|
|
|
+ if (world.paperConfig.queueLightUpdates) {
|
|
|
|
|
+ if (lightUpdates > 0) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (int x = locX - 2; x <= locX + 2; ++x) {
|
|
|
|
|
+ for (int z = locZ - 2; z <= locZ + 2; ++z) {
|
|
|
|
|
+ if ((x == 0 && z == 0) || (x == locX && z == locZ)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Chunk chunk = MCUtil.getLoadedChunkWithoutMarkingActive(world, x, z);
|
|
|
|
|
+ if (chunk != null && chunk.lightUpdates > 0) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // Paper end
|
|
|
|
|
}
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
|
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
|
|
|
@@ -0,0 +0,0 @@ public class ChunkProviderServer implements IChunkProvider {
|
|
|
|
|
Chunk chunk = (Chunk) this.chunks.get(olong);
|
|
|
|
|
if (event.isCancelled()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
+ chunk.lightingQueue.processUnload(); // Paper
|
|
|
|
|
|
|
|
|
|
if (chunk != null && chunk.d) {
|
|
|
|
|
+ if (chunk.hasLightUpdates()) continue; // Paper - Don't unload chunks with pending light updates.
|
|
|
|
|
// CraftBukkit start
|
|
|
|
|
ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk);
|
|
|
|
|
this.world.getServer().getPluginManager().callEvent(event);
|
|
|
|
|
// 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
|
|
|
@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.CraftServer;
|
|
|
|
|
// CraftBukkit end
|
|
|
|
|
import co.aikar.timings.MinecraftTimings; // Paper
|
|
|
|
|
|
|
|
|
|
+// Paper start
|
|
|
|
|
+import java.util.LinkedList;
|
|
|
|
|
+import java.util.Queue;
|
|
|
|
|
+// Paper end
|
|
|
|
|
+
|
|
|
|
|
public abstract class MinecraftServer implements Runnable, ICommandListener, IAsyncTaskHandler, IMojangStatistics {
|
|
|
|
|
|
|
|
|
|
public static final Logger LOGGER = LogManager.getLogger();
|
|
|
|
|
@@ -0,0 +0,0 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs
|
|
|
|
|
public final Thread primaryThread;
|
|
|
|
|
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
|
|
|
|
|
public int autosavePeriod;
|
|
|
|
|
+ public final Queue<Runnable> lightingQueue = new LinkedList<Runnable>(); // Paper - Queued light updates
|
|
|
|
|
// CraftBukkit end
|
|
|
|
|
|
|
|
|
|
public MinecraftServer(OptionSet options, Proxy proxy, DataConverterManager dataconvertermanager, YggdrasilAuthenticationService yggdrasilauthenticationservice, MinecraftSessionService minecraftsessionservice, GameProfileRepository gameprofilerepository, UserCache usercache) {
|
|
|
|
|
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) {
|
|
|
|
|
@@ -0,0 +0,0 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs
|
|
|
|
|
this.methodProfiler.b();
|
|
|
|
|
this.methodProfiler.b();
|
|
|
|
|
|
|
|
|
|
+ // Paper start - Flush light updates
|
|
|
|
|
+ if (!lightingQueue.isEmpty()) {
|
|
|
|
|
+ MinecraftTimings.lightingQueueTimer.startTiming();
|
|
|
|
|
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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/net/minecraft/server/PaperLightingQueue.java
|
|
|
|
|
@@ -0,0 +0,0 @@
|
|
|
|
|
+package net.minecraft.server;
|
|
|
|
|
+
|
|
|
|
|
+ int updatesThisTick = 0;
|
|
|
|
|
+ long cachedTime = System.currentTimeMillis();
|
|
|
|
|
+ long startTime = cachedTime - (this.h[this.ticks % 100] / 1000000);
|
|
|
|
|
+ int maxTickTimeCap = MathHelper.floor((TICK_TIME / 1000000) * 0.8);
|
|
|
|
|
+ int maxTickTime = Math.max(0, (int) (maxTickTimeCap - (cachedTime - startTime)));
|
|
|
|
|
+ Runnable lightUpdate;
|
|
|
|
|
+import co.aikar.timings.Timing;
|
|
|
|
|
+import java.util.ArrayDeque;
|
|
|
|
|
+
|
|
|
|
|
+ while (maxTickTime > 0 && (lightUpdate = lightingQueue.poll()) != null) {
|
|
|
|
|
+ lightUpdate.run();
|
|
|
|
|
+ if (++updatesThisTick % 10 == 0) {
|
|
|
|
|
+ long currentTime = System.currentTimeMillis();
|
|
|
|
|
+ if (currentTime - cachedTime > maxTickTime) {
|
|
|
|
|
+ break;
|
|
|
|
|
+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<Runnable> {
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ cachedTime = currentTime;
|
|
|
|
|
+ maxTickTime = Math.max(0, (int) (maxTickTimeCap - (currentTime - startTime)));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ MinecraftTimings.lightingQueueTimer.stopTiming();
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // Paper end
|
|
|
|
|
+
|
|
|
|
|
org.spigotmc.WatchdogThread.tick(); // Spigot
|
|
|
|
|
co.aikar.timings.TimingsManager.FULL_SERVER_TICK.stopTiming(); // Paper
|
|
|
|
|
}
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/server/World.java
|
|
|
|
|
@@ -214,17 +230,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
|
|
|
|
|
if (iblockdata.c() != iblockdata1.c() || iblockdata.d() != iblockdata1.d()) {
|
|
|
|
|
this.methodProfiler.a("checkLight");
|
|
|
|
|
- this.w(blockposition);
|
|
|
|
|
+ // Paper start - Queue light update
|
|
|
|
|
+ if (!paperConfig.queueLightUpdates) {
|
|
|
|
|
+ this.w(blockposition);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ++chunk.lightUpdates;
|
|
|
|
|
+ getMinecraftServer().lightingQueue.add(() -> {
|
|
|
|
|
+ this.w(blockposition);
|
|
|
|
|
+ --chunk.lightUpdates;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ // Paper end
|
|
|
|
|
+ chunk.lightingQueue.add(() -> this.w(blockposition)); // Paper - Queue light update
|
|
|
|
|
this.methodProfiler.b();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|