368 lines
18 KiB
Diff
368 lines
18 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/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
index fa6d91886d433c0b7d77c1d059a23ee194ae14a4..56e292e6550b19d0cae9ebad369da730ca1cabd8 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
|
@@ -111,6 +111,19 @@ public class ChunkHolder {
|
|
Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = (Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>) statusFuture.getNow(null);
|
|
return (either == null) ? null : (LevelChunk) either.left().orElse(null);
|
|
}
|
|
+
|
|
+ public ChunkAccess getAvailableChunkNow() {
|
|
+ // TODO can we just getStatusFuture(EMPTY)?
|
|
+ for (ChunkStatus curr = ChunkStatus.FULL, next = curr.getPreviousStatus(); curr != next; curr = next, next = next.getPreviousStatus()) {
|
|
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.getFutureIfPresentUnchecked(curr);
|
|
+ Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = future.getNow(null);
|
|
+ if (either == null || !either.left().isPresent()) {
|
|
+ continue;
|
|
+ }
|
|
+ return either.left().get();
|
|
+ }
|
|
+ return null;
|
|
+ }
|
|
// CraftBukkit end
|
|
|
|
public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus leastStatus) {
|
|
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
index 25826b41812f2d9ac29806ad7a9242e5674376fc..dd7de2c3db10d9d606d47c52eba40e71034fc11a 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
@@ -84,6 +84,7 @@ import net.minecraft.world.level.chunk.ProtoChunk;
|
|
import net.minecraft.world.level.chunk.UpgradeData;
|
|
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
|
|
import net.minecraft.world.level.chunk.storage.ChunkStorage;
|
|
+import net.minecraft.world.level.chunk.storage.RegionFile;
|
|
import net.minecraft.world.level.entity.ChunkStatusUpdateListener;
|
|
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
|
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
|
|
@@ -1083,12 +1084,61 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
// Paper end
|
|
|
|
@Nullable
|
|
- private CompoundTag readChunk(ChunkPos pos) throws IOException {
|
|
+ public CompoundTag readChunk(ChunkPos pos) throws IOException { // Paper - private -> public
|
|
CompoundTag nbttagcompound = this.read(pos);
|
|
+ // Paper start - Cache chunk status on disk
|
|
+ if (nbttagcompound == null) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ nbttagcompound = this.getChunkData(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, pos, level); // 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) {
|
|
+ RegionFile regionFile = regionFileCache.getRegionFileIfLoaded(chunkPos);
|
|
|
|
- return nbttagcompound == null ? null : this.getChunkData(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, pos, level); // CraftBukkit
|
|
+ return regionFile == null ? null : regionFile.getStatusIfCached(chunkPos.x, chunkPos.z);
|
|
}
|
|
|
|
+ public ChunkStatus getChunkStatusOnDisk(ChunkPos chunkPos) throws IOException {
|
|
+ RegionFile regionFile = regionFileCache.getFile(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 {
|
|
+ RegionFile regionFile = regionFileCache.getFile(chunkPos, false);
|
|
+
|
|
+ regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound));
|
|
+ }
|
|
+
|
|
+ public ChunkAccess getUnloadingChunk(int chunkX, int chunkZ) {
|
|
+ ChunkHolder chunkHolder = this.pendingUnloads.get(ChunkPos.asLong(chunkX, chunkZ));
|
|
+ return chunkHolder == null ? null : chunkHolder.getAvailableChunkNow();
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
boolean noPlayersCloseForSpawning(ChunkPos chunkPos) {
|
|
// Spigot start
|
|
return this.isOutsideOfRange(chunkPos, false);
|
|
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
index 77bb2fb280293da4a38f9acc6503e84997ecb399..c47d1772044913475a60292162ef4be594bed4c6 100644
|
|
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
|
@@ -52,10 +52,10 @@ public class ServerChunkCache extends ChunkSource {
|
|
private static final List<ChunkStatus> CHUNK_STATUSES = ChunkStatus.getStatusList(); public static final List<ChunkStatus> getPossibleChunkStatuses() { return ServerChunkCache.CHUNK_STATUSES; } // Paper - OBFHELPER
|
|
private final DistanceManager distanceManager;
|
|
public final ChunkGenerator generator;
|
|
- final ServerLevel level;
|
|
- public final Thread mainThread; // Paper - package-private -> public
|
|
- final ThreadedLevelLightEngine lightEngine;
|
|
- public final ServerChunkCache.MainThreadExecutor mainThreadProcessor; // Paper - private -> public
|
|
+ private final ServerLevel level;
|
|
+ public final Thread mainThread; // Paper - private -> public
|
|
+ private final ThreadedLevelLightEngine lightEngine;
|
|
+ public final ServerChunkCache.MainThreadExecutor mainThreadProcessor; // Paper private -> public
|
|
public final ChunkMap chunkMap;
|
|
private final DimensionDataStorage dataStorage;
|
|
private long lastInhabitedUpdate;
|
|
@@ -324,6 +324,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
}
|
|
// Paper end
|
|
// Paper start - async chunk io
|
|
+ @Nullable
|
|
public ChunkAccess getChunkAtImmediately(int x, int z) {
|
|
ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
|
|
if (holder == null) {
|
|
@@ -922,7 +923,7 @@ public class ServerChunkCache extends ChunkSource {
|
|
return this.lastSpawnState;
|
|
}
|
|
|
|
- private final class MainThreadExecutor extends BlockableEventLoop<Runnable> {
|
|
+ public final class MainThreadExecutor extends BlockableEventLoop<Runnable> { // Paper - package -> public
|
|
|
|
MainThreadExecutor(Level world) {
|
|
super("Chunk source main thread executor for " + world.dimension().location());
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
|
|
index 6e0cf8ee76143301c939fc4af5eeb091abdcbc5c..066f03ee7b4feda9ec2b0984ee7cf63fa0b9e4fc 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
|
|
@@ -204,6 +204,7 @@ public class ChunkStatus {
|
|
return this.name;
|
|
}
|
|
|
|
+ public ChunkStatus getPreviousStatus() { return this.getParent(); } // Paper - OBFHELPER
|
|
public ChunkStatus getParent() {
|
|
return this.parent;
|
|
}
|
|
@@ -224,6 +225,17 @@ public class ChunkStatus {
|
|
return this.chunkType;
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public static ChunkStatus getStatus(String name) {
|
|
+ try {
|
|
+ // We need this otherwise we return EMPTY for invalid names
|
|
+ ResourceLocation key = new ResourceLocation(name);
|
|
+ return Registry.CHUNK_STATUS.getOptional(key).orElse(null);
|
|
+ } catch (Exception ex) {
|
|
+ return null; // invalid name
|
|
+ }
|
|
+ }
|
|
+ // Paper end
|
|
public static ChunkStatus byName(String id) {
|
|
return (ChunkStatus) Registry.CHUNK_STATUS.get(ResourceLocation.tryParse(id));
|
|
}
|
|
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
index 2621739b8dd11860084ea574c243cb8ba167ac40..fc320450878279a6aa48019fbde35bb183f5f06e 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
|
|
@@ -545,6 +545,17 @@ public class ChunkSerializer {
|
|
return nbttagcompound;
|
|
}
|
|
|
|
+ // Paper start
|
|
+ public static ChunkStatus getStatus(CompoundTag compound) {
|
|
+ if (compound == null) {
|
|
+ return null;
|
|
+ }
|
|
+
|
|
+ // Note: Copied from below
|
|
+ return ChunkStatus.getStatus(compound.getCompound("Level").getString("Status"));
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag nbt) {
|
|
if (nbt != null) {
|
|
ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getCompound("Level").getString("Status"));
|
|
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 357da4846344d1182ab7149c4d352d5019384715..832392cc2adc94e1fcb1055d91eb465529da1e92 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
|
|
@@ -23,6 +23,7 @@ import java.nio.file.StandardOpenOption;
|
|
import javax.annotation.Nullable;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.world.level.ChunkPos;
|
|
+import net.minecraft.world.level.chunk.ChunkStatus;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
|
|
@@ -49,6 +50,30 @@ public class RegionFile implements AutoCloseable {
|
|
protected final RegionBitmap usedSectors;
|
|
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
|
|
|
|
+ // 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");
|
|
+ }
|
|
+ this.statuses[getChunkLocation(x, z)] = status;
|
|
+ }
|
|
+
|
|
+ public 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(File file, File directory, boolean dsync) throws IOException {
|
|
this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync);
|
|
}
|
|
@@ -395,6 +420,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;
|
|
}
|
|
@@ -405,6 +431,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 211ab6cffe78c61fcff12ef7ffba904c4cae57b2..1bee455235ece8aa299a2baeede027d251e6ff57 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
|
|
@@ -143,6 +143,7 @@ public class RegionFileStorage implements AutoCloseable { // Paper - no final
|
|
|
|
try {
|
|
NbtIo.write(nbt, (DataOutput) dataoutputstream);
|
|
+ regionfile.setStatus(pos.x, pos.z, ChunkSerializer.getStatus(nbt)); // Paper - cache status on disk
|
|
} catch (Throwable throwable) {
|
|
if (dataoutputstream != null) {
|
|
try {
|
|
@@ -205,3 +206,4 @@ public class RegionFileStorage implements AutoCloseable { // Paper - no final
|
|
|
|
}
|
|
}
|
|
+
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index 98b2d054b6436e3fdb8fadd03369a65cf4156843..f9c58de7fa8b3c2ab5ac78cf0b366df69e0b40df 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -20,6 +20,7 @@ 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;
|
|
import net.minecraft.core.BlockPos;
|
|
@@ -418,8 +419,22 @@ 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.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.world.getChunkSource().getChunkAtIfCachedImmediately(x, z) != null || this.world.getChunkSource().chunkMap.read(new ChunkPos(x, z)) != null; // Paper (TODO check if the first part can be removed)
|
|
+ return world.getChunkSource().chunkMap.getChunkStatusOnDisk(new ChunkPos(x, z)) == ChunkStatus.FULL;
|
|
+ // Paper end
|
|
} catch (IOException ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
@@ -530,20 +545,48 @@ public class CraftWorld 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.getFile(chunkPos, false);
|
|
+ } catch (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
|