2021-06-11 14:02:28 +02:00
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
2022-07-27 21:49:24 +02:00
index 47cb3be2d778a55bfbe733a9cc2965c8bf28a47a..33483dc272dbdee74767934f4b379a675162b7d1 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
2022-07-27 21:49:24 +02:00
@@ -1250,9 +1250,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
2022-06-08 04:25:49 +02:00
// 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
2022-07-27 21:49:24 +02:00
@@ -1261,6 +1265,63 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
2022-06-08 04:25:49 +02:00
// CraftBukkit end
}
+ // Paper start - Cache chunk status on disk
+ @Nullable
+ public CompoundTag readConvertChunkSync(ChunkPos pos) throws IOException {
+ CompoundTag nbttagcompound = this.readSync(pos);
2021-06-11 14:02:28 +02:00
+ // Paper start - Cache chunk status on disk
+ if (nbttagcompound == null) {
+ return null;
+ }
+
2022-06-08 04:25:49 +02:00
+ nbttagcompound = this.upgradeChunkTag(nbttagcompound, pos); // CraftBukkit
2021-06-11 14:02:28 +02:00
+ if (nbttagcompound == null) {
+ return null;
+ }
+
+ this.updateChunkStatusOnDisk(pos, nbttagcompound);
+
+ return nbttagcompound;
+ // Paper end
+ }
+
+ // Paper start - chunk status cache "api"
+ public ChunkStatus getChunkStatusOnDiskIfCached(ChunkPos chunkPos) {
2022-06-08 04:25:49 +02:00
+ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos);
+
2021-06-11 14:02:28 +02:00
+ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
2022-06-08 04:25:49 +02:00
+ }
+
2021-06-11 14:02:28 +02:00
+ public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException {
2022-06-08 04:25:49 +02:00
+ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, true);
2021-06-11 14:02:28 +02:00
+
2021-06-13 15:46:28 +02:00
+ if (regionFile == null || !regionFileCache.chunkExists(chunkPos)) {
2021-06-11 14:02:28 +02:00
+ return null;
+ }
+
+ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+
+ if (status != null) {
+ return status;
+ }
+
+ this.readChunk(chunkPos);
2021-06-13 15:46:28 +02:00
+
2021-06-11 14:02:28 +02:00
+ return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
2021-06-13 15:46:28 +02:00
+ }
+
2021-06-11 14:02:28 +02:00
+ public void updateChunkStatusOnDisk(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException {
2022-06-08 04:25:49 +02:00
+ net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, false);
2021-06-11 14:02:28 +02:00
+
+ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound));
+ }
+
+ public ChunkAccess getUnloadingChunk(int chunkX, int chunkZ) {
+ ChunkHolder chunkHolder = this.pendingUnloads.get(ChunkPos.asLong(chunkX, chunkZ));
+ return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow();
+ }
+ // Paper end
+
2021-11-24 05:25:34 +01:00
boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) {
2021-06-11 14:02:28 +02:00
// Spigot start
2021-11-24 05:25:34 +01:00
return this.anyPlayerCloseEnoughForSpawning(pos, false);
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
2022-06-08 04:25:49 +02:00
index 441d46635caedfae3cb2f46d30b8d9ae95636e7b..e6240f891e396d91e31b02fdf3084be77e9d6697 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
2022-06-08 04:25:49 +02:00
@@ -292,6 +292,17 @@ public class ChunkStatus {
2021-06-11 14:02:28 +02:00
return this.chunkType;
}
+ // Paper start
+ public static ChunkStatus getStatus(String name) {
+ try {
+ // We need this otherwise we return EMPTY for invalid names
+ ResourceLocation key = new ResourceLocation(name);
+ return Registry.CHUNK_STATUS.getOptional(key).orElse(null);
+ } catch (Exception ex) {
+ return null; // invalid name
+ }
+ }
+ // Paper end
public static ChunkStatus byName(String id) {
return (ChunkStatus) Registry.CHUNK_STATUS.get(ResourceLocation.tryParse(id));
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
2022-06-08 04:25:49 +02:00
index 8cc2a2c026eb44461cd94faeb64fb2151d2d3898..8480310bd389ad55b8138a20da59cbcffb973819 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
2022-06-08 04:25:49 +02:00
@@ -623,6 +623,17 @@ public class ChunkSerializer {
2021-11-24 05:25:34 +01:00
}));
2021-06-11 14:02:28 +02:00
}
+ // Paper start
2021-11-24 05:25:34 +01:00
+ public static @Nullable ChunkStatus getStatus(@Nullable CompoundTag compound) {
2021-06-11 14:02:28 +02:00
+ if (compound == null) {
+ return null;
+ }
+
+ // Note: Copied from below
2021-11-27 11:42:09 +01:00
+ return ChunkStatus.getStatus(compound.getString("Status"));
2021-06-11 14:02:28 +02:00
+ }
+ // Paper end
+
2021-06-13 15:46:28 +02:00
public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag nbt) {
2021-11-24 05:25:34 +01:00
return nbt != null ? ChunkStatus.byName(nbt.getString("Status")).getChunkType() : ChunkStatus.ChunkType.PROTOCHUNK;
}
2021-06-11 14:02:28 +02:00
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
2022-03-01 06:43:03 +01:00
index 330fb8e6565a5c0490af0c5ca0e7355d81a82e58..861a25a15f1aab20e3245b6d5cdad5d23bdfd6d0 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
2022-03-01 06:43:03 +01:00
@@ -51,6 +51,30 @@ public class RegionFile implements AutoCloseable {
2021-06-13 15:46:28 +02:00
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
2021-11-24 05:25:34 +01:00
public final Path regionFile; // Paper
2021-06-11 14:02:28 +02:00
+ // Paper start - Cache chunk status
2022-03-01 06:43:03 +01:00
+ private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32];
2021-06-11 14:02:28 +02:00
+
+ private boolean closed;
+
+ // invoked on write/read
2022-03-01 06:43:03 +01:00
+ public void setStatus(int x, int z, net.minecraft.world.level.chunk.ChunkStatus status) {
2021-06-11 14:02:28 +02:00
+ if (this.closed) {
+ // We've used an invalid region file.
+ throw new IllegalStateException("RegionFile is closed");
+ }
+ this.statuses[getChunkLocation(x, z)] = status;
+ }
+
2022-03-01 06:43:03 +01:00
+ public net.minecraft.world.level.chunk.ChunkStatus getStatusIfCached(int x, int z) {
2021-06-11 14:02:28 +02:00
+ 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
+
2021-11-24 23:26:32 +01:00
public RegionFile(Path file, Path directory, boolean dsync) throws IOException {
this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync);
2021-06-11 14:02:28 +02:00
}
2022-03-01 06:43:03 +01:00
@@ -398,6 +422,7 @@ public class RegionFile implements AutoCloseable {
2021-06-11 14:02:28 +02:00
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;
}
2022-03-01 06:43:03 +01:00
@@ -408,6 +433,7 @@ public class RegionFile implements AutoCloseable {
2021-06-13 15:46:28 +02:00
synchronized (this) {
try {
// Paper end
2021-06-11 14:02:28 +02:00
+ 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
2022-06-09 10:51:45 +02:00
index e734d82c2863caca8abc6bb8bb82882e971c36a8..a96a6af2bcec3134b7caa32299bd07af50e83b89 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
2021-11-27 16:07:45 +01:00
@@ -245,6 +245,7 @@ public class RegionFileStorage implements AutoCloseable {
2021-06-11 14:02:28 +02:00
2021-06-13 15:46:28 +02:00
try {
NbtIo.write(nbt, (DataOutput) dataoutputstream);
+ regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - cache status on disk
2021-06-16 08:25:38 +02:00
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
2021-06-13 15:46:28 +02:00
} catch (Throwable throwable) {
if (dataoutputstream != null) {
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
2022-07-04 16:38:06 +02:00
index 2518674a967a5fbf2229a0fdd60447a62cfbf856..cd70aa0fa7a575c3c1f6434db74c1cc8342fddb4 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
2022-07-04 16:38:06 +02:00
@@ -300,9 +300,23 @@ public class CraftWorld extends CraftRegionAccessor implements World {
2021-06-11 14:02:28 +02:00
@Override
public boolean isChunkGenerated(int x, int z) {
+ // Paper start - Fix this method
+ if (!Bukkit.isPrimaryThread()) {
2022-06-08 04:25:49 +02:00
+ return java.util.concurrent.CompletableFuture.supplyAsync(() -> {
2021-06-11 14:02:28 +02:00
+ 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 {
2022-06-08 04:25:49 +02:00
- return this.isChunkLoaded(x, z) || this.world.getChunkSource().chunkMap.read(new ChunkPos(x, z)).get().isPresent();
- } catch (InterruptedException | ExecutionException ex) {
2021-06-11 14:02:28 +02:00
+ return world.getChunkSource().chunkMap.getChunkStatusOnDisk(new ChunkPos(x, z)) == ChunkStatus.FULL;
+ // Paper end
2022-06-08 04:25:49 +02:00
+ } catch (IOException ex) {
2021-06-11 14:02:28 +02:00
throw new RuntimeException(ex);
}
2022-06-08 04:25:49 +02:00
}
2022-07-04 16:38:06 +02:00
@@ -413,20 +427,48 @@ public class CraftWorld extends CraftRegionAccessor implements World {
2021-06-11 14:02:28 +02:00
@Override
public boolean loadChunk(int x, int z, boolean generate) {
org.spigotmc.AsyncCatcher.catchOp("chunk load"); // Spigot
2021-06-13 15:46:28 +02:00
- ChunkAccess chunk = this.world.getChunkSource().getChunk(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); // Paper
2021-06-11 14:02:28 +02:00
+ // 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
2021-06-13 15:46:28 +02:00
- chunk = this.world.getChunkSource().getChunk(x, z, ChunkStatus.FULL, true);
2021-06-11 14:02:28 +02:00
- }
+ 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) {
2021-06-13 15:46:28 +02:00
- this.world.getChunkSource().addRegionTicket(TicketType.PLUGIN, new ChunkPos(x, z), 1, Unit.INSTANCE);
2021-06-11 14:02:28 +02:00
- return true;
+ net.minecraft.world.level.chunk.storage.RegionFile file;
+ try {
2021-11-24 05:25:34 +01:00
+ file = world.getChunkSource().chunkMap.regionFileCache.getRegionFile(chunkPos, false);
2021-06-11 14:02:28 +02:00
+ } catch (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