2020-05-06 11:48:49 +02:00
|
|
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
2020-04-11 10:44:21 +02:00
|
|
|
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
|
2020-05-06 11:48:49 +02:00
|
|
|
index 747305619b5050e7dad3ae0ccd62200ee2246b5a..746b5b55896eaf39edf92073f61796d7096c48c2 100644
|
2020-04-11 10:44:21 +02:00
|
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
2020-04-24 11:33:33 +02:00
|
|
|
@@ -308,6 +308,7 @@ public class ChunkProviderServer extends IChunkProvider {
|
2020-04-11 10:44:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private long asyncLoadSeqCounter;
|
|
|
|
+ public static boolean IS_CHUNK_LOAD_BLOCKING_MAIN = false;
|
|
|
|
|
|
|
|
public void getChunkAtAsynchronously(int x, int z, boolean gen, java.util.function.Consumer<Chunk> onComplete) {
|
|
|
|
if (Thread.currentThread() != this.serverThread) {
|
2020-04-24 11:33:33 +02:00
|
|
|
@@ -465,10 +466,18 @@ public class ChunkProviderServer extends IChunkProvider {
|
2020-04-11 10:44:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
gameprofilerfiller.c("getChunkCacheMiss");
|
|
|
|
+ // Paper start - Chunk Load/Gen Priority
|
|
|
|
+ boolean prevBlocking = IS_CHUNK_LOAD_BLOCKING_MAIN;
|
|
|
|
+ IS_CHUNK_LOAD_BLOCKING_MAIN = true;
|
|
|
|
+ // Paper end
|
|
|
|
CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = this.getChunkFutureMainThread(i, j, chunkstatus, flag);
|
|
|
|
|
|
|
|
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
|
2020-04-24 11:33:33 +02:00
|
|
|
@@ -478,6 +487,11 @@ public class ChunkProviderServer extends IChunkProvider {
|
2020-04-11 10:44:21 +02:00
|
|
|
com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
|
2020-04-26 08:05:41 +02:00
|
|
|
this.world.timings.syncChunkLoad.stopTiming(); // Paper
|
2020-04-11 10:44:21 +02:00
|
|
|
} // Paper
|
|
|
|
+ PlayerChunk playerChunk = this.getChunk(ChunkCoordIntPair.pair(x, z));
|
|
|
|
+ if (playerChunk != null) {
|
|
|
|
+ playerChunk.clearChunkUrgent();
|
|
|
|
+ }
|
|
|
|
+ IS_CHUNK_LOAD_BLOCKING_MAIN = prevBlocking;// Paper
|
|
|
|
ichunkaccess = (IChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
|
|
|
|
return ichunkaccess1;
|
|
|
|
}, (playerchunk_failure) -> {
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
|
2020-05-06 11:48:49 +02:00
|
|
|
index 3d610e41969768da0d2848fa1ae195035ccfd660..4b341c81fc97076a69aede729008c54674fa7adf 100644
|
2020-04-11 10:44:21 +02:00
|
|
|
--- 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) {
|
2020-05-06 10:35:20 +02:00
|
|
|
@@ -349,7 +460,7 @@ public class PlayerChunk {
|
2020-04-11 10:44:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
2020-05-06 11:48:49 +02:00
|
|
|
index 00f26ae23da65453073fc06ffec8a349ef28dd7e..6c178492b75134ba25b7730273bb550b693a7e4a 100644
|
2020-04-11 10:44:21 +02:00
|
|
|
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
|
2020-05-06 09:44:47 +02:00
|
|
|
@@ -291,6 +291,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
2020-04-11 10:44:21 +02:00
|
|
|
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) {
|
2020-05-06 09:44:47 +02:00
|
|
|
@@ -308,6 +309,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
2020-04-11 10:44:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-05-06 09:44:47 +02:00
|
|
|
@@ -771,23 +773,28 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
2020-04-11 10:44:21 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
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();
|
2020-05-06 09:44:47 +02:00
|
|
|
@@ -815,6 +822,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
2020-04-11 10:44:21 +02:00
|
|
|
return CompletableFuture.completedFuture(Either.right(playerchunk_failure));
|
|
|
|
});
|
|
|
|
}, (runnable) -> {
|
|
|
|
+ playerchunk.onNeighborsDone(); // Paper
|
|
|
|
this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error
|
|
|
|
});
|
|
|
|
}
|
2020-05-06 09:44:47 +02:00
|
|
|
@@ -827,6 +835,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
2020-04-11 10:44:21 +02:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
+ public ChunkStatus getNeededStatusByRadius(ChunkStatus chunkstatus, int i) { return a(chunkstatus, i); } // Paper - OBFHELPER
|
|
|
|
private ChunkStatus a(ChunkStatus chunkstatus, int i) {
|
|
|
|
ChunkStatus chunkstatus1;
|
|
|
|
|
2020-05-06 09:44:47 +02:00
|
|
|
@@ -951,9 +960,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
|
2020-04-11 10:44:21 +02:00
|
|
|
|
|
|
|
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);
|