mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-30 20:21:51 +01:00
353 lines
18 KiB
Diff
353 lines
18 KiB
Diff
From e245683a13582175b68dbfa9babb6bef2b3d2fd5 Mon Sep 17 00:00:00 2001
|
|
From: Aikar <aikar@aikar.co>
|
|
Date: Thu, 9 Apr 2020 00:09:26 -0400
|
|
Subject: [PATCH] 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.
|
|
|
|
We also cap the throughput of these task processes to 1 per world per 0.1ms or
|
|
200 max per tick, to ensure that high volume of tasks do not overload the current
|
|
tick time.
|
|
|
|
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 69e26a8267..434833d50e 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/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java
|
|
new file mode 100644
|
|
index 0000000000..e0ad725b2e
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/destroystokyo/paper/util/map/Long2ObjectLinkedOpenHashMapFastCopy.java
|
|
@@ -0,0 +1,32 @@
|
|
+package com.destroystokyo.paper.util.map;
|
|
+
|
|
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
|
+
|
|
+public class Long2ObjectLinkedOpenHashMapFastCopy<V> extends Long2ObjectLinkedOpenHashMap<V> {
|
|
+
|
|
+ public void copyFrom(Long2ObjectLinkedOpenHashMapFastCopy<V> map) {
|
|
+ if (key.length < map.key.length) {
|
|
+ key = null;
|
|
+ key = new long[map.key.length];
|
|
+ }
|
|
+ if (value.length < map.value.length) {
|
|
+ value = null;
|
|
+ //noinspection unchecked
|
|
+ value = (V[]) new Object[map.value.length];
|
|
+ }
|
|
+ if (link.length < map.link.length) {
|
|
+ link = null;
|
|
+ link = new long[map.link.length];
|
|
+ }
|
|
+ System.arraycopy(map.key, 0, this.key, 0, map.key.length);
|
|
+ System.arraycopy(map.value, 0, this.value, 0, map.value.length);
|
|
+ System.arraycopy(map.link, 0, this.link, 0, map.link.length);
|
|
+ this.size = map.size;
|
|
+ this.mask = map.mask;
|
|
+ this.first = map.first;
|
|
+ this.last = map.last;
|
|
+ this.n = map.n;
|
|
+ this.maxFill = map.maxFill;
|
|
+ this.containsNullKey = map.containsNullKey;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index e627440c41..15450f36a4 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -602,6 +602,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");
|
|
@@ -611,6 +612,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();
|
|
@@ -669,7 +671,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()) {
|
|
@@ -752,6 +754,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
|
|
}
|
|
}
|
|
});
|
|
@@ -893,6 +896,39 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
super.executeTask(runnable);
|
|
}
|
|
|
|
+ // Paper start
|
|
+ private long lastChunkTask = 0;
|
|
+ public void midTickLoadChunks() {
|
|
+ MinecraftServer server = ChunkProviderServer.this.world.getMinecraftServer();
|
|
+ try (co.aikar.timings.Timing ignored = co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming()) {
|
|
+ while (server.canSleepForTick()) {
|
|
+ try {
|
|
+ // always try to load chunks as long as we aren't falling behind, restrain generation/other updates only.
|
|
+ boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ChunkProviderServer.this.world.asyncChunkTaskManager.pollNextChunkTask(); // Paper
|
|
+ ChunkProviderServer.this.tickDistanceManager();
|
|
+ if (execChunkTask) {
|
|
+ continue;
|
|
+ }
|
|
+ long now = System.nanoTime();
|
|
+ // cap to 1 task every 0.1ms per world and max 200 tasks per tick.
|
|
+ // Anything that doesn't make it past this can load during sleep
|
|
+ // we do not want to use this.executeNext as that also processes chunk loads and might count against task counter.
|
|
+ // We also have already ticked the distance manager above too.
|
|
+ if (server.chunksTasksRan < 200 && now - lastChunkTask > 100000 && super.executeNext()) {
|
|
+ ChunkProviderServer.this.lightEngine.queueUpdate();
|
|
+ server.chunksTasksRan++;
|
|
+ lastChunkTask = now;
|
|
+ }
|
|
+ break;
|
|
+ } finally {
|
|
+ // from below: process pending Chunk loadCallback() and unloadCallback() after each run task
|
|
+ playerChunkMap.callbackExecutor.run();
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ // 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 06c395000f..936434110c 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -919,6 +919,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
// Paper end
|
|
tickSection = curTime;
|
|
}
|
|
+ chunksTasksRan = 0; // Paper
|
|
// Spigot end
|
|
|
|
//MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
|
|
@@ -989,7 +990,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
|
|
}
|
|
|
|
- private boolean canSleepForTick() {
|
|
+ public boolean canSleepForTick() { // Paper
|
|
// CraftBukkit start
|
|
return this.forceTicks || this.isEntered() || SystemUtils.getMonotonicMillis() < (this.ac ? this.ab : this.nextTick);
|
|
}
|
|
@@ -1017,6 +1018,20 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
|
});
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public int chunksTasksRan = 0;
|
|
+ public void midTickLoadChunks() {
|
|
+ if (!this.canSleepForTick()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ Iterator<WorldServer> iterator = this.getWorlds().iterator();
|
|
+ while (this.canSleepForTick() && iterator.hasNext()) {
|
|
+ iterator.next().getChunkProvider().serverThreadQueue.midTickLoadChunks();
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
@Override
|
|
protected TickTask postToMainThread(Runnable runnable) {
|
|
return new TickTask(this.ticks, runnable);
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
index ce645a69bd..32fed8a39a 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
@@ -55,8 +55,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
public static final int GOLDEN_TICKET = 33 + ChunkStatus.b();
|
|
- public final Long2ObjectLinkedOpenHashMap<PlayerChunk> updatingChunks = new Long2ObjectLinkedOpenHashMap();
|
|
- public final Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunks = new Long2ObjectLinkedOpenHashMap(); // Paper - remove copying, make mt safe
|
|
+ public final com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<PlayerChunk> updatingChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<>();
|
|
+ public final com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<PlayerChunk> visibleChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<>(); // Paper - remove copying, make mt safe
|
|
+ public final com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<PlayerChunk> pendingVisibleChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<>(); // Paper - remove copying, this is used if the visible chunks is updated while iterating only
|
|
public transient Long2ObjectLinkedOpenHashMap<PlayerChunk> visibleChunksClone; // Paper - remove copying, make mt safe
|
|
private final Long2ObjectLinkedOpenHashMap<PlayerChunk> pendingUnload;
|
|
final LongSet loadedChunks; // Paper - private -> package
|
|
@@ -266,10 +267,10 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
// Paper start - remove cloning of visible chunks unless accessed as a collection async
|
|
private static final boolean DEBUG_ASYNC_VISIBLE_CHUNKS = Boolean.getBoolean("paper.debug-async-visible-chunks");
|
|
private boolean isIterating = false;
|
|
+ private boolean hasPendingVisibleUpdate = false;
|
|
public void forEachVisibleChunk(java.util.function.Consumer<PlayerChunk> consumer) {
|
|
org.spigotmc.AsyncCatcher.catchOp("forEachVisibleChunk");
|
|
boolean prev = isIterating;
|
|
- boolean wasUpdating = updatingChunksModified;
|
|
isIterating = true;
|
|
try {
|
|
for (PlayerChunk value : this.visibleChunks.values()) {
|
|
@@ -277,8 +278,9 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
}
|
|
} finally {
|
|
this.isIterating = prev;
|
|
- if (!this.isIterating && updatingChunksModified && !wasUpdating) {
|
|
- this.updateVisibleChunks();
|
|
+ if (!this.isIterating && hasPendingVisibleUpdate) {
|
|
+ this.visibleChunks.copyFrom(this.pendingVisibleChunks);
|
|
+ this.pendingVisibleChunks.clear();
|
|
}
|
|
}
|
|
}
|
|
@@ -289,7 +291,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
synchronized (this.visibleChunks) {
|
|
if (DEBUG_ASYNC_VISIBLE_CHUNKS) new Throwable("Async getVisibleChunks").printStackTrace();
|
|
if (this.visibleChunksClone == null) {
|
|
- this.visibleChunksClone = this.visibleChunks.clone();
|
|
+ this.visibleChunksClone = this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.clone() : this.visibleChunks.clone();
|
|
}
|
|
return this.visibleChunksClone;
|
|
}
|
|
@@ -302,11 +304,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
// Paper start - mt safe get
|
|
if (Thread.currentThread() != this.world.serverThread) {
|
|
synchronized (this.visibleChunks) {
|
|
- return (PlayerChunk) this.visibleChunks.get(i);
|
|
+ return (PlayerChunk) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(i) : this.visibleChunks.get(i));
|
|
}
|
|
}
|
|
+ return (PlayerChunk) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(i) : this.visibleChunks.get(i));
|
|
// Paper end
|
|
- return (PlayerChunk) this.visibleChunks.get(i);
|
|
}
|
|
|
|
protected IntSupplier c(long i) {
|
|
@@ -682,16 +684,21 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
});
|
|
}
|
|
|
|
- protected boolean updateVisibleChunks() { return b(); } // Paper - OBFHELPER
|
|
protected boolean b() {
|
|
- if (!this.updatingChunksModified || this.isIterating) { // Paper
|
|
+ if (!this.updatingChunksModified) {
|
|
return false;
|
|
} else {
|
|
// Paper start - stop cloning visibleChunks
|
|
synchronized (this.visibleChunks) {
|
|
- this.visibleChunks.clear();
|
|
- this.visibleChunks.putAll(this.updatingChunks);
|
|
- this.visibleChunksClone = null;
|
|
+ if (isIterating) {
|
|
+ hasPendingVisibleUpdate = true;
|
|
+ this.pendingVisibleChunks.copyFrom(this.updatingChunks);
|
|
+ } else {
|
|
+ hasPendingVisibleUpdate = false;
|
|
+ this.pendingVisibleChunks.clear();
|
|
+ this.visibleChunks.copyFrom(this.updatingChunks);
|
|
+ this.visibleChunksClone = null;
|
|
+ }
|
|
}
|
|
// Paper end
|
|
|
|
@@ -785,7 +792,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> ret = new CompletableFuture<>();
|
|
|
|
Consumer<ChunkRegionLoader.InProgressChunkHolder> chunkHolderConsumer = (ChunkRegionLoader.InProgressChunkHolder holder) -> {
|
|
- PlayerChunkMap.this.executor.addTask(() -> {
|
|
+ com.destroystokyo.paper.io.chunk.ChunkTaskManager.queueChunkWaitTask(() -> {
|
|
ret.complete(syncLoadComplete.apply(holder, null));
|
|
});
|
|
};
|
|
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
|
|
index c7ec8cbc11..43573287f2 100644
|
|
--- a/src/main/java/net/minecraft/server/WorldServer.java
|
|
+++ b/src/main/java/net/minecraft/server/WorldServer.java
|
|
@@ -420,6 +420,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();
|
|
@@ -432,6 +433,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
|
|
@@ -470,7 +472,9 @@ public class WorldServer extends World {
|
|
org.spigotmc.ActivationRange.activateEntities(this); // Spigot
|
|
timings.entityTick.startTiming(); // Spigot
|
|
TimingHistory.entityTicks += this.globalEntityList.size(); // Paper
|
|
+ int entitiesTicked = 0; // Paper
|
|
while (objectiterator.hasNext()) {
|
|
+ if (entitiesTicked++ % 100 == 0) this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
Entry<Entity> entry = (Entry) objectiterator.next();
|
|
Entity entity1 = (Entity) entry.getValue();
|
|
Entity entity2 = entity1.getVehicle();
|
|
@@ -517,6 +521,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) {
|
|
@@ -527,7 +532,9 @@ public class WorldServer extends World {
|
|
|
|
gameprofilerfiller.exit();
|
|
timings.tickEntities.stopTiming(); // Spigot
|
|
+ this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
this.tickBlockEntities();
|
|
+ this.getMinecraftServer().midTickLoadChunks(); // Paper
|
|
}
|
|
|
|
gameprofilerfiller.exit();
|
|
--
|
|
2.25.1
|
|
|