2019-12-12 21:58:07 +01:00
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/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
2020-05-06 11:48:49 +02:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
2019-12-12 21:58:07 +01:00
--- 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 {
private final WorldServer world;
2020-01-28 01:16:53 +01:00
public final Thread serverThread; // Paper - private -> public
2019-12-12 21:58:07 +01:00
private final LightEngineThreaded lightEngine;
- private final ChunkProviderServer.a serverThreadQueue;
+ public final ChunkProviderServer.a serverThreadQueue; // Paper private -> public
public final PlayerChunkMap playerChunkMap;
private final WorldPersistentData worldPersistentData;
private long lastTickTime;
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
2020-01-29 02:26:07 +01:00
return ret;
2019-12-12 21:58:07 +01:00
}
+
+ @Nullable
+ public IChunkAccess getChunkAtImmediately(int x, int z) {
+ long k = ChunkCoordIntPair.pair(x, z);
+
+ // Note: Bypass cache to make this MT-Safe
+
+ PlayerChunk playerChunk = this.getChunk(k);
+ if (playerChunk == null) {
+ return null;
+ }
+
+ return playerChunk.getAvailableChunkNow();
+
+ }
// Paper end
@Nullable
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
2020-05-06 11:48:49 +02:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
2019-12-12 21:58:07 +01:00
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
@@ -0,0 +0,0 @@ public class ChunkRegionLoader {
}
2020-04-12 22:50:50 +02:00
// Paper end
2019-12-12 21:58:07 +01:00
+ // Paper start
+ public static ChunkStatus getStatus(NBTTagCompound compound) {
+ if (compound == null) {
+ return null;
+ }
+
+ // Note: Copied from below
+ return ChunkStatus.getStatus(compound.getCompound("Level").getString("Status"));
+ }
+ // Paper end
+
public static ChunkStatus.Type a(@Nullable NBTTagCompound nbttagcompound) {
if (nbttagcompound != null) {
ChunkStatus chunkstatus = ChunkStatus.a(nbttagcompound.getCompound("Level").getString("Status"));
diff --git a/src/main/java/net/minecraft/server/ChunkStatus.java b/src/main/java/net/minecraft/server/ChunkStatus.java
2020-05-06 11:48:49 +02:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
2019-12-12 21:58:07 +01:00
--- a/src/main/java/net/minecraft/server/ChunkStatus.java
+++ b/src/main/java/net/minecraft/server/ChunkStatus.java
@@ -0,0 +0,0 @@ public class ChunkStatus {
return this.s;
}
+ public ChunkStatus getPreviousStatus() { return this.e(); } // Paper - OBFHELPER
public ChunkStatus e() {
return this.u;
}
@@ -0,0 +0,0 @@ public class ChunkStatus {
return this.y;
}
+ // Paper start
+ public static ChunkStatus getStatus(String name) {
+ try {
+ // We need this otherwise we return EMPTY for invalid names
+ MinecraftKey key = new MinecraftKey(name);
+ return IRegistry.CHUNK_STATUS.getOptional(key).orElse(null);
+ } catch (Exception ex) {
+ return null; // invalid name
+ }
+ }
+ // Paper end
public static ChunkStatus a(String s) {
return (ChunkStatus) IRegistry.CHUNK_STATUS.get(MinecraftKey.a(s));
}
diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java
2020-05-06 11:48:49 +02:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
2019-12-12 21:58:07 +01:00
--- a/src/main/java/net/minecraft/server/IChunkLoader.java
+++ b/src/main/java/net/minecraft/server/IChunkLoader.java
@@ -0,0 +0,0 @@ import javax.annotation.Nullable;
public class IChunkLoader implements AutoCloseable {
- private final IOWorker a;
+ private final IOWorker a; public IOWorker getIOWorker() { return a; } // Paper - OBFHELPER
protected final DataFixer b;
@Nullable
private PersistentStructureLegacy c;
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
2020-05-06 11:48:49 +02:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
2019-12-12 21:58:07 +01:00
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -0,0 +0,0 @@ public class PlayerChunk {
Either<IChunkAccess, PlayerChunk.Failure> either = (Either<IChunkAccess, PlayerChunk.Failure>) statusFuture.getNow(null);
return either == null ? null : (Chunk) either.left().orElse(null);
}
+
+ public IChunkAccess getAvailableChunkNow() {
+ // TODO can we just getStatusFuture(EMPTY)?
+ for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getPreviousStatus(); curr != next; curr = next, next = next.getPreviousStatus()) {
+ CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> future = this.getStatusFutureUnchecked(curr);
+ Either<IChunkAccess, PlayerChunk.Failure> either = future.getNow(null);
+ if (either == null || !either.left().isPresent()) {
+ continue;
+ }
+ return either.left().get();
+ }
+ return null;
+ }
// Paper end
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
2020-05-06 11:48:49 +02:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
2019-12-12 21:58:07 +01:00
--- 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 {
}
@Nullable
- private NBTTagCompound readChunkData(ChunkCoordIntPair chunkcoordintpair) throws IOException {
+ public NBTTagCompound readChunkData(ChunkCoordIntPair chunkcoordintpair) throws IOException { // Paper - private -> public
NBTTagCompound nbttagcompound = this.read(chunkcoordintpair);
+ // Paper start - Cache chunk status on disk
+ if (nbttagcompound == null) {
+ return null;
+ }
+
2020-06-26 01:38:24 +02:00
+ nbttagcompound = this.getChunkData(this.world.getTypeKey(), this.l, nbttagcompound, chunkcoordintpair, world); // CraftBukkit
2019-12-12 21:58:07 +01:00
+ if (nbttagcompound == null) {
+ return null;
+ }
+
+ this.updateChunkStatusOnDisk(chunkcoordintpair, nbttagcompound);
+
+ return nbttagcompound;
+ // Paper end
+ }
+
+ // Paper start - chunk status cache "api"
+ public ChunkStatus getChunkStatusOnDiskIfCached(ChunkCoordIntPair chunkPos) {
+ RegionFile regionFile = this.getIOWorker().getRegionFileCache().getRegionFileIfLoaded(chunkPos);
+
+ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
2020-06-26 01:38:24 +02:00
+ }
+
2019-12-12 21:58:07 +01:00
+ public ChunkStatus getChunkStatusOnDisk(ChunkCoordIntPair chunkPos) throws IOException {
2020-06-26 01:38:24 +02:00
+ RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, true);
2019-12-12 21:58:07 +01:00
+
2020-06-26 01:38:24 +02:00
+ if (regionFile == null || !regionFile.chunkExists(chunkPos)) {
2019-12-12 21:58:07 +01:00
+ return null;
+ }
+
+ ChunkStatus status = regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
+
+ if (status != null) {
+ return status;
+ }
+
+ this.readChunkData(chunkPos);
2020-06-26 01:38:24 +02:00
- return nbttagcompound == null ? null : this.getChunkData(this.world.getTypeKey(), this.l, nbttagcompound, chunkcoordintpair, world); // CraftBukkit
2019-12-12 21:58:07 +01:00
+ return regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
2020-06-26 01:38:24 +02:00
}
2019-12-12 21:58:07 +01:00
+ public void updateChunkStatusOnDisk(ChunkCoordIntPair chunkPos, @Nullable NBTTagCompound compound) throws IOException {
+ RegionFile regionFile = this.getIOWorker().getRegionFileCache().getFile(chunkPos, false);
+
+ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkRegionLoader.getStatus(compound));
+ }
+
+ public IChunkAccess getUnloadingChunk(int chunkX, int chunkZ) {
+ PlayerChunk chunkHolder = this.pendingUnload.get(ChunkCoordIntPair.pair(chunkX, chunkZ));
+ return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow();
2020-01-28 01:16:53 +01:00
+ }
2019-12-12 21:58:07 +01:00
+ // Paper end
2020-01-28 01:16:53 +01:00
+
2019-12-12 21:58:07 +01:00
boolean isOutsideOfRange(ChunkCoordIntPair chunkcoordintpair) {
// Spigot start
2020-01-28 01:16:53 +01:00
return isOutsideOfRange(chunkcoordintpair, false);
2019-12-12 21:58:07 +01:00
diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java
2020-05-06 11:48:49 +02:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
2019-12-12 21:58:07 +01:00
--- 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 {
2020-08-25 04:22:08 +02:00
protected final RegionFileBitSet freeSectors;
public final File file; // Paper
2019-12-12 21:58:07 +01:00
+ // 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");
+ }
2020-08-02 07:39:36 +02:00
+ this.statuses[getChunkLocation(x, z)] = status;
2019-12-12 21:58:07 +01:00
+ }
+
+ public ChunkStatus getStatusIfCached(int x, int z) {
+ if (this.closed) {
+ // We've used an invalid region file.
+ throw new IllegalStateException("RegionFile is closed");
+ }
2020-08-02 07:39:36 +02:00
+ final int location = getChunkLocation(x, z);
2019-12-12 21:58:07 +01:00
+ return this.statuses[location];
+ }
+ // Paper end
+
2020-06-26 01:38:24 +02:00
public RegionFile(File file, File file1, boolean flag) throws IOException {
this(file.toPath(), file1.toPath(), RegionFileCompression.b, flag);
2019-12-12 21:58:07 +01:00
}
@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable {
return this.getOffset(chunkcoordintpair) != 0;
}
2020-08-02 07:39:36 +02:00
+ private static int getChunkLocation(int x, int z) { return (x & 31) + (z & 31) * 32; } // Paper - OBFHELPER - sort of, mirror of logic below
2019-12-12 21:58:07 +01:00
private static int g(ChunkCoordIntPair chunkcoordintpair) {
return chunkcoordintpair.j() + chunkcoordintpair.k() * 32;
}
public void close() throws IOException {
+ this.closed = true; // Paper
try {
2020-06-26 01:38:24 +02:00
this.d();
2019-12-12 21:58:07 +01:00
} finally {
diff --git a/src/main/java/net/minecraft/server/RegionFileCache.java b/src/main/java/net/minecraft/server/RegionFileCache.java
2020-05-06 11:48:49 +02:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
2019-12-12 21:58:07 +01:00
--- a/src/main/java/net/minecraft/server/RegionFileCache.java
+++ b/src/main/java/net/minecraft/server/RegionFileCache.java
@@ -0,0 +0,0 @@ public final class RegionFileCache implements AutoCloseable {
2020-06-26 01:38:24 +02:00
this.c = flag;
2019-12-12 21:58:07 +01:00
}
- private RegionFile getFile(ChunkCoordIntPair chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
+
+ // Paper start
+ public RegionFile getRegionFileIfLoaded(ChunkCoordIntPair chunkcoordintpair) {
+ return this.cache.getAndMoveToFirst(ChunkCoordIntPair.pair(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()));
+ }
+
+ // Paper end
+ public RegionFile getFile(ChunkCoordIntPair chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - private > public
long i = ChunkCoordIntPair.pair(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ());
RegionFile regionfile = (RegionFile) this.cache.getAndMoveToFirst(i);
@@ -0,0 +0,0 @@ public final class RegionFileCache implements AutoCloseable {
try {
NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) dataoutputstream);
+ regionfile.setStatus(chunkcoordintpair.x, chunkcoordintpair.z, ChunkRegionLoader.getStatus(nbttagcompound)); // Paper - cache status on disk
2020-08-25 04:22:08 +02:00
regionfile.setOversized(chunkcoordintpair.x, chunkcoordintpair.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
2019-12-12 21:58:07 +01:00
} catch (Throwable throwable1) {
throwable = throwable1;
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
2020-05-06 11:48:49 +02:00
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
2019-12-12 21:58:07 +01:00
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -0,0 +0,0 @@ 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;
2020-04-27 09:34:45 +02:00
import net.minecraft.server.ArraySetSorted;
2019-12-12 21:58:07 +01:00
@@ -0,0 +0,0 @@ public class CraftWorld 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.getChunkProvider().serverThreadQueue).join();
+ }
+ IChunkAccess chunk = world.getChunkProvider().getChunkAtImmediately(x, z);
+ if (chunk == null) {
+ chunk = world.getChunkProvider().playerChunkMap.getUnloadingChunk(x, z);
+ }
+ if (chunk != null) {
+ return chunk instanceof ProtoChunkExtension || chunk instanceof net.minecraft.server.Chunk;
+ }
try {
- return world.getChunkProvider().getChunkAtIfCachedImmediately(x, z) != null || world.getChunkProvider().playerChunkMap.read(new ChunkCoordIntPair(x, z)) != null; // Paper (TODO check if the first part can be removed)
+ return world.getChunkProvider().playerChunkMap.getChunkStatusOnDisk(new ChunkCoordIntPair(x, z)) == ChunkStatus.FULL;
+ // Paper end
} catch (IOException ex) {
throw new RuntimeException(ex);
}
@@ -0,0 +0,0 @@ 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
+ // 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);
- }
+ if (!generate) {
- if (chunk instanceof net.minecraft.server.Chunk) {
- world.getChunkProvider().addTicket(TicketType.PLUGIN, new ChunkCoordIntPair(x, z), 1, Unit.INSTANCE);
- return true;
+ IChunkAccess immediate = world.getChunkProvider().getChunkAtImmediately(x, z);
+ if (immediate == null) {
+ immediate = world.getChunkProvider().playerChunkMap.getUnloadingChunk(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;
+ }
+
+ net.minecraft.server.RegionFile file;
+ try {
+ file = world.getChunkProvider().playerChunkMap.getIOWorker().getRegionFileCache().getFile(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, chunkPos, 1, Unit.INSTANCE);
+ world.getChunkProvider().getChunkAt(x, z, ChunkStatus.FULL, true);
+ return true;
+ // Paper end
}
@Override