diff --git a/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch b/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch index d336b76564..0561b403fb 100644 --- a/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch +++ b/feature-patches/1060-Only-write-chunk-data-to-disk-if-it-serializes-witho.patch @@ -7,22 +7,23 @@ Subject: [PATCH] Only write chunk data to disk if it serializes without This ensures at least a valid version of the chunk exists on disk, even if outdated -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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + +diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java +index 7da388ffab162c282cad0f297bb7304f3c2abbaf..ff4fc280409f680f3879a495e37cf1925b1a38f1 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -24,6 +24,7 @@ import org.slf4j.Logger; - } - // Paper end + public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile { // Paper - rewrite chunk system + private static final Logger LOGGER = LogUtils.getLogger(); + public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Paper - don't write garbage data to disk if writing serialization fails - private class ChunkBuffer extends ByteArrayOutputStream implements ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemChunkBuffer { // Paper - rewrite chunk system - - private final ChunkPos pos; -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - super.write(RegionFile.this.version.getId()); - this.pos = chunkcoordintpair; + private static final int SECTOR_BYTES = 4096; + @VisibleForTesting + protected static final int SECTOR_INTS = 1024; +@@ -455,6 +456,24 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + this.pos = pos; } + + // Paper start - don't write garbage data to disk if writing serialization fails + @Override + public void write(final int b) { @@ -40,23 +41,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + super.write(b, off, len); + } + // Paper end - don't write garbage data to disk if writing serialization fails - ++ + @Override public void close() throws IOException { - ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); -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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 -@@ -0,0 +0,0 @@ import net.minecraft.world.level.ChunkPos; + ByteBuffer byteBuffer = ByteBuffer.wrap(this.buf, 0, this.count); +diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index e35bb5534e2fbd2e30154a15ff6d39baa121608f..d263f78fa610ce6f6fb5a0f5e064e3d8335c2199 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -15,6 +15,7 @@ import net.minecraft.util.ExceptionCollector; + import net.minecraft.world.level.ChunkPos; public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage { // Paper - rewrite chunk system - + private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper -+ public static final String ANVIL_EXTENSION = ".mca"; private static final int MAX_CACHE_SIZE = 256; - public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); -@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap<>(); +@@ -119,11 +120,24 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise // (and, the regionfile parameter is unused for writing until the write call) final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData writeData = ((ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile)regionFile).moonrise$startWrite(compound, pos); @@ -81,39 +82,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 return writeData; } -@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - try { - NbtIo.write(nbt, (DataOutput) dataoutputstream); - regionfile.setOversized(pos.x, pos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone +@@ -326,9 +340,17 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + if (chunkData == null) { + regionFile.clear(chunkPos); + } else { +- try (DataOutputStream chunkDataOutputStream = regionFile.getChunkDataOutputStream(chunkPos)) { ++ DataOutputStream chunkDataOutputStream = regionFile.getChunkDataOutputStream(chunkPos); // Paper - Only write if successful ++ try { // Paper - Only write if successful + NbtIo.write(chunkData, chunkDataOutputStream); + regionFile.setOversized(chunkPos.x, chunkPos.z, false); // Paper - We don't do this anymore, mojang stores differently, but clear old meta flag if it exists to get rid of our own meta file once last oversized is gone + // Paper start - don't write garbage data to disk if writing serialization fails -+ dataoutputstream.close(); // Only write if successful ++ chunkDataOutputStream.close(); + } catch (final RegionFileSizeException ex) { -+ regionfile.clear(pos); ++ regionFile.clear(chunkPos); + final int maxSize = RegionFile.MAX_CHUNK_SIZE / (1024 * 1024); -+ LOGGER.error("Chunk at (" + pos.x + "," + pos.z + ") in regionfile '" + regionfile.getPath().toString() + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk."); -+ return; ++ LOGGER.error("Chunk at (" + chunkPos.x + "," + chunkPos.z + ") in regionfile '" + regionFile.getPath().toString() + "' exceeds max size of " + maxSize + "MiB, it has been deleted from disk."); + // Paper end - don't write garbage data to disk if writing serialization fails - } catch (Throwable throwable) { - if (dataoutputstream != null) { - try { -- dataoutputstream.close(); -+ //dataoutputstream.close(); // Paper - don't write garbage data to disk if writing serialization fails - } catch (Throwable throwable1) { - throwable.addSuppressed(throwable1); - } -@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - - throw throwable; } -- -- if (dataoutputstream != null) { -- dataoutputstream.close(); -- } -+ // Paper - don't write garbage data to disk if writing serialization fails; move into try block to only write if successfully serialized } - } -@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise +@@ -370,4 +392,13 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise public RegionStorageInfo info() { return this.info; } @@ -121,7 +109,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start - don't write garbage data to disk if writing serialization fails + public static final class RegionFileSizeException extends RuntimeException { + -+ public RegionFileSizeException(String message) { ++ public RegionFileSizeException(final String message) { + super(message); + } + } diff --git a/feature-patches/1063-Entity-load-save-limit-per-chunk.patch b/feature-patches/1063-Entity-load-save-limit-per-chunk.patch index 78788ef3c8..a931c2cffe 100644 --- a/feature-patches/1063-Entity-load-save-limit-per-chunk.patch +++ b/feature-patches/1063-Entity-load-save-limit-per-chunk.patch @@ -8,11 +8,12 @@ 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/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -+++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -@@ -0,0 +0,0 @@ public final class ChunkEntitySlices { + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +index 7aea4e343581b977d11af90f9f65eac3532eade1..d21ce54ebb5724c04eadf56a2cde701d5eeb5db2 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +@@ -104,7 +104,18 @@ public final class ChunkEntitySlices { } final ListTag entitiesTag = new ListTag(); @@ -31,21 +32,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 CompoundTag compoundTag = new CompoundTag(); if (entity.save(compoundTag)) { entitiesTag.add(compoundTag); -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -0,0 +0,0 @@ public class EntityType implements FeatureElement, EntityTypeT - final Spliterator spliterator = entityNbtList.spliterator(); - +diff --git a/net/minecraft/world/entity/EntityType.java b/net/minecraft/world/entity/EntityType.java +index 73cdfa5a315ed259b38dfa946a0b7955d9ac9f50..49201d6664656ebe34c84c1c84b5ea4878729062 100644 +--- a/net/minecraft/world/entity/EntityType.java ++++ b/net/minecraft/world/entity/EntityType.java +@@ -1420,9 +1420,20 @@ public class EntityType implements FeatureElement, EntityTypeT + public static Stream loadEntitiesRecursive(final List entityTags, final Level level, final EntitySpawnReason spawnReason) { + final Spliterator spliterator = entityTags.spliterator(); return StreamSupport.stream(new Spliterator() { + final java.util.Map, Integer> loadedEntityCounts = new java.util.HashMap<>(); // Paper - Entity load/save limit per chunk + @Override public boolean tryAdvance(Consumer consumer) { - return spliterator.tryAdvance((nbtbase) -> { - EntityType.loadEntityRecursive((CompoundTag) nbtbase, world, reason, (entity) -> { + return spliterator.tryAdvance(tag -> EntityType.loadEntityRecursive((CompoundTag)tag, level, spawnReason, entity -> { + // Paper start - Entity load/save limit per chunk + final EntityType entityType = entity.getType(); -+ final int saveLimit = world.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1); ++ final int saveLimit = level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1); + if (saveLimit > -1) { + if (this.loadedEntityCounts.getOrDefault(entityType, 0) >= saveLimit) { + return null; @@ -53,19 +54,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.loadedEntityCounts.merge(entityType, 1, Integer::sum); + } + // Paper end - Entity load/save limit per chunk - 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 -@@ -0,0 +0,0 @@ public class EntityStorage implements EntityPersistentStorage { + consumer.accept(entity); + return entity; + })); +diff --git a/net/minecraft/world/level/chunk/storage/EntityStorage.java b/net/minecraft/world/level/chunk/storage/EntityStorage.java +index da05fb780c55381a7a08ced51d01764a645740b2..2856206eafddfcbcc1b65408deda40357f43a6f8 100644 +--- a/net/minecraft/world/level/chunk/storage/EntityStorage.java ++++ b/net/minecraft/world/level/chunk/storage/EntityStorage.java +@@ -93,7 +93,18 @@ public class EntityStorage implements EntityPersistentStorage { } } else { ListTag listTag = new ListTag(); + final java.util.Map, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper - Entity load/save limit per chunk - dataList.getEntities().forEach(entity -> { + entities.getEntities().forEach(entity -> { + // Paper start - Entity load/save limit per chunk + final EntityType entityType = entity.getType(); + final int saveLimit = this.level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1); @@ -76,6 +77,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + savedEntityCounts.merge(entityType, 1, Integer::sum); + } + // Paper end - Entity load/save limit per chunk - CompoundTag compoundTagx = new CompoundTag(); - if (entity.save(compoundTagx)) { - listTag.add(compoundTagx); + CompoundTag compoundTag1 = new CompoundTag(); + if (entity.save(compoundTag1)) { + listTag.add(compoundTag1); diff --git a/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch b/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch index 0aa11765a3..83577de6ed 100644 --- a/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch +++ b/feature-patches/1064-Attempt-to-recalculate-regionfile-header-if-it-is-co.patch @@ -9,11 +9,12 @@ 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/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 -@@ -0,0 +0,0 @@ import java.util.BitSet; + +diff --git a/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/net/minecraft/world/level/chunk/storage/RegionBitmap.java +index 64a718c98f799c62a5bb28e1e8e5f66cc96c915d..666f2e967c99f78422c83fb20e1a3bf3efa7845e 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionBitmap.java ++++ b/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(); @@ -38,17 +39,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end - Attempt to recalculate regionfile header if it is corrupt + - public void force(int start, int size) { - this.used.set(start, start + size); + public void force(int sectorOffset, int sectorCount) { + this.used.set(sectorOffset, sectorOffset + sectorCount); } -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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - private final IntBuffer timestamps; +diff --git a/net/minecraft/world/level/chunk/storage/RegionFile.java b/net/minecraft/world/level/chunk/storage/RegionFile.java +index ff4fc280409f680f3879a495e37cf1925b1a38f1..a4621c96fd456c5cdd1b6847931806e677b26b30 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFile.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFile.java +@@ -46,6 +46,355 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche @VisibleForTesting - protected final RegionBitmap usedSectors; + protected final RegionBitmap usedSectors = new RegionBitmap(); + + // Paper start - Attempt to recalculate regionfile header if it is corrupt + private static long roundToSectors(long bytes) { + long sectors = bytes >>> 12; // 4096 = 2^12 @@ -57,9 +58,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return sectors + (sign >>> 63); + } + -+ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag(); ++ private static final net.minecraft.nbt.CompoundTag OVERSIZED_COMPOUND = new net.minecraft.nbt.CompoundTag(); + -+ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { ++ private @Nullable net.minecraft.nbt.CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { + try { + if (chunkDataLength < 0) { + return null; @@ -91,7 +92,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position())); + -+ return NbtIo.read(new DataInputStream(input)); ++ return net.minecraft.nbt.NbtIo.read(new DataInputStream(input)); + } catch (Exception ex) { + return null; + } @@ -142,7 +143,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // 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) ++ net.minecraft.nbt.CompoundTag[] compounds = new net.minecraft.nbt.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]; @@ -154,7 +155,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + 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); ++ net.minecraft.nbt.CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength); + if (compound == null || compound == OVERSIZED_COMPOUND) { + continue; + } @@ -166,7 +167,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5); + -+ CompoundTag otherCompound = compounds[location]; ++ net.minecraft.nbt.CompoundTag otherCompound = compounds[location]; + + if (otherCompound != null && SerializableChunkData.getLastWorldSaveTime(otherCompound) > SerializableChunkData.getLastWorldSaveTime(compound)) { + continue; // don't overwrite newer data. @@ -177,7 +178,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + boolean isAikarOversized = false; + if (Files.exists(aikarOversizedFile)) { + try { -+ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); ++ net.minecraft.nbt.CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); + if (SerializableChunkData.getLastWorldSaveTime(compound) == SerializableChunkData.getLastWorldSaveTime(aikarOversizedCompound)) { + // best we got for an id. hope it's good enough + isAikarOversized = true; @@ -236,14 +237,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + continue; + } + -+ CompoundTag compound = null; ++ net.minecraft.nbt.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); ++ compound = net.minecraft.nbt.NbtIo.read((java.io.DataInput)in); + compression = compressionType; + break; // reaches here iff readNBT does not throw + } catch (Exception ex) { @@ -397,63 +398,55 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + final boolean canRecalcHeader; // final forces compile fail on new constructor + // Paper end - Attempt to recalculate regionfile header if it is corrupt - ++ // Paper start - rewrite chunk system @Override -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - throw new IllegalArgumentException("Expected directory, got " + String.valueOf(directory.toAbsolutePath())); + public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(final net.minecraft.nbt.CompoundTag data, final ChunkPos pos) throws IOException { +@@ -74,6 +423,7 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + throw new IllegalArgumentException("Expected directory, got " + externalFileDir.toAbsolutePath()); } else { - this.externalFileDir = directory; + this.externalFileDir = externalFileDir; + this.canRecalcHeader = RegionFileStorage.isChunkDataFolder(this.externalFileDir); // Paper - add can recalc flag this.offsets = this.header.asIntBuffer(); - ((java.nio.Buffer) this.offsets).limit(1024); // CraftBukkit - decompile error - ((java.nio.Buffer) this.header).position(4096); // CraftBukkit - decompile error -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - RegionFile.LOGGER.warn("Region file {} has truncated header: {}", path, i); - } + this.offsets.limit(1024); + this.header.position(4096); +@@ -94,11 +444,13 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche -- long j = Files.size(path); -+ final long j = Files.size(path); final long regionFileSize = j; // Paper - recalculate header on header corruption + long size = Files.size(path); -- for (int k = 0; k < 1024; ++k) { -- int l = this.offsets.get(k); +- for (int i1 = 0; i1 < 1024; i1++) { + 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 ++ for (int i1 = 0; i1 < 1024; i1++) { final int headerLocation = i1; // Paper - we expect this to be the header location + int i2 = this.offsets.get(i1); + if (i2 != 0) { +- int sectorNumber = getSectorNumber(i2); +- int numSectors = getNumSectors(i2); ++ final int sectorNumber = getSectorNumber(i2); // Paper - we expect this to be offset in file in sectors ++ int numSectors = getNumSectors(i2); // Paper - diff on change, we expect this to be sector length of region - watch out for reassignments // Spigot start - if (j1 == 255) { + if (numSectors == 255) { // We're maxed out, so we need to read the proper length from the section -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - j1 = (realLen.getInt(0) + 4) / 4096 + 1; - } +@@ -109,18 +461,62 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche // 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[]{path, 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", path, 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[]{path, 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 + if (sectorNumber < 2) { + LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", path, i1, sectorNumber); +- this.offsets.put(i1, 0); ++ //this.offsets.put(i1, 0); // Paper - we catch this, but need it in the header for the summary change + } else if (numSectors == 0) { + LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", path, i1); +- this.offsets.put(i1, 0); ++ //this.offsets.put(i1, 0); // Paper - we catch this, but need it in the header for the summary change + } else if (sectorNumber * 4096L > size) { + LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", path, i1, sectorNumber); +- this.offsets.put(i1, 0); ++ //this.offsets.put(i1, 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 +- this.usedSectors.force(sectorNumber, numSectors); ++ //this.usedSectors.force(sectorNumber, numSectors); // 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 (sectorNumber < 2 || numSectors <= 0 || ((long)sectorNumber * 4096L) > size) { + if (canRecalcHeader) { + LOGGER.error("Detected invalid header for regionfile " + this.path.toAbsolutePath() + "! Recalculating header..."); + needsHeaderRecalc = true; @@ -471,7 +464,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + continue; + } + } -+ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength); ++ boolean failedToAllocate = !this.usedSectors.tryAllocate(sectorNumber, numSectors); + if (failedToAllocate) { + LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.path.toAbsolutePath()); } @@ -499,20 +492,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end } - } -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + } +@@ -130,10 +526,35 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche } 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); +- String string = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; ++ String string = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Paper - diff on change + return this.externalFileDir.resolve(string); } + // Paper start -+ private static ChunkPos getOversizedChunkPair(Path file) { ++ private static @Nullable ChunkPos getOversizedChunkPair(Path file) { + String fileName = file.getFileName().toString(); + + if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) { @@ -537,81 +529,79 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper end + @Nullable - public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException { - int i = this.getOffset(pos); -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - ((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()}); + public synchronized DataInputStream getChunkDataInputStream(ChunkPos chunkPos) throws IOException { + int offset = this.getOffset(chunkPos); +@@ -155,30 +576,67 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche + byteBuffer.flip(); + if (byteBuffer.remaining() < 5) { + LOGGER.error("Chunk {} header is truncated: expected {} but read {}", chunkPos, i, byteBuffer.remaining()); + // Paper start - recalculate header on regionfile corruption + if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); ++ return this.getChunkDataInputStream(chunkPos); + } + // Paper end - recalculate header on regionfile corruption return null; } else { - int i1 = bytebuffer.getInt(); -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - - if (i1 == 0) { - RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos); + int _int = byteBuffer.getInt(); + byte b = byteBuffer.get(); + if (_int == 0) { + LOGGER.warn("Chunk {} is allocated, but stream is missing", chunkPos); + // Paper start - recalculate header on regionfile corruption + if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); ++ return this.getChunkDataInputStream(chunkPos); + } + // Paper end - recalculate header on regionfile corruption return null; } else { - int j1 = i1 - 1; -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche - if (RegionFile.isExternalStreamChunk(b0)) { - if (j1 != 0) { - RegionFile.LOGGER.warn("Chunk has both internal and external streams"); + int i1 = _int - 1; + if (isExternalStreamChunk(b)) { + if (i1 != 0) { + 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); ++ return this.getChunkDataInputStream(chunkPos); + } + // Paper end - recalculate header on regionfile corruption } -- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); +- return this.createExternalChunkInputStream(chunkPos, getExternalChunkVersion(b)); + // Paper start - recalculate header on regionfile corruption -+ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); ++ final DataInputStream ret = this.createExternalChunkInputStream(chunkPos, getExternalChunkVersion(b)); + if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); ++ return this.getChunkDataInputStream(chunkPos); + } + 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()}); + } else if (i1 > byteBuffer.remaining()) { + LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", chunkPos, i1, byteBuffer.remaining()); + // Paper start - recalculate header on regionfile corruption + if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); ++ return this.getChunkDataInputStream(chunkPos); + } + // Paper end - recalculate header on regionfile corruption return null; - } else if (j1 < 0) { - RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos); + } else if (i1 < 0) { + LOGGER.error("Declared size {} of chunk {} is negative", _int, chunkPos); + // Paper start - recalculate header on regionfile corruption + if (this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); ++ return this.getChunkDataInputStream(chunkPos); + } + // Paper end - recalculate header on regionfile corruption return null; } else { - JvmProfiler.INSTANCE.onRegionFileRead(this.info, pos, this.version, j1); -- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); + JvmProfiler.INSTANCE.onRegionFileRead(this.info, chunkPos, this.version, i1); +- return this.createChunkInputStream(chunkPos, b, createStream(byteBuffer, i1)); + // Paper start - recalculate header on regionfile corruption -+ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); ++ final DataInputStream ret = this.createChunkInputStream(chunkPos, b, createStream(byteBuffer, i1)); + if (ret == null && this.canRecalcHeader && this.recalculateHeader()) { -+ return this.getChunkDataInputStream(pos); ++ return this.getChunkDataInputStream(chunkPos); + } + return ret; + // Paper end - recalculate header on regionfile corruption } } } -@@ -0,0 +0,0 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche +@@ -361,9 +819,14 @@ public class RegionFile implements AutoCloseable, ca.spottedleaf.moonrise.patche } private ByteBuffer createExternalStub() { @@ -620,22 +610,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + private ByteBuffer createExternalStub(RegionFileVersion version) { + // Paper end - add version param - ByteBuffer bytebuffer = ByteBuffer.allocate(5); + ByteBuffer byteBuffer = ByteBuffer.allocate(5); + byteBuffer.putInt(1); +- byteBuffer.put((byte)(this.version.getId() | 128)); ++ byteBuffer.put((byte)(version.getId() | 128)); + byteBuffer.flip(); + return byteBuffer; + } +diff --git a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +index d263f78fa610ce6f6fb5a0f5e064e3d8335c2199..dad7f94b611cf0fc68b1a3878c458233f6bb6d61 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -23,6 +23,36 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + private final Path folder; + private final boolean sync; - 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 -@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - } - } - // Paper end - rewrite chunk system + // Paper start - recalculate region file headers + private final boolean isChunkData; + @@ -666,40 +655,42 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + // Paper end - - protected RegionFileStorage(RegionStorageInfo storageKey, Path directory, boolean dsync) { // Paper - protected - this.folder = directory; - this.sync = dsync; - this.info = storageKey; + // Paper start - rewrite chunk system + private static final int REGION_SHIFT = 5; + private static final int MAX_NON_EXISTING_CACHE = 1024 * 4; +@@ -216,6 +246,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + this.folder = folder; + this.sync = sync; + this.info = info; + this.isChunkData = isChunkDataFolder(this.folder); // Paper - recalculate region file headers } - // Paper start - rewrite chunk system -@@ -0,0 +0,0 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - try { - if (datainputstream != null) { - nbttagcompound = NbtIo.read((DataInput) datainputstream); -+ // Paper start - recover from corrupt regionfile header -+ if (this.isChunkData) { -+ ChunkPos chunkPos = SerializableChunkData.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.getPath().toAbsolutePath()); -+ if (regionfile.recalculateHeader()) { -+ return this.read(pos); -+ } -+ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + pos + " for " + regionfile.getPath().toAbsolutePath()); -+ return null; -+ } -+ } -+ // Paper end - recover from corrupt regionfile header - break label43; - } + @org.jetbrains.annotations.Contract("_, false -> !null") @Nullable private RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { // CraftBukkit +@@ -309,6 +340,19 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } -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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 -@@ -0,0 +0,0 @@ import org.slf4j.Logger; + var4 = NbtIo.read(chunkDataInputStream); ++ // Paper start - recover from corrupt regionfile header ++ if (this.isChunkData) { ++ ChunkPos headerChunkPos = SerializableChunkData.getChunkCoordinate(var4); ++ if (!headerChunkPos.equals(chunkPos)) { ++ net.minecraft.server.MinecraftServer.LOGGER.error("Attempting to read chunk data at " + chunkPos + " but got chunk data for " + headerChunkPos + " instead! Attempting regionfile recalculation for regionfile " + regionFile.getPath().toAbsolutePath()); ++ if (regionFile.recalculateHeader()) { ++ return this.read(chunkPos); ++ } ++ net.minecraft.server.MinecraftServer.LOGGER.error("Can't recalculate regionfile header, regenerating chunk " + chunkPos + " for " + regionFile.getPath().toAbsolutePath()); ++ return null; ++ } ++ } ++ // Paper end - recover from corrupt regionfile header + } + + return var4; +diff --git a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java +index 0c739ca5b01ac0ec35a11fd01c5fc65de97c2852..de7deee4b79c969a7797bd57b657a16404c15303 100644 +--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java +@@ -21,7 +21,7 @@ import org.slf4j.Logger; public class RegionFileVersion { private static final Logger LOGGER = LogUtils.getLogger(); @@ -708,11 +699,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private static final Object2ObjectMap VERSIONS_BY_NAME = new Object2ObjectOpenHashMap<>(); public static final RegionFileVersion VERSION_GZIP = register( new RegionFileVersion( -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SerializableChunkData.java -@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry biomeRegistry, ChunkPos chun +diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +index 70a9972252576e039ac126f6057a6ed66b80cdfc..d783c3580ea274a0a9cb07449eb8037bc5a04d76 100644 +--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java ++++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +@@ -120,6 +120,18 @@ public record SerializableChunkData( } } // Paper end - guard against serializing mismatching coordinates @@ -731,12 +722,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // Paper start - Do not let the server load chunks from newer versions private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion(); -@@ -0,0 +0,0 @@ public record SerializableChunkData(Registry biomeRegistry, ChunkPos chun - nbttagcompound.putInt("xPos", this.chunkPos.x); - nbttagcompound.putInt("yPos", this.minSectionY); - nbttagcompound.putInt("zPos", this.chunkPos.z); -- nbttagcompound.putLong("LastUpdate", this.lastUpdateTime); -+ nbttagcompound.putLong("LastUpdate", this.lastUpdateTime); // Paper - Diff on change - nbttagcompound.putLong("InhabitedTime", this.inhabitedTime); - nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString()); - DataResult dataresult; // CraftBukkit - decompile error +@@ -604,7 +616,7 @@ public record SerializableChunkData( + compoundTag.putInt("xPos", this.chunkPos.x); + compoundTag.putInt("yPos", this.minSectionY); + compoundTag.putInt("zPos", this.chunkPos.z); +- compoundTag.putLong("LastUpdate", this.lastUpdateTime); ++ compoundTag.putLong("LastUpdate", this.lastUpdateTime); // Paper - Diff on change + compoundTag.putLong("InhabitedTime", this.inhabitedTime); + compoundTag.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString()); + if (this.blendingData != null) { diff --git a/feature-patches/1067-Optimise-general-POI-access.patch b/feature-patches/1067-Optimise-general-POI-access.patch index 92dfac7dd4..9b56c38a8e 100644 --- a/feature-patches/1067-Optimise-general-POI-access.patch +++ b/feature-patches/1067-Optimise-general-POI-access.patch @@ -30,12 +30,13 @@ This patch also specifically optimises other areas of code to use PoiAccess. For example, some villager AI and portaling code had to be specifically modified. -diff --git a/src/main/java/io/papermc/paper/util/PoiAccess.java b/src/main/java/io/papermc/paper/util/PoiAccess.java + +diff --git a/io/papermc/paper/util/PoiAccess.java b/io/papermc/paper/util/PoiAccess.java new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +index 0000000000000000000000000000000000000000..f39294b1f83c4022be5ced4da781103a1eee2daf --- /dev/null -+++ b/src/main/java/io/papermc/paper/util/PoiAccess.java -@@ -0,0 +0,0 @@ ++++ b/io/papermc/paper/util/PoiAccess.java +@@ -0,0 +1,806 @@ +package io.papermc.paper.util; + +import ca.spottedleaf.moonrise.common.util.CoordinateUtils; @@ -842,38 +843,38 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + throw new RuntimeException(); + } +} -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -@@ -0,0 +0,0 @@ public class AcquirePoi { - return true; - } - }; -- Set, BlockPos>> set = poiManager.findAllClosestFirstWithType( -- poiPredicate, predicate2, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE -- ) -- .limit(5L) -- .filter(pairx -> worldPosBiPredicate.test(world, (BlockPos)pairx.getSecond())) -- .collect(Collectors.toSet()); -+ // Paper start - optimise POI access -+ final java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); -+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); -+ final Set, BlockPos>> set = new java.util.HashSet<>(poiposes.size()); -+ for (final Pair, BlockPos> poiPose : poiposes) { -+ if (worldPosBiPredicate.test(world, poiPose.getSecond())) { -+ set.add(poiPose); -+ } -+ } -+ // Paper end - optimise POI access - Path path = findPathToPois(entity, set); - if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -@@ -0,0 +0,0 @@ public class NearestBedSensor extends Sensor { +diff --git a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +index 9de13a78b2a8be181c02ab330bfa9abb936a83db..b9174ae7e3a3e2de2d570b95ab5012ac3c3a2eda 100644 +--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java ++++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +@@ -84,12 +84,16 @@ public class AcquirePoi { + return true; + } + }; +- Set, BlockPos>> set = poiManager.findAllClosestFirstWithType( +- acquirablePois, predicate1, mob.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE +- ) +- .limit(5L) +- .filter(pair1 -> predicate.test(level, pair1.getSecond())) +- .collect(Collectors.toSet()); ++ // Paper start - optimise POI access ++ final java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); ++ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, acquirablePois, predicate1, mob.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); ++ final Set, BlockPos>> set = new java.util.HashSet<>(poiposes.size()); ++ for (final Pair, BlockPos> poiPose : poiposes) { ++ if (predicate.test(level, poiPose.getSecond())) { ++ set.add(poiPose); ++ } ++ } ++ // Paper end - optimise POI access + Path path = findPathToPois(mob, set); + if (path != null && path.canReach()) { + BlockPos target = path.getTarget(); +diff --git a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +index 6e9325f0800a35637fdec5edb8a514ea03741762..066faa704338c573472381e1ebd063e0d52aaaa4 100644 +--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +@@ -53,11 +53,12 @@ public class NearestBedSensor extends Sensor { return true; } }; @@ -889,82 +890,82 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); + // Paper end - optimise POI access if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); - Optional> optional = poiManager.getType(blockPos); -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage im + BlockPos target = path.getTarget(); + Optional> type = poiManager.getType(target); +diff --git a/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/net/minecraft/world/entity/ai/village/poi/PoiManager.java +index 5c5724f5e3ad640f55aecbc1d8f71d1f59ecdc62..618fc0eb4fe70e46e55f3aa28e8eac1d2d01b6d9 100644 +--- a/net/minecraft/world/entity/ai/village/poi/PoiManager.java ++++ b/net/minecraft/world/entity/ai/village/poi/PoiManager.java +@@ -254,36 +254,47 @@ public class PoiManager extends SectionStorage im public Optional find( - Predicate> typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus + Predicate> typePredicate, Predicate posPredicate, BlockPos pos, int distance, PoiManager.Occupancy status ) { -- return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst(); +- return this.findAll(typePredicate, posPredicate, pos, distance, status).findFirst(); + // Paper start - re-route to faster logic -+ BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false); ++ BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, distance, status, false); + return Optional.ofNullable(ret); + // Paper end } - public Optional findClosest(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.getInRange(typePredicate, pos, radius, occupationStatus) -- .map(PoiRecord::getPos) -- .min(Comparator.comparingDouble(poiPos -> poiPos.distSqr(pos))); + public Optional findClosest(Predicate> typePredicate, BlockPos pos, int distance, PoiManager.Occupancy status) { +- return this.getInRange(typePredicate, pos, distance, status).map(PoiRecord::getPos).min(Comparator.comparingDouble(blockPos -> blockPos.distSqr(pos))); + // Paper start - re-route to faster logic -+ BlockPos closestPos = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false); ++ BlockPos closestPos = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, distance, distance * distance, status, false); + return Optional.ofNullable(closestPos); + // Paper end - re-route to faster logic } public Optional, BlockPos>> findClosestWithType( - Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus + Predicate> typePredicate, BlockPos pos, int distance, PoiManager.Occupancy status ) { -- return this.getInRange(typePredicate, pos, radius, occupationStatus) -- .min(Comparator.comparingDouble(poi -> poi.getPos().distSqr(pos))) -- .map(poi -> Pair.of(poi.getPoiType(), poi.getPos())); +- return this.getInRange(typePredicate, pos, distance, status) +- .min(Comparator.comparingDouble(poiRecord -> poiRecord.getPos().distSqr(pos))) +- .map(poiRecord -> Pair.of(poiRecord.getPoiType(), poiRecord.getPos())); + // Paper start - re-route to faster logic + return Optional.ofNullable(io.papermc.paper.util.PoiAccess.findClosestPoiDataTypeAndPosition( -+ this, typePredicate, null, pos, radius, radius * radius, occupationStatus, false ++ this, typePredicate, null, pos, distance, distance * distance, status, false + )); + // Paper end - re-route to faster logic } public Optional findClosest( - Predicate> typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus + Predicate> typePredicate, Predicate posPredicate, BlockPos pos, int distance, PoiManager.Occupancy status ) { -- return this.getInRange(typePredicate, pos, radius, occupationStatus) +- return this.getInRange(typePredicate, pos, distance, status) - .map(PoiRecord::getPos) - .filter(posPredicate) -- .min(Comparator.comparingDouble(poiPos -> poiPos.distSqr(pos))); +- .min(Comparator.comparingDouble(blockPos -> blockPos.distSqr(pos))); + // Paper start - re-route to faster logic -+ BlockPos closestPos = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, posPredicate, pos, radius, radius * radius, occupationStatus, false); ++ BlockPos closestPos = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, posPredicate, pos, distance, distance * distance, status, false); + return Optional.ofNullable(closestPos); + // Paper end - re-route to faster logic } - public Optional take(Predicate> typePredicate, BiPredicate, BlockPos> posPredicate, BlockPos pos, int radius) { -- return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE) -- .filter(poi -> posPredicate.test(poi.getPoiType(), poi.getPos())) + public Optional take( + Predicate> typePredicate, BiPredicate, BlockPos> combinedTypePosPredicate, BlockPos pos, int distance + ) { +- return this.getInRange(typePredicate, pos, distance, PoiManager.Occupancy.HAS_SPACE) +- .filter(poiRecord -> combinedTypePosPredicate.test(poiRecord.getPoiType(), poiRecord.getPos())) - .findFirst() + // Paper start - re-route to faster logic + final @javax.annotation.Nullable PoiRecord closest = io.papermc.paper.util.PoiAccess.findClosestPoiDataRecord( -+ this, typePredicate, posPredicate, pos, radius, radius * radius, Occupancy.HAS_SPACE, false ++ this, typePredicate, combinedTypePosPredicate, pos, distance, distance * distance, Occupancy.HAS_SPACE, false + ); + return Optional.ofNullable(closest) + // Paper end - re-route to faster logic - .map(poi -> { - poi.acquireTicket(); - return poi.getPos(); -@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage im - int radius, + .map(poiRecord -> { + poiRecord.acquireTicket(); + return poiRecord.getPos(); +@@ -298,8 +309,21 @@ public class PoiManager extends SectionStorage im + int distance, RandomSource random ) { -- List list = Util.toShuffledList(this.getInRange(typePredicate, pos, radius, occupationStatus), random); -- return list.stream().filter(poi -> positionPredicate.test(poi.getPos())).findFirst().map(PoiRecord::getPos); +- List list = Util.toShuffledList(this.getInRange(typePredicate, pos, distance, status), random); +- return list.stream().filter(poiRecord -> posPredicate.test(poiRecord.getPos())).findFirst().map(PoiRecord::getPos); + // Paper start - re-route to faster logic + List list = new java.util.ArrayList<>(); + io.papermc.paper.util.PoiAccess.findAnyPoiRecords( -+ this, typePredicate, positionPredicate, pos, radius, occupationStatus, false, Integer.MAX_VALUE, list ++ this, typePredicate, posPredicate, pos, distance, status, false, Integer.MAX_VALUE, list + ); + + // the old method shuffled the list and then tried to find the first element in it that @@ -979,11 +980,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } public boolean release(BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -@@ -0,0 +0,0 @@ import org.slf4j.Logger; +diff --git a/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/net/minecraft/world/entity/ai/village/poi/PoiSection.java +index 39cd1e3d8192d7077d6b7864d33933097cc6b986..b92ba4d194fd3af94c7af5d8e150fc4297c73ab8 100644 +--- a/net/minecraft/world/entity/ai/village/poi/PoiSection.java ++++ b/net/minecraft/world/entity/ai/village/poi/PoiSection.java +@@ -26,7 +26,7 @@ import org.slf4j.Logger; public class PoiSection implements ca.spottedleaf.moonrise.patches.chunk_system.level.poi.ChunkSystemPoiSection { // Paper - rewrite chunk system private static final Logger LOGGER = LogUtils.getLogger(); private final Short2ObjectMap records = new Short2ObjectOpenHashMap<>(); @@ -992,48 +993,43 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private final Runnable setDirty; private boolean isValid; -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -@@ -0,0 +0,0 @@ public class SectionStorage implements AutoCloseable, ca.spottedleaf.moonr +diff --git a/net/minecraft/world/level/chunk/storage/SectionStorage.java b/net/minecraft/world/level/chunk/storage/SectionStorage.java +index 778bd73a938c94ecb85ca0f8b686ff4e1baee040..79d4ce7712f16995b0de3be86477fb43ab3961d7 100644 +--- a/net/minecraft/world/level/chunk/storage/SectionStorage.java ++++ b/net/minecraft/world/level/chunk/storage/SectionStorage.java +@@ -131,11 +131,11 @@ public class SectionStorage implements AutoCloseable, ca.spottedleaf.moonr } @Nullable -- protected Optional get(long pos) { -+ public Optional get(long pos) { // Paper - public - return this.storage.get(pos); +- protected Optional get(long sectionKey) { ++ public Optional get(long sectionKey) { // Paper - public + return this.storage.get(sectionKey); } -- protected Optional getOrLoad(long pos) { -+ public Optional getOrLoad(long pos) { // Paper - public - if (this.outsideStoredRange(pos)) { +- protected Optional getOrLoad(long sectionKey) { ++ public Optional getOrLoad(long sectionKey) { // Paper - public + if (this.outsideStoredRange(sectionKey)) { return Optional.empty(); } else { -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -@@ -0,0 +0,0 @@ public class PortalForcer { - // int i = flag ? 16 : 128; +diff --git a/net/minecraft/world/level/portal/PortalForcer.java b/net/minecraft/world/level/portal/PortalForcer.java +index ada2da62d3a40d67e64f5f8d7299f78b5c6f53cb..90ae71eb8cc7f925eb212f39731d70f3bff5ef0a 100644 +--- a/net/minecraft/world/level/portal/PortalForcer.java ++++ b/net/minecraft/world/level/portal/PortalForcer.java +@@ -48,13 +48,38 @@ public class PortalForcer { + PoiManager poiManager = this.level.getPoiManager(); + // int i = isNether ? 16 : 128; // CraftBukkit end - -- villageplace.ensureLoadedAndValid(this.level, blockposition, i); -- Stream stream = villageplace.getInSquare((holder) -> { // CraftBukkit - decompile error -- return holder.is(PoiTypes.NETHER_PORTAL); -- }, blockposition, i, PoiManager.Occupancy.ANY).map(PoiRecord::getPos); -- -- Objects.requireNonNull(worldborder); -- return stream.filter(worldborder::isWithinBounds).filter(pos -> !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))).filter((blockposition1) -> { // Paper - Configurable nether ceiling damage -- return this.level.getBlockState(blockposition1).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); -- }).min(Comparator.comparingDouble((BlockPos blockposition1) -> { // CraftBukkit - decompile error -- return blockposition1.distSqr(blockposition); -- }).thenComparingInt(Vec3i::getY)); +- poiManager.ensureLoadedAndValid(this.level, exitPos, i); +- return poiManager.getInSquare(holder -> holder.is(PoiTypes.NETHER_PORTAL), exitPos, i, PoiManager.Occupancy.ANY) +- .map(PoiRecord::getPos) +- .filter(worldBorder::isWithinBounds) +- .filter(pos -> !(this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) // Paper - Configurable nether ceiling damage +- .filter(blockPos -> this.level.getBlockState(blockPos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS)) +- .min(Comparator.comparingDouble(blockPos -> blockPos.distSqr(exitPos)).thenComparingInt(Vec3i::getY)); + // Paper start - optimise portals -+ Optional optional; + java.util.List records = new java.util.ArrayList<>(); + io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords( -+ villageplace, ++ poiManager, + type -> type.is(PoiTypes.NETHER_PORTAL), + (BlockPos pos) -> { + net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.status.ChunkStatus.EMPTY); @@ -1042,12 +1038,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // why would we generate the chunk? + return false; + } -+ if (!worldborder.isWithinBounds(pos) || (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) { // Paper - Configurable nether ceiling damage ++ if (!worldBorder.isWithinBounds(pos) || (this.level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> pos.getY() >= v))) { // Paper - Configurable nether ceiling damage + return false; + } + return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); + }, -+ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records ++ exitPos, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records + ); + + // this gets us most of the way there, but we bias towards lower y values.