diff options
Diffstat (limited to 'patches/server/0334-Fix-World-isChunkGenerated-calls.patch')
-rw-r--r-- | patches/server/0334-Fix-World-isChunkGenerated-calls.patch | 292 |
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 |