e035fd7034
Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: cc9aa21a SPIGOT-6399, SPIGOT-7344: Clarify collidable behavior for player entities f23325b6 Add API for per-world simulation distances 26e1774e Add API for per-world view distances 0b541e60 Add PlayerLoginEvent#getRealAddress 5f027d2d PR-949: Add Vector#fromJOML() overloads for read-only vector types CraftBukkit Changes: bcf56171a PR-1321: Clean up some stuff which got missed during previous PRs 7f833a2d1 SPIGOT-7462: Players no longer drop XP after dying near a Sculk Catalyst 752aac669 Implement APIs for per world view and simulation distances 57d7ef433 Preserve empty enchantment tags for glow effect 465ec3fb4 Remove connected check on setScoreboard f90ce621e Use one PermissibleBase for all command blocks 5876cca44 SPIGOT-7550: Fix creation of Arrow instances f03fc3aa3 SPIGOT-7549: ServerTickManager#setTickRate incorrect Precondition 9d7f49b01 SPIGOT-7548: Fix wrong spawn location for experience orb and dropped item Spigot Changes: ed9ba9a4 Drop no longer required patch ignoring -o option 86b5dd6a SPIGOT-7546: Fix hardcoded check for outdated client message aa7cde7a Remove obsolete APIs for per world view and simulation distances 6dff577e Remove obsolete patch preserving empty `ench` tags a3bf95b8 Remove obsolete PlayerLoginEvent#getRealAddress 1b02f5d6 Remove obsolete connected check on setScoreboard patch acf717eb Remove obsolete command block PermissibleBase patch 053fa2a9 Remove redundant patch dealing with null tile entities
247 Zeilen
12 KiB
Diff
247 Zeilen
12 KiB
Diff
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 45ee0cd242271883412284625230822d9c8a5452..1faa4e0626022f08faa47327b61b6250814e0b82 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -679,9 +679,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
|
|
@@ -690,6 +694,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 72dacdc271325c814fb43cd6daaf3a209801ffda..647ce340c81606ab86d33e1f9dec1fb0afc262d8 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
|
|
@@ -50,6 +50,30 @@ public class RegionFile implements AutoCloseable {
|
|
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(); // 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);
|
|
}
|
|
@@ -397,6 +421,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;
|
|
}
|
|
@@ -407,6 +432,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 42dc999d820e62c6a222afbd9239cc671fc7de53..b850dba2b0fa5bc762b170ed7083cf8904761f17 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
|
|
@@ -289,6 +289,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 9814ce8c4f12c82f493ffdd6e44ed2fdfb4e893b..f88a88682862c714db04cfabd1eed501f60d7edc 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -310,9 +310,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);
|
|
}
|
|
}
|
|
@@ -426,20 +440,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
|