Paper/patches/server/0982-Fix-World-isChunkGenerated-calls.patch
Nassim Jahnke 6e71f41536
Updated Upstream (CraftBukkit/Spigot)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

CraftBukkit Changes:
65247583f SPIGOT-7857: Improve ItemMeta block data deserialization
05d80500d SPIGOT-7857: Fix spurious internal NBT tag when deserializing BlockStateMeta
cebb58e9a SPIGOT-7804: Fix written book serialization
efcdd5d38 SPIGOT-7794: Cancelling InventoryItemMoveEvent destroys items
b568ba572 SPIGOT-7789: Fix NPE in CraftMetaFirework applyToItem
f057cf449 Remove outdated build delay

Spigot Changes:
f6a48054 SPIGOT-7835: Fix issue with custom hopper settings
bb63b137 Rebuild patches
e1142b4d Rebuild patches
2024-08-24 11:23:57 +02:00

244 lines
12 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Sat, 15 Jun 2019 08:54:33 -0700
Subject: [PATCH] Fix World#isChunkGenerated calls
Optimize World#loadChunk() too
This patch also adds a chunk status cache on region files (note that
its only purpose is to cache the status on DISK)
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index d6ecee1db17cb9eaeffa94b3d8dd150238fdefe5..1d2d4d38ff3414896d07f7f4e40d0edb9a8c3993 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -735,9 +735,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end
private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkPos) {
- return this.read(chunkPos).thenApplyAsync((optional) -> {
- return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit
- }, Util.backgroundExecutor());
+ // Paper start - Cache chunk status on disk
+ try {
+ return CompletableFuture.completedFuture(Optional.ofNullable(this.readConvertChunkSync(chunkPos)));
+ } catch (Throwable thr) {
+ return CompletableFuture.failedFuture(thr);
+ }
+ // Paper end - Cache chunk status on disk
}
// CraftBukkit start
@@ -746,6 +750,60 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// CraftBukkit end
}
+ // Paper start - Cache chunk status on disk
+ @Nullable
+ public CompoundTag readConvertChunkSync(ChunkPos pos) throws IOException {
+ CompoundTag nbttagcompound = this.readSync(pos);
+ if (nbttagcompound == null) {
+ return null;
+ }
+
+ nbttagcompound = this.upgradeChunkTag(nbttagcompound, pos); // CraftBukkit
+ if (nbttagcompound == null) {
+ return null;
+ }
+
+ this.updateChunkStatusOnDisk(pos, nbttagcompound);
+
+ return nbttagcompound;
+ }
+
+ public ChunkStatus getChunkStatusOnDiskIfCached(ChunkPos chunkPos) {
+ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos);
+
+ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+ }
+
+ public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException {
+ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, true);
+
+ if (regionFile == null || !regionFileCache.chunkExists(chunkPos)) {
+ return null;
+ }
+
+ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+
+ if (status != null) {
+ return status;
+ }
+
+ this.readChunk(chunkPos);
+
+ return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+ }
+
+ public void updateChunkStatusOnDisk(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException {
+ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, false);
+
+ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound));
+ }
+
+ public ChunkAccess getUnloadingChunk(int chunkX, int chunkZ) {
+ ChunkHolder chunkHolder = io.papermc.paper.chunk.system.ChunkSystem.getUnloadingChunkHolder(this.level, chunkX, chunkZ);
+ return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow();
+ }
+ // Paper end - Cache chunk status on disk
+
public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { // Paper - public
// Spigot start
return this.anyPlayerCloseEnoughForSpawning(pos, false);
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
index f994f91dd4b4a0a6d540a5605af0b6623e229f84..cf43daa019b239464401784938d01af83f9da47c 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -52,6 +52,29 @@ public class RegionFile implements AutoCloseable {
@VisibleForTesting
protected final RegionBitmap usedSectors;
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper
+ // Paper start - Cache chunk status
+ private final net.minecraft.world.level.chunk.status.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.status.ChunkStatus[32 * 32];
+
+ private boolean closed;
+
+ // invoked on write/read
+ public void setStatus(int x, int z, net.minecraft.world.level.chunk.status.ChunkStatus status) {
+ if (this.closed) {
+ // We've used an invalid region file.
+ throw new IllegalStateException("RegionFile is closed");
+ }
+ this.statuses[getChunkLocation(x, z)] = status;
+ }
+
+ public net.minecraft.world.level.chunk.status.ChunkStatus getStatusIfCached(int x, int z) {
+ if (this.closed) {
+ // We've used an invalid region file.
+ throw new IllegalStateException("RegionFile is closed");
+ }
+ final int location = getChunkLocation(x, z);
+ return this.statuses[location];
+ }
+ // Paper end - Cache chunk status
public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException {
this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format
@@ -417,6 +440,7 @@ public class RegionFile implements AutoCloseable {
return this.getOffset(pos) != 0;
}
+ private static int getChunkLocation(int x, int z) { return (x & 31) + (z & 31) * 32; } // Paper - Cache chunk status; OBFHELPER - sort of, mirror of logic below
private static int getOffsetIndex(ChunkPos pos) {
return pos.getRegionLocalX() + pos.getRegionLocalZ() * 32;
}
@@ -427,6 +451,7 @@ public class RegionFile implements AutoCloseable {
synchronized (this) {
try {
// Paper end
+ this.closed = true; // Paper - Cache chunk status
try {
this.padToFullSector();
} finally {
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
index c2838ae91b7f078369b63503df57a1eb5d2b0df5..c33640859aab837c85f3e860fe2241a0e78bb09a 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -268,6 +268,7 @@ public class RegionFileStorage implements AutoCloseable {
try {
NbtIo.write(nbt, (DataOutput) dataoutputstream);
+ regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - Cache chunk status
regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone
// Paper start - don't write garbage data to disk if writing serialization fails
dataoutputstream.close(); // Only write if successful
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 9c06c3729b09726e1da6ff8fb975cef2aeba9643..8f50d893f8f9dea306756b640abd2373ee028a86 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -391,9 +391,23 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean isChunkGenerated(int x, int z) {
+ // Paper start - Fix this method
+ if (!Bukkit.isPrimaryThread()) {
+ return java.util.concurrent.CompletableFuture.supplyAsync(() -> {
+ return CraftWorld.this.isChunkGenerated(x, z);
+ }, world.getChunkSource().mainThreadProcessor).join();
+ }
+ ChunkAccess chunk = world.getChunkSource().getChunkAtImmediately(x, z);
+ if (chunk == null) {
+ chunk = world.getChunkSource().chunkMap.getUnloadingChunk(x, z);
+ }
+ if (chunk != null) {
+ return chunk instanceof ImposterProtoChunk || chunk instanceof net.minecraft.world.level.chunk.LevelChunk;
+ }
try {
- return this.isChunkLoaded(x, z) || this.world.getChunkSource().chunkMap.read(new ChunkPos(x, z)).get().isPresent();
- } catch (InterruptedException | ExecutionException ex) {
+ return world.getChunkSource().chunkMap.getChunkStatusOnDisk(new ChunkPos(x, z)) == ChunkStatus.FULL;
+ } catch (java.io.IOException ex) {
+ // Paper end - Fix this method
throw new RuntimeException(ex);
}
}
@@ -572,20 +586,48 @@ public class CraftWorld extends CraftRegionAccessor implements World {
public boolean loadChunk(int x, int z, boolean generate) {
org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot
warnUnsafeChunk("loading a faraway chunk", x, z); // Paper
- ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper
-
- // If generate = false, but the chunk already exists, we will get this back.
- if (chunk instanceof ImposterProtoChunk) {
- // We then cycle through again to get the full chunk immediately, rather than after the ticket addition
- chunk = this.world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true);
- }
-
- if (chunk instanceof net.minecraft.world.level.chunk.LevelChunk) {
- this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 0, Unit.INSTANCE); // Paper
+ // Paper start - Optimize this method
+ ChunkPos chunkPos = new ChunkPos(x, z);
+ ChunkAccess immediate = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z);
+ if (immediate != null) {
+ // Plugins should use plugin tickets instead of this method to keep a chunk perpetually loaded
return true;
}
- return false;
+ if (!generate) {
+ immediate = world.getChunkSource().chunkMap.getUnloadingChunk(x, z);
+ if (immediate != null) {
+ if (!(immediate instanceof ImposterProtoChunk) && !(immediate instanceof net.minecraft.world.level.chunk.LevelChunk)) {
+ return false; // not full status
+ }
+ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper
+ world.getChunk(x, z); // make sure we're at ticket level 32 or lower
+ return true;
+ }
+ net.minecraft.world.level.chunk.storage.RegionFile file;
+ try {
+ file = world.getChunkSource().chunkMap.regionFileCache.getRegionFile(chunkPos, false);
+ } catch (java.io.IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ ChunkStatus status = file.getStatusIfCached(x, z);
+ if (!file.hasChunk(chunkPos) || (status != null && status != ChunkStatus.FULL)) {
+ return false;
+ }
+
+ ChunkAccess chunk = world.getChunkSource().getChunk(x, z, ChunkStatus.EMPTY, true);
+ if (!(chunk instanceof ImposterProtoChunk) && !(chunk instanceof net.minecraft.world.level.chunk.LevelChunk)) {
+ return false;
+ }
+
+ // fall through to load
+ // we do this so we do not re-read the chunk data on disk
+ }
+ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 0, Unit.INSTANCE); // Paper
+ world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true);
+ return true;
+ // Paper end - Optimize this method
}
@Override