Re-add legacy getChunkAtAsynchronously to ChunkProviderServer

Apparently some plugins use it
This commit is contained in:
Spottedleaf 2022-09-02 04:41:08 -07:00
parent 0ee53db50a
commit a2df674cc0
3 changed files with 95 additions and 93 deletions

View file

@ -2587,103 +2587,25 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
// Paper end
+ // Paper start - async chunk io
+ private long asyncLoadSeqCounter;
+
+ public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkAtAsynchronously(int x, int z, boolean gen, boolean isUrgent) {
+ if (Thread.currentThread() != this.mainThread) {
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = new CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>>();
+ this.mainThreadProcessor.execute(() -> {
+ this.getChunkAtAsynchronously(x, z, gen, isUrgent).whenComplete((chunk, ex) -> {
+ if (ex != null) {
+ future.completeExceptionally(ex);
+ } else {
+ future.complete(chunk);
+ }
+ });
+ });
+ return future;
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> ret = new CompletableFuture<>();
+
+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority;
+ if (isUrgent) {
+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER;
+ } else {
+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL;
+ }
+
+ long k = ChunkPos.asLong(x, z);
+ ChunkPos chunkPos = new ChunkPos(x, z);
+
+ ChunkAccess ichunkaccess;
+
+ // try cache
+ for (int l = 0; l < 4; ++l) {
+ if (k == this.lastChunkPos[l] && ChunkStatus.FULL == this.lastChunkStatus[l]) {
+ ichunkaccess = this.lastChunk[l];
+ if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
+
+ // move to first in cache
+
+ for (int i1 = 3; i1 > 0; --i1) {
+ this.lastChunkPos[i1] = this.lastChunkPos[i1 - 1];
+ this.lastChunkStatus[i1] = this.lastChunkStatus[i1 - 1];
+ this.lastChunk[i1] = this.lastChunk[i1 - 1];
+ }
+
+ this.lastChunkPos[0] = k;
+ this.lastChunkStatus[0] = ChunkStatus.FULL;
+ this.lastChunk[0] = ichunkaccess;
+
+ return CompletableFuture.completedFuture(Either.left(ichunkaccess));
+ }
+ net.minecraft.server.ChunkSystem.scheduleChunkLoad(this.level, x, z, gen, ChunkStatus.FULL, true, priority, (chunk) -> {
+ if (chunk == null) {
+ ret.complete(ChunkHolder.UNLOADED_CHUNK);
+ } else {
+ ret.complete(Either.left(chunk));
+ }
+ }
+
+ if (gen) {
+ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent);
+ }
+
+ ChunkAccess current = this.getChunkAtImmediately(x, z); // we want to bypass ticket restrictions
+ if (current != null) {
+ if (!(current instanceof net.minecraft.world.level.chunk.ImposterProtoChunk) && !(current instanceof LevelChunk)) {
+ return CompletableFuture.completedFuture(ChunkHolder.UNLOADED_CHUNK);
+ }
+ // we know the chunk is at full status here (either in read-only mode or the real thing)
+ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent);
+ }
+
+ // here we don't know what status it is and we're not supposed to generate
+ // so we asynchronously load empty status
+ return this.bringToStatusAsync(x, z, chunkPos, ChunkStatus.EMPTY, isUrgent).thenCompose((either) -> {
+ ChunkAccess chunk = either.left().orElse(null);
+ if (!(chunk instanceof net.minecraft.world.level.chunk.ImposterProtoChunk) && !(chunk instanceof LevelChunk)) {
+ // the chunk on disk was not a full status chunk
+ return CompletableFuture.completedFuture(ChunkHolder.UNLOADED_CHUNK);
+ }
+ // bring to full status if required
+ return this.bringToFullStatusAsync(x, z, chunkPos, isUrgent);
+ });
+ }
+
+ private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> bringToFullStatusAsync(int x, int z, ChunkPos chunkPos, boolean isUrgent) {
+ return this.bringToStatusAsync(x, z, chunkPos, ChunkStatus.FULL, isUrgent);
+ }
+
+ private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> bringToStatusAsync(int x, int z, ChunkPos chunkPos, ChunkStatus status, boolean isUrgent) {
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.getChunkFutureMainThread(x, z, status, true, isUrgent);
+ Long identifier = Long.valueOf(this.asyncLoadSeqCounter++);
+ int ticketLevel = net.minecraft.server.MCUtil.getTicketLevelFor(status);
+ this.addTicketAtLevel(TicketType.ASYNC_LOAD, chunkPos, ticketLevel, identifier);
+
+ return future.thenComposeAsync((Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either) -> {
+ // either left -> success
+ // either right -> failure
+
+ this.removeTicketAtLevel(TicketType.ASYNC_LOAD, chunkPos, ticketLevel, identifier);
+ this.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); // allow unloading
+
+ Optional<ChunkHolder.ChunkLoadingFailure> failure = either.right();
+
+ if (failure.isPresent()) {
+ // failure
+ throw new IllegalStateException("Chunk failed to load: " + failure.get().toString());
+ }
+
+ return CompletableFuture.completedFuture(either);
+ }, this.mainThreadProcessor);
+ return ret;
+ }
+ // Paper end - async chunk io

View file

@ -798,8 +798,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource {
return CompletableFuture.completedFuture(either);
}, this.mainThreadProcessor);
return ret;
}
+
+ public boolean markUrgent(ChunkPos coords) {

View file

@ -6022,6 +6022,86 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, onLoad
+ );
+ }
+
+ void chunkLoadAccept(int chunkX, int chunkZ, ChunkAccess chunk, java.util.function.Consumer<ChunkAccess> consumer) {
+ try {
+ consumer.accept(chunk);
+ } catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) {
+ throw (ThreadDeath)throwable;
+ }
+ LOGGER.error("Load callback for chunk " + chunkX + "," + chunkZ + " in world '" + this.level.getWorld().getName() + "' threw an exception", throwable);
+ }
+ }
+
+ void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel,
+ java.util.function.Consumer<ChunkAccess> consumer) {
+ if (ticketLevel <= 33) {
+ this.getFullChunkAsync(chunkX, chunkZ, (java.util.function.Consumer)consumer);
+ return;
+ }
+
+ net.minecraft.server.ChunkSystem.scheduleChunkLoad(
+ this.level, chunkX, chunkZ, ChunkHolder.getStatus(ticketLevel), true,
+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL, consumer
+ );
+ }
+
+
+ public final void getChunkAtAsynchronously(int chunkX, int chunkZ, ChunkStatus status, boolean gen, boolean allowSubTicketLevel, java.util.function.Consumer<ChunkAccess> onLoad) {
+ // try to fire sync
+ int chunkStatusTicketLevel = 33 + ChunkStatus.getDistance(status);
+ ChunkHolder playerChunk = this.chunkMap.getUpdatingChunkIfPresent(io.papermc.paper.util.CoordinateUtils.getChunkKey(chunkX, chunkZ));
+ if (playerChunk != null) {
+ ChunkStatus holderStatus = playerChunk.getChunkHolderStatus();
+ ChunkAccess immediate = playerChunk.getAvailableChunkNow();
+ if (immediate != null) {
+ if (allowSubTicketLevel ? immediate.getStatus().isOrAfter(status) : (playerChunk.getTicketLevel() <= chunkStatusTicketLevel && holderStatus != null && holderStatus.isOrAfter(status))) {
+ this.chunkLoadAccept(chunkX, chunkZ, immediate, onLoad);
+ return;
+ } else {
+ if (gen || (!allowSubTicketLevel && immediate.getStatus().isOrAfter(status))) {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+ return;
+ } else {
+ this.chunkLoadAccept(chunkX, chunkZ, null, onLoad);
+ return;
+ }
+ }
+ }
+ }
+
+ // need to fire async
+
+ if (gen && !allowSubTicketLevel) {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+ return;
+ }
+
+ this.getChunkAtAsynchronously(chunkX, chunkZ, net.minecraft.server.MCUtil.getTicketLevelFor(ChunkStatus.EMPTY), (ChunkAccess chunk) -> {
+ if (chunk == null) {
+ throw new IllegalStateException("Chunk cannot be null");
+ }
+
+ if (!chunk.getStatus().isOrAfter(status)) {
+ if (gen) {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+ return;
+ } else {
+ ServerChunkCache.this.chunkLoadAccept(chunkX, chunkZ, null, onLoad);
+ return;
+ }
+ } else {
+ if (allowSubTicketLevel) {
+ ServerChunkCache.this.chunkLoadAccept(chunkX, chunkZ, chunk, onLoad);
+ return;
+ } else {
+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad);
+ return;
+ }
+ }
+ });
+ }
+ // Paper end
+
+ // Paper start