mirror of
https://github.com/PaperMC/Paper.git
synced 2024-09-12 06:58:11 +02:00
While it wasn't really "broken" before, if plugins use NMS (which they really should't be) and mess with entity management themselves, and get it wrong, they could ultimately corrupt our state expectations. I've been unable to reproduce any issues locally, but these changes are the result of me analyzing the code pretty deeply and seeing about how to make it more durable to abnormal usage. Any servers seeing oddities, please run with -Ddebug.entities=true and send me any logs triggered.
281 lines
12 KiB
Diff
281 lines
12 KiB
Diff
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
|
|
|