Paper/Spigot-Server-Patches/0463-Implement-Chunk-Priority-Urgency-System-for-World-Ge.patch
Aikar e5f6489602
Add Urgent API for Async Chunks API and use it for Async Teleport
This also cleans up the implementation of Async Chunks to get rid of most
Consumer callbacks and instead return futures.

This lets us propogate errors correctly up the future chain
(barring one isn't lost even deeper in the chain...)

So exceptions can now bubble to plugins using getChunkAtAsync
2020-05-10 00:57:03 -04:00

330 Zeilen
18 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sat, 11 Apr 2020 03:56:07 -0400
Subject: [PATCH] Implement Chunk Priority / Urgency System for World Gen
Mark chunks that are blocking main thread for world generation as urgent
Implements a general priority system so that chunks that are sorted in
the generator queues can prioritize certain chunks over another.
Urgent chunks will jump to the front of the line, ensuring that a
sync chunk load on an ungenerated chunk does not lag the server for
a long period of time if the servers generator queues are filled with
lots of chunks already.
This massively reduces the lag spikes from sync chunk gens.
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 21cf5ac6c38968c45bdd0f09f60743ad216cd925..fb315503b0aad2cb52cb70b5b033d33fcecd1d22 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -464,10 +464,14 @@ public class ChunkProviderServer extends IChunkProvider {
}
gameprofilerfiller.c("getChunkCacheMiss");
- CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = this.getChunkFutureMainThread(i, j, chunkstatus, flag);
+ CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = this.getChunkFutureMainThread(i, j, chunkstatus, flag, true); // Paper
if (!completablefuture.isDone()) { // Paper
// Paper start - async chunk io/loading
+ PlayerChunk playerChunk = this.getChunk(ChunkCoordIntPair.pair(x, z));
+ if (playerChunk != null) {
+ playerChunk.markChunkUrgent(chunkstatus);
+ }
this.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.world, x, z);
// Paper end
@@ -477,6 +481,10 @@ public class ChunkProviderServer extends IChunkProvider {
com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
this.world.timings.syncChunkLoad.stopTiming(); // Paper
} // Paper
+ PlayerChunk playerChunk = this.getChunk(ChunkCoordIntPair.pair(x, z));
+ if (playerChunk != null) {
+ playerChunk.clearChunkUrgent();
+ }
ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
return ichunkaccess1;
}, (playerchunk_failure) -> {
@@ -508,6 +516,11 @@ public class ChunkProviderServer extends IChunkProvider {
}
private CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag) {
+ // Paper start
+ return getChunkFutureMainThread(i, j, chunkstatus, flag, false);
+ }
+ private CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag, boolean isUrgent) {
+ // Paper end
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
long k = chunkcoordintpair.pair();
int l = 33 + ChunkStatus.a(chunkstatus);
@@ -535,6 +548,11 @@ public class ChunkProviderServer extends IChunkProvider {
}
}
}
+ // Paper start
+ if (playerchunk != null && isUrgent) {
+ playerchunk.markChunkUrgent(chunkstatus);
+ }
+ // Paper end
return this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap);
}
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index 3d610e41969768da0d2848fa1ae195035ccfd660..a6e7bcf79df9f4ad5b0a779f3eecf85f121bedc8 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -43,6 +43,111 @@ public class PlayerChunk {
long lastAutoSaveTime; // Paper - incremental autosave
long inactiveTimeStart; // Paper - incremental autosave
+ // Paper start - Chunk gen/load priority system
+ volatile int chunkPriority = 0;
+ volatile boolean isUrgent = false;
+ final java.util.List<PlayerChunk> urgentNeighbors = new java.util.ArrayList<>();
+ volatile PlayerChunk rootUrgentOriginator;
+ volatile PlayerChunk urgentOriginator;
+ public void onNeighborRequest(PlayerChunk neighbor, ChunkStatus status) {
+ if (isUrgent && !neighbor.isUrgent && !java.util.Objects.equals(neighbor, rootUrgentOriginator) && !java.util.Objects.equals(neighbor, urgentOriginator)) {
+ synchronized (this.urgentNeighbors) {
+ if (!neighbor.isUrgent) {
+ neighbor.markChunkUrgent(status, this.rootUrgentOriginator, this);
+ this.urgentNeighbors.add(neighbor);
+ }
+ }
+ }
+ }
+
+ public void onNeighborsDone() {
+ List<PlayerChunk> urgentNeighbors;
+ synchronized (this.urgentNeighbors) {
+ urgentNeighbors = new java.util.ArrayList<>(this.urgentNeighbors);
+ this.urgentNeighbors.clear();
+ }
+ for (PlayerChunk urgentNeighbor : urgentNeighbors) {
+ if (urgentNeighbor != null) {
+ urgentNeighbor.clearChunkUrgent(this);
+ }
+ }
+ }
+
+ public void clearChunkUrgent() {
+ clearChunkUrgent(this);
+ }
+ public void clearChunkUrgent(PlayerChunk requester) {
+ if (this.isUrgent && java.util.Objects.equals(requester, this.urgentOriginator)) {
+ this.isUrgent = false;
+ this.urgentOriginator = null;
+ this.rootUrgentOriginator = null;
+ this.onNeighborsDone();
+ }
+ }
+
+ public void markChunkUrgent(ChunkStatus targetStatus) {
+ this.markChunkUrgent(targetStatus, this , this);
+ }
+ public void markChunkUrgent(ChunkStatus targetStatus, PlayerChunk rootUrgentOriginator, PlayerChunk urgentOriginator) {
+ if (!this.isUrgent) {
+ this.rootUrgentOriginator = rootUrgentOriginator;
+ this.urgentOriginator = urgentOriginator;
+ this.isUrgent = true;
+ int x = location.x;
+ int z = location.z;
+ IChunkAccess chunk = getAvailableChunkNow();
+ final ChunkStatus chunkCurrentStatus = chunk == null ? null : chunk.getChunkStatus();
+ final ChunkStatus completedStatus = this.getChunkHolderStatus();
+ final ChunkStatus nextStatus = getNextStatus(completedStatus != null ? completedStatus : ChunkStatus.EMPTY);
+
+ if (chunkCurrentStatus == null || completedStatus == null) {
+ this.chunkMap.world.asyncChunkTaskManager.raisePriority(x, z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
+ // next status is empty, empty has no neighbours needing loading
+ return;
+ }
+
+ if (!targetStatus.isAtLeastStatus(nextStatus)) {
+ // we don't want a status greater-than the one we already have, don't prioritise these loads - they will get in the way
+ return;
+ }
+
+ // at this point we want a chunk that has a status higher than the one we have already completed
+
+ // does the next status need neighbours at all?
+ final int requiredNeighbours = nextStatus.getNeighborRadius();
+ if (requiredNeighbours <= 0) {
+ // no it doesn't, we're done here. we've already prioritised this chunk, no neighbours need prioritising
+ return;
+ }
+
+ // even though we might want a higher status than targetFinalStatus, we cannot queue neighbours for it - we
+ // instead use the current chunk status in progress (nextCompletedStatus) to ensure we aren't waiting on
+ // unprioritised logic for the next status to complete
+
+ for (int cx = -requiredNeighbours; cx <= requiredNeighbours; ++cx) {
+ for (int cz = -requiredNeighbours; cz <= requiredNeighbours; ++cz) {
+ if (cx == 0 && cz == 0) {
+ continue;
+ }
+ PlayerChunk neighbor = this.chunkMap.getUpdatingChunk(ChunkCoordIntPair.asLong(x + cz, z + cx));
+ if (neighbor == null) {
+ continue;
+ }
+
+ IChunkAccess neighborChunk = neighbor.getAvailableChunkNow();
+ ChunkStatus neededStatus = this.chunkMap.getNeededStatusByRadius(nextStatus, Math.max(Math.abs(cx), Math.abs(cz)));
+ ChunkStatus neighborCurrentStatus = neighborChunk != null ? neighborChunk.getChunkStatus() : ChunkStatus.EMPTY;
+ if (nextStatus == ChunkStatus.LIGHT || !neighborCurrentStatus.isAtLeastStatus(neededStatus)) {
+ // we don't need to gen neighbours if our current chunk's status has already gone through the gen
+ // light is always an exception, no matter what if we go through light we need its neighbours - the light engine requires them
+ this.onNeighborRequest(neighbor, neededStatus);
+ }
+ }
+ }
+ }
+ }
+ // Paper end
+
public PlayerChunk(ChunkCoordIntPair chunkcoordintpair, int i, LightEngine lightengine, PlayerChunk.c playerchunk_c, PlayerChunk.d playerchunk_d) {
this.statusFutures = new AtomicReferenceArray(PlayerChunk.CHUNK_STATUSES.size());
this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE;
@@ -139,6 +244,12 @@ public class PlayerChunk {
}
return null;
}
+ public static ChunkStatus getNextStatus(ChunkStatus status) {
+ if (status == ChunkStatus.FULL) {
+ return status;
+ }
+ return CHUNK_STATUSES.get(status.getStatusIndex() + 1);
+ }
// Paper end
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> getStatusFutureUnchecked(ChunkStatus chunkstatus) {
@@ -349,7 +460,7 @@ public class PlayerChunk {
}
public int k() {
- return this.n;
+ return Math.max(1, this.n - this.chunkPriority - (isUrgent ? 20 : 0)); // Paper - allow modifying priority, subtracts 20 if urgent
}
private void d(int i) {
@@ -441,6 +552,7 @@ public class PlayerChunk {
Chunk fullChunk = either.left().get();
PlayerChunk.this.isFullChunkReady = true;
fullChunk.playerChunk = PlayerChunk.this;
+ this.clearChunkUrgent();
}
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index 00f26ae23da65453073fc06ffec8a349ef28dd7e..6c178492b75134ba25b7730273bb550b693a7e4a 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -291,6 +291,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
List<CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>>> list = Lists.newArrayList();
int j = chunkcoordintpair.x;
int k = chunkcoordintpair.z;
+ PlayerChunk requestingNeighbor = this.requestingNeighbor; // Paper
for (int l = -i; l <= i; ++l) {
for (int i1 = -i; i1 <= i; ++i1) {
@@ -308,6 +309,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}
ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1);
+ if (requestingNeighbor != null) requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus); // Paper
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = playerchunk.a(chunkstatus, this);
list.add(completablefuture);
@@ -771,23 +773,28 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
};
CompletableFuture<NBTTagCompound> chunkSaveFuture = this.world.asyncChunkTaskManager.getChunkSaveFuture(chunkcoordintpair.x, chunkcoordintpair.z);
+ PlayerChunk playerChunk = getUpdatingChunk(chunkcoordintpair.pair());
+ boolean isBlockingMain = playerChunk != null && playerChunk.isUrgent;
+ int priority = isBlockingMain ? com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY : com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY;
if (chunkSaveFuture != null) {
- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z,
- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture);
- this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY);
+ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isBlockingMain, chunkSaveFuture);
} else {
- this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z,
- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false);
+ this.world.asyncChunkTaskManager.scheduleChunkLoad(chunkcoordintpair.x, chunkcoordintpair.z, priority, chunkHolderConsumer, isBlockingMain);
}
+ this.world.asyncChunkTaskManager.raisePriority(chunkcoordintpair.x, chunkcoordintpair.z, priority);
return ret;
// Paper end
}
+ private PlayerChunk requestingNeighbor; // Paper
private CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> b(PlayerChunk playerchunk, ChunkStatus chunkstatus) {
ChunkCoordIntPair chunkcoordintpair = playerchunk.i();
+ PlayerChunk prevNeighbor = requestingNeighbor; // Paper
+ this.requestingNeighbor = playerchunk; // Paper
CompletableFuture<Either<List<IChunkAccess>, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, chunkstatus.f(), (i) -> {
return this.a(chunkstatus, i);
});
+ this.requestingNeighbor = prevNeighbor; // Paper
this.world.getMethodProfiler().c(() -> {
return "chunkGenerate " + chunkstatus.d();
@@ -815,6 +822,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
return CompletableFuture.completedFuture(Either.right(playerchunk_failure));
});
}, (runnable) -> {
+ playerchunk.onNeighborsDone(); // Paper
this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error
});
}
@@ -827,6 +835,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}));
}
+ public ChunkStatus getNeededStatusByRadius(ChunkStatus chunkstatus, int i) { return a(chunkstatus, i); } // Paper - OBFHELPER
private ChunkStatus a(ChunkStatus chunkstatus, int i) {
ChunkStatus chunkstatus1;
@@ -951,9 +960,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> a(PlayerChunk playerchunk) {
ChunkCoordIntPair chunkcoordintpair = playerchunk.i();
+ PlayerChunk prevNeighbor = this.requestingNeighbor; // Paper
+ this.requestingNeighbor = playerchunk; // Paper
CompletableFuture<Either<List<IChunkAccess>, PlayerChunk.Failure>> completablefuture = this.a(chunkcoordintpair, 1, (i) -> {
return ChunkStatus.FULL;
});
+ this.requestingNeighbor = prevNeighbor; // Paper
CompletableFuture<Either<Chunk, PlayerChunk.Failure>> completablefuture1 = completablefuture.thenApplyAsync((either) -> {
return either.flatMap((list) -> {
Chunk chunk = (Chunk) list.get(list.size() / 2);
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index b184f8ed8ce86965c3ef9aef179126a4d69275e8..64c643aa15d6ea68f9dad3104cc41e412255cee3 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -1,5 +1,6 @@
package org.bukkit.craftbukkit;
+import com.destroystokyo.paper.io.PrioritizedTaskQueue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -2472,10 +2473,15 @@ public class CraftWorld implements World {
}
}
- return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
+ CompletableFuture<Chunk> future = this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> {
net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) either.left().orElse(null);
return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk());
}, MinecraftServer.getServer());
+ if (urgent) {
+ world.asyncChunkTaskManager.raisePriority(x, z, PrioritizedTaskQueue.HIGHEST_PRIORITY);
+ }
+ return future;
+
}
// Paper end