de6dfedfef
Obfuscate multiple chunks at a time over the server thread pool. Will speed up chunk processing when anti xray is enabled. Co-authored-by: Aikar <aikar@aikar.co>
263 lines
13 KiB
Diff
263 lines
13 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Aikar <aikar@aikar.co>
|
|
Date: Thu, 9 Apr 2020 00:09:26 -0400
|
|
Subject: [PATCH] Mid Tick Chunk Tasks - Speed up processing of chunk loads and
|
|
generation
|
|
|
|
Credit to Spotted for the idea
|
|
|
|
A lot of the new chunk system requires constant back and forth the main thread
|
|
to handle priority scheduling and ensuring conflicting tasks do not run at the
|
|
same time.
|
|
|
|
The issue is, these queues are only checked at either:
|
|
|
|
A) Sync Chunk Loads
|
|
B) End of Tick while sleeping
|
|
|
|
This results in generating chunks sitting waiting for a full tick to
|
|
complete before it will even start the next unit of work to do.
|
|
|
|
Additionally, this also delays loading of chunks until this same timing.
|
|
|
|
We will now periodically poll the chunk task queues throughout the tick,
|
|
looking for work to do.
|
|
We do this in a fair method that considers all worlds, not just the one being
|
|
ticked, so that each world can get 1 task procesed each before the next pass.
|
|
|
|
In a view distance of 15, chunk loading performance was visually faster on the client.
|
|
|
|
Flying at high speed in spectator mode was able to keep up with chunk loading (as long as they are already generated)
|
|
|
|
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
index a58ef60d9976b3afc50e94364cf474bd2e5fdfd6..dd07223978c9aa648673d96ba7b3db1160d43bbf 100644
|
|
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
@@ -13,6 +13,7 @@ import java.util.Map;
|
|
public final class MinecraftTimings {
|
|
|
|
public static final Timing serverOversleep = Timings.ofSafe("Server Oversleep");
|
|
+ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
|
|
public static final Timing playerListTimer = Timings.ofSafe("Player List");
|
|
public static final Timing commandFunctionsTimer = Timings.ofSafe("Command Functions");
|
|
public static final Timing connectionTimer = Timings.ofSafe("Connection Handler");
|
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
index f1b41e16c8ce8323a896339c5d822f8ff7d8f7e6..f8f225e18fa38cad917f52a379233e0a7a869b07 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
@@ -425,4 +425,9 @@ public class PaperConfig {
|
|
log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag.");
|
|
}
|
|
}
|
|
+
|
|
+ public static int midTickChunkTasks = 1000;
|
|
+ private static void midTickChunkTasks() {
|
|
+ midTickChunkTasks = getInt("settings.chunk-tasks-per-tick", midTickChunkTasks);
|
|
+ }
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index a1b5e6b90fc93f83186cf3ebf3e158767008c69a..2ef8506f10426b8a5877e30986c105c0d95be774 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -689,6 +689,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.getMethodProfiler().enter("purge");
|
|
this.world.timings.doChunkMap.startTiming(); // Spigot
|
|
this.chunkMapDistance.purgeTickets();
|
|
+ this.world.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
this.tickDistanceManager();
|
|
this.world.timings.doChunkMap.stopTiming(); // Spigot
|
|
this.world.getMethodProfiler().exitEnter("chunks");
|
|
@@ -698,6 +699,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.timings.doChunkUnload.startTiming(); // Spigot
|
|
this.world.getMethodProfiler().exitEnter("unload");
|
|
this.playerChunkMap.unloadChunks(booleansupplier);
|
|
+ this.world.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
this.world.timings.doChunkUnload.stopTiming(); // Spigot
|
|
this.world.getMethodProfiler().exit();
|
|
this.clearCache();
|
|
@@ -756,7 +758,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
entityPlayer.playerNaturallySpawnedEvent.callEvent();
|
|
};
|
|
// Paper end
|
|
- this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping
|
|
+ final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping
|
|
Optional<Chunk> optional = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left();
|
|
|
|
if (optional.isPresent()) {
|
|
@@ -839,6 +841,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
this.world.timings.chunkTicks.startTiming(); // Spigot // Paper
|
|
this.world.a(chunk, k);
|
|
this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper
|
|
+ if (chunksTicked[0]++ % 10 == 0) this.world.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
}
|
|
}
|
|
});
|
|
@@ -980,6 +983,41 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
super.executeTask(runnable);
|
|
}
|
|
|
|
+ // Paper start
|
|
+ private long lastMidTickChunkTask = 0;
|
|
+ public boolean pollChunkLoadTasks() {
|
|
+ if (com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask()) {
|
|
+ try {
|
|
+ ChunkProviderServer.this.tickDistanceManager();
|
|
+ } finally {
|
|
+ // from below: process pending Chunk loadCallback() and unloadCallback() after each run task
|
|
+ playerChunkMap.callbackExecutor.run();
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+ return false;
|
|
+ }
|
|
+ public void midTickLoadChunks() {
|
|
+ MinecraftServer server = ChunkProviderServer.this.world.getMinecraftServer();
|
|
+ // always try to load chunks, restrain generation/other updates only. don't count these towards tick count
|
|
+ //noinspection StatementWithEmptyBody
|
|
+ while (pollChunkLoadTasks()) {}
|
|
+
|
|
+ if (System.nanoTime() - lastMidTickChunkTask < 200000) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ for (;server.midTickChunksTasksRan < com.destroystokyo.paper.PaperConfig.midTickChunkTasks && server.canSleepForTick();) {
|
|
+ if (this.executeNext()) {
|
|
+ server.midTickChunksTasksRan++;
|
|
+ lastMidTickChunkTask = System.nanoTime();
|
|
+ } else {
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
protected boolean executeNext() {
|
|
// CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
index 508aa4866e47f3865a8c51f7c8e3b842be216923..11349565e71d97384d3dcab9cdf6ea168c6d2f4f 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -910,6 +910,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
// Paper end
|
|
tickSection = curTime;
|
|
}
|
|
+ midTickChunksTasksRan = 0; // Paper
|
|
// Spigot end
|
|
|
|
//MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
|
|
@@ -980,7 +981,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
|
|
}
|
|
|
|
- private boolean canSleepForTick() {
|
|
+ public boolean canSleepForTick() { // Paper
|
|
// CraftBukkit start
|
|
if (isOversleep) return canOversleep();// Paper - because of our changes, this logic is broken
|
|
return this.forceTicks || this.isEntered() || SystemUtils.getMonotonicMillis() < (this.ac ? this.ab : this.nextTick);
|
|
@@ -1010,6 +1011,23 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
});
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public int midTickChunksTasksRan = 0;
|
|
+ private long midTickLastRan = 0;
|
|
+ public void midTickLoadChunks() {
|
|
+ if (!isMainThread() || System.nanoTime() - midTickLastRan < 1000000) {
|
|
+ // only check once per 0.25ms incase this code is called in a hot method
|
|
+ return;
|
|
+ }
|
|
+ try (co.aikar.timings.Timing ignored = co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming()) {
|
|
+ for (WorldServer value : this.getWorlds()) {
|
|
+ value.getChunkProvider().serverThreadQueue.midTickLoadChunks();
|
|
+ }
|
|
+ midTickLastRan = System.nanoTime();
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
protected TickTask postToMainThread(Runnable runnable) {
|
|
return new TickTask(this.ticks, runnable);
|
|
@@ -1096,6 +1114,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
// Paper start - move oversleep into full server tick
|
|
isOversleep = true;MinecraftTimings.serverOversleep.startTiming();
|
|
this.awaitTasks(() -> {
|
|
+ midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick
|
|
return !this.canOversleep();
|
|
});
|
|
isOversleep = false;MinecraftTimings.serverOversleep.stopTiming();
|
|
@@ -1178,13 +1197,16 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
}
|
|
|
|
protected void b(BooleanSupplier booleansupplier) {
|
|
+ midTickLoadChunks(); // Paper
|
|
MinecraftTimings.bukkitSchedulerTimer.startTiming(); // Spigot // Paper
|
|
this.server.getScheduler().mainThreadHeartbeat(this.ticks); // CraftBukkit
|
|
MinecraftTimings.bukkitSchedulerTimer.stopTiming(); // Spigot // Paper
|
|
+ midTickLoadChunks(); // Paper
|
|
this.methodProfiler.enter("commandFunctions");
|
|
MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper
|
|
this.getFunctionData().tick();
|
|
MinecraftTimings.commandFunctionsTimer.stopTiming(); // Spigot // Paper
|
|
+ midTickLoadChunks(); // Paper
|
|
this.methodProfiler.exitEnter("levels");
|
|
Iterator iterator = this.getWorlds().iterator();
|
|
|
|
@@ -1195,7 +1217,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
processQueue.remove().run();
|
|
}
|
|
MinecraftTimings.processQueueTimer.stopTiming(); // Spigot
|
|
-
|
|
+ midTickLoadChunks(); // Paper
|
|
MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper
|
|
// Send time updates to everyone, it will get the right time from the world the player is in.
|
|
// Paper start - optimize time updates
|
|
@@ -1238,9 +1260,11 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
this.methodProfiler.enter("tick");
|
|
|
|
try {
|
|
+ midTickLoadChunks(); // Paper
|
|
worldserver.timings.doTick.startTiming(); // Spigot
|
|
worldserver.doTick(booleansupplier);
|
|
worldserver.timings.doTick.stopTiming(); // Spigot
|
|
+ midTickLoadChunks(); // Paper
|
|
} catch (Throwable throwable) {
|
|
// Spigot Start
|
|
CrashReport crashreport;
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index dfc9f4d5738b01e3f80af8f3d25aec51b6e9d8d9..69e6614049e490dc70baf745761c62b173cf693e 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -432,6 +432,7 @@ public class WorldServer extends World {
|
|
}
|
|
timings.scheduledBlocks.stopTiming(); // Spigot
|
|
|
|
+ this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
gameprofilerfiller.exitEnter("raid");
|
|
this.timings.raids.startTiming(); // Paper - timings
|
|
this.persistentRaid.a();
|
|
@@ -444,6 +445,7 @@ public class WorldServer extends World {
|
|
timings.doSounds.startTiming(); // Spigot
|
|
this.ad();
|
|
timings.doSounds.stopTiming(); // Spigot
|
|
+ this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
this.ticking = false;
|
|
gameprofilerfiller.exitEnter("entities");
|
|
boolean flag3 = true || !this.players.isEmpty() || !this.getForceLoadedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players
|
|
@@ -529,6 +531,7 @@ public class WorldServer extends World {
|
|
timings.entityTick.stopTiming(); // Spigot
|
|
|
|
this.tickingEntities = false;
|
|
+ this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
|
|
try (co.aikar.timings.Timing ignored = this.timings.newEntities.startTiming()) { // Paper - timings
|
|
while ((entity = (Entity) this.entitiesToAdd.poll()) != null) {
|
|
@@ -539,6 +542,7 @@ public class WorldServer extends World {
|
|
|
|
gameprofilerfiller.exit();
|
|
timings.tickEntities.stopTiming(); // Spigot
|
|
+ this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
this.tickBlockEntities();
|
|
}
|
|
|