mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-30 16:19:03 +01:00
fd5c98a9ef
Removes synchronization from sending packets Makes normal packet sends no longer need to be wrapped and queued like it use to work. Adds more packet queue immunities on top of keep alive to let the following scenarios go out without delay: - Keep Alive - Chat - Kick - All of the packets during the Player Joined World event Hoping that latter one helps join timeout issues more too for slow connections. Removes processing packet queue off of main thread - for the few cases where it is allowed, order is not necessary nor should it even be happening concurrently in first place (handshaking/login/status) Ensures packets sent asynchronously are dispatched on main thread This helps ensure safety for ProtocolLib as packet listeners are commonly accessing world state. This will allow you to schedule a packet to be sent async, but itll be dispatched sync for packet listeners to process. This should solve some deadlock risks This may provide a decent performance improvement because thread synchronization incurs a cache reset so by avoiding ever entering a synchronized block, we get to avoid that, and packet sending is a really hot activity.
264 lines
No EOL
13 KiB
Diff
264 lines
No EOL
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 223d3b1125d..37341d2d2e7 100644
|
|
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
@@ -0,0 +0,0 @@ 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 647f6fc8efb..9f1662ece53 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
|
@@ -0,0 +0,0 @@ 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 c457e3b772e..747305619b5 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 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");
|
|
@@ -0,0 +0,0 @@ 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();
|
|
@@ -0,0 +0,0 @@ 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()) {
|
|
@@ -0,0 +0,0 @@ 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
|
|
}
|
|
}
|
|
});
|
|
@@ -0,0 +0,0 @@ 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 < 1000000) {
|
|
+ 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 77adc64e30c..3c25436f158 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -0,0 +0,0 @@ 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
|
|
@@ -0,0 +0,0 @@ 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);
|
|
@@ -0,0 +0,0 @@ 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 < 250000) {
|
|
+ // 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);
|
|
@@ -0,0 +0,0 @@ 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();
|
|
@@ -0,0 +0,0 @@ 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();
|
|
|
|
@@ -0,0 +0,0 @@ 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
|
|
@@ -0,0 +0,0 @@ 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 2a7a47c6707..9b5f24c262e 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -0,0 +0,0 @@ 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();
|
|
@@ -0,0 +0,0 @@ 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
|
|
@@ -0,0 +0,0 @@ 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) {
|
|
@@ -0,0 +0,0 @@ public class WorldServer extends World {
|
|
|
|
gameprofilerfiller.exit();
|
|
timings.tickEntities.stopTiming(); // Spigot
|
|
+ this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
this.tickBlockEntities();
|
|
}
|
|
|
|
--
|