Paper/patches/server/0316-Fix-World-isChunkGenerated-calls.patch
Jake Potrebic 90fe0d58a5
Updated Upstream (Bukkit/CraftBukkit/Spigot) (#9825)
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

Bukkit Changes:
897a0a23 SPIGOT-5753: Back PotionType by a minecraft registry
255b2aa1 SPIGOT-7080: Add World#locateNearestBiome
ff984826 Remove javadoc.io doc links

CraftBukkit Changes:
71b0135cc SPIGOT-5753: Back PotionType by a minecraft registry
a6bcb8489 SPIGOT-7080: Add World#locateNearestBiome
ad0e57434 SPIGOT-7502: CraftMetaItem - cannot deserialize BlockStateTag
b3efca57a SPIGOT-6400: Use Mockito instead of InvocationHandler
38c599f9d PR-1272: Only allow one entity in CraftItem instead of two
f065271ac SPIGOT-7498: ChunkSnapshot.getBlockEmittedLight() gets 64 blocks upper in Overworld

Spigot Changes:
e0e223fe Remove javadoc.io doc links
2023-10-22 20:12:00 +01:00

247 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 c69be9cb3f03ec50e4e57d7e1e93a83701e4cd6c..5b7260b5a14cb9d2b90cf3c411d119e6bfa84046 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -680,9 +680,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
@@ -691,6 +695,63 @@ 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);
+ // Paper start - Cache chunk status on disk
+ if (nbttagcompound == null) {
+ return null;
+ }
+
+ nbttagcompound = this.upgradeChunkTag(nbttagcompound, pos); // CraftBukkit
+ if (nbttagcompound == null) {
+ return null;
+ }
+
+ this.updateChunkStatusOnDisk(pos, nbttagcompound);
+
+ return nbttagcompound;
+ // Paper end
+ }
+
+ // Paper start - chunk status cache "api"
+ 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
+
boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) {
// 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 72dacdc271325c814fb43cd6daaf3a209801ffda..647ce340c81606ab86d33e1f9dec1fb0afc262d8 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
@@ -50,6 +50,30 @@ public class RegionFile implements AutoCloseable {
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // Paper
public final Path regionFile; // Paper
+ // Paper start - Cache chunk status
+ private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32];
+
+ private boolean closed;
+
+ // invoked on write/read
+ public void setStatus(int x, int z, net.minecraft.world.level.chunk.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.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
+
public RegionFile(Path file, Path directory, boolean dsync) throws IOException {
this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync);
}
@@ -397,6 +421,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 - OBFHELPER - sort of, mirror of logic below
private static int getOffsetIndex(ChunkPos pos) {
return pos.getRegionLocalX() + pos.getRegionLocalZ() * 32;
}
@@ -407,6 +432,7 @@ public class RegionFile implements AutoCloseable {
synchronized (this) {
try {
// Paper end
+ this.closed = true; // Paper
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 42dc999d820e62c6a222afbd9239cc671fc7de53..b850dba2b0fa5bc762b170ed7083cf8904761f17 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
@@ -289,6 +289,7 @@ public class RegionFileStorage implements AutoCloseable {
try {
NbtIo.write(nbt, (DataOutput) dataoutputstream);
+ regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - cache status on disk
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
} catch (Throwable throwable) {
if (dataoutputstream != null) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index fdb3de1dee72a0e90a42a9d39fa021c0b81922ef..6c13c3c55d58d654d28d5eebcc6dd9789f6565dd 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -308,9 +308,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;
+ // Paper end
+ } catch (java.io.IOException ex) {
throw new RuntimeException(ex);
}
}
@@ -424,20 +438,48 @@ public class CraftWorld extends CraftRegionAccessor implements World {
@Override
public boolean loadChunk(int x, int z, boolean generate) {
org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot
- ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper
+ // Paper start - Optimize this method
+ ChunkPos chunkPos = new ChunkPos(x, z);
- // 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 (!generate) {
+ ChunkAccess immediate = world.getChunkSource().getChunkAtImmediately(x, z);
+ if (immediate == null) {
+ 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, 1, Unit.INSTANCE);
+ world.getChunk(x, z); // make sure we're at ticket level 32 or lower
+ return true;
+ }
- if (chunk instanceof net.minecraft.world.level.chunk.LevelChunk) {
- this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE);
- 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
}
- return false;
+ world.getChunkSource().addRegionTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE);
+ world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true);
+ return true;
+ // Paper end
}
@Override