From 9e4bffef0092d03e82189ba1aeeb28a591b37b6c Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
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
---
 Spigot-Server-Patches/Anti-Xray.patch         |   2 +-
 Spigot-Server-Patches/Fix-MC-154214.patch     |   2 +-
 .../Fix-World-isChunkGenerated-calls.patch    | 141 ++++++++++++------
 ...Status-cache-when-saving-protochunks.patch |   4 +-
 4 files changed, 103 insertions(+), 46 deletions(-)

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<Either<IChunkAccess, PlayerChunk.Failure>> 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;