diff --git a/Spigot-Server-Patches/Anti-Xray.patch b/Spigot-Server-Patches/Anti-Xray.patch index 2c645e55dd..d6901b9fbc 100644 --- a/Spigot-Server-Patches/Anti-Xray.patch +++ b/Spigot-Server-Patches/Anti-Xray.patch @@ -1565,7 +1565,7 @@ index 761cd1355..956a47132 100644 this.a(new PacketPlayOutMultiBlockChange(this.dirtyCount, this.dirtyBlocks, chunk), false); diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index f21961d3a..f5b35b95b 100644 +index 7cb5438e4..a43927781 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { diff --git a/Spigot-Server-Patches/Fix-MC-154214.patch b/Spigot-Server-Patches/Fix-MC-154214.patch index 150e4b6d1e..c68c137019 100644 --- a/Spigot-Server-Patches/Fix-MC-154214.patch +++ b/Spigot-Server-Patches/Fix-MC-154214.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Fix MC-154214 Avoid adding player tickets when they're out of range of the closest player diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java -index 99c7537ef..757b505ea 100644 +index 99c7537ef2..757b505eaf 100644 --- a/src/main/java/net/minecraft/server/ChunkMapDistance.java +++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java @@ -0,0 +0,0 @@ public abstract class ChunkMapDistance { diff --git a/Spigot-Server-Patches/Fix-World-isChunkGenerated-calls.patch b/Spigot-Server-Patches/Fix-World-isChunkGenerated-calls.patch index 4c5986b22c..5c085f7eca 100644 --- a/Spigot-Server-Patches/Fix-World-isChunkGenerated-calls.patch +++ b/Spigot-Server-Patches/Fix-World-isChunkGenerated-calls.patch @@ -8,7 +8,7 @@ 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/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java -index bff81bdb8..dab86960a 100644 +index fef44094b..539c65f85 100644 --- a/src/main/java/net/minecraft/server/ChunkProviderServer.java +++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java @@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider { @@ -134,7 +134,7 @@ index 0daf5f99e..761cd1355 100644 public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index 884c8a908..e0798c1c8 100644 +index 884c8a908..e2f8b18d9 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @@ -156,15 +156,48 @@ index 884c8a908..e0798c1c8 100644 + return null; + } + -+ this.getRegionFile(chunkcoordintpair, false).setStatus(chunkcoordintpair.x, chunkcoordintpair.z, ChunkRegionLoader.getStatus(nbttagcompound)); ++ this.updateChunkStatusOnDisk(chunkcoordintpair, nbttagcompound); + + return nbttagcompound; + // Paper end ++ } ++ ++ // Paper start - chunk status cache "api" ++ public ChunkStatus getChunkStatusOnDiskIfCached(ChunkCoordIntPair chunkPos) { ++ RegionFile regionFile = this.getRegionFileIfLoaded(chunkPos); ++ ++ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ } ++ ++ public ChunkStatus getChunkStatusOnDisk(ChunkCoordIntPair chunkPos) throws IOException { ++ RegionFile regionFile = this.getRegionFile(chunkPos, false); ++ ++ if (!regionFile.chunkExists(chunkPos)) { ++ return null; ++ } ++ ++ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ ++ if (status != null) { ++ return status; ++ } ++ ++ this.readChunkData(chunkPos); ++ ++ return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z); ++ } ++ ++ public void updateChunkStatusOnDisk(ChunkCoordIntPair chunkPos, @Nullable NBTTagCompound compound) throws IOException { ++ RegionFile regionFile = this.getRegionFile(chunkPos, false); ++ ++ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkRegionLoader.getStatus(compound)); } ++ // Paper end // Spigot Start + boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair, boolean reducedRange) { diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java -index 2e14d8465..d610253b9 100644 +index 2e14d8465..66c8b0307 100644 --- a/src/main/java/net/minecraft/server/RegionFile.java +++ b/src/main/java/net/minecraft/server/RegionFile.java @@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable { @@ -193,28 +226,19 @@ index 2e14d8465..d610253b9 100644 + final int location = this.getChunkLocation(new ChunkCoordIntPair(x, z)); + return this.statuses[location]; + } -+ -+ public ChunkStatus getStatus(int x, int z, PlayerChunkMap playerChunkMap) throws IOException { -+ if (this.closed) { -+ // We've used an invalid region file. -+ throw new java.io.EOFException("RegionFile is closed"); -+ } -+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z); -+ int location = this.getChunkLocation(chunkPos); -+ ChunkStatus cached = this.statuses[location]; -+ if (cached != null) { -+ return cached; -+ } -+ -+ playerChunkMap.readChunkData(chunkPos); // This will set our status (yes it's disgusting) -+ -+ return this.statuses[location]; -+ } + // Paper end + public RegionFile(File file) throws IOException { this.b = new RandomAccessFile(file, "rw"); this.file = file; // Spigot // Paper - We need this earlier +@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable { + return this.c[this.f(chunkcoordintpair)]; + } + ++ public final boolean chunkExists(ChunkCoordIntPair chunkPos) { return this.d(chunkPos); } // Paper - OBFHELPER + public boolean d(ChunkCoordIntPair chunkcoordintpair) { + return this.getOffset(chunkcoordintpair) != 0; + } @@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable { this.writeInt(i); // Paper - Avoid 3 io write calls } @@ -265,7 +289,7 @@ index 6f34d8aea..d2b328945 100644 printOversizedLog("ChunkTooLarge even after reduction. Trying in overzealous mode.", regionfile.file, chunkX, chunkZ); // Eek, major fail. We have retry logic, so reduce threshholds and fall back diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index bb0f75f52..ece1a68c1 100644 +index bb0f75f52..f01e8a87e 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -0,0 +0,0 @@ public class CraftWorld implements World { @@ -284,8 +308,7 @@ index bb0f75f52..ece1a68c1 100644 + } try { - return world.getChunkProvider().getChunkAtIfCachedImmediately(x, z) != null || world.getChunkProvider().playerChunkMap.chunkExists(new ChunkCoordIntPair(x, z)); // Paper -+ return world.getChunkProvider().playerChunkMap.getRegionFile(new ChunkCoordIntPair(x, z), false) -+ .getStatus(x, z, world.getChunkProvider().playerChunkMap) == ChunkStatus.FULL; ++ return world.getChunkProvider().playerChunkMap.getChunkStatusOnDisk(new ChunkCoordIntPair(x, z)) == ChunkStatus.FULL; + // Paper end } catch (IOException ex) { throw new RuntimeException(ex); @@ -295,32 +318,50 @@ index bb0f75f52..ece1a68c1 100644 public boolean loadChunk(int x, int z, boolean generate) { org.spigotmc.AsyncCatcher.catchOp( "chunk load"); // Spigot - IChunkAccess chunk = world.getChunkProvider().getChunkAt(x, z, generate || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); -- ++ // Paper start - Optimize this method ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z); + - // If generate = false, but the chunk already exists, we will get this back. - if (chunk instanceof ProtoChunkExtension) { - // We then cycle through again to get the full chunk immediately, rather than after the ticket addition - chunk = world.getChunkProvider().getChunkAt(x, z, ChunkStatus.FULL, true); -- } -+ // Paper - Optimize this method -+ if (!generate) { -+ net.minecraft.server.RegionFile file = world.getChunkProvider().playerChunkMap.getRegionFileIfLoaded(new ChunkCoordIntPair(x, z)); -+ if (file != null && file.getStatusIfCached(x, z) != ChunkStatus.FULL) { -+ return false; ++ IChunkAccess immediate = world.getChunkProvider().getChunkAtImmediately(x, z); ++ if (immediate != null) { ++ if (!(immediate instanceof ProtoChunkExtension) && !(immediate instanceof net.minecraft.server.Chunk)) { ++ return false; // not full status + } ++ world.getChunkProvider().addTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); ++ world.getChunkAt(x, z); // make sure we're at ticket level 32 or lower ++ return true; + } - if (chunk instanceof net.minecraft.server.Chunk) { - world.getChunkProvider().addTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 1, Unit.INSTANCE); - return true; ++ if (!generate) { ++ net.minecraft.server.RegionFile file; ++ try { ++ file = world.getChunkProvider().playerChunkMap.getRegionFile(chunkPos, false); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ ChunkStatus status = file.getStatusIfCached(x, z); ++ if (!file.chunkExists(chunkPos) || (status != null && status != ChunkStatus.FULL)) { ++ return false; ++ } ++ + IChunkAccess chunk = world.getChunkProvider().getChunkAt(x, z, ChunkStatus.EMPTY, true); + if (!(chunk instanceof ProtoChunkExtension) && !(chunk instanceof net.minecraft.server.Chunk)) { + return false; + } ++ + // fall through to load + // we do this so we do not re-read the chunk data on disk } -- + - return false; -+ world.getChunkProvider().addTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 1, Unit.INSTANCE); ++ world.getChunkProvider().addTicket(TicketType.PLUGIN, chunkPos, 1, Unit.INSTANCE); + world.getChunkProvider().getChunkAt(x, z, ChunkStatus.FULL, true); + return true; + // Paper end @@ -334,31 +375,47 @@ index bb0f75f52..ece1a68c1 100644 - // copied from loadChunk() - // this function is identical except we do not add a plugin ticket - IChunkAccess chunk = world.getChunkProvider().getChunkAt(x, z, gen || isChunkGenerated(x, z) ? ChunkStatus.FULL : ChunkStatus.EMPTY, true); -- ++ // Note: Copied from loadChunk() ++ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(x, z); + - // If generate = false, but the chunk already exists, we will get this back. - if (chunk instanceof ProtoChunkExtension) { - // We then cycle through again to get the full chunk immediately, rather than after the ticket addition - chunk = world.getChunkProvider().getChunkAt(x, z, ChunkStatus.FULL, true); -- } -- ++ IChunkAccess immediate = world.getChunkProvider().getChunkAtImmediately(x, z); ++ if (immediate != null) { ++ if (!(immediate instanceof ProtoChunkExtension) && !(immediate instanceof net.minecraft.server.Chunk)) { ++ return null; // not full status ++ } ++ return world.getChunkAt(x, z).bukkitChunk; // make sure we're at ticket level 32 or lower + } + - if (chunk instanceof net.minecraft.server.Chunk) { - return ((net.minecraft.server.Chunk)chunk).bukkitChunk; -+ // Note: Copied from loadChunk() + if (!gen) { -+ net.minecraft.server.RegionFile file = world.getChunkProvider().playerChunkMap.getRegionFileIfLoaded(new ChunkCoordIntPair(x, z)); -+ if (file != null && file.getStatusIfCached(x, z) != ChunkStatus.FULL) { ++ net.minecraft.server.RegionFile file; ++ try { ++ file = world.getChunkProvider().playerChunkMap.getRegionFile(chunkPos, false); ++ } catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ ChunkStatus status = file.getStatusIfCached(x, z); ++ if (!file.chunkExists(chunkPos) || (status != null && status != ChunkStatus.FULL)) { + return null; + } ++ + IChunkAccess chunk = world.getChunkProvider().getChunkAt(x, z, ChunkStatus.EMPTY, true); + if (!(chunk instanceof ProtoChunkExtension) && !(chunk instanceof net.minecraft.server.Chunk)) { + return null; + } ++ + // fall through to load -+ // we do this so we do not re-read the chunk data on disk ++ // we load at empty so we don't double-load chunk data in this case } -- + - return null; -+ return ((net.minecraft.server.Chunk)world.getChunkProvider().getChunkAt(x, z, ChunkStatus.FULL, true)).bukkitChunk; ++ return world.getChunkAt(x, z).bukkitChunk; } @Override diff --git a/Spigot-Server-Patches/Use-ChunkStatus-cache-when-saving-protochunks.patch b/Spigot-Server-Patches/Use-ChunkStatus-cache-when-saving-protochunks.patch index d55191037a..ef78feb442 100644 --- a/Spigot-Server-Patches/Use-ChunkStatus-cache-when-saving-protochunks.patch +++ b/Spigot-Server-Patches/Use-ChunkStatus-cache-when-saving-protochunks.patch @@ -7,7 +7,7 @@ The cache should contain the chunk status when saving. If not it will load it. diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java -index e0798c1c8..f21961d3a 100644 +index e2f8b18d9..7cb5438e4 100644 --- a/src/main/java/net/minecraft/server/PlayerChunkMap.java +++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java @@ -0,0 +0,0 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @@ -17,7 +17,7 @@ index e0798c1c8..f21961d3a 100644 - nbttagcompound = this.readChunkData(chunkcoordintpair); - if (nbttagcompound != null && ChunkRegionLoader.a(nbttagcompound) == ChunkStatus.Type.LEVELCHUNK) { + // Paper start - Optimize save by using status cache -+ ChunkStatus statusOnDisk = this.getRegionFile(ichunkaccess.getPos(), false).getStatus(ichunkaccess.getPos().x, ichunkaccess.getPos().z, this); ++ ChunkStatus statusOnDisk = this.getChunkStatusOnDisk(chunkcoordintpair); + if (statusOnDisk != null && statusOnDisk.getType() == ChunkStatus.Type.LEVELCHUNK) { + // Paper end return false;