From 36c483161908a9701e004fc0fce039c58407910c Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Sun, 7 Jul 2019 23:52:08 -0700 Subject: [PATCH] Fix loadChunk(x, z, false) I was not correctly checking if the status was even cached. Actually fix it this time Do not forget about the async chunk placeholder Actually fix it this time I hope No plugin tickets for getChunkAtGen(x, z, boolean) Change ChunkStatus ABI This is required for asynchronous IO. async io will require calls to getChunkStatusIfCached to return the chunk status for a chunk currently queued to save - this cannot be reasonably done with current ABI --- ...401-Fix-World-isChunkGenerated-calls.patch | 157 ++++++++++++------ .../0403-Fix-MC-154214.patch | 4 +- ...Status-cache-when-saving-protochunks.patch | 6 +- Spigot-Server-Patches/0406-Anti-Xray.patch | 6 +- 4 files changed, 115 insertions(+), 58 deletions(-) diff --git a/Spigot-Server-Patches/0401-Fix-World-isChunkGenerated-calls.patch b/Spigot-Server-Patches/0401-Fix-World-isChunkGenerated-calls.patch index 4874912a0..ef7df9cd8 100644 --- a/Spigot-Server-Patches/0401-Fix-World-isChunkGenerated-calls.patch +++ b/Spigot-Server-Patches/0401-Fix-World-isChunkGenerated-calls.patch @@ -1,4 +1,4 @@ -From f652935fc9f95af75b49dc0e665ed32fe7eb74e0 Mon Sep 17 00:00:00 2001 +From 56ad1b929e7dcae4e58f7d68c31c749ace2e2b1e Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Sat, 15 Jun 2019 08:54:33 -0700 Subject: [PATCH] Fix World#isChunkGenerated calls @@ -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 @@ -27,7 +27,7 @@ public class ChunkProviderServer extends IChunkProvider { @@ -134,10 +134,10 @@ 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 -@@ -807,10 +807,23 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -807,11 +807,56 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { } @Nullable @@ -156,18 +156,51 @@ 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 -@@ -31,6 +31,47 @@ public class RegionFile implements AutoCloseable { +@@ -31,6 +31,30 @@ public class RegionFile implements AutoCloseable { private final int[] d = new int[1024];private int[] timestamps = d; // Paper - OBFHELPER private final List e; private List getFreeSectors() { return this.e; } // Paper - OBFHELPER @@ -193,29 +226,20 @@ 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 -@@ -299,6 +340,7 @@ public class RegionFile implements AutoCloseable { +@@ -286,6 +310,7 @@ 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; + } +@@ -299,6 +324,7 @@ public class RegionFile implements AutoCloseable { this.writeInt(i); // Paper - Avoid 3 io write calls } @@ -223,7 +247,7 @@ index 2e14d8465..d610253b9 100644 private int f(ChunkCoordIntPair chunkcoordintpair) { return chunkcoordintpair.j() + chunkcoordintpair.k() * 32; } -@@ -312,6 +354,7 @@ public class RegionFile implements AutoCloseable { +@@ -312,6 +338,7 @@ public class RegionFile implements AutoCloseable { } public void close() throws IOException { @@ -265,10 +289,10 @@ 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 -@@ -393,8 +393,20 @@ public class CraftWorld implements World { +@@ -393,8 +393,19 @@ public class CraftWorld implements World { @Override public boolean isChunkGenerated(int x, int z) { @@ -284,81 +308,114 @@ 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); } -@@ -506,20 +518,24 @@ public class CraftWorld implements World { +@@ -506,20 +517,45 @@ public class CraftWorld implements World { @Override 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 } @Override -@@ -2249,21 +2265,20 @@ public class CraftWorld implements World { +@@ -2249,21 +2285,40 @@ public class CraftWorld implements World { // Paper start private Chunk getChunkAtGen(int x, int z, boolean gen) { - // 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/0403-Fix-MC-154214.patch b/Spigot-Server-Patches/0403-Fix-MC-154214.patch index eaefd8465..a79655e61 100644 --- a/Spigot-Server-Patches/0403-Fix-MC-154214.patch +++ b/Spigot-Server-Patches/0403-Fix-MC-154214.patch @@ -1,4 +1,4 @@ -From 258e62129db2aeb3cdabadfeaf0baf682ab9d983 Mon Sep 17 00:00:00 2001 +From af0d14664907ee01ca8462ecf282c3c46d8941ef Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Sun, 16 Jun 2019 23:30:25 -0700 Subject: [PATCH] Fix MC-154214 @@ -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 @@ -359,12 +359,18 @@ public abstract class ChunkMapDistance { diff --git a/Spigot-Server-Patches/0405-Use-ChunkStatus-cache-when-saving-protochunks.patch b/Spigot-Server-Patches/0405-Use-ChunkStatus-cache-when-saving-protochunks.patch index ce0ebca94..ff632af49 100644 --- a/Spigot-Server-Patches/0405-Use-ChunkStatus-cache-when-saving-protochunks.patch +++ b/Spigot-Server-Patches/0405-Use-ChunkStatus-cache-when-saving-protochunks.patch @@ -1,4 +1,4 @@ -From 3423bf44d5532a8e2d8a7b2fdf6aa278aa1702b1 Mon Sep 17 00:00:00 2001 +From ba7006117301143d6877b70bfd4f0043feb4fa5d Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Sat, 22 Jun 2019 04:20:47 -0700 Subject: [PATCH] Use ChunkStatus cache when saving protochunks @@ -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 @@ -723,8 +723,10 @@ 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; diff --git a/Spigot-Server-Patches/0406-Anti-Xray.patch b/Spigot-Server-Patches/0406-Anti-Xray.patch index 8ae33fb83..f8ab1f3d3 100644 --- a/Spigot-Server-Patches/0406-Anti-Xray.patch +++ b/Spigot-Server-Patches/0406-Anti-Xray.patch @@ -1,4 +1,4 @@ -From ff5ffde5a598d484afed73de5c1e35743cb184bf Mon Sep 17 00:00:00 2001 +From 3db1a7a3ad9eac4b9e25423bad3a4281d2cdcba5 Mon Sep 17 00:00:00 2001 From: stonar96 Date: Mon, 20 Aug 2018 03:03:58 +0200 Subject: [PATCH] Anti-Xray @@ -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 @@ -494,7 +494,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { @@ -1577,7 +1577,7 @@ index f21961d3a..f5b35b95b 100644 }, this.executor); } -@@ -1116,7 +1116,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { +@@ -1148,7 +1148,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { private void a(EntityPlayer entityplayer, Packet[] apacket, Chunk chunk) { if (apacket[0] == null) {