aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0316-Fix-World-isChunkGenerated-calls.patch
blob: bae56ed1ed6b69cffb5cddf3095e32d881889342 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
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 af92411006c3d281815b3f4c3de5f0280d3a5901..50a201c08f143117a050305b0dde6873a04efb8b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -687,9 +687,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
     // Paper end
 
     private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkPos) {
-        return this.read(chunkPos).thenApplyAsync((optional) -> {
-            return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit
-        }, Util.backgroundExecutor());
+        // Paper start - Cache chunk status on disk
+        try {
+            return CompletableFuture.completedFuture(Optional.ofNullable(this.readConvertChunkSync(chunkPos)));
+        } catch (Throwable thr) {
+            return CompletableFuture.failedFuture(thr);
+        }
+        // Paper end - Cache chunk status on disk
     }
 
     // CraftBukkit start
@@ -698,6 +702,63 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
         // CraftBukkit end
     }
 
+    // Paper start - Cache chunk status on disk
+    @Nullable
+    public CompoundTag readConvertChunkSync(ChunkPos pos) throws IOException {
+        CompoundTag nbttagcompound = this.readSync(pos);
+        // Paper start - Cache chunk status on disk
+        if (nbttagcompound == null) {
+            return null;
+        }
+
+        nbttagcompound = this.upgradeChunkTag(nbttagcompound, pos); // 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) {
+        net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos);
+
+        return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+    }
+
+    public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException {
+        net.minecraft.world.level.chunk.storage.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 {
+        net.minecraft.world.level.chunk.storage.RegionFile regionFile = regionFileCache.getRegionFile(chunkPos, false);
+
+        regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound));
+    }
+
+    public ChunkAccess getUnloadingChunk(int chunkX, int chunkZ) {
+        ChunkHolder chunkHolder = io.papermc.paper.chunk.system.ChunkSystem.getUnloadingChunkHolder(this.level, 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/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
index 584985272a02eb5b61a22cf2404fbd97a55a3358..cda87a66fe80bf910f629c64e36c1fecbad81d77 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
@@ -51,6 +51,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 net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32];
+
+    private boolean closed;
+
+    // invoked on write/read
+    public void setStatus(int x, int z, net.minecraft.world.level.chunk.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 net.minecraft.world.level.chunk.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 +422,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 +433,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 96f129cb13642dc9667464b58c025fa0ed700cfd..29da08c58200c24fd03003937d30eb41234cabc9 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
@@ -290,6 +290,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 a248b738de3936d73b80600687a9b88b425a494c..f16ab24c0ff5b51432a8cbf9b5e7274c6b99ede6 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -308,9 +308,23 @@ public class CraftWorld extends CraftRegionAccessor implements World {
 
     @Override
     public boolean isChunkGenerated(int x, int z) {
+        // Paper start - Fix this method
+        if (!Bukkit.isPrimaryThread()) {
+            return java.util.concurrent.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.isChunkLoaded(x, z) || this.world.getChunkSource().chunkMap.read(new ChunkPos(x, z)).get().isPresent();
-        } catch (InterruptedException | ExecutionException ex) {
+            return world.getChunkSource().chunkMap.getChunkStatusOnDisk(new ChunkPos(x, z)) == ChunkStatus.FULL;
+            // Paper end
+        } catch (java.io.IOException ex) {
             throw new RuntimeException(ex);
         }
     }
@@ -424,20 +438,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 (java.io.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