more patches updated
This commit is contained in:
parent
5d0d11e4b6
commit
2b29fe37a2
8 changed files with 95 additions and 130 deletions
|
@ -1,252 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Sat, 13 Sep 2014 23:14:43 -0400
|
||||
Subject: [PATCH] Configurable Keep Spawn Loaded range per world
|
||||
|
||||
This lets you disable it for some worlds and lower it for others.
|
||||
|
||||
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
||||
index 84b0304c538766949990f26f1e4a792a633f67f4..70fc301b25fb1e2271255b3d3b6facaf0cb87bad 100644
|
||||
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
||||
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
|
||||
@@ -55,6 +55,12 @@ public class PaperWorldConfig {
|
||||
}
|
||||
}
|
||||
|
||||
+ public short keepLoadedRange;
|
||||
+ private void keepLoadedRange() {
|
||||
+ keepLoadedRange = (short) (getInt("keep-spawn-loaded-range", Math.min(spigotConfig.viewDistance, 10)) * 16);
|
||||
+ log( "Keep Spawn Loaded Range: " + (keepLoadedRange/16));
|
||||
+ }
|
||||
+
|
||||
private boolean getBoolean(String path, boolean def) {
|
||||
config.addDefault("world-settings.default." + path, def);
|
||||
return config.getBoolean("world-settings." + worldName + "." + path, config.getBoolean("world-settings.default." + path));
|
||||
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
index 6c86d1d8c43448f4c83084a731bedd92d85b01fc..ad4ff2f7ee8d01ff3a49b9b2a8feb59131ae5afb 100644
|
||||
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
@@ -788,35 +788,36 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
|
||||
// CraftBukkit start
|
||||
public void loadSpawn(ChunkProgressListener worldloadlistener, ServerLevel worldserver) {
|
||||
- if (!worldserver.getWorld().getKeepSpawnInMemory()) {
|
||||
- return;
|
||||
- }
|
||||
+ ServerChunkCache chunkproviderserver = worldserver.getChunkSource(); // Paper
|
||||
|
||||
// WorldServer worldserver = this.E();
|
||||
this.forceTicks = true;
|
||||
// CraftBukkit end
|
||||
+ if (worldserver.getWorld().getKeepSpawnInMemory()) { // Paper
|
||||
|
||||
MinecraftServer.LOGGER.info("Preparing start region for dimension {}", worldserver.dimension().location());
|
||||
BlockPos blockposition = worldserver.getSharedSpawnPos();
|
||||
|
||||
worldloadlistener.updateSpawnPos(new ChunkPos(blockposition));
|
||||
- ServerChunkCache chunkproviderserver = worldserver.getChunkSource();
|
||||
+ //ChunkProviderServer chunkproviderserver = worldserver.getChunkProvider(); // Paper - move up
|
||||
|
||||
chunkproviderserver.getLightEngine().setTaskPerBatch(500);
|
||||
this.nextTickTime = Util.getMillis();
|
||||
- chunkproviderserver.addRegionTicket(TicketType.START, new ChunkPos(blockposition), 11, Unit.INSTANCE);
|
||||
-
|
||||
- while (chunkproviderserver.getTickingGenerated() != 441) {
|
||||
- // CraftBukkit start
|
||||
- // this.nextTickTime = SystemUtils.getMonotonicMillis() + 10L;
|
||||
- this.executeModerately();
|
||||
- // CraftBukkit end
|
||||
- }
|
||||
+ // Paper start - configurable spawn reason
|
||||
+ int radiusBlocks = worldserver.paperConfig.keepLoadedRange;
|
||||
+ int radiusChunks = radiusBlocks / 16 + ((radiusBlocks & 15) != 0 ? 1 : 0);
|
||||
+ int totalChunks = ((radiusChunks) * 2 + 1);
|
||||
+ totalChunks *= totalChunks;
|
||||
+ worldloadlistener.setChunkRadius(radiusBlocks / 16);
|
||||
+
|
||||
+ worldserver.addTicketsForSpawn(radiusBlocks, blockposition);
|
||||
+ // Paper end
|
||||
|
||||
// CraftBukkit start
|
||||
// this.nextTickTime = SystemUtils.getMonotonicMillis() + 10L;
|
||||
this.executeModerately();
|
||||
// Iterator iterator = this.worldServer.values().iterator();
|
||||
+ }
|
||||
|
||||
if (true) {
|
||||
ServerLevel worldserver1 = worldserver;
|
||||
@@ -839,7 +840,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
// this.nextTick = SystemUtils.getMonotonicMillis() + 10L;
|
||||
this.executeModerately();
|
||||
// CraftBukkit end
|
||||
- worldloadlistener.stop();
|
||||
+ if (worldserver.getWorld().getKeepSpawnInMemory()) worldloadlistener.stop(); // Paper
|
||||
chunkproviderserver.getLightEngine().setTaskPerBatch(5);
|
||||
// CraftBukkit start
|
||||
// this.updateSpawnFlags();
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
index 891633981124a5b864af0f9bd50f11e48da2835d..1befd73091687cdda980d74a22b1909a4dc0038b 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -61,6 +61,7 @@ import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
|
||||
import net.minecraft.network.protocol.game.DebugPackets;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
+import net.minecraft.server.MCUtil;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.ServerScoreboard;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
@@ -1555,12 +1556,84 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
||||
return ((MapIndex) this.getServer().overworld().getDataStorage().computeIfAbsent(MapIndex::load, MapIndex::new, "idcounts")).getFreeAuxValueForMap();
|
||||
}
|
||||
|
||||
+ // Paper start - helper function for configurable spawn radius
|
||||
+ public void addTicketsForSpawn(int radiusInBlocks, BlockPos spawn) {
|
||||
+ // In order to respect vanilla behavior, which is ensuring everything but the spawn border can tick, we add tickets
|
||||
+ // with level 31 for the non-border spawn chunks
|
||||
+ ServerChunkCache chunkproviderserver = this.getChunkSource();
|
||||
+ int tickRadius = radiusInBlocks - 16;
|
||||
+
|
||||
+ // add ticking chunks
|
||||
+ for (int x = -tickRadius; x <= tickRadius; x += 16) {
|
||||
+ for (int z = -tickRadius; z <= tickRadius; z += 16) {
|
||||
+ // radius of 2 will have the current chunk be level 31
|
||||
+ chunkproviderserver.addRegionTicket(TicketType.START, new ChunkPos(spawn.offset(x, 0, z)), 2, Unit.INSTANCE);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // add border chunks
|
||||
+
|
||||
+ // add border along x axis (including corner chunks)
|
||||
+ for (int x = -radiusInBlocks; x <= radiusInBlocks; x += 16) {
|
||||
+ // top
|
||||
+ chunkproviderserver.addRegionTicket(TicketType.START, new ChunkPos(spawn.offset(x, 0, radiusInBlocks)), 1, Unit.INSTANCE); // level 32
|
||||
+ // bottom
|
||||
+ chunkproviderserver.addRegionTicket(TicketType.START, new ChunkPos(spawn.offset(x, 0, -radiusInBlocks)), 1, Unit.INSTANCE); // level 32
|
||||
+ }
|
||||
+
|
||||
+ // add border along z axis (excluding corner chunks)
|
||||
+ for (int z = -radiusInBlocks + 16; z < radiusInBlocks; z += 16) {
|
||||
+ // right
|
||||
+ chunkproviderserver.addRegionTicket(TicketType.START, new ChunkPos(spawn.offset(radiusInBlocks, 0, z)), 1, Unit.INSTANCE); // level 32
|
||||
+ // left
|
||||
+ chunkproviderserver.addRegionTicket(TicketType.START, new ChunkPos(spawn.offset(-radiusInBlocks, 0, z)), 1, Unit.INSTANCE); // level 32
|
||||
+ }
|
||||
+ }
|
||||
+ public void removeTicketsForSpawn(int radiusInBlocks, BlockPos spawn) {
|
||||
+ // In order to respect vanilla behavior, which is ensuring everything but the spawn border can tick, we added tickets
|
||||
+ // with level 31 for the non-border spawn chunks
|
||||
+ ServerChunkCache chunkproviderserver = this.getChunkSource();
|
||||
+ int tickRadius = radiusInBlocks - 16;
|
||||
+
|
||||
+ // remove ticking chunks
|
||||
+ for (int x = -tickRadius; x <= tickRadius; x += 16) {
|
||||
+ for (int z = -tickRadius; z <= tickRadius; z += 16) {
|
||||
+ // radius of 2 will have the current chunk be level 31
|
||||
+ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.offset(x, 0, z)), 2, Unit.INSTANCE);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // remove border chunks
|
||||
+
|
||||
+ // remove border along x axis (including corner chunks)
|
||||
+ for (int x = -radiusInBlocks; x <= radiusInBlocks; x += 16) {
|
||||
+ // top
|
||||
+ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.offset(x, 0, radiusInBlocks)), 1, Unit.INSTANCE); // level 32
|
||||
+ // bottom
|
||||
+ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.offset(x, 0, -radiusInBlocks)), 1, Unit.INSTANCE); // level 32
|
||||
+ }
|
||||
+
|
||||
+ // remove border along z axis (excluding corner chunks)
|
||||
+ for (int z = -radiusInBlocks + 16; z < radiusInBlocks; z += 16) {
|
||||
+ // right
|
||||
+ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.offset(radiusInBlocks, 0, z)), 1, Unit.INSTANCE); // level 32
|
||||
+ // left
|
||||
+ chunkproviderserver.removeRegionTicket(TicketType.START, new ChunkPos(spawn.offset(-radiusInBlocks, 0, z)), 1, Unit.INSTANCE); // level 32
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
public void setDefaultSpawnPos(BlockPos pos, float angle) {
|
||||
- ChunkPos chunkcoordintpair = new ChunkPos(new BlockPos(this.levelData.getXSpawn(), 0, this.levelData.getZSpawn()));
|
||||
+ // Paper - configurable spawn radius
|
||||
+ BlockPos prevSpawn = this.getSharedSpawnPos();
|
||||
+ //ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(new BlockPosition(this.worldData.a(), 0, this.worldData.c()));
|
||||
|
||||
this.levelData.setSpawn(pos, angle);
|
||||
- this.getChunkSource().removeRegionTicket(TicketType.START, chunkcoordintpair, 11, Unit.INSTANCE);
|
||||
- this.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(pos), 11, Unit.INSTANCE);
|
||||
+ if (this.keepSpawnInMemory) {
|
||||
+ // if this keepSpawnInMemory is false a plugin has already removed our tickets, do not re-add
|
||||
+ this.removeTicketsForSpawn(this.paperConfig.keepLoadedRange, prevSpawn);
|
||||
+ this.addTicketsForSpawn(this.paperConfig.keepLoadedRange, pos);
|
||||
+ }
|
||||
this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle));
|
||||
}
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/progress/ChunkProgressListener.java b/src/main/java/net/minecraft/server/level/progress/ChunkProgressListener.java
|
||||
index 1b565b2809c2d367e21971c5154f35c9763995e6..b0f899835ded29aff108d1674bf4a1a6c89693db 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/progress/ChunkProgressListener.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/progress/ChunkProgressListener.java
|
||||
@@ -12,4 +12,6 @@ public interface ChunkProgressListener {
|
||||
void start();
|
||||
|
||||
void stop();
|
||||
+
|
||||
+ void setChunkRadius(int radius); // Paper - allow changing chunk radius
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/server/level/progress/LoggerChunkProgressListener.java b/src/main/java/net/minecraft/server/level/progress/LoggerChunkProgressListener.java
|
||||
index 4185e6bcf9b2bb65b2a0fa5fcbeb5684615169a7..dbc29442f2b2ad3ea451910f4944e90114a317c7 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/progress/LoggerChunkProgressListener.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/progress/LoggerChunkProgressListener.java
|
||||
@@ -11,12 +11,19 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class LoggerChunkProgressListener implements ChunkProgressListener {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
- private final int maxCount;
|
||||
+ private int maxCount; // Paper - remove final
|
||||
private int count;
|
||||
private long startTime;
|
||||
private long nextTickTime = Long.MAX_VALUE;
|
||||
|
||||
public LoggerChunkProgressListener(int radius) {
|
||||
+ // Paper start - Allow changing radius later for configurable spawn patch
|
||||
+ this.setChunkRadius(radius); // Move to method
|
||||
+ }
|
||||
+
|
||||
+ @Override
|
||||
+ public void setChunkRadius(int radius) {
|
||||
+ // Paper end
|
||||
int i = radius * 2 + 1;
|
||||
this.maxCount = i * i;
|
||||
}
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
index 3d13316726582283147714264f95595c518ff67f..85bc46d7a2528254f888c027c2a4e2240f8e11d1 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
@@ -1337,15 +1337,21 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
||||
|
||||
@Override
|
||||
public void setKeepSpawnInMemory(boolean keepLoaded) {
|
||||
- world.keepSpawnInMemory = keepLoaded;
|
||||
+ // Paper start - Configurable spawn radius
|
||||
+ if (keepLoaded == world.keepSpawnInMemory) {
|
||||
+ // do nothing, nothing has changed
|
||||
+ return;
|
||||
+ }
|
||||
+ this.world.keepSpawnInMemory = keepLoaded;
|
||||
// Grab the worlds spawn chunk
|
||||
BlockPos chunkcoordinates = this.world.getSharedSpawnPos();
|
||||
if (keepLoaded) {
|
||||
- this.world.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(chunkcoordinates), 11, Unit.INSTANCE);
|
||||
+ this.world.addTicketsForSpawn(this.world.paperConfig.keepLoadedRange, chunkcoordinates);
|
||||
} else {
|
||||
- // TODO: doesn't work well if spawn changed....
|
||||
- this.world.getChunkSource().removeRegionTicket(TicketType.START, new ChunkPos(chunkcoordinates), 11, Unit.INSTANCE);
|
||||
+ // TODO: doesn't work well if spawn changed.... // Paper - resolved
|
||||
+ this.world.removeTicketsForSpawn(this.world.paperConfig.keepLoadedRange, chunkcoordinates);
|
||||
}
|
||||
+ // Paper end
|
||||
}
|
||||
|
||||
@Override
|
|
@ -1,84 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shane Freeder <theboyetronic@gmail.com>
|
||||
Date: Wed, 29 May 2019 04:01:22 +0100
|
||||
Subject: [PATCH] ChunkMapDistance CME
|
||||
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
index ffa3dc07ec566464ce10abe793de61efa9673fe9..d226247b93a2f1f7e039b0963d2e27b5dd11c15e 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
@@ -73,6 +73,7 @@ public class ChunkHolder {
|
||||
private boolean resendLight;
|
||||
private CompletableFuture<Void> pendingFullStateConfirmation;
|
||||
|
||||
+ boolean isUpdateQueued = false; // Paper
|
||||
private final ChunkMap chunkMap; // Paper
|
||||
|
||||
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
|
||||
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
index 48bb57d3a431ea466425eb7da821c0aea900bd4c..45c7ebe67019cdbe88b6617a95d5c40d3a68286c 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
|
||||
@@ -47,7 +47,16 @@ public abstract class DistanceManager {
|
||||
private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker();
|
||||
private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
|
||||
private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
|
||||
- final Set<ChunkHolder> chunksToUpdateFutures = Sets.newHashSet();
|
||||
+ // Paper start use a queue, but still keep unique requirement
|
||||
+ public final java.util.Queue<ChunkHolder> pendingChunkUpdates = new java.util.ArrayDeque<ChunkHolder>() {
|
||||
+ @Override
|
||||
+ public boolean add(ChunkHolder o) {
|
||||
+ if (o.isUpdateQueued) return true;
|
||||
+ o.isUpdateQueued = true;
|
||||
+ return super.add(o);
|
||||
+ }
|
||||
+ };
|
||||
+ // Paper end
|
||||
final ChunkTaskPriorityQueueSorter ticketThrottler;
|
||||
final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> ticketThrottlerInput;
|
||||
final ProcessorHandle<ChunkTaskPriorityQueueSorter.Release> ticketThrottlerReleaser;
|
||||
@@ -108,26 +117,14 @@ public abstract class DistanceManager {
|
||||
;
|
||||
}
|
||||
|
||||
- if (!this.chunksToUpdateFutures.isEmpty()) {
|
||||
- // CraftBukkit start
|
||||
- // Iterate pending chunk updates with protection against concurrent modification exceptions
|
||||
- java.util.Iterator<ChunkHolder> iter = this.chunksToUpdateFutures.iterator();
|
||||
- int expectedSize = this.chunksToUpdateFutures.size();
|
||||
- do {
|
||||
- ChunkHolder playerchunk = iter.next();
|
||||
- iter.remove();
|
||||
- expectedSize--;
|
||||
-
|
||||
- playerchunk.updateFutures(playerchunkmap, this.mainThreadExecutor);
|
||||
-
|
||||
- // Reset iterator if set was modified using add()
|
||||
- if (this.chunksToUpdateFutures.size() != expectedSize) {
|
||||
- expectedSize = this.chunksToUpdateFutures.size();
|
||||
- iter = this.chunksToUpdateFutures.iterator();
|
||||
- }
|
||||
- } while (iter.hasNext());
|
||||
- // CraftBukkit end
|
||||
-
|
||||
+ // Paper start
|
||||
+ if (!this.pendingChunkUpdates.isEmpty()) {
|
||||
+ while(!this.pendingChunkUpdates.isEmpty()) {
|
||||
+ ChunkHolder remove = this.pendingChunkUpdates.remove();
|
||||
+ remove.isUpdateQueued = false;
|
||||
+ remove.updateFutures(playerchunkmap, this.mainThreadExecutor);
|
||||
+ }
|
||||
+ // Paper end
|
||||
return true;
|
||||
} else {
|
||||
if (!this.ticketsToRelease.isEmpty()) {
|
||||
@@ -385,7 +382,7 @@ public abstract class DistanceManager {
|
||||
if (k != level) {
|
||||
playerchunk = DistanceManager.this.updateChunkScheduling(id, level, playerchunk, k);
|
||||
if (playerchunk != null) {
|
||||
- DistanceManager.this.chunksToUpdateFutures.add(playerchunk);
|
||||
+ DistanceManager.this.pendingChunkUpdates.add(playerchunk);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,430 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sat, 1 Jun 2019 13:00:55 -0700
|
||||
Subject: [PATCH] Chunk debug command
|
||||
|
||||
Prints all chunk information to a text file into the debug
|
||||
folder in the root server folder. The format is in JSON, and
|
||||
the data format is described in MCUtil#dumpChunks(File)
|
||||
|
||||
The command will output server version and all online players to the
|
||||
file as well. We do not log anything but the location, world and
|
||||
username of the player.
|
||||
|
||||
Also logs the value of these config values (note not all are paper's):
|
||||
- keep spawn loaded value
|
||||
- spawn radius
|
||||
- view distance
|
||||
|
||||
Each chunk has the following logged:
|
||||
- Coordinate
|
||||
- Ticket level & its corresponding state
|
||||
- Whether it is queued for unload
|
||||
- Chunk status (may be unloaded)
|
||||
- All tickets on the chunk
|
||||
|
||||
Example log:
|
||||
https://gist.githubusercontent.com/Spottedleaf/0131e7710ffd5d531e5fd246c3367380/raw/169ae1b2e240485f99bc7a6bd8e78d90e3af7397/chunks-2019-06-01_19.57.05.txt
|
||||
|
||||
For references on certain keywords (ticket, status, etc), please see:
|
||||
|
||||
https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528273&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528273
|
||||
https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528577&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528577
|
||||
|
||||
diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
||||
index 1eb45df9dca5d0c31ac46709e706136a246cb8ea..005361c38b02713fb823d0be40954400d59f0c4d 100644
|
||||
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
||||
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
|
||||
@@ -6,13 +6,15 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
-import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
+import net.minecraft.server.level.ChunkHolder;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
+import net.minecraft.resources.ResourceLocation;
|
||||
+import net.minecraft.server.MCUtil;
|
||||
import org.apache.commons.lang3.tuple.MutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -41,7 +43,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class PaperCommand extends Command {
|
||||
private static final String BASE_PERM = "bukkit.command.paper.";
|
||||
- private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version").build();
|
||||
+ private static final ImmutableSet<String> SUBCOMMANDS = ImmutableSet.<String>builder().add("heap", "entity", "reload", "version", "debug", "chunkinfo").build();
|
||||
|
||||
public PaperCommand(String name) {
|
||||
super(name);
|
||||
@@ -69,6 +71,21 @@ public class PaperCommand extends Command {
|
||||
if (args.length == 3)
|
||||
return getListMatchingLast(sender, args, EntityType.getEntityNameList().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new));
|
||||
break;
|
||||
+ case "debug":
|
||||
+ if (args.length == 2) {
|
||||
+ return getListMatchingLast(sender, args, "help", "chunks");
|
||||
+ }
|
||||
+ break;
|
||||
+ case "chunkinfo":
|
||||
+ List<String> worldNames = new ArrayList<>();
|
||||
+ worldNames.add("*");
|
||||
+ for (org.bukkit.World world : Bukkit.getWorlds()) {
|
||||
+ worldNames.add(world.getName());
|
||||
+ }
|
||||
+ if (args.length == 2) {
|
||||
+ return getListMatchingLast(sender, args, worldNames);
|
||||
+ }
|
||||
+ break;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -135,6 +152,12 @@ public class PaperCommand extends Command {
|
||||
case "reload":
|
||||
doReload(sender);
|
||||
break;
|
||||
+ case "debug":
|
||||
+ doDebug(sender, args);
|
||||
+ break;
|
||||
+ case "chunkinfo":
|
||||
+ doChunkInfo(sender, args);
|
||||
+ break;
|
||||
case "ver":
|
||||
if (!testPermission(sender, "version")) break; // "ver" needs a special check because it's an alias. All other commands are checked up before the switch statement (because they are present in the SUBCOMMANDS set)
|
||||
case "version":
|
||||
@@ -152,6 +175,114 @@ public class PaperCommand extends Command {
|
||||
return true;
|
||||
}
|
||||
|
||||
+ private void doChunkInfo(CommandSender sender, String[] args) {
|
||||
+ List<org.bukkit.World> worlds;
|
||||
+ if (args.length < 2 || args[1].equals("*")) {
|
||||
+ worlds = Bukkit.getWorlds();
|
||||
+ } else {
|
||||
+ worlds = new ArrayList<>(args.length - 1);
|
||||
+ for (int i = 1; i < args.length; ++i) {
|
||||
+ org.bukkit.World world = Bukkit.getWorld(args[i]);
|
||||
+ if (world == null) {
|
||||
+ sender.sendMessage(ChatColor.RED + "World '" + args[i] + "' is invalid");
|
||||
+ return;
|
||||
+ }
|
||||
+ worlds.add(world);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ int accumulatedTotal = 0;
|
||||
+ int accumulatedInactive = 0;
|
||||
+ int accumulatedBorder = 0;
|
||||
+ int accumulatedTicking = 0;
|
||||
+ int accumulatedEntityTicking = 0;
|
||||
+
|
||||
+ for (org.bukkit.World bukkitWorld : worlds) {
|
||||
+ ServerLevel world = ((CraftWorld)bukkitWorld).getHandle();
|
||||
+
|
||||
+ int total = 0;
|
||||
+ int inactive = 0;
|
||||
+ int border = 0;
|
||||
+ int ticking = 0;
|
||||
+ int entityTicking = 0;
|
||||
+
|
||||
+ for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) {
|
||||
+ if (chunk.getFullChunkUnchecked() == null) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ ++total;
|
||||
+
|
||||
+ ChunkHolder.FullChunkStatus state = ChunkHolder.getFullChunkStatus(chunk.getTicketLevel());
|
||||
+
|
||||
+ switch (state) {
|
||||
+ case INACCESSIBLE:
|
||||
+ ++inactive;
|
||||
+ continue;
|
||||
+ case BORDER:
|
||||
+ ++border;
|
||||
+ continue;
|
||||
+ case TICKING:
|
||||
+ ++ticking;
|
||||
+ continue;
|
||||
+ case ENTITY_TICKING:
|
||||
+ ++entityTicking;
|
||||
+ continue;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ accumulatedTotal += total;
|
||||
+ accumulatedInactive += inactive;
|
||||
+ accumulatedBorder += border;
|
||||
+ accumulatedTicking += ticking;
|
||||
+ accumulatedEntityTicking += entityTicking;
|
||||
+
|
||||
+ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + bukkitWorld.getName() + ChatColor.DARK_AQUA + ":");
|
||||
+ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + total + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA
|
||||
+ + inactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + border + ChatColor.BLUE + " Ticking: "
|
||||
+ + ChatColor.DARK_AQUA + ticking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + entityTicking);
|
||||
+ }
|
||||
+ if (worlds.size() > 1) {
|
||||
+ sender.sendMessage(ChatColor.BLUE + "Chunks in " + ChatColor.GREEN + "all listed worlds" + ChatColor.DARK_AQUA + ":");
|
||||
+ sender.sendMessage(ChatColor.BLUE + "Total: " + ChatColor.DARK_AQUA + accumulatedTotal + ChatColor.BLUE + " Inactive: " + ChatColor.DARK_AQUA
|
||||
+ + accumulatedInactive + ChatColor.BLUE + " Border: " + ChatColor.DARK_AQUA + accumulatedBorder + ChatColor.BLUE + " Ticking: "
|
||||
+ + ChatColor.DARK_AQUA + accumulatedTicking + ChatColor.BLUE + " Entity: " + ChatColor.DARK_AQUA + accumulatedEntityTicking);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void doDebug(CommandSender sender, String[] args) {
|
||||
+ if (args.length < 2) {
|
||||
+ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ String debugType = args[1].toLowerCase(Locale.ENGLISH);
|
||||
+ switch (debugType) {
|
||||
+ case "chunks":
|
||||
+ if (args.length >= 3 && args[2].toLowerCase(Locale.ENGLISH).equals("help")) {
|
||||
+ sender.sendMessage(ChatColor.RED + "Use /paper debug chunks to dump loaded chunk information to a file");
|
||||
+ break;
|
||||
+ }
|
||||
+ File file = new File(new File(new File("."), "debug"),
|
||||
+ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt");
|
||||
+ sender.sendMessage(ChatColor.GREEN + "Writing chunk information dump to " + file.toString());
|
||||
+ try {
|
||||
+ MCUtil.dumpChunks(file);
|
||||
+ sender.sendMessage(ChatColor.GREEN + "Successfully written chunk information!");
|
||||
+ } catch (Throwable thr) {
|
||||
+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr);
|
||||
+ sender.sendMessage(ChatColor.RED + "Failed to dump chunk information, see console");
|
||||
+ }
|
||||
+
|
||||
+ break;
|
||||
+ case "help":
|
||||
+ // fall through to default
|
||||
+ default:
|
||||
+ sender.sendMessage(ChatColor.RED + "Use /paper debug [chunks] help for more information on a specific command");
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
/*
|
||||
* Ported from MinecraftForge - author: LexManos <LexManos@gmail.com> - License: LGPLv2.1
|
||||
*/
|
||||
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
|
||||
index f1135616657692a2611dde02e2dc5ac4d8127269..892524ffb54a3e4f43e1a6f259a736334bad9b58 100644
|
||||
--- a/src/main/java/net/minecraft/server/MCUtil.java
|
||||
+++ b/src/main/java/net/minecraft/server/MCUtil.java
|
||||
@@ -9,13 +9,27 @@ import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
+import net.minecraft.server.level.ChunkHolder;
|
||||
+import net.minecraft.server.level.ChunkMap;
|
||||
+import net.minecraft.server.level.DistanceManager;
|
||||
+import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
+import net.minecraft.server.level.ServerPlayer;
|
||||
+import net.minecraft.server.level.Ticket;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
+import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
+import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import org.apache.commons.lang.exception.ExceptionUtils;
|
||||
+import com.google.gson.JsonArray;
|
||||
+import com.google.gson.JsonObject;
|
||||
+import com.google.gson.internal.Streams;
|
||||
+import com.google.gson.stream.JsonWriter;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
+import com.mojang.datafixers.util.Either;
|
||||
+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
@@ -24,8 +38,11 @@ import org.spigotmc.AsyncCatcher;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
+import java.io.*;
|
||||
+import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
+import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -535,6 +552,172 @@ public final class MCUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
+ public static ChunkStatus getChunkStatus(ChunkHolder chunk) {
|
||||
+ List<ChunkStatus> statuses = net.minecraft.server.level.ServerChunkCache.CHUNK_STATUSES;
|
||||
+ for (int i = statuses.size() - 1; i >= 0; --i) {
|
||||
+ ChunkStatus curr = statuses.get(i);
|
||||
+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = chunk.getFutureIfPresentUnchecked(curr);
|
||||
+ if (future != ChunkHolder.UNLOADED_CHUNK_FUTURE) {
|
||||
+ return curr;
|
||||
+ }
|
||||
+ }
|
||||
+ return null; // unloaded
|
||||
+ }
|
||||
+
|
||||
+ public static void dumpChunks(File file) throws IOException {
|
||||
+ file.getParentFile().mkdirs();
|
||||
+ file.createNewFile();
|
||||
+ /*
|
||||
+ * Json format:
|
||||
+ *
|
||||
+ * Main data format:
|
||||
+ * -server-version:<string>
|
||||
+ * -data-version:<int>
|
||||
+ * -worlds:
|
||||
+ * -name:<world name>
|
||||
+ * -view-distance:<int>
|
||||
+ * -keep-spawn-loaded:<boolean>
|
||||
+ * -keep-spawn-loaded-range:<int>
|
||||
+ * -visible-chunk-count:<int>
|
||||
+ * -loaded-chunk-count:<int>
|
||||
+ * -verified-fully-loaded-chunks:<int>
|
||||
+ * -players:<array of player>
|
||||
+ * -chunk-data:<array of chunks>
|
||||
+ *
|
||||
+ * Player format:
|
||||
+ * -name:<string>
|
||||
+ * -x:<double>
|
||||
+ * -y:<double>
|
||||
+ * -z:<double>
|
||||
+ *
|
||||
+ * Chunk Format:
|
||||
+ * -x:<integer>
|
||||
+ * -z:<integer>
|
||||
+ * -ticket-level:<integer>
|
||||
+ * -state:<string>
|
||||
+ * -queued-for-unload:<boolean>
|
||||
+ * -status:<string>
|
||||
+ * -tickets:<array of tickets>
|
||||
+ *
|
||||
+ *
|
||||
+ * Ticket format:
|
||||
+ * -ticket-type:<string>
|
||||
+ * -ticket-level:<int>
|
||||
+ * -add-tick:<long>
|
||||
+ * -object-reason:<string> // This depends on the type of ticket. ie POST_TELEPORT -> entity id
|
||||
+ */
|
||||
+ List<org.bukkit.World> worlds = org.bukkit.Bukkit.getWorlds();
|
||||
+ JsonObject data = new JsonObject();
|
||||
+
|
||||
+ data.addProperty("server-version", org.bukkit.Bukkit.getVersion());
|
||||
+ data.addProperty("data-version", 0);
|
||||
+
|
||||
+ JsonArray worldsData = new JsonArray();
|
||||
+
|
||||
+ for (org.bukkit.World bukkitWorld : worlds) {
|
||||
+ JsonObject worldData = new JsonObject();
|
||||
+
|
||||
+ ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle();
|
||||
+ ChunkMap chunkMap = world.getChunkSource().chunkMap;
|
||||
+ Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunks = chunkMap.visibleChunkMap;
|
||||
+ DistanceManager chunkMapDistance = chunkMap.distanceManager;
|
||||
+ List<ChunkHolder> allChunks = new ArrayList<>(visibleChunks.values());
|
||||
+ List<ServerPlayer> players = world.players;
|
||||
+
|
||||
+ int fullLoadedChunks = 0;
|
||||
+
|
||||
+ for (ChunkHolder chunk : allChunks) {
|
||||
+ if (chunk.getFullChunkUnchecked() != null) {
|
||||
+ ++fullLoadedChunks;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // sorting by coordinate makes the log easier to read
|
||||
+ allChunks.sort((ChunkHolder v1, ChunkHolder v2) -> {
|
||||
+ if (v1.pos.x != v2.pos.x) {
|
||||
+ return Integer.compare(v1.pos.x, v2.pos.x);
|
||||
+ }
|
||||
+ return Integer.compare(v1.pos.z, v2.pos.z);
|
||||
+ });
|
||||
+
|
||||
+ worldData.addProperty("name", world.getWorld().getName());
|
||||
+ worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
|
||||
+ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
|
||||
+ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange);
|
||||
+ worldData.addProperty("visible-chunk-count", visibleChunks.size());
|
||||
+ worldData.addProperty("loaded-chunk-count", chunkMap.entitiesInLevel.size());
|
||||
+ worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks);
|
||||
+
|
||||
+ JsonArray playersData = new JsonArray();
|
||||
+
|
||||
+ for (ServerPlayer player : players) {
|
||||
+ JsonObject playerData = new JsonObject();
|
||||
+
|
||||
+ playerData.addProperty("name", player.getScoreboardName());
|
||||
+ playerData.addProperty("x", player.getX());
|
||||
+ playerData.addProperty("y", player.getY());
|
||||
+ playerData.addProperty("z", player.getZ());
|
||||
+
|
||||
+ playersData.add(playerData);
|
||||
+
|
||||
+ }
|
||||
+
|
||||
+ worldData.add("players", playersData);
|
||||
+
|
||||
+ JsonArray chunksData = new JsonArray();
|
||||
+
|
||||
+ for (ChunkHolder playerChunk : allChunks) {
|
||||
+ JsonObject chunkData = new JsonObject();
|
||||
+
|
||||
+ Set<Ticket<?>> tickets = chunkMapDistance.tickets.get(playerChunk.pos.longKey);
|
||||
+ ChunkStatus status = getChunkStatus(playerChunk);
|
||||
+
|
||||
+ chunkData.addProperty("x", playerChunk.pos.x);
|
||||
+ chunkData.addProperty("z", playerChunk.pos.z);
|
||||
+ chunkData.addProperty("ticket-level", playerChunk.getTicketLevel());
|
||||
+ chunkData.addProperty("state", ChunkHolder.getFullChunkStatus(playerChunk.getTicketLevel()).toString());
|
||||
+ chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.longKey));
|
||||
+ chunkData.addProperty("status", status == null ? "unloaded" : status.toString());
|
||||
+
|
||||
+ JsonArray ticketsData = new JsonArray();
|
||||
+
|
||||
+ if (tickets != null) {
|
||||
+ for (Ticket<?> ticket : tickets) {
|
||||
+ JsonObject ticketData = new JsonObject();
|
||||
+
|
||||
+ ticketData.addProperty("ticket-type", ticket.getType().toString());
|
||||
+ ticketData.addProperty("ticket-level", ticket.getTicketLevel());
|
||||
+ ticketData.addProperty("object-reason", String.valueOf(ticket.key));
|
||||
+ ticketData.addProperty("add-tick", ticket.createdTick);
|
||||
+
|
||||
+ ticketsData.add(ticketData);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ chunkData.add("tickets", ticketsData);
|
||||
+ chunksData.add(chunkData);
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ worldData.add("chunk-data", chunksData);
|
||||
+ worldsData.add(worldData);
|
||||
+ }
|
||||
+
|
||||
+ data.add("worlds", worldsData);
|
||||
+
|
||||
+ StringWriter stringWriter = new StringWriter();
|
||||
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
+ jsonWriter.setIndent(" ");
|
||||
+ jsonWriter.setLenient(false);
|
||||
+ Streams.write(data, jsonWriter);
|
||||
+
|
||||
+ String fileData = stringWriter.toString();
|
||||
+
|
||||
+ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, "UTF-8")) {
|
||||
+ out.print(fileData);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) {
|
||||
return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status);
|
||||
}
|
|
@ -1,253 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Fri, 15 Feb 2019 01:08:19 -0500
|
||||
Subject: [PATCH] Allow Saving of Oversized Chunks
|
||||
|
||||
Note 1.17 update: With 1.17, Entities are no longer stored in chunk slices, so this needs updating!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
The Minecraft World Region File format has a hard cap of 1MB per chunk.
|
||||
This is due to the fact that the header of the file format only allocates
|
||||
a single byte for sector count, meaning a maximum of 256 sectors, at 4k per sector.
|
||||
|
||||
This limit can be reached fairly easily with books, resulting in the chunk being unable
|
||||
to save to the world. Worse off, is that nothing printed when this occured, and silently
|
||||
performed a chunk rollback on next load.
|
||||
|
||||
This leads to security risk with duplication and is being actively exploited.
|
||||
|
||||
This patch catches the too large scenario, falls back and moves any large Entity
|
||||
or Tile Entity into a new compound, and this compound is saved into a different file.
|
||||
|
||||
On Chunk Load, we check for oversized status, and if so, we load the extra file and
|
||||
merge the Entities and Tile Entities from the oversized chunk back into the level to
|
||||
then be loaded as normal.
|
||||
|
||||
Once a chunk is returned back to normal size, the oversized flag will clear, and no
|
||||
extra data file will exist.
|
||||
|
||||
This fix maintains compatability with all existing Anvil Region Format tools as it
|
||||
does not alter the save format. They will just not know about the extra entities.
|
||||
|
||||
This fix also maintains compatability if someone switches server jars to one without
|
||||
this fix, as the data will remain in the oversized file. Once the server returns
|
||||
to a jar with this fix, the data will be restored.
|
||||
|
||||
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 298b5abbc792dd33be38acbd1c572c9778c4d2d2..46226dd2d16a9f4017661712fe2bfc0c46f63cb2 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
|
||||
@@ -20,8 +20,12 @@ import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
+import java.util.zip.InflaterInputStream; // Paper
|
||||
+
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.Util;
|
||||
+import net.minecraft.nbt.CompoundTag;
|
||||
+import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -48,6 +52,7 @@ public class RegionFile implements AutoCloseable {
|
||||
@VisibleForTesting
|
||||
protected final RegionBitmap usedSectors;
|
||||
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
|
||||
+ public final File regionFile; // Paper
|
||||
|
||||
public RegionFile(File file, File directory, boolean dsync) throws IOException {
|
||||
this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync);
|
||||
@@ -55,6 +60,8 @@ public class RegionFile implements AutoCloseable {
|
||||
|
||||
public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
|
||||
this.header = ByteBuffer.allocateDirect(8192);
|
||||
+ this.regionFile = file.toFile(); // Paper
|
||||
+ initOversizedState(); // Paper
|
||||
this.usedSectors = new RegionBitmap();
|
||||
this.version = outputChunkStreamVersion;
|
||||
if (!Files.isDirectory(directory, new LinkOption[0])) {
|
||||
@@ -433,6 +440,74 @@ public class RegionFile implements AutoCloseable {
|
||||
|
||||
}
|
||||
|
||||
+ // Paper start
|
||||
+ private final byte[] oversized = new byte[1024];
|
||||
+ private int oversizedCount = 0;
|
||||
+
|
||||
+ private synchronized void initOversizedState() throws IOException {
|
||||
+ File metaFile = getOversizedMetaFile();
|
||||
+ if (metaFile.exists()) {
|
||||
+ final byte[] read = java.nio.file.Files.readAllBytes(metaFile.toPath());
|
||||
+ System.arraycopy(read, 0, oversized, 0, oversized.length);
|
||||
+ for (byte temp : oversized) {
|
||||
+ oversizedCount += temp;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static int getChunkIndex(int x, int z) {
|
||||
+ return (x & 31) + (z & 31) * 32;
|
||||
+ }
|
||||
+ synchronized boolean isOversized(int x, int z) {
|
||||
+ return this.oversized[getChunkIndex(x, z)] == 1;
|
||||
+ }
|
||||
+ synchronized void setOversized(int x, int z, boolean oversized) throws IOException {
|
||||
+ final int offset = getChunkIndex(x, z);
|
||||
+ boolean previous = this.oversized[offset] == 1;
|
||||
+ this.oversized[offset] = (byte) (oversized ? 1 : 0);
|
||||
+ if (!previous && oversized) {
|
||||
+ oversizedCount++;
|
||||
+ } else if (!oversized && previous) {
|
||||
+ oversizedCount--;
|
||||
+ }
|
||||
+ if (previous && !oversized) {
|
||||
+ File oversizedFile = getOversizedFile(x, z);
|
||||
+ if (oversizedFile.exists()) {
|
||||
+ oversizedFile.delete();
|
||||
+ }
|
||||
+ }
|
||||
+ if (oversizedCount > 0) {
|
||||
+ if (previous != oversized) {
|
||||
+ writeOversizedMeta();
|
||||
+ }
|
||||
+ } else if (previous) {
|
||||
+ File oversizedMetaFile = getOversizedMetaFile();
|
||||
+ if (oversizedMetaFile.exists()) {
|
||||
+ oversizedMetaFile.delete();
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void writeOversizedMeta() throws IOException {
|
||||
+ java.nio.file.Files.write(getOversizedMetaFile().toPath(), oversized);
|
||||
+ }
|
||||
+
|
||||
+ private File getOversizedMetaFile() {
|
||||
+ return new File(this.regionFile.getParentFile(), this.regionFile.getName().replaceAll("\\.mca$", "") + ".oversized.nbt");
|
||||
+ }
|
||||
+
|
||||
+ private File getOversizedFile(int x, int z) {
|
||||
+ return new File(this.regionFile.getParentFile(), this.regionFile.getName().replaceAll("\\.mca$", "") + "_oversized_" + x + "_" + z + ".nbt");
|
||||
+ }
|
||||
+
|
||||
+ synchronized CompoundTag getOversizedData(int x, int z) throws IOException {
|
||||
+ File file = getOversizedFile(x, z);
|
||||
+ try (DataInputStream out = new DataInputStream(new BufferedInputStream(new InflaterInputStream(new java.io.FileInputStream(file))))) {
|
||||
+ return NbtIo.read((java.io.DataInput) out);
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+ // Paper end
|
||||
private class ChunkBuffer extends ByteArrayOutputStream {
|
||||
|
||||
private final ChunkPos pos;
|
||||
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 ebb1a050beab9530942c4498335f084c89faef06..24092d3d3d234b6f1f2b90e22d90f297532358cc 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
|
||||
@@ -10,7 +10,9 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
+import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
+import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.ExceptionCollector;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
@@ -81,6 +83,74 @@ public class RegionFileStorage implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
+ // Paper start
|
||||
+ private static void printOversizedLog(String msg, File file, int x, int z) {
|
||||
+ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed.");
|
||||
+ }
|
||||
+
|
||||
+ private static final int DEFAULT_SIZE_THRESHOLD = 1024 * 8;
|
||||
+ private static final int OVERZEALOUS_TOTAL_THRESHOLD = 1024 * 64;
|
||||
+ private static final int OVERZEALOUS_THRESHOLD = 1024;
|
||||
+ private static int SIZE_THRESHOLD = DEFAULT_SIZE_THRESHOLD;
|
||||
+ private static void resetFilterThresholds() {
|
||||
+ SIZE_THRESHOLD = Math.max(1024 * 4, Integer.getInteger("Paper.FilterThreshhold", DEFAULT_SIZE_THRESHOLD));
|
||||
+ }
|
||||
+ static {
|
||||
+ resetFilterThresholds();
|
||||
+ }
|
||||
+
|
||||
+ static boolean isOverzealous() {
|
||||
+ return SIZE_THRESHOLD == OVERZEALOUS_THRESHOLD;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException {
|
||||
+ synchronized (regionfile) {
|
||||
+ try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) {
|
||||
+ CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z);
|
||||
+ CompoundTag chunk = NbtIo.read((DataInput) datainputstream);
|
||||
+ if (oversizedData == null) {
|
||||
+ return chunk;
|
||||
+ }
|
||||
+ CompoundTag oversizedLevel = oversizedData.getCompound("Level");
|
||||
+ CompoundTag level = chunk.getCompound("Level");
|
||||
+
|
||||
+ mergeChunkList(level, oversizedLevel, "Entities");
|
||||
+ mergeChunkList(level, oversizedLevel, "TileEntities");
|
||||
+
|
||||
+ chunk.put("Level", level);
|
||||
+
|
||||
+ return chunk;
|
||||
+ } catch (Throwable throwable) {
|
||||
+ throwable.printStackTrace();
|
||||
+ throw throwable;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static void mergeChunkList(CompoundTag level, CompoundTag oversizedLevel, String key) {
|
||||
+ ListTag levelList = level.getList(key, 10);
|
||||
+ ListTag oversizedList = oversizedLevel.getList(key, 10);
|
||||
+
|
||||
+ if (!oversizedList.isEmpty()) {
|
||||
+ levelList.addAll(oversizedList);
|
||||
+ level.put(key, levelList);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static int getNBTSize(Tag nbtBase) {
|
||||
+ DataOutputStream test = new DataOutputStream(new org.apache.commons.io.output.NullOutputStream());
|
||||
+ try {
|
||||
+ nbtBase.write(test);
|
||||
+ return test.size();
|
||||
+ } catch (IOException e) {
|
||||
+ e.printStackTrace();
|
||||
+ return 0;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Paper End
|
||||
+
|
||||
@Nullable
|
||||
public CompoundTag read(ChunkPos pos) throws IOException {
|
||||
// CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
|
||||
@@ -92,6 +162,12 @@ public class RegionFileStorage implements AutoCloseable {
|
||||
try { // Paper
|
||||
DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
|
||||
|
||||
+ // Paper start
|
||||
+ if (regionfile.isOversized(pos.x, pos.z)) {
|
||||
+ printOversizedLog("Loading Oversized Chunk!", regionfile.regionFile, pos.x, pos.z);
|
||||
+ return readOversizedChunk(regionfile, pos);
|
||||
+ }
|
||||
+ // Paper end
|
||||
CompoundTag nbttagcompound;
|
||||
label43:
|
||||
{
|
||||
@@ -143,6 +219,7 @@ public class RegionFileStorage implements AutoCloseable {
|
||||
|
||||
try {
|
||||
NbtIo.write(nbt, (DataOutput) dataoutputstream);
|
||||
+ 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) {
|
||||
try {
|
|
@ -1,21 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: William Blake Galbreath <blake.galbreath@gmail.com>
|
||||
Date: Sat, 20 Apr 2019 19:47:34 -0500
|
||||
Subject: [PATCH] Expose the internal current tick
|
||||
|
||||
|
||||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
index 0dbab8b372d174f343c7d57b147692a68ba2fb95..8242d6d7471c78cc298d5c385f075e368ab80edf 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||||
@@ -2542,5 +2542,10 @@ public final class CraftServer implements Server {
|
||||
}
|
||||
return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name);
|
||||
}
|
||||
+
|
||||
+ @Override
|
||||
+ public int getCurrentTick() {
|
||||
+ return net.minecraft.server.MinecraftServer.currentTick;
|
||||
+ }
|
||||
// Paper end
|
||||
}
|
|
@ -1,328 +0,0 @@
|
|||
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 d226247b93a2f1f7e039b0963d2e27b5dd11c15e..1ea32090783ac5b885b95ae2009dd61de3063ae0 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
|
||||
@@ -110,6 +110,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.getParent(); curr != next; curr = next, next = next.getParent()) {
|
||||
+ 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 6d7f1fcd1709967acadd67b610c15f61d95a211b..6a676995b157702b4bd423ff8590b6c682102b61 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;
|
||||
@@ -1133,10 +1134,59 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
||||
@Nullable
|
||||
public CompoundTag readChunk(ChunkPos pos) throws IOException {
|
||||
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 30b88f0e8ad40438af8a7d5bec1471a60ddd977b..5d920d7df52c12f5f1d1d8111340800cbddaac78 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -482,6 +482,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) {
|
||||
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..1c7b18db0053bca6e7750225a79f7d95843edabe 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkStatus.java
|
||||
@@ -224,6 +224,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 b8bdd69bda00ba0eb72c161d3b49101b8221ac41..1eaedda19b05e1ec429fa505c72c9e2743eb32b7 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
|
||||
@@ -561,6 +561,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 46226dd2d16a9f4017661712fe2bfc0c46f63cb2..c22391a0d4b7db49bd3994b0887939a7d8019391 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
|
||||
@@ -27,6 +27,7 @@ import net.minecraft.Util;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
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;
|
||||
|
||||
@@ -54,6 +55,30 @@ public class RegionFile implements AutoCloseable {
|
||||
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
|
||||
public final File regionFile; // 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);
|
||||
}
|
||||
@@ -401,6 +426,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;
|
||||
}
|
||||
@@ -411,6 +437,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 24092d3d3d234b6f1f2b90e22d90f297532358cc..43510774d489bfdd30f10d521e424fa1363b8919 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
|
||||
@@ -219,6 +219,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 85bc46d7a2528254f888c027c2a4e2240f8e11d1..d580a66dd741b63dd8ed89d7b976b1612cfb4d90 100644
|
||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
||||
@@ -19,6 +19,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;
|
||||
@@ -280,8 +281,22 @@ public class CraftWorld extends CraftRegionAccessor 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);
|
||||
}
|
||||
@@ -392,20 +407,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.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
|
Loading…
Add table
Add a link
Reference in a new issue