Add some patches, fix compile
This commit is contained in:
parent
e88856dd75
commit
ea01aa335a
637 changed files with 568 additions and 495 deletions
|
@ -0,0 +1,64 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Aikar <aikar@aikar.co>
|
||||
Date: Wed, 6 May 2020 05:00:57 -0400
|
||||
Subject: [PATCH] Handle Oversized Tile Entities in chunks
|
||||
|
||||
Splits out Extra Packets if too many TE's are encountered to prevent
|
||||
creating too large of a packet to sed.
|
||||
|
||||
Co authored by Spottedleaf
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
|
||||
index f47eeb70661661610ef1a96dd9da67785825c126..0ef3e9b472e35bd2572b04722781abf7d4a1094b 100644
|
||||
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
|
||||
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
|
||||
@@ -24,6 +24,14 @@ public class ClientboundLevelChunkPacketData {
|
||||
private final CompoundTag heightmaps;
|
||||
private final byte[] buffer;
|
||||
private final List<ClientboundLevelChunkPacketData.BlockEntityInfo> blockEntitiesData;
|
||||
+ // Paper start
|
||||
+ private final java.util.List<net.minecraft.network.protocol.Packet> extraPackets = new java.util.ArrayList<>();
|
||||
+ private static final int TE_LIMIT = Integer.getInteger("Paper.excessiveTELimit", 750);
|
||||
+
|
||||
+ public List<net.minecraft.network.protocol.Packet> getExtraPackets() {
|
||||
+ return this.extraPackets;
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
public ClientboundLevelChunkPacketData(LevelChunk chunk) {
|
||||
this.heightmaps = new CompoundTag();
|
||||
@@ -37,8 +45,18 @@ public class ClientboundLevelChunkPacketData {
|
||||
this.buffer = new byte[calculateChunkSize(chunk)];
|
||||
extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chunk);
|
||||
this.blockEntitiesData = Lists.newArrayList();
|
||||
+ int totalTileEntities = 0; // Paper
|
||||
|
||||
for(Map.Entry<BlockPos, BlockEntity> entry2 : chunk.getBlockEntities().entrySet()) {
|
||||
+ // Paper start
|
||||
+ if (++totalTileEntities > TE_LIMIT) {
|
||||
+ var packet = entry2.getValue().getUpdatePacket();
|
||||
+ if (packet != null) {
|
||||
+ this.extraPackets.add(packet);
|
||||
+ continue;
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
this.blockEntitiesData.add(ClientboundLevelChunkPacketData.BlockEntityInfo.create(entry2.getValue()));
|
||||
}
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
|
||||
index 26e46d751c8f8162c2bafe2fc109fc91dc4b7c0f..ff42a3e76500ad6e087b7f1cd6ec45acde124180 100644
|
||||
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
|
||||
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
|
||||
@@ -57,4 +57,11 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
|
||||
public ClientboundLightUpdatePacketData getLightData() {
|
||||
return this.lightData;
|
||||
}
|
||||
+
|
||||
+ // Paper start - handle over-sized TE packets
|
||||
+ @Override
|
||||
+ public java.util.List<Packet> getExtraPackets() {
|
||||
+ return this.chunkData.getExtraPackets();
|
||||
+ }
|
||||
+ // Paper end
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Fri, 27 Dec 2019 09:42:26 -0800
|
||||
Subject: [PATCH] Guard against serializing mismatching chunk coordinate
|
||||
|
||||
Should help if something dumb happens
|
||||
|
||||
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 a7ee469bb2880a78540b79ae691ea449dfe22ce4..0d8cfc0808a8f204d57cec40929b489099eca815 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
|
||||
@@ -94,6 +94,18 @@ public class ChunkSerializer {
|
||||
|
||||
public ChunkSerializer() {}
|
||||
|
||||
+ // Paper start - guard against serializing mismatching coordinates
|
||||
+ // TODO Note: This needs to be re-checked each update
|
||||
+ public static ChunkPos getChunkCoordinate(CompoundTag chunkData) {
|
||||
+ final int dataVersion = ChunkStorage.getVersion(chunkData);
|
||||
+ if (dataVersion < 2842) { // Level tag is removed after this version
|
||||
+ final CompoundTag levelData = chunkData.getCompound("Level");
|
||||
+ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos"));
|
||||
+ } else {
|
||||
+ return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos"));
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
// Paper start
|
||||
public static final class InProgressChunkHolder {
|
||||
|
||||
@@ -115,7 +127,7 @@ public class ChunkSerializer {
|
||||
|
||||
public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) {
|
||||
// Paper end
|
||||
- ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos"));
|
||||
+ ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate
|
||||
|
||||
if (!Objects.equals(chunkPos, chunkcoordintpair1)) {
|
||||
ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkPos, chunkPos, chunkcoordintpair1});
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
||||
index b4e7c9b317d532d4915932f8f79dfebf2b63ff16..e4b3a70ff9f906a10f2ba3c07642193ca3269db7 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
||||
@@ -178,6 +178,13 @@ public class ChunkStorage implements AutoCloseable {
|
||||
|
||||
// Paper start - async chunk io
|
||||
public void write(ChunkPos chunkPos, CompoundTag nbt) throws IOException {
|
||||
+ // Paper start
|
||||
+ if (nbt != null && !chunkPos.equals(ChunkSerializer.getChunkCoordinate(nbt))) {
|
||||
+ String world = (this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap)this).level.getWorld().getName() : null;
|
||||
+ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos.toString()
|
||||
+ + " but compound says coordinate is " + ChunkSerializer.getChunkCoordinate(nbt).toString() + (world == null ? " for an unknown world" : (" for world: " + world)));
|
||||
+ }
|
||||
+ // Paper end
|
||||
this.regionFileCache.write(chunkPos, nbt);
|
||||
// Paper end - Async chunk loading
|
||||
if (this.legacyStructureHandler != null) {
|
|
@ -1,44 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Andrew Steinborn <git@steinborn.me>
|
||||
Date: Sun, 5 Jul 2020 22:38:18 -0400
|
||||
Subject: [PATCH] Optimize NetworkManager Exception Handling
|
||||
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/network/ConnectionProtocol.java b/src/main/java/net/minecraft/network/ConnectionProtocol.java
|
||||
index a30fa3d54b70ab63d24dd67a69f39742335aff1e..a79da4bc0ed06ff8ede0a34f00194f927251099b 100644
|
||||
--- a/src/main/java/net/minecraft/network/ConnectionProtocol.java
|
||||
+++ b/src/main/java/net/minecraft/network/ConnectionProtocol.java
|
||||
@@ -329,6 +329,7 @@ public enum ConnectionProtocol implements BundlerInfo.Provider {
|
||||
|
||||
@Nullable
|
||||
public Packet<?> createPacket(int id, FriendlyByteBuf buf) {
|
||||
+ if (id < 0 || id >= this.idToDeserializer.size()) return null; // Paper
|
||||
Function<FriendlyByteBuf, ? extends Packet<T>> function = this.idToDeserializer.get(id);
|
||||
return function != null ? function.apply(buf) : null;
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/network/Varint21FrameDecoder.java b/src/main/java/net/minecraft/network/Varint21FrameDecoder.java
|
||||
index 99b581052f937b0f2d6b5d73de699008c1d51774..81ec2011a93bb94200ad750f4666ba1cd8be11c5 100644
|
||||
--- a/src/main/java/net/minecraft/network/Varint21FrameDecoder.java
|
||||
+++ b/src/main/java/net/minecraft/network/Varint21FrameDecoder.java
|
||||
@@ -8,9 +8,20 @@ import io.netty.handler.codec.CorruptedFrameException;
|
||||
import java.util.List;
|
||||
|
||||
public class Varint21FrameDecoder extends ByteToMessageDecoder {
|
||||
+ private final byte[] lenBuf = new byte[3]; // Paper
|
||||
+ @Override
|
||||
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) {
|
||||
+ // Paper start - if channel is not active just discard the packet
|
||||
+ if (!channelHandlerContext.channel().isActive()) {
|
||||
+ byteBuf.skipBytes(byteBuf.readableBytes());
|
||||
+ return;
|
||||
+ }
|
||||
+ // Paper end
|
||||
byteBuf.markReaderIndex();
|
||||
- byte[] bs = new byte[3];
|
||||
+ // Paper start - reuse temporary length buffer
|
||||
+ byte[] bs = lenBuf;
|
||||
+ java.util.Arrays.fill(bs, (byte) 0);
|
||||
+ // Paper end
|
||||
|
||||
for(int i = 0; i < bs.length; ++i) {
|
||||
if (!byteBuf.isReadable()) {
|
|
@ -1,35 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Zach Brown <zach@zachbr.io>
|
||||
Date: Tue, 23 Jul 2019 20:44:47 -0500
|
||||
Subject: [PATCH] Do not let the server load chunks from newer versions
|
||||
|
||||
If the server attempts to load a chunk generated by a newer version of
|
||||
the game, immediately stop the server to prevent data corruption.
|
||||
|
||||
You can override this functionality at your own peril.
|
||||
|
||||
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 dec862552b08e6b9f9ad1f88db1e07dcc43d7fa3..fce679e2b72d2ce1c8e75710041937c2c0566044 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
|
||||
@@ -124,8 +124,20 @@ public class ChunkSerializer {
|
||||
InProgressChunkHolder holder = loadChunk(world, poiStorage, chunkPos, nbt, true);
|
||||
return holder.protoChunk;
|
||||
}
|
||||
+ // Paper start
|
||||
+ private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion();
|
||||
+ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion");
|
||||
+ // Paper end
|
||||
|
||||
public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) {
|
||||
+ // Paper start - Do NOT attempt to load chunks saved with newer versions
|
||||
+ if (nbt.contains("DataVersion", 99)) {
|
||||
+ int dataVersion = nbt.getInt("DataVersion");
|
||||
+ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) {
|
||||
+ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace();
|
||||
+ System.exit(1);
|
||||
+ }
|
||||
+ }
|
||||
// Paper end
|
||||
ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
|
||||
Date: Wed, 18 Nov 2020 20:52:25 -0800
|
||||
Subject: [PATCH] Entity load/save limit per chunk
|
||||
|
||||
Adds a config option to limit the number of entities saved and loaded
|
||||
to a chunk. The default values of -1 disable the limit. Although
|
||||
defaults are only included for certain entites, this allows setting
|
||||
limits for any entity type.
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
|
||||
index c2aafe4e1afec2793735bf7b0bbd6af94ad393f8..c7e127e62b7616997c123c47d86ed751c12fd151 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
|
||||
@@ -626,9 +626,20 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
|
||||
final Spliterator<? extends Tag> spliterator = entityNbtList.spliterator();
|
||||
|
||||
return StreamSupport.stream(new Spliterator<Entity>() {
|
||||
+ final java.util.Map<EntityType<?>, Integer> loadedEntityCounts = new java.util.HashMap<>(); // Paper
|
||||
public boolean tryAdvance(Consumer<? super Entity> consumer) {
|
||||
return spliterator.tryAdvance((nbtbase) -> {
|
||||
EntityType.loadEntityRecursive((CompoundTag) nbtbase, world, (entity) -> {
|
||||
+ // Paper start
|
||||
+ final EntityType<?> entityType = entity.getType();
|
||||
+ final int saveLimit = world.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1);
|
||||
+ if (saveLimit > -1) {
|
||||
+ if (this.loadedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) {
|
||||
+ return null;
|
||||
+ }
|
||||
+ this.loadedEntityCounts.merge(entityType, 1, Integer::sum);
|
||||
+ }
|
||||
+ // Paper end
|
||||
consumer.accept(entity);
|
||||
return entity;
|
||||
});
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
|
||||
index 0ec0be22f7292d57c40da6f1f4575bdebf8dbd09..060e064625969610539dbf969ce773b877a7c579 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
|
||||
@@ -110,7 +110,18 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
|
||||
}
|
||||
|
||||
ListTag listTag = new ListTag();
|
||||
+ final java.util.Map<net.minecraft.world.entity.EntityType<?>, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper
|
||||
entities.forEach((entity) -> { // diff here: use entities parameter
|
||||
+ // Paper start
|
||||
+ final EntityType<?> entityType = entity.getType();
|
||||
+ final int saveLimit = level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1);
|
||||
+ if (saveLimit > -1) {
|
||||
+ if (savedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) {
|
||||
+ return;
|
||||
+ }
|
||||
+ savedEntityCounts.merge(entityType, 1, Integer::sum);
|
||||
+ }
|
||||
+ // Paper end
|
||||
CompoundTag compoundTag = new CompoundTag();
|
||||
if (entity.save(compoundTag)) {
|
||||
listTag.add(compoundTag);
|
|
@ -1,180 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Mon, 6 Apr 2020 04:20:44 -0700
|
||||
Subject: [PATCH] Execute chunk tasks mid-tick
|
||||
|
||||
This will help the server load chunks if tick times are high.
|
||||
|
||||
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
||||
index 4d861f9a58f8ea238471af22f387854d855b1801..efbf77024d235d8af9f7efc938c17afd76a51b0c 100644
|
||||
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
|
||||
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
||||
@@ -47,6 +47,8 @@ public final class MinecraftTimings {
|
||||
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
|
||||
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
|
||||
|
||||
+ public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
|
||||
+
|
||||
private static final Map<Class<?>, String> taskNameCache = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
private MinecraftTimings() {}
|
||||
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
index 64081583ee2a29b43d487b3730342dce7ac85e94..b54a3dcbd94582158a7119b92b02be5a4e32877c 100644
|
||||
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
||||
@@ -1302,6 +1302,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
|
||||
private boolean pollTaskInternal() {
|
||||
if (super.pollTask()) {
|
||||
+ this.executeMidTickTasks(); // Paper - execute chunk tasks mid tick
|
||||
return true;
|
||||
} else {
|
||||
if (this.haveTime()) {
|
||||
@@ -2705,4 +2706,74 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
}
|
||||
// Paper end
|
||||
+
|
||||
+ // Paper start - execute chunk tasks mid tick
|
||||
+ static final long CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME = 25L * 1000L; // 25us
|
||||
+ static final long MAX_CHUNK_EXEC_TIME = 1000L; // 1us
|
||||
+
|
||||
+ static final long TASK_EXECUTION_FAILURE_BACKOFF = 5L * 1000L; // 5us
|
||||
+
|
||||
+ private static long lastMidTickExecute;
|
||||
+ private static long lastMidTickExecuteFailure;
|
||||
+
|
||||
+ private boolean tickMidTickTasks() {
|
||||
+ // give all worlds a fair chance at by targetting them all.
|
||||
+ // if we execute too many tasks, that's fine - we have logic to correctly handle overuse of allocated time.
|
||||
+ boolean executed = false;
|
||||
+ for (ServerLevel world : this.getAllLevels()) {
|
||||
+ long currTime = System.nanoTime();
|
||||
+ if (currTime - world.lastMidTickExecuteFailure <= TASK_EXECUTION_FAILURE_BACKOFF) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ if (!world.getChunkSource().pollTask()) {
|
||||
+ // we need to back off if this fails
|
||||
+ world.lastMidTickExecuteFailure = currTime;
|
||||
+ } else {
|
||||
+ executed = true;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return executed;
|
||||
+ }
|
||||
+
|
||||
+ public final void executeMidTickTasks() {
|
||||
+ org.spigotmc.AsyncCatcher.catchOp("mid tick chunk task execution");
|
||||
+ long startTime = System.nanoTime();
|
||||
+ if ((startTime - lastMidTickExecute) <= CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME || (startTime - lastMidTickExecuteFailure) <= TASK_EXECUTION_FAILURE_BACKOFF) {
|
||||
+ // it's shown to be bad to constantly hit the queue (chunk loads slow to a crawl), even if no tasks are executed.
|
||||
+ // so, backoff to prevent this
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming();
|
||||
+ try {
|
||||
+ for (;;) {
|
||||
+ boolean moreTasks = this.tickMidTickTasks();
|
||||
+ long currTime = System.nanoTime();
|
||||
+ long diff = currTime - startTime;
|
||||
+
|
||||
+ if (!moreTasks || diff >= MAX_CHUNK_EXEC_TIME) {
|
||||
+ if (!moreTasks) {
|
||||
+ lastMidTickExecuteFailure = currTime;
|
||||
+ }
|
||||
+
|
||||
+ // note: negative values reduce the time
|
||||
+ long overuse = diff - MAX_CHUNK_EXEC_TIME;
|
||||
+ if (overuse >= (10L * 1000L * 1000L)) { // 10ms
|
||||
+ // make sure something like a GC or dumb plugin doesn't screw us over...
|
||||
+ overuse = 10L * 1000L * 1000L; // 10ms
|
||||
+ }
|
||||
+
|
||||
+ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME;
|
||||
+ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME);
|
||||
+
|
||||
+ lastMidTickExecute = currTime + extraSleep;
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+ } finally {
|
||||
+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming();
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - execute chunk tasks mid tick
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
index 828de28f2777e2477a9c6545c8af96c4ca4e352b..2a31265ac49b7a6e32105530d00952ee0c0d4331 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
|
||||
@@ -599,6 +599,8 @@ public class ServerChunkCache extends ChunkSource {
|
||||
Collections.shuffle(shuffled);
|
||||
iterator1 = shuffled.iterator();
|
||||
}
|
||||
+
|
||||
+ int chunksTicked = 0; // Paper
|
||||
try {
|
||||
while (iterator1.hasNext()) {
|
||||
LevelChunk chunk1 = iterator1.next();
|
||||
@@ -616,6 +618,7 @@ public class ServerChunkCache extends ChunkSource {
|
||||
|
||||
if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - the chunk is known ticking
|
||||
this.level.tickChunk(chunk1, k);
|
||||
+ if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
|
||||
}
|
||||
}
|
||||
// Paper start - optimise chunk tick iteration
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
index 0f6d4f80677c54a94a1bbfb861a3bcce17b83489..1d0f87185a8a74394bc2da29828407fd4210754c 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -215,6 +215,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
||||
private final StructureCheck structureCheck;
|
||||
private final boolean tickTime;
|
||||
private final RandomSequences randomSequences;
|
||||
+ public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick
|
||||
|
||||
// CraftBukkit start
|
||||
public final LevelStorageSource.LevelStorageAccess convertable;
|
||||
@@ -1185,6 +1186,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
||||
if (fluid1.is(fluid)) {
|
||||
fluid1.tick(this, pos);
|
||||
}
|
||||
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick
|
||||
|
||||
}
|
||||
|
||||
@@ -1194,6 +1196,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
||||
if (iblockdata.is(block)) {
|
||||
iblockdata.tick(this, pos, this.random);
|
||||
}
|
||||
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - exec chunk tasks during world tick
|
||||
|
||||
}
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
|
||||
index 7113fdf06bc526af62d08313f8c47e14a2f4b652..e8a705c432c4f10944eaac33f7d4670916070715 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/Level.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
||||
@@ -909,6 +909,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
||||
// Spigot end
|
||||
} else if (this.shouldTickBlocksAt(tickingblockentity.getPos())) {
|
||||
tickingblockentity.tick();
|
||||
+ // Paper start - execute chunk tasks during tick
|
||||
+ if ((this.tileTickPosition & 7) == 0) {
|
||||
+ MinecraftServer.getServer().executeMidTickTasks();
|
||||
+ }
|
||||
+ // Paper end - execute chunk tasks during tick
|
||||
}
|
||||
}
|
||||
this.blockEntityTickers.removeAll(toRemove);
|
||||
@@ -923,6 +928,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
|
||||
public <T extends Entity> void guardEntityTick(Consumer<T> tickConsumer, T entity) {
|
||||
try {
|
||||
tickConsumer.accept(entity);
|
||||
+ MinecraftServer.getServer().executeMidTickTasks(); // Paper - execute chunk tasks mid tick
|
||||
} catch (Throwable throwable) {
|
||||
if (throwable instanceof ThreadDeath) throw throwable; // Paper
|
||||
// Paper start - Prevent tile entity and entity crashes
|
|
@ -1,777 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sun, 2 Feb 2020 02:25:10 -0800
|
||||
Subject: [PATCH] Attempt to recalculate regionfile header if it is corrupt
|
||||
|
||||
Instead of trying to relocate the chunk, which is seems to never
|
||||
be the correct choice, so we end up duplicating or swapping chunks,
|
||||
we instead drop the current regionfile header and recalculate -
|
||||
hoping that at least then we don't swap chunks, and maybe recover
|
||||
them all.
|
||||
|
||||
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 fce679e2b72d2ce1c8e75710041937c2c0566044..9c6a2884c34a9f6e775103da42480cd6b8c693b3 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
|
||||
@@ -70,6 +70,18 @@ import net.minecraft.world.ticks.ProtoChunkTicks;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class ChunkSerializer {
|
||||
+ // Paper start
|
||||
+ // TODO: Check on update
|
||||
+ public static long getLastWorldSaveTime(CompoundTag chunkData) {
|
||||
+ final int dataVersion = ChunkStorage.getVersion(chunkData);
|
||||
+ if (dataVersion < 2842) { // Level tag is removed after this version
|
||||
+ final CompoundTag levelData = chunkData.getCompound("Level");
|
||||
+ return levelData.getLong("LastUpdate");
|
||||
+ } else {
|
||||
+ return chunkData.getLong("LastUpdate");
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
|
||||
public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null); // Paper - Anti-Xray - Add preset block states
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
@@ -450,7 +462,7 @@ public class ChunkSerializer {
|
||||
nbttagcompound.putInt("xPos", chunkcoordintpair.x);
|
||||
nbttagcompound.putInt("yPos", chunk.getMinSection());
|
||||
nbttagcompound.putInt("zPos", chunkcoordintpair.z);
|
||||
- nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading
|
||||
+ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Paper - diff on change
|
||||
nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime());
|
||||
nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
|
||||
BlendingData blendingdata = chunk.getBlendingData();
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
||||
index e4b3a70ff9f906a10f2ba3c07642193ca3269db7..a5da3333e87bcc9def785a8e742a30d38c0ecc27 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
|
||||
@@ -41,7 +41,7 @@ public class ChunkStorage implements AutoCloseable {
|
||||
this.fixerUpper = dataFixer;
|
||||
// Paper start - async chunk io
|
||||
// remove IO worker
|
||||
- this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - nuke IOWorker
|
||||
+ this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - nuke IOWorker // Paper
|
||||
// Paper end - async chunk io
|
||||
}
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
|
||||
index c8298a597818227de33a4afce4698ec0666cf758..6baceb6ce9021c489be6e79d338a9704285afa26 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java
|
||||
@@ -9,6 +9,27 @@ import java.util.BitSet;
|
||||
public class RegionBitmap {
|
||||
private final BitSet used = new BitSet();
|
||||
|
||||
+ // Paper start
|
||||
+ public final void copyFrom(RegionBitmap other) {
|
||||
+ BitSet thisBitset = this.used;
|
||||
+ BitSet otherBitset = other.used;
|
||||
+
|
||||
+ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) {
|
||||
+ thisBitset.set(i, otherBitset.get(i));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ public final boolean tryAllocate(int from, int length) {
|
||||
+ BitSet bitset = this.used;
|
||||
+ int firstSet = bitset.nextSetBit(from);
|
||||
+ if (firstSet > 0 && firstSet < (from + length)) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ bitset.set(from, from + length);
|
||||
+ return true;
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
public void force(int start, int size) {
|
||||
this.used.set(start, start + size);
|
||||
}
|
||||
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 cda87a66fe80bf910f629c64e36c1fecbad81d77..9bae47f99336c377beb72c4b50b7f01cb4db15da 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
|
||||
@@ -51,6 +51,355 @@ public class RegionFile implements AutoCloseable {
|
||||
public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
|
||||
public final Path regionFile; // Paper
|
||||
|
||||
+ // Paper start - try to recover from RegionFile header corruption
|
||||
+ private static long roundToSectors(long bytes) {
|
||||
+ long sectors = bytes >>> 12; // 4096 = 2^12
|
||||
+ long remainingBytes = bytes & 4095;
|
||||
+ long sign = -remainingBytes; // sign is 1 if nonzero
|
||||
+ return sectors + (sign >>> 63);
|
||||
+ }
|
||||
+
|
||||
+ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag();
|
||||
+
|
||||
+ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException {
|
||||
+ try {
|
||||
+ if (chunkDataLength < 0) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ long offset = sector * 4096L + 4L; // offset for chunk data
|
||||
+
|
||||
+ if ((offset + chunkDataLength) > fileLength) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength);
|
||||
+ if (chunkDataLength != this.file.read(chunkData, offset)) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ ((java.nio.Buffer)chunkData).flip();
|
||||
+
|
||||
+ byte compressionType = chunkData.get();
|
||||
+ if (compressionType < 0) { // compressionType & 128 != 0
|
||||
+ // oversized chunk
|
||||
+ return OVERSIZED_COMPOUND;
|
||||
+ }
|
||||
+
|
||||
+ RegionFileVersion compression = RegionFileVersion.fromId(compressionType);
|
||||
+ if (compression == null) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position()));
|
||||
+
|
||||
+ return NbtIo.read(new DataInputStream(input));
|
||||
+ } catch (Exception ex) {
|
||||
+ return null;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private int getLength(long sector) throws IOException {
|
||||
+ ByteBuffer length = ByteBuffer.allocate(4);
|
||||
+ if (4 != this.file.read(length, sector * 4096L)) {
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
+ return length.getInt(0);
|
||||
+ }
|
||||
+
|
||||
+ private void backupRegionFile() {
|
||||
+ Path backup = this.regionFile.getParent().resolve(this.regionFile.getFileName() + "." + new java.util.Random().nextLong() + ".backup");
|
||||
+ this.backupRegionFile(backup);
|
||||
+ }
|
||||
+
|
||||
+ private void backupRegionFile(Path to) {
|
||||
+ try {
|
||||
+ this.file.force(true);
|
||||
+ LOGGER.warn("Backing up regionfile \"" + this.regionFile.toAbsolutePath() + "\" to " + to.toAbsolutePath());
|
||||
+ java.nio.file.Files.copy(this.regionFile, to, java.nio.file.StandardCopyOption.COPY_ATTRIBUTES);
|
||||
+ LOGGER.warn("Backed up the regionfile to " + to.toAbsolutePath());
|
||||
+ } catch (IOException ex) {
|
||||
+ LOGGER.error("Failed to backup to " + to.toAbsolutePath(), ex);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private static boolean inSameRegionfile(ChunkPos first, ChunkPos second) {
|
||||
+ return (first.x & ~31) == (second.x & ~31) && (first.z & ~31) == (second.z & ~31);
|
||||
+ }
|
||||
+
|
||||
+ // note: only call for CHUNK regionfiles
|
||||
+ boolean recalculateHeader() throws IOException {
|
||||
+ if (!this.canRecalcHeader) {
|
||||
+ return false;
|
||||
+ }
|
||||
+ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.regionFile);
|
||||
+ if (ourLowerLeftPosition == null) {
|
||||
+ LOGGER.error("Unable to get chunk location of regionfile " + this.regionFile.toAbsolutePath() + ", cannot recover header");
|
||||
+ return false;
|
||||
+ }
|
||||
+ synchronized (this) {
|
||||
+ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.regionFile.toAbsolutePath(), new Throwable());
|
||||
+
|
||||
+ // try to backup file so maybe it could be sent to us for further investigation
|
||||
+
|
||||
+ this.backupRegionFile();
|
||||
+ CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data)
|
||||
+ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes
|
||||
+ int[] sectorOffsets = new int[32 * 32]; // in sectors
|
||||
+ boolean[] hasAikarOversized = new boolean[32 * 32];
|
||||
+
|
||||
+ long fileLength = this.file.size();
|
||||
+ long totalSectors = roundToSectors(fileLength);
|
||||
+
|
||||
+ // search the regionfile from start to finish for the most up-to-date chunk data
|
||||
+
|
||||
+ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip
|
||||
+ int chunkDataLength = this.getLength(i);
|
||||
+ CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength);
|
||||
+ if (compound == null || compound == OVERSIZED_COMPOUND) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(compound);
|
||||
+ if (!inSameRegionfile(ourLowerLeftPosition, chunkPos)) {
|
||||
+ LOGGER.error("Ignoring absolute chunk " + chunkPos + " in regionfile as it is not contained in the bounds of the regionfile '" + this.regionFile.toAbsolutePath() + "'. It should be in regionfile (" + (chunkPos.x >> 5) + "," + (chunkPos.z >> 5) + ")");
|
||||
+ continue;
|
||||
+ }
|
||||
+ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5);
|
||||
+
|
||||
+ CompoundTag otherCompound = compounds[location];
|
||||
+
|
||||
+ if (otherCompound != null && ChunkSerializer.getLastWorldSaveTime(otherCompound) > ChunkSerializer.getLastWorldSaveTime(compound)) {
|
||||
+ continue; // don't overwrite newer data.
|
||||
+ }
|
||||
+
|
||||
+ // aikar oversized?
|
||||
+ Path aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z);
|
||||
+ boolean isAikarOversized = false;
|
||||
+ if (Files.exists(aikarOversizedFile)) {
|
||||
+ try {
|
||||
+ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z);
|
||||
+ if (ChunkSerializer.getLastWorldSaveTime(compound) == ChunkSerializer.getLastWorldSaveTime(aikarOversizedCompound)) {
|
||||
+ // best we got for an id. hope it's good enough
|
||||
+ isAikarOversized = true;
|
||||
+ }
|
||||
+ } catch (Exception ex) {
|
||||
+ LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.regionFile.toAbsolutePath() + ", oversized data for this chunk will be lost", ex);
|
||||
+ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ hasAikarOversized[location] = isAikarOversized;
|
||||
+ compounds[location] = compound;
|
||||
+ rawLengths[location] = chunkDataLength + 4;
|
||||
+ sectorOffsets[location] = (int)i;
|
||||
+
|
||||
+ int chunkSectorLength = (int)roundToSectors(rawLengths[location]);
|
||||
+ i += chunkSectorLength;
|
||||
+ --i; // gets incremented next iteration
|
||||
+ }
|
||||
+
|
||||
+ // forge style oversized data is already handled by the local search, and aikar data we just hope
|
||||
+ // we get it right as aikar data has no identifiers we could use to try and find its corresponding
|
||||
+ // local data compound
|
||||
+
|
||||
+ java.nio.file.Path containingFolder = this.externalFileDir;
|
||||
+ Path[] regionFiles = Files.list(containingFolder).toArray(Path[]::new);
|
||||
+ boolean[] oversized = new boolean[32 * 32];
|
||||
+ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32];
|
||||
+
|
||||
+ if (regionFiles != null) {
|
||||
+ int lowerXBound = ourLowerLeftPosition.x; // inclusive
|
||||
+ int lowerZBound = ourLowerLeftPosition.z; // inclusive
|
||||
+ int upperXBound = lowerXBound + 32 - 1; // inclusive
|
||||
+ int upperZBound = lowerZBound + 32 - 1; // inclusive
|
||||
+
|
||||
+ // read mojang oversized data
|
||||
+ for (Path regionFile : regionFiles) {
|
||||
+ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile);
|
||||
+ if (oversizedCoords == null) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) {
|
||||
+ continue; // not in our regionfile
|
||||
+ }
|
||||
+
|
||||
+ // ensure oversized data is valid & is newer than data in the regionfile
|
||||
+
|
||||
+ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5);
|
||||
+
|
||||
+ byte[] chunkData;
|
||||
+ try {
|
||||
+ chunkData = Files.readAllBytes(regionFile);
|
||||
+ } catch (Exception ex) {
|
||||
+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", data will be lost", ex);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ CompoundTag compound = null;
|
||||
+
|
||||
+ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them
|
||||
+ RegionFileVersion compression = null;
|
||||
+ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) {
|
||||
+ try {
|
||||
+ DataInputStream in = new DataInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData))); // typical java
|
||||
+ compound = NbtIo.read((java.io.DataInput)in);
|
||||
+ compression = compressionType;
|
||||
+ break; // reaches here iff readNBT does not throw
|
||||
+ } catch (Exception ex) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (compound == null) {
|
||||
+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.toAbsolutePath() + ", it's corrupt. Its data will be lost");
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (!ChunkSerializer.getChunkCoordinate(compound).equals(oversizedCoords)) {
|
||||
+ LOGGER.error("Can't use oversized chunk stored in " + regionFile.toAbsolutePath() + ", got absolute chunkpos: " + ChunkSerializer.getChunkCoordinate(compound) + ", expected " + oversizedCoords);
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ if (compounds[location] == null || ChunkSerializer.getLastWorldSaveTime(compound) > ChunkSerializer.getLastWorldSaveTime(compounds[location])) {
|
||||
+ oversized[location] = true;
|
||||
+ oversizedCompressionTypes[location] = compression;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // now we need to calculate a new offset header
|
||||
+
|
||||
+ int[] calculatedOffsets = new int[32 * 32];
|
||||
+ RegionBitmap newSectorAllocations = new RegionBitmap();
|
||||
+ newSectorAllocations.force(0, 2); // make space for header
|
||||
+
|
||||
+ // allocate sectors for normal chunks
|
||||
+
|
||||
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
||||
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
||||
+ int location = chunkX | (chunkZ << 5);
|
||||
+
|
||||
+ if (oversized[location]) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ int rawLength = rawLengths[location]; // bytes
|
||||
+ int sectorOffset = sectorOffsets[location]; // sectors
|
||||
+ int sectorLength = (int)roundToSectors(rawLength);
|
||||
+
|
||||
+ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) {
|
||||
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
|
||||
+ } else {
|
||||
+ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + ", chunk will be regenerated");
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // allocate sectors for oversized chunks
|
||||
+
|
||||
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
||||
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
||||
+ int location = chunkX | (chunkZ << 5);
|
||||
+
|
||||
+ if (!oversized[location]) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ int sectorOffset = newSectorAllocations.allocate(1);
|
||||
+ int sectorLength = 1;
|
||||
+
|
||||
+ try {
|
||||
+ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096);
|
||||
+ // only allocate in the new offsets if the write succeeds
|
||||
+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized
|
||||
+ } catch (IOException ex) {
|
||||
+ newSectorAllocations.free(sectorOffset, sectorLength);
|
||||
+ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath() + " will be regenerated");
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // rewrite aikar oversized data
|
||||
+
|
||||
+ this.oversizedCount = 0;
|
||||
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
||||
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
||||
+ int location = chunkX | (chunkZ << 5);
|
||||
+ int isAikarOversized = hasAikarOversized[location] ? 1 : 0;
|
||||
+
|
||||
+ this.oversizedCount += isAikarOversized;
|
||||
+ this.oversized[location] = (byte)isAikarOversized;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (this.oversizedCount > 0) {
|
||||
+ try {
|
||||
+ this.writeOversizedMeta();
|
||||
+ } catch (Exception ex) {
|
||||
+ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.regionFile.toAbsolutePath(), ex);
|
||||
+ Files.deleteIfExists(this.getOversizedMetaFile());
|
||||
+ }
|
||||
+ } else {
|
||||
+ Files.deleteIfExists(this.getOversizedMetaFile());
|
||||
+ }
|
||||
+
|
||||
+ this.usedSectors.copyFrom(newSectorAllocations);
|
||||
+
|
||||
+ // before we overwrite the old sectors, print a summary of the chunks that got changed.
|
||||
+
|
||||
+ LOGGER.info("Starting summary of changes for regionfile " + this.regionFile.toAbsolutePath());
|
||||
+
|
||||
+ for (int chunkX = 0; chunkX < 32; ++chunkX) {
|
||||
+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) {
|
||||
+ int location = chunkX | (chunkZ << 5);
|
||||
+
|
||||
+ int oldOffset = this.offsets.get(location);
|
||||
+ int newOffset = calculatedOffsets[location];
|
||||
+
|
||||
+ if (oldOffset == newOffset) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ this.offsets.put(location, newOffset); // overwrite incorrect offset
|
||||
+
|
||||
+ if (oldOffset == 0) {
|
||||
+ // found lost data
|
||||
+ LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.toAbsolutePath());
|
||||
+ } else if (newOffset == 0) {
|
||||
+ LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.regionFile.toAbsolutePath() + ", it will be regenerated");
|
||||
+ } else {
|
||||
+ LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.regionFile.toAbsolutePath());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ LOGGER.info("End of change summary for regionfile " + this.regionFile.toAbsolutePath());
|
||||
+
|
||||
+ // simply destroy the timestamp header, it's not used
|
||||
+
|
||||
+ for (int i = 0; i < 32 * 32; ++i) {
|
||||
+ this.timestamps.put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this
|
||||
+ }
|
||||
+
|
||||
+ // write new header
|
||||
+ try {
|
||||
+ this.flush();
|
||||
+ this.file.force(true); // try to ensure it goes through...
|
||||
+ LOGGER.info("Successfully wrote new header to disk for regionfile " + this.regionFile.toAbsolutePath());
|
||||
+ } catch (IOException ex) {
|
||||
+ LOGGER.error("Failed to write new header to disk for regionfile " + this.regionFile.toAbsolutePath(), ex);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ final boolean canRecalcHeader; // final forces compile fail on new constructor
|
||||
+ // Paper end
|
||||
+
|
||||
// Paper start - Cache chunk status
|
||||
private final net.minecraft.world.level.chunk.ChunkStatus[] statuses = new net.minecraft.world.level.chunk.ChunkStatus[32 * 32];
|
||||
|
||||
@@ -78,8 +427,19 @@ public class RegionFile implements AutoCloseable {
|
||||
public RegionFile(Path file, Path directory, boolean dsync) throws IOException {
|
||||
this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync);
|
||||
}
|
||||
+ // Paper start - add can recalc flag
|
||||
+ public RegionFile(Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException {
|
||||
+ this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader);
|
||||
+ }
|
||||
+ // Paper end - add can recalc flag
|
||||
|
||||
public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException {
|
||||
+ // Paper start - add can recalc flag
|
||||
+ this(file, directory, outputChunkStreamVersion, dsync, false);
|
||||
+ }
|
||||
+ public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException {
|
||||
+ this.canRecalcHeader = canRecalcHeader;
|
||||
+ // Paper end - add can recalc flag
|
||||
this.header = ByteBuffer.allocateDirect(8192);
|
||||
this.regionFile = file; // Paper
|
||||
initOversizedState(); // Paper
|
||||
@@ -108,14 +468,16 @@ public class RegionFile implements AutoCloseable {
|
||||
RegionFile.LOGGER.warn("Region file {} has truncated header: {}", file, i);
|
||||
}
|
||||
|
||||
- long j = Files.size(file);
|
||||
+ final long j = Files.size(file); final long regionFileSize = j; // Paper - recalculate header on header corruption
|
||||
|
||||
- for (int k = 0; k < 1024; ++k) {
|
||||
- int l = this.offsets.get(k);
|
||||
+ boolean needsHeaderRecalc = false; // Paper - recalculate header on header corruption
|
||||
+ boolean hasBackedUp = false; // Paper - recalculate header on header corruption
|
||||
+ for (int k = 0; k < 1024; ++k) { final int headerLocation = k; // Paper - we expect this to be the header location
|
||||
+ final int l = this.offsets.get(k);
|
||||
|
||||
if (l != 0) {
|
||||
- int i1 = RegionFile.getSectorNumber(l);
|
||||
- int j1 = RegionFile.getNumSectors(l);
|
||||
+ final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Paper - we expect this to be offset in file in sectors
|
||||
+ int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments
|
||||
// Spigot start
|
||||
if (j1 == 255) {
|
||||
// We're maxed out, so we need to read the proper length from the section
|
||||
@@ -124,32 +486,102 @@ public class RegionFile implements AutoCloseable {
|
||||
j1 = (realLen.getInt(0) + 4) / 4096 + 1;
|
||||
}
|
||||
// Spigot end
|
||||
+ sectorLength = j1; // Paper - diff on change, we expect this to be sector length of region
|
||||
|
||||
if (i1 < 2) {
|
||||
RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{file, k, i1});
|
||||
- this.offsets.put(k, 0);
|
||||
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
|
||||
} else if (j1 == 0) {
|
||||
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", file, k);
|
||||
- this.offsets.put(k, 0);
|
||||
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
|
||||
} else if ((long) i1 * 4096L > j) {
|
||||
RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", new Object[]{file, k, i1});
|
||||
- this.offsets.put(k, 0);
|
||||
+ //this.offsets.put(k, 0); // Paper - we catch this, but need it in the header for the summary change
|
||||
} else {
|
||||
- this.usedSectors.force(i1, j1);
|
||||
+ //this.usedSectors.force(i1, j1); // Paper - move this down so we can check if it fails to allocate
|
||||
+ }
|
||||
+ // Paper start - recalculate header on header corruption
|
||||
+ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) {
|
||||
+ if (canRecalcHeader) {
|
||||
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() + "! Recalculating header...");
|
||||
+ needsHeaderRecalc = true;
|
||||
+ break;
|
||||
+ } else {
|
||||
+ // location = chunkX | (chunkZ << 5);
|
||||
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() +
|
||||
+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
|
||||
+ if (!hasBackedUp) {
|
||||
+ hasBackedUp = true;
|
||||
+ this.backupRegionFile();
|
||||
+ }
|
||||
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
|
||||
+ this.offsets.put(headerLocation, 0); // delete the entry from header
|
||||
+ continue;
|
||||
+ }
|
||||
+ }
|
||||
+ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength);
|
||||
+ if (failedToAllocate) {
|
||||
+ LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.regionFile.toAbsolutePath());
|
||||
}
|
||||
+ if (failedToAllocate & !canRecalcHeader) {
|
||||
+ // location = chunkX | (chunkZ << 5);
|
||||
+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.toAbsolutePath() +
|
||||
+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header");
|
||||
+ if (!hasBackedUp) {
|
||||
+ hasBackedUp = true;
|
||||
+ this.backupRegionFile();
|
||||
+ }
|
||||
+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too
|
||||
+ this.offsets.put(headerLocation, 0); // delete the entry from header
|
||||
+ continue;
|
||||
+ }
|
||||
+ needsHeaderRecalc |= failedToAllocate;
|
||||
+ // Paper end - recalculate header on header corruption
|
||||
}
|
||||
}
|
||||
+ // Paper start - recalculate header on header corruption
|
||||
+ // we move the recalc here so comparison to old header is correct when logging to console
|
||||
+ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues
|
||||
+ LOGGER.error("Recalculating regionfile " + this.regionFile.toAbsolutePath() + ", header gave erroneous offsets & locations");
|
||||
+ this.recalculateHeader();
|
||||
+ }
|
||||
+ // Paper end
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Path getExternalChunkPath(ChunkPos chunkPos) {
|
||||
- String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc";
|
||||
+ String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change
|
||||
|
||||
return this.externalFileDir.resolve(s);
|
||||
}
|
||||
|
||||
+ // Paper start
|
||||
+ private static ChunkPos getOversizedChunkPair(Path file) {
|
||||
+ String fileName = file.getFileName().toString();
|
||||
+
|
||||
+ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ String[] split = fileName.split("\\.");
|
||||
+
|
||||
+ if (split.length != 4) {
|
||||
+ return null;
|
||||
+ }
|
||||
+
|
||||
+ try {
|
||||
+ int x = Integer.parseInt(split[1]);
|
||||
+ int z = Integer.parseInt(split[2]);
|
||||
+
|
||||
+ return new ChunkPos(x, z);
|
||||
+ } catch (NumberFormatException ex) {
|
||||
+ return null;
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
+
|
||||
@Nullable
|
||||
public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException {
|
||||
int i = this.getOffset(pos);
|
||||
@@ -173,6 +605,11 @@ public class RegionFile implements AutoCloseable {
|
||||
((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
|
||||
if (bytebuffer.remaining() < 5) {
|
||||
RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()});
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
return null;
|
||||
} else {
|
||||
int i1 = bytebuffer.getInt();
|
||||
@@ -180,6 +617,11 @@ public class RegionFile implements AutoCloseable {
|
||||
|
||||
if (i1 == 0) {
|
||||
RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos);
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
return null;
|
||||
} else {
|
||||
int j1 = i1 - 1;
|
||||
@@ -187,17 +629,44 @@ public class RegionFile implements AutoCloseable {
|
||||
if (RegionFile.isExternalStreamChunk(b0)) {
|
||||
if (j1 != 0) {
|
||||
RegionFile.LOGGER.warn("Chunk has both internal and external streams");
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
}
|
||||
|
||||
- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0));
|
||||
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ return ret;
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
} else if (j1 > bytebuffer.remaining()) {
|
||||
RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", new Object[]{pos, j1, bytebuffer.remaining()});
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
return null;
|
||||
} else if (j1 < 0) {
|
||||
RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos);
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ if (this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
return null;
|
||||
} else {
|
||||
- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
|
||||
+ // Paper start - recalculate header on regionfile corruption
|
||||
+ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1));
|
||||
+ if (ret == null && this.canRecalcHeader && this.recalculateHeader()) {
|
||||
+ return this.getChunkDataInputStream(pos);
|
||||
+ }
|
||||
+ return ret;
|
||||
+ // Paper end - recalculate header on regionfile corruption
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,10 +841,15 @@ public class RegionFile implements AutoCloseable {
|
||||
}
|
||||
|
||||
private ByteBuffer createExternalStub() {
|
||||
+ // Paper start - add version param
|
||||
+ return this.createExternalStub(this.version);
|
||||
+ }
|
||||
+ private ByteBuffer createExternalStub(RegionFileVersion version) {
|
||||
+ // Paper end - add version param
|
||||
ByteBuffer bytebuffer = ByteBuffer.allocate(5);
|
||||
|
||||
bytebuffer.putInt(1);
|
||||
- bytebuffer.put((byte) (this.version.getId() | 128));
|
||||
+ bytebuffer.put((byte) (version.getId() | 128)); // Paper - replace with version param
|
||||
((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
|
||||
return bytebuffer;
|
||||
}
|
||||
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 d3d4d10a77af51cff4da201201bac325427fc20c..c2643f61fe5efb62d15f0798db691c76c7674a6e 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
|
||||
@@ -25,6 +25,7 @@ public class RegionFileStorage implements AutoCloseable {
|
||||
public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap();
|
||||
private final Path folder;
|
||||
private final boolean sync;
|
||||
+ private final boolean isChunkData; // Paper
|
||||
|
||||
// Paper start - cache regionfile does not exist state
|
||||
static final int MAX_NON_EXISTING_CACHE = 1024 * 64;
|
||||
@@ -56,6 +57,12 @@ public class RegionFileStorage implements AutoCloseable {
|
||||
// Paper end - cache regionfile does not exist state
|
||||
|
||||
protected RegionFileStorage(Path directory, boolean dsync) { // Paper - protected constructor
|
||||
+ // Paper start - add isChunkData param
|
||||
+ this(directory, dsync, false);
|
||||
+ }
|
||||
+ RegionFileStorage(Path directory, boolean dsync, boolean isChunkData) {
|
||||
+ this.isChunkData = isChunkData;
|
||||
+ // Paper end - add isChunkData param
|
||||
this.folder = directory;
|
||||
this.sync = dsync;
|
||||
}
|
||||
@@ -125,7 +132,7 @@ public class RegionFileStorage implements AutoCloseable {
|
||||
// Paper - only create directory if not existing only - moved down
|
||||
Path path = this.folder;
|
||||
int j = chunkcoordintpair.getRegionX();
|
||||
- Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
|
||||
+ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Paper - diff on change
|
||||
if (existingOnly && !java.nio.file.Files.exists(path1)) { // Paper start - cache regionfile does not exist state
|
||||
this.markNonExisting(regionPos);
|
||||
return null; // CraftBukkit
|
||||
@@ -134,7 +141,7 @@ public class RegionFileStorage implements AutoCloseable {
|
||||
}
|
||||
// Paper end - cache regionfile does not exist state
|
||||
FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above
|
||||
- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync);
|
||||
+ RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync, this.isChunkData); // Paper - allow for chunk regionfiles to regen header
|
||||
|
||||
this.regionCache.putAndMoveToFirst(i, regionfile1);
|
||||
// Paper start
|
||||
@@ -219,6 +226,13 @@ public class RegionFileStorage implements AutoCloseable {
|
||||
if (regionfile == null) {
|
||||
return null;
|
||||
}
|
||||
+ // Paper start - Add regionfile parameter
|
||||
+ return this.read(pos, regionfile);
|
||||
+ }
|
||||
+ public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException {
|
||||
+ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile
|
||||
+ // if we decide to re-read
|
||||
+ // Paper end
|
||||
// CraftBukkit end
|
||||
try { // Paper
|
||||
DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
|
||||
@@ -235,6 +249,20 @@ public class RegionFileStorage implements AutoCloseable {
|
||||
try {
|
||||
if (datainputstream != null) {
|
||||
nbttagcompound = NbtIo.read((DataInput) datainputstream);
|
||||
+ // Paper start - recover from corrupt regionfile header
|
||||
+ if (this.isChunkData) {
|
||||
+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound);
|
||||
+ if (!chunkPos.equals(pos)) {
|
||||
+ net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos + " but got chunk data for " + chunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.toAbsolutePath());
|
||||
+ if (regionfile.recalculateHeader()) {
|
||||
+ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once.
|
||||
+ return this.read(pos, regionfile);
|
||||
+ }
|
||||
+ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.regionFile.toAbsolutePath());
|
||||
+ return null;
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - recover from corrupt regionfile header
|
||||
break label43;
|
||||
}
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
||||
index 5fa7a842431dd64c7a0dc5d8e940563a2aeef463..4411e427d3b6b592f8a18e61b6c59309cf699d3f 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
||||
@@ -14,7 +14,7 @@ import javax.annotation.Nullable;
|
||||
import net.minecraft.util.FastBufferedInputStream;
|
||||
|
||||
public class RegionFileVersion {
|
||||
- private static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>();
|
||||
+ public static final Int2ObjectMap<RegionFileVersion> VERSIONS = new Int2ObjectOpenHashMap<>(); // Paper - private -> public
|
||||
public static final RegionFileVersion VERSION_GZIP = register(new RegionFileVersion(1, (stream) -> {
|
||||
return new FastBufferedInputStream(new GZIPInputStream(stream));
|
||||
}, (stream) -> {
|
|
@ -1,295 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Thu, 26 Mar 2020 21:59:32 -0700
|
||||
Subject: [PATCH] Detail more information in watchdog dumps
|
||||
|
||||
- Dump position, world, velocity, and uuid for currently ticking entities
|
||||
- Dump player name, player uuid, position, and world for packet handling
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
|
||||
index 95f5ec348ab24b28c19b46cea7b023a1d49998b5..890d14503e157d6c07a6db1f180f49f81118e301 100644
|
||||
--- a/src/main/java/net/minecraft/network/Connection.java
|
||||
+++ b/src/main/java/net/minecraft/network/Connection.java
|
||||
@@ -528,7 +528,13 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
if (!(this.packetListener instanceof net.minecraft.server.network.ServerLoginPacketListenerImpl loginPacketListener)
|
||||
|| loginPacketListener.state != net.minecraft.server.network.ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT
|
||||
|| Connection.joinAttemptsThisTick++ < MAX_PER_TICK) {
|
||||
+ // Paper start - detailed watchdog information
|
||||
+ net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener);
|
||||
+ try { // Paper end - detailed watchdog information
|
||||
tickablepacketlistener.tick();
|
||||
+ } finally { // Paper start - detailed watchdog information
|
||||
+ net.minecraft.network.protocol.PacketUtils.packetProcessing.pop();
|
||||
+ } // Paper end - detailed watchdog information
|
||||
}
|
||||
// Paper end
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
|
||||
index 4a1148a76020089caf01f888f87afdbb35788dc0..52a84eeb3b7df782cbf91aac6df42fb8f99077f6 100644
|
||||
--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
|
||||
+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
|
||||
@@ -15,6 +15,24 @@ public class PacketUtils {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
+ // Paper start - detailed watchdog information
|
||||
+ public static final java.util.concurrent.ConcurrentLinkedDeque<PacketListener> packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>();
|
||||
+ static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong();
|
||||
+
|
||||
+ public static long getTotalProcessedPackets() {
|
||||
+ return totalMainThreadPacketsProcessed.get();
|
||||
+ }
|
||||
+
|
||||
+ public static java.util.List<PacketListener> getCurrentPacketProcessors() {
|
||||
+ java.util.List<PacketListener> ret = new java.util.ArrayList<>(4);
|
||||
+ for (PacketListener listener : packetProcessing) {
|
||||
+ ret.add(listener);
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+ }
|
||||
+ // Paper end - detailed watchdog information
|
||||
+
|
||||
public PacketUtils() {}
|
||||
|
||||
public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException {
|
||||
@@ -24,6 +42,8 @@ public class PacketUtils {
|
||||
public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T listener, BlockableEventLoop<?> engine) throws RunningOnDifferentThreadException {
|
||||
if (!engine.isSameThread()) {
|
||||
engine.executeIfPossible(() -> {
|
||||
+ packetProcessing.push(listener); // Paper - detailed watchdog information
|
||||
+ try { // Paper - detailed watchdog information
|
||||
if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerGamePacketListenerImpl && ((ServerGamePacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590
|
||||
if (listener.isAcceptingMessages()) {
|
||||
co.aikar.timings.Timing timing = co.aikar.timings.MinecraftTimings.getPacketTiming(packet); // Paper - timings
|
||||
@@ -39,6 +59,12 @@ public class PacketUtils {
|
||||
} else {
|
||||
PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
|
||||
}
|
||||
+ // Paper start - detailed watchdog information
|
||||
+ } finally {
|
||||
+ totalMainThreadPacketsProcessed.getAndIncrement();
|
||||
+ packetProcessing.pop();
|
||||
+ }
|
||||
+ // Paper end - detailed watchdog information
|
||||
|
||||
});
|
||||
throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD;
|
||||
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
index 1d0f87185a8a74394bc2da29828407fd4210754c..d6429d721116aac2a4df8d0b217e9efcb698094e 100644
|
||||
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
|
||||
@@ -1200,7 +1200,26 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
||||
|
||||
}
|
||||
|
||||
+ // Paper start - log detailed entity tick information
|
||||
+ // TODO replace with varhandle
|
||||
+ static final java.util.concurrent.atomic.AtomicReference<Entity> currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>();
|
||||
+
|
||||
+ public static List<Entity> getCurrentlyTickingEntities() {
|
||||
+ Entity ticking = currentlyTickingEntity.get();
|
||||
+ List<Entity> ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking });
|
||||
+
|
||||
+ return ret;
|
||||
+ }
|
||||
+ // Paper end - log detailed entity tick information
|
||||
+
|
||||
public void tickNonPassenger(Entity entity) {
|
||||
+ // Paper start - log detailed entity tick information
|
||||
+ io.papermc.paper.util.TickThread.ensureTickThread("Cannot tick an entity off-main");
|
||||
+ try {
|
||||
+ if (currentlyTickingEntity.get() == null) {
|
||||
+ currentlyTickingEntity.lazySet(entity);
|
||||
+ }
|
||||
+ // Paper end - log detailed entity tick information
|
||||
++TimingHistory.entityTicks; // Paper - timings
|
||||
// Spigot start
|
||||
co.aikar.timings.Timing timer; // Paper
|
||||
@@ -1240,7 +1259,13 @@ public class ServerLevel extends Level implements WorldGenLevel {
|
||||
this.tickPassenger(entity, entity1);
|
||||
}
|
||||
// } finally { timer.stopTiming(); } // Paper - timings - move up
|
||||
-
|
||||
+ // Paper start - log detailed entity tick information
|
||||
+ } finally {
|
||||
+ if (currentlyTickingEntity.get() == entity) {
|
||||
+ currentlyTickingEntity.lazySet(null);
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - log detailed entity tick information
|
||||
}
|
||||
|
||||
private void tickPassenger(Entity vehicle, Entity passenger) {
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
|
||||
index 0d2506350e2c57044bf7c751627b6e0d76077e82..0e494502bc6d52f277d240d839e02b51fccf05fc 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
||||
@@ -1024,7 +1024,42 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
|
||||
return this.onGround;
|
||||
}
|
||||
|
||||
+ // Paper start - detailed watchdog information
|
||||
+ public final Object posLock = new Object(); // Paper - log detailed entity tick information
|
||||
+
|
||||
+ private Vec3 moveVector;
|
||||
+ private double moveStartX;
|
||||
+ private double moveStartY;
|
||||
+ private double moveStartZ;
|
||||
+
|
||||
+ public final Vec3 getMoveVector() {
|
||||
+ return this.moveVector;
|
||||
+ }
|
||||
+
|
||||
+ public final double getMoveStartX() {
|
||||
+ return this.moveStartX;
|
||||
+ }
|
||||
+
|
||||
+ public final double getMoveStartY() {
|
||||
+ return this.moveStartY;
|
||||
+ }
|
||||
+
|
||||
+ public final double getMoveStartZ() {
|
||||
+ return this.moveStartZ;
|
||||
+ }
|
||||
+ // Paper end - detailed watchdog information
|
||||
+
|
||||
public void move(MoverType movementType, Vec3 movement) {
|
||||
+ // Paper start - detailed watchdog information
|
||||
+ io.papermc.paper.util.TickThread.ensureTickThread("Cannot move an entity off-main");
|
||||
+ synchronized (this.posLock) {
|
||||
+ this.moveStartX = this.getX();
|
||||
+ this.moveStartY = this.getY();
|
||||
+ this.moveStartZ = this.getZ();
|
||||
+ this.moveVector = movement;
|
||||
+ }
|
||||
+ try {
|
||||
+ // Paper end - detailed watchdog information
|
||||
if (this.noPhysics) {
|
||||
this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z);
|
||||
} else {
|
||||
@@ -1194,6 +1229,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
|
||||
this.level().getProfiler().pop();
|
||||
}
|
||||
}
|
||||
+ // Paper start - detailed watchdog information
|
||||
+ } finally {
|
||||
+ synchronized (this.posLock) { // Paper
|
||||
+ this.moveVector = null;
|
||||
+ } // Paper
|
||||
+ }
|
||||
+ // Paper end - detailed watchdog information
|
||||
}
|
||||
|
||||
private boolean isStateClimbable(BlockState state) {
|
||||
@@ -4224,7 +4266,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
|
||||
}
|
||||
|
||||
public void setDeltaMovement(Vec3 velocity) {
|
||||
+ synchronized (this.posLock) { // Paper
|
||||
this.deltaMovement = velocity;
|
||||
+ } // Paper
|
||||
}
|
||||
|
||||
public void addDeltaMovement(Vec3 velocity) {
|
||||
@@ -4310,7 +4354,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
|
||||
}
|
||||
// Paper end - fix MC-4
|
||||
if (this.position.x != x || this.position.y != y || this.position.z != z) {
|
||||
+ synchronized (this.posLock) { // Paper
|
||||
this.position = new Vec3(x, y, z);
|
||||
+ } // Paper
|
||||
int i = Mth.floor(x);
|
||||
int j = Mth.floor(y);
|
||||
int k = Mth.floor(z);
|
||||
diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
|
||||
index 7071327fab87d53e794374c701d7c2748c439aaa..50c72e5db369a180f425eaaa0411cb8871bc3463 100644
|
||||
--- a/src/main/java/org/spigotmc/WatchdogThread.java
|
||||
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
|
||||
@@ -22,6 +22,78 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
|
||||
private volatile long lastTick;
|
||||
private volatile boolean stopping;
|
||||
|
||||
+ // Paper start - log detailed tick information
|
||||
+ private void dumpEntity(net.minecraft.world.entity.Entity entity) {
|
||||
+ Logger log = Bukkit.getServer().getLogger();
|
||||
+ double posX, posY, posZ;
|
||||
+ net.minecraft.world.phys.Vec3 mot;
|
||||
+ double moveStartX, moveStartY, moveStartZ;
|
||||
+ net.minecraft.world.phys.Vec3 moveVec;
|
||||
+ synchronized (entity.posLock) {
|
||||
+ posX = entity.getX();
|
||||
+ posY = entity.getY();
|
||||
+ posZ = entity.getZ();
|
||||
+ mot = entity.getDeltaMovement();
|
||||
+ moveStartX = entity.getMoveStartX();
|
||||
+ moveStartY = entity.getMoveStartY();
|
||||
+ moveStartZ = entity.getMoveStartZ();
|
||||
+ moveVec = entity.getMoveVector();
|
||||
+ }
|
||||
+
|
||||
+ String entityType = net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString();
|
||||
+ java.util.UUID entityUUID = entity.getUUID();
|
||||
+ net.minecraft.world.level.Level world = entity.level();
|
||||
+
|
||||
+ log.log(Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName());
|
||||
+ log.log(Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger());
|
||||
+ log.log(Level.SEVERE, "Entity UUID: " + entityUUID);
|
||||
+ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")");
|
||||
+ log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)");
|
||||
+ log.log(Level.SEVERE, "Entity AABB: " + entity.getBoundingBox());
|
||||
+ if (moveVec != null) {
|
||||
+ log.log(Level.SEVERE, "Move call information: ");
|
||||
+ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")");
|
||||
+ log.log(Level.SEVERE, "Move vector: " + moveVec.toString());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ private void dumpTickingInfo() {
|
||||
+ Logger log = Bukkit.getServer().getLogger();
|
||||
+
|
||||
+ // ticking entities
|
||||
+ for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) {
|
||||
+ this.dumpEntity(entity);
|
||||
+ net.minecraft.world.entity.Entity vehicle = entity.getVehicle();
|
||||
+ if (vehicle != null) {
|
||||
+ log.log(Level.SEVERE, "Detailing vehicle for above entity:");
|
||||
+ this.dumpEntity(vehicle);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // packet processors
|
||||
+ for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) {
|
||||
+ if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) {
|
||||
+ net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player;
|
||||
+ long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets();
|
||||
+ if (player == null) {
|
||||
+ log.log(Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener);
|
||||
+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
|
||||
+ } else {
|
||||
+ this.dumpEntity(player);
|
||||
+ net.minecraft.world.entity.Entity vehicle = player.getVehicle();
|
||||
+ if (vehicle != null) {
|
||||
+ log.log(Level.SEVERE, "Detailing vehicle for above entity:");
|
||||
+ this.dumpEntity(vehicle);
|
||||
+ }
|
||||
+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets);
|
||||
+ }
|
||||
+ } else {
|
||||
+ log.log(Level.SEVERE, "Handling packet for connection: " + packetListener);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - log detailed tick information
|
||||
+
|
||||
private WatchdogThread(long timeoutTime, boolean restart)
|
||||
{
|
||||
super( "Paper Watchdog Thread" );
|
||||
@@ -120,6 +192,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
|
||||
log.log( Level.SEVERE, "------------------------------" );
|
||||
log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
|
||||
io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(isLongTimeout); // Paper // Paper - rewrite chunk system
|
||||
+ this.dumpTickingInfo(); // Paper - log detailed tick information
|
||||
WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
|
||||
log.log( Level.SEVERE, "------------------------------" );
|
||||
//
|
|
@ -1,34 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Sat, 18 Jul 2020 16:03:57 -0700
|
||||
Subject: [PATCH] Distance manager tick timings
|
||||
|
||||
Recently this has been taking up more time, so add a timings to
|
||||
really figure out how much.
|
||||
|
||||
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
||||
index efbf77024d235d8af9f7efc938c17afd76a51b0c..670dcfa32d003870091b75937f1603a5ac9fa7d1 100644
|
||||
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
|
||||
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
||||
@@ -46,6 +46,7 @@ public final class MinecraftTimings {
|
||||
|
||||
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
|
||||
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
|
||||
+ public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager
|
||||
|
||||
public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
|
||||
|
||||
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
||||
index 8e52ebe8d12f5da3d877b0e4ff3723229fb47db1..abd0217cf0bff183c8e262edc173a53403797c1a 100644
|
||||
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
||||
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
||||
@@ -1315,7 +1315,9 @@ public final class ChunkHolderManager {
|
||||
}
|
||||
|
||||
public boolean processTicketUpdates() {
|
||||
+ co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager
|
||||
return this.processTicketUpdates(true, true, null);
|
||||
+ } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager
|
||||
}
|
||||
|
||||
private static final ThreadLocal<List<ChunkProgressionTask>> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>();
|
|
@ -0,0 +1,187 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Thu, 27 Aug 2020 20:51:40 -0700
|
||||
Subject: [PATCH] Remove streams for villager AI
|
||||
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
|
||||
index b45c4f50705f80163d44d9e588f86a5770f5be38..10cbb80c7cd9ba30150d8d935c0d115719c35509 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java
|
||||
@@ -52,7 +52,7 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
|
||||
if (this.hasRequiredMemories(entity)) {
|
||||
this.status = Behavior.Status.RUNNING;
|
||||
this.orderPolicy.apply(this.behaviors);
|
||||
- this.runningPolicy.apply(this.behaviors.stream(), world, entity, time);
|
||||
+ this.runningPolicy.apply(this.behaviors.entries, world, entity, time);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -61,11 +61,13 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
|
||||
|
||||
@Override
|
||||
public final void tickOrStop(ServerLevel world, E entity, long time) {
|
||||
- this.behaviors.stream().filter((task) -> {
|
||||
- return task.getStatus() == Behavior.Status.RUNNING;
|
||||
- }).forEach((task) -> {
|
||||
- task.tickOrStop(world, entity, time);
|
||||
- });
|
||||
+ // Paper start
|
||||
+ for (BehaviorControl<? super E> task : this.behaviors) {
|
||||
+ if (task.getStatus() == Behavior.Status.RUNNING) {
|
||||
+ task.tickOrStop(world, entity, time);
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end
|
||||
if (this.behaviors.stream().noneMatch((task) -> {
|
||||
return task.getStatus() == Behavior.Status.RUNNING;
|
||||
})) {
|
||||
@@ -77,11 +79,11 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
|
||||
@Override
|
||||
public final void doStop(ServerLevel world, E entity, long time) {
|
||||
this.status = Behavior.Status.STOPPED;
|
||||
- this.behaviors.stream().filter((task) -> {
|
||||
- return task.getStatus() == Behavior.Status.RUNNING;
|
||||
- }).forEach((task) -> {
|
||||
- task.doStop(world, entity, time);
|
||||
- });
|
||||
+ for (BehaviorControl<? super E> behavior : this.behaviors) {
|
||||
+ if (behavior.getStatus() == Behavior.Status.RUNNING) {
|
||||
+ behavior.doStop(world, entity, time);
|
||||
+ }
|
||||
+ }
|
||||
this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory);
|
||||
}
|
||||
|
||||
@@ -117,25 +119,31 @@ public class GateBehavior<E extends LivingEntity> implements BehaviorControl<E>
|
||||
public static enum RunningPolicy {
|
||||
RUN_ONE {
|
||||
@Override
|
||||
- public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
|
||||
- tasks.filter((task) -> {
|
||||
- return task.getStatus() == Behavior.Status.STOPPED;
|
||||
- }).filter((task) -> {
|
||||
- return task.tryStart(world, entity, time);
|
||||
- }).findFirst();
|
||||
+ // Paper start - remove streams
|
||||
+ public <E extends LivingEntity> void apply(List<ShufflingList.WeightedEntry<BehaviorControl<? super E>>> tasks, ServerLevel world, E entity, long time) {
|
||||
+ for (ShufflingList.WeightedEntry<BehaviorControl<? super E>> task : tasks) {
|
||||
+ final BehaviorControl<? super E> behavior = task.getData();
|
||||
+ if (behavior.getStatus() == Behavior.Status.STOPPED && behavior.tryStart(world, entity, time)) {
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - remove streams
|
||||
}
|
||||
},
|
||||
TRY_ALL {
|
||||
@Override
|
||||
- public <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time) {
|
||||
- tasks.filter((task) -> {
|
||||
- return task.getStatus() == Behavior.Status.STOPPED;
|
||||
- }).forEach((task) -> {
|
||||
- task.tryStart(world, entity, time);
|
||||
- });
|
||||
+ // Paper start - remove streams
|
||||
+ public <E extends LivingEntity> void apply(List<ShufflingList.WeightedEntry<BehaviorControl<? super E>>> tasks, ServerLevel world, E entity, long time) {
|
||||
+ for (ShufflingList.WeightedEntry<BehaviorControl<? super E>> task : tasks) {
|
||||
+ final BehaviorControl<? super E> behavior = task.getData();
|
||||
+ if (behavior.getStatus() == Behavior.Status.STOPPED) {
|
||||
+ behavior.tryStart(world, entity, time);
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - remove streams
|
||||
}
|
||||
};
|
||||
|
||||
- public abstract <E extends LivingEntity> void apply(Stream<BehaviorControl<? super E>> tasks, ServerLevel world, E entity, long time);
|
||||
+ public abstract <E extends LivingEntity> void apply(List<ShufflingList.WeightedEntry<BehaviorControl<? super E>>> tasks, ServerLevel world, E entity, long time); // Paper - remove streams
|
||||
}
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java
|
||||
index 731ef21dbbd25d6924717de42f4569a9b5935643..fe3ab3d388f0481fb0db06b7f730f868dbf8e8a5 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java
|
||||
@@ -14,7 +14,7 @@ import java.util.stream.Stream;
|
||||
import net.minecraft.util.RandomSource;
|
||||
|
||||
public class ShufflingList<U> implements Iterable<U> {
|
||||
- protected final List<ShufflingList.WeightedEntry<U>> entries;
|
||||
+ public final List<ShufflingList.WeightedEntry<U>> entries; // Paper - public
|
||||
private final RandomSource random = RandomSource.create();
|
||||
private final boolean isUnsafe; // Paper
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
|
||||
index 1dfcc5cba6ffb463acf161a23fff1ca452184290..61a164c5bfc86faa3f4d04a66e0257016cfd937d 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java
|
||||
@@ -25,13 +25,16 @@ public class NearestItemSensor extends Sensor<Mob> {
|
||||
protected void doTick(ServerLevel world, Mob entity) {
|
||||
Brain<?> brain = entity.getBrain();
|
||||
List<ItemEntity> list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(32.0D, 16.0D, 32.0D), (itemEntity) -> {
|
||||
- return true;
|
||||
+ return itemEntity.closerThan(entity, MAX_DISTANCE_TO_WANTED_ITEM) && entity.wantsToPickUp(itemEntity.getItem()); // Paper - move predicate into getEntities
|
||||
});
|
||||
- list.sort(Comparator.comparingDouble(entity::distanceToSqr));
|
||||
+ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); // better to take the sort perf hit than using line of sight more than we need to.
|
||||
+ // Paper start - remove streams
|
||||
// Paper start - remove streams in favour of lists
|
||||
ItemEntity nearest = null;
|
||||
- for (ItemEntity entityItem : list) {
|
||||
- if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 32.0D) && entity.hasLineOfSight(entityItem)) {
|
||||
+ for (int i = 0; i < list.size(); i++) {
|
||||
+ ItemEntity entityItem = list.get(i);
|
||||
+ if (entity.hasLineOfSight(entityItem)) {
|
||||
+ // Paper end - remove streams
|
||||
nearest = entityItem;
|
||||
break;
|
||||
}
|
||||
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java
|
||||
index 312775d0430f793720211dc29bb293503e799d11..75d9c4f011b5a97def215784c92bb57bbb35d06b 100644
|
||||
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java
|
||||
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java
|
||||
@@ -21,25 +21,30 @@ public class PlayerSensor extends Sensor<LivingEntity> {
|
||||
|
||||
@Override
|
||||
protected void doTick(ServerLevel world, LivingEntity entity) {
|
||||
- List<Player> players = new java.util.ArrayList<>(world.players());
|
||||
- players.removeIf(player -> !EntitySelector.NO_SPECTATORS.test(player) || !entity.closerThan(player, 16.0D));
|
||||
- players.sort(Comparator.comparingDouble(entity::distanceTo));
|
||||
+ // Paper start - remove streams
|
||||
+ List<Player> players = (List)world.getNearbyPlayers(entity, entity.getX(), entity.getY(), entity.getZ(), 16.0D, EntitySelector.NO_SPECTATORS);
|
||||
+ players.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2)));
|
||||
Brain<?> brain = entity.getBrain();
|
||||
|
||||
brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players);
|
||||
|
||||
- Player nearest = null, nearestTargetable = null;
|
||||
- for (Player player : players) {
|
||||
- if (Sensor.isEntityTargetable(entity, player)) {
|
||||
- if (nearest == null) nearest = player;
|
||||
- if (Sensor.isEntityAttackable(entity, player)) {
|
||||
- nearestTargetable = player;
|
||||
- break; // Both variables are assigned, no reason to loop further
|
||||
- }
|
||||
+ Player firstTargetable = null;
|
||||
+ Player firstAttackable = null;
|
||||
+ for (int index = 0, len = players.size(); index < len; ++index) {
|
||||
+ Player player = players.get(index);
|
||||
+ if (firstTargetable == null && isEntityTargetable(entity, player)) {
|
||||
+ firstTargetable = player;
|
||||
+ }
|
||||
+ if (firstAttackable == null && isEntityAttackable(entity, player)) {
|
||||
+ firstAttackable = player;
|
||||
+ }
|
||||
+
|
||||
+ if (firstAttackable != null && firstTargetable != null) {
|
||||
+ break;
|
||||
}
|
||||
}
|
||||
- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest);
|
||||
- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, nearestTargetable);
|
||||
- // Paper end
|
||||
+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable);
|
||||
+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable));
|
||||
+ // Paper end - remove streams
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
||||
Date: Fri, 30 Oct 2020 22:37:16 -0700
|
||||
Subject: [PATCH] Add packet limiter config
|
||||
|
||||
Example config:
|
||||
packet-limiter:
|
||||
kick-message: '&cSent too many packets'
|
||||
limits:
|
||||
all:
|
||||
interval: 7.0
|
||||
max-packet-rate: 500.0
|
||||
ServerboundPlaceRecipePacket:
|
||||
interval: 4.0
|
||||
max-packet-rate: 5.0
|
||||
action: DROP
|
||||
|
||||
all section refers to all incoming packets, the action for all is
|
||||
hard coded to KICK.
|
||||
|
||||
For specific limits, the section name is the class's name,
|
||||
and an action can be defined: DROP or KICK
|
||||
|
||||
If interval or rate are less-than 0, the limit is ignored
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
|
||||
index 890d14503e157d6c07a6db1f180f49f81118e301..9a9924a645c71e0cec30e29a9defcd1e22e2e8ef 100644
|
||||
--- a/src/main/java/net/minecraft/network/Connection.java
|
||||
+++ b/src/main/java/net/minecraft/network/Connection.java
|
||||
@@ -158,6 +158,22 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
}
|
||||
}
|
||||
// Paper end - allow controlled flushing
|
||||
+ // Paper start - packet limiter
|
||||
+ protected final Object PACKET_LIMIT_LOCK = new Object();
|
||||
+ protected final @Nullable io.papermc.paper.util.IntervalledCounter allPacketCounts = io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.isEnabled() ? new io.papermc.paper.util.IntervalledCounter(
|
||||
+ (long)(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.interval() * 1.0e9)
|
||||
+ ) : null;
|
||||
+ protected final java.util.Map<Class<? extends net.minecraft.network.protocol.Packet<?>>, io.papermc.paper.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>();
|
||||
+
|
||||
+ private boolean stopReadingPackets;
|
||||
+ private void killForPacketSpam() {
|
||||
+ this.sendPacket(new ClientboundDisconnectPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)), PacketSendListener.thenRun(() -> {
|
||||
+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage));
|
||||
+ }));
|
||||
+ this.setReadOnly();
|
||||
+ this.stopReadingPackets = true;
|
||||
+ }
|
||||
+ // Paper end - packet limiter
|
||||
|
||||
public Connection(PacketFlow side) {
|
||||
this.receiving = side;
|
||||
@@ -246,6 +262,48 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
|
||||
|
||||
protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet<?> packet) {
|
||||
if (this.channel.isOpen()) {
|
||||
+ // Paper start - packet limiter
|
||||
+ if (this.stopReadingPackets) {
|
||||
+ return;
|
||||
+ }
|
||||
+ if (this.allPacketCounts != null ||
|
||||
+ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.containsKey(packet.getClass())) {
|
||||
+ long time = System.nanoTime();
|
||||
+ synchronized (PACKET_LIMIT_LOCK) {
|
||||
+ if (this.allPacketCounts != null) {
|
||||
+ this.allPacketCounts.updateAndAdd(1, time);
|
||||
+ if (this.allPacketCounts.getRate() >= io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.maxPacketRate()) {
|
||||
+ this.killForPacketSpam();
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (Class<?> check = packet.getClass(); check != Object.class; check = check.getSuperclass()) {
|
||||
+ io.papermc.paper.configuration.GlobalConfiguration.PacketLimiter.PacketLimit packetSpecificLimit =
|
||||
+ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.get(check);
|
||||
+ if (packetSpecificLimit == null || !packetSpecificLimit.isEnabled()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ io.papermc.paper.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> {
|
||||
+ return new io.papermc.paper.util.IntervalledCounter((long)(packetSpecificLimit.interval() * 1.0e9));
|
||||
+ });
|
||||
+ counter.updateAndAdd(1, time);
|
||||
+ if (counter.getRate() >= packetSpecificLimit.maxPacketRate()) {
|
||||
+ switch (packetSpecificLimit.action()) {
|
||||
+ case DROP:
|
||||
+ return;
|
||||
+ case KICK:
|
||||
+ String deobfedPacketName = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(check.getName());
|
||||
+ String playerName = this.getPlayer() == null ? "Player (null)" : this.getPlayer().getName().getString();
|
||||
+ Connection.LOGGER.warn("{} kicked for packet spamming: {}", playerName, deobfedPacketName.substring(deobfedPacketName.lastIndexOf(".") + 1));
|
||||
+ this.killForPacketSpam();
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ // Paper end - packet limiter
|
||||
try {
|
||||
Connection.genericsFtw(packet, this.packetListener);
|
||||
} catch (RunningOnDifferentThreadException cancelledpackethandleexception) {
|
File diff suppressed because it is too large
Load diff
|
@ -1,46 +0,0 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Astralchroma <astralchroma@proton.me>
|
||||
Date: Thu, 27 Oct 2022 22:19:31 +0100
|
||||
Subject: [PATCH] Configurable Region Compression Format
|
||||
|
||||
|
||||
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 dcfe090c269d4cbcc2eb1b6f85392848bb34656c..0c5ac12b1f395bba8b7fc50baf8e825ba6488f6c 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
|
||||
@@ -425,11 +425,11 @@ public class RegionFile implements AutoCloseable {
|
||||
// Paper end
|
||||
|
||||
public RegionFile(Path file, Path directory, boolean dsync) throws IOException {
|
||||
- this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync);
|
||||
+ this(file, directory, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format
|
||||
}
|
||||
// Paper start - add can recalc flag
|
||||
public RegionFile(Path file, Path directory, boolean dsync, boolean canRecalcHeader) throws IOException {
|
||||
- this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader);
|
||||
+ this(file, directory, RegionFileVersion.getCompressionFormat(), dsync, canRecalcHeader); // Paper - Configurable region compression format
|
||||
}
|
||||
// Paper end - add can recalc flag
|
||||
|
||||
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
||||
index 4411e427d3b6b592f8a18e61b6c59309cf699d3f..ee27a553b426d3e1e1317bbeb39a3c2d46520e59 100644
|
||||
--- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
||||
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
|
||||
@@ -30,6 +30,17 @@ public class RegionFileVersion {
|
||||
}, (stream) -> {
|
||||
return stream;
|
||||
}));
|
||||
+
|
||||
+ // Paper Start - Configurable region compression format
|
||||
+ public static RegionFileVersion getCompressionFormat() {
|
||||
+ return switch (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.compressionFormat) {
|
||||
+ case GZIP -> VERSION_GZIP;
|
||||
+ case ZLIB -> VERSION_DEFLATE;
|
||||
+ case NONE -> VERSION_NONE;
|
||||
+ };
|
||||
+ }
|
||||
+ // Paper End
|
||||
+
|
||||
private final int id;
|
||||
private final RegionFileVersion.StreamWrapper<InputStream> inputWrapper;
|
||||
private final RegionFileVersion.StreamWrapper<OutputStream> outputWrapper;
|
Loading…
Add table
Add a link
Reference in a new issue