aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0334-Fix-World-isChunkGenerated-calls.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0334-Fix-World-isChunkGenerated-calls.patch')
-rw-r--r--patches/server/0334-Fix-World-isChunkGenerated-calls.patch292
1 files changed, 292 insertions, 0 deletions
diff --git a/patches/server/0334-Fix-World-isChunkGenerated-calls.patch b/patches/server/0334-Fix-World-isChunkGenerated-calls.patch
new file mode 100644
index 0000000000..41006d2ad6
--- /dev/null
+++ b/patches/server/0334-Fix-World-isChunkGenerated-calls.patch
@@ -0,0 +1,292 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+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 87f055f8338d4ce2f9ff76bdc6c0b7ffc266ce78..9dd2f5d7ea6a1d6744916c403bdd852bb66e45b8 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -87,6 +87,7 @@ import net.minecraft.world.level.chunk.ProtoChunk;
+ import net.minecraft.world.level.chunk.UpgradeData;
+ import net.minecraft.world.level.chunk.storage.ChunkSerializer;
+ import net.minecraft.world.level.chunk.storage.ChunkStorage;
++import net.minecraft.world.level.chunk.storage.RegionFile;
+ import net.minecraft.world.level.entity.ChunkStatusUpdateListener;
+ import net.minecraft.world.level.levelgen.blending.BlendingData;
+ import net.minecraft.world.level.levelgen.structure.StructureStart;
+@@ -1178,10 +1179,59 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ @Nullable
+ public CompoundTag readChunk(ChunkPos pos) throws IOException {
+ CompoundTag nbttagcompound = this.read(pos);
++ // Paper start - Cache chunk status on disk
++ if (nbttagcompound == null) {
++ return null;
++ }
++
++ nbttagcompound = this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator.getTypeNameForDataFixer(), pos, level); // 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) {
++ RegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos);
+
+- return nbttagcompound == null ? null : this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator.getTypeNameForDataFixer(), pos, level); // CraftBukkit
++ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+ }
+
++ public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException {
++ 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 {
++ RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, false);
++
++ 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
++
+ boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) {
+ // Spigot start
+ return this.anyPlayerCloseEnoughForSpawning(pos, false);
+diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
+index 328f482a0bae8d2f8013ae9a90f0500ef889ffb5..6c72854aa975800bd6160d104936a5ba978f4d67 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
+@@ -290,6 +290,17 @@ public class ChunkStatus {
+ 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
+index 89de1589833dcce8028fd402aea8a3e57dc29e86..3e631d55d30831a4063e23f9dbc7a315d11a7b68 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+@@ -604,6 +604,17 @@ public class ChunkSerializer {
+ }));
+ }
+
++ // Paper start
++ public static @Nullable ChunkStatus getStatus(@Nullable CompoundTag compound) {
++ if (compound == null) {
++ return null;
++ }
++
++ // Note: Copied from below
++ return ChunkStatus.getStatus(compound.getString("Status"));
++ }
++ // Paper end
++
+ public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag nbt) {
+ return nbt != null ? ChunkStatus.byName(nbt.getString("Status")).getChunkType() : ChunkStatus.ChunkType.PROTOCHUNK;
+ }
+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 44de464b5f2190944c7a7316a76e13f9c3b954ab..293cce2c80fbdc18480977f5f6b24d6b4fa8dcf3 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
+@@ -24,6 +24,7 @@ import net.minecraft.Util;
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.nbt.NbtIo;
+ import net.minecraft.world.level.ChunkPos;
++import net.minecraft.world.level.chunk.ChunkStatus;
+ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+
+@@ -51,6 +52,30 @@ public class RegionFile implements AutoCloseable {
+ public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
+ public final Path regionFile; // Paper
+
++ // Paper start - Cache chunk status
++ private final ChunkStatus[] statuses = new ChunkStatus[32 * 32];
++
++ private boolean closed;
++
++ // invoked on write/read
++ public void setStatus(int x, int z, 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 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);
+ }
+@@ -398,6 +423,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;
+ }
+@@ -408,6 +434,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 2cbc17288b1dc52edb2bdad29976d0f551b1e176..2ee32657a49937418b352a138aca21fbb27857e6 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
+@@ -245,6 +245,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 255616aa45b06487c67aa6011dbe29e18d82bc68..706d5718997181279f7ec715526b4d8f2b6162a2 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -19,6 +19,7 @@ import java.util.Objects;
+ import java.util.Random;
+ import java.util.Set;
+ import java.util.UUID;
++import java.util.concurrent.CompletableFuture;
+ import java.util.function.Predicate;
+ import java.util.stream.Collectors;
+ import net.minecraft.core.BlockPos;
+@@ -282,8 +283,22 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+
+ @Override
+ public boolean isChunkGenerated(int x, int z) {
++ // Paper start - Fix this method
++ if (!Bukkit.isPrimaryThread()) {
++ return 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.world.getChunkSource().getChunkAtIfCachedImmediately(x, z) != null || this.world.getChunkSource().chunkMap.read(new ChunkPos(x, z)) != null; // Paper (TODO check if the first part can be removed)
++ return world.getChunkSource().chunkMap.getChunkStatusOnDisk(new ChunkPos(x, z)) == ChunkStatus.FULL;
++ // Paper end
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+@@ -395,20 +410,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 (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