diff --git a/patches/server/0003-Region-format-configuration.patch b/patches/server/0003-Region-format-configuration.patch index a6b4f23..9717bdf 100644 --- a/patches/server/0003-Region-format-configuration.patch +++ b/patches/server/0003-Region-format-configuration.patch @@ -5,18 +5,15 @@ Subject: [PATCH] Region format configuration diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -index 7da7e0aeb5eac9ac73a3570e716f1ceb11fd7027..b86c90cc3601e666998cfa12f44515f605bb53eb 100644 +index 7da7e0aeb5eac9ac73a3570e716f1ceb11fd7027..f08bcc9ae1770fa847d8a5e873a554bef5485100 100644 --- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java +++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -@@ -192,4 +192,10 @@ public class KaiijuConfig { +@@ -192,4 +192,7 @@ public class KaiijuConfig { } return builder.build(); } + -+ public static boolean regionFormatDebug = false; -+ + private static void regionFormatSettings() { -+ regionFormatDebug = getBoolean("region-format.debug", regionFormatDebug); + } } diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuWorldConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuWorldConfig.java @@ -85,7 +82,7 @@ index 0000000000000000000000000000000000000000..7164d9cd03186f0657783f83de3d6435 + } +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index cc1a7c4d38874ec218f7151685ae6cc00295c2e4..06233380f7580726753de34c1fb23d6347c9ac94 100644 +index 7e5645d9cb64ce17f60c85619f5640c8de4b1e86..bc9204d2c925437e9ff5c5d62d9faf38c2938e48 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -893,7 +893,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop SUPPORTED_VERSIONS = Arrays.asList((byte) 1, (byte) 2); -+ ++ private static final LinearRegionFileFlusher linearRegionFileFlusher = new LinearRegionFileFlusher(); + + private final byte[][] buffer = new byte[1024][]; + private final int[] bufferUncompressedSize = new int[1024]; + + private final int[] chunkTimestamps = new int[1024]; -+ private final Object markedToSaveLock = new Object(); + private final ChunkStatus[] statuses = new ChunkStatus[1024]; + + private final LZ4Compressor compressor; + private final LZ4FastDecompressor decompressor; + -+ private boolean markedToSave = false; -+ private boolean close = false; -+ + public final ReentrantLock fileLock = new ReentrantLock(true); -+ public Path regionFile; -+ + private final int compressionLevel; + -+ public Path getRegionFile() { -+ return this.regionFile; -+ } ++ private AtomicBoolean markedToSave = new AtomicBoolean(false); ++ public boolean closed = false; ++ public Path path; + -+ public ReentrantLock getFileLock() { -+ return this.fileLock; -+ } + + public LinearRegionFile(Path file, int compression) throws IOException { -+ this.regionFile = file; ++ this.path = file; + this.compressionLevel = compression; -+ + this.compressor = LZ4Factory.fastestInstance().fastCompressor(); + this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); + -+ File regionFile = new File(this.regionFile.toString()); ++ File regionFile = new File(this.path.toString()); + + Arrays.fill(this.bufferUncompressedSize, 0); + -+ if(regionFile.canRead()) { -+ FileInputStream fileStream = new FileInputStream(regionFile); -+ DataInputStream rawDataStream = new DataInputStream(fileStream); ++ if (!regionFile.canRead()) return; ++ ++ try (FileInputStream fileStream = new FileInputStream(regionFile); ++ DataInputStream rawDataStream = new DataInputStream(fileStream)) { + + long superBlock = rawDataStream.readLong(); + if (superBlock != SUPERBLOCK) -+ throw new RuntimeException("Invalid superblock: " + superBlock + " file " + file); ++ throw new RuntimeException("Invalid superblock: " + superBlock + " in " + file); + + byte version = rawDataStream.readByte(); + if (!SUPPORTED_VERSIONS.contains(version)) -+ throw new RuntimeException("Invalid version: " + version + " file " + file); ++ throw new RuntimeException("Invalid version: " + version + " in " + file); + + // Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused. + rawDataStream.skipBytes(11); @@ -204,7 +224,7 @@ index 0000000000000000000000000000000000000000..b7ce89429675bde7f037793305275f4b + int dataCount = rawDataStream.readInt(); + long fileLength = file.toFile().length(); + if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) -+ throw new IOException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE)); ++ throw new IOException("Invalid file length: " + this.path + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE)); + + rawDataStream.skipBytes(8); // Skip data hash (Long): Unused. + @@ -213,133 +233,124 @@ index 0000000000000000000000000000000000000000..b7ce89429675bde7f037793305275f4b + + superBlock = rawDataStream.readLong(); + if (superBlock != SUPERBLOCK) -+ throw new IOException("Footer superblock invalid " + this.regionFile); ++ throw new IOException("Footer superblock invalid " + this.path); + -+ DataInputStream dataStream = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed))); ++ try (DataInputStream dataStream = new DataInputStream(new ZstdInputStream(new ByteArrayInputStream(rawCompressed)))) { + -+ int[] starts = new int[1024]; -+ for(int i = 0; i < 1024; i++) { -+ starts[i] = dataStream.readInt(); -+ dataStream.skipBytes(4); // Skip timestamps (Int): Unused. -+ } ++ int[] starts = new int[1024]; ++ for (int i = 0; i < 1024; i++) { ++ starts[i] = dataStream.readInt(); ++ dataStream.skipBytes(4); // Skip timestamps (Int): Unused. ++ } + -+ for(int i = 0; i < 1024; i++) { -+ if(starts[i] > 0) { -+ int size = starts[i]; -+ byte[] b = new byte[size]; -+ dataStream.readFully(b, 0, size); ++ for (int i = 0; i < 1024; i++) { ++ if (starts[i] > 0) { ++ int size = starts[i]; ++ byte[] b = new byte[size]; ++ dataStream.readFully(b, 0, size); + -+ int maxCompressedLength = this.compressor.maxCompressedLength(size); -+ byte[] compressed = new byte[maxCompressedLength]; -+ int compressedLength = this.compressor.compress(b, 0, size, compressed, 0, maxCompressedLength); -+ b = new byte[compressedLength]; -+ System.arraycopy(compressed, 0, b, 0, compressedLength); ++ int maxCompressedLength = this.compressor.maxCompressedLength(size); ++ byte[] compressed = new byte[maxCompressedLength]; ++ int compressedLength = this.compressor.compress(b, 0, size, compressed, 0, maxCompressedLength); ++ b = new byte[compressedLength]; ++ System.arraycopy(compressed, 0, b, 0, compressedLength); + -+ this.buffer[i] = b; -+ this.bufferUncompressedSize[i] = size; ++ this.buffer[i] = b; ++ this.bufferUncompressedSize[i] = size; ++ } + } + } + } -+ this.start(); + } + -+ private synchronized void markToSave() { -+ synchronized(markedToSaveLock) { -+ markedToSave = true; -+ } ++ public Path getRegionFile() { ++ return this.path; + } + -+ private synchronized boolean isMarkedToSave() { -+ synchronized(markedToSaveLock) { -+ if(markedToSave) { -+ markedToSave = false; -+ return true; -+ } -+ return false; -+ } ++ public ReentrantLock getFileLock() { ++ return this.fileLock; ++ } ++ ++ public void flush() throws IOException { ++ if (isMarkedToSave()) flushWrapper(); // sync + } + -+ public void run() { ++ private void markToSave() { ++ linearRegionFileFlusher.scheduleSave(this); ++ markedToSave.set(true); ++ } ++ ++ public boolean isMarkedToSave() { ++ return markedToSave.getAndSet(false); ++ } ++ ++ public void flushWrapper() { + try { -+ while(true) { -+ if(markedToSave) { -+ try { -+ flush(); -+ } catch(IOException ex) { -+ LOGGER.error("Region file " + this.regionFile.toAbsolutePath() + " flush failed"); -+ } -+ } -+ for(int i = 0 ; i < 100 ; i++) { -+ Thread.sleep(100); -+ if(close) return; -+ } -+ } -+ } catch(InterruptedException ignored) {} ++ save(); ++ } catch (IOException e) { ++ LOGGER.error("Failed to flush region file " + path.toAbsolutePath(), e); ++ } + } + -+ public synchronized boolean doesChunkExist(ChunkPos pos) throws Exception { ++ public boolean doesChunkExist(ChunkPos pos) throws Exception { + throw new Exception("doesChunkExist is a stub"); + } + -+ public synchronized void flush() throws IOException { -+ if(!isMarkedToSave()) return; -+ ++ private synchronized void save() throws IOException { + long timestamp = getTimestamp(); + short chunkCount = 0; + -+ File tempFile = new File(regionFile.toString() + ".tmp"); -+ FileOutputStream fileStream = new FileOutputStream(tempFile); -+ -+ ByteArrayOutputStream zstdByteArray = new ByteArrayOutputStream(); -+ ZstdOutputStream zstdStream = new ZstdOutputStream(zstdByteArray, this.compressionLevel); -+ zstdStream.setChecksum(true); -+ DataOutputStream zstdDataStream = new DataOutputStream(zstdStream); -+ DataOutputStream dataStream = new DataOutputStream(fileStream); -+ -+ dataStream.writeLong(SUPERBLOCK); -+ dataStream.writeByte(VERSION); -+ dataStream.writeLong(timestamp); -+ dataStream.writeByte(this.compressionLevel); -+ -+ ArrayList byteBuffers = new ArrayList<>(); -+ for(int i = 0; i < 1024; i++) { -+ if(this.bufferUncompressedSize[i] != 0) { -+ chunkCount += 1; -+ byte[] content = new byte[bufferUncompressedSize[i]]; -+ this.decompressor.decompress(buffer[i], 0, content, 0, bufferUncompressedSize[i]); -+ -+ byteBuffers.add(content); -+ } else byteBuffers.add(null); -+ } -+ for(int i = 0; i < 1024; i++) { -+ zstdDataStream.writeInt(this.bufferUncompressedSize[i]); // Write uncompressed size -+ zstdDataStream.writeInt(this.chunkTimestamps[i]); // Write timestamp -+ } -+ for(int i = 0; i < 1024; i++) { -+ if(byteBuffers.get(i) != null) -+ zstdDataStream.write(byteBuffers.get(i), 0, byteBuffers.get(i).length); -+ } -+ zstdDataStream.close(); ++ File tempFile = new File(path.toString() + ".tmp"); ++ ++ try (FileOutputStream fileStream = new FileOutputStream(tempFile); ++ ByteArrayOutputStream zstdByteArray = new ByteArrayOutputStream(); ++ ZstdOutputStream zstdStream = new ZstdOutputStream(zstdByteArray, this.compressionLevel); ++ DataOutputStream zstdDataStream = new DataOutputStream(zstdStream); ++ DataOutputStream dataStream = new DataOutputStream(fileStream)) { ++ ++ dataStream.writeLong(SUPERBLOCK); ++ dataStream.writeByte(VERSION); ++ dataStream.writeLong(timestamp); ++ dataStream.writeByte(this.compressionLevel); + -+ dataStream.writeShort(chunkCount); ++ ArrayList byteBuffers = new ArrayList<>(); ++ for (int i = 0; i < 1024; i++) { ++ if (this.bufferUncompressedSize[i] != 0) { ++ chunkCount += 1; ++ byte[] content = new byte[bufferUncompressedSize[i]]; ++ this.decompressor.decompress(buffer[i], 0, content, 0, bufferUncompressedSize[i]); + -+ byte[] compressed = zstdByteArray.toByteArray(); ++ byteBuffers.add(content); ++ } else byteBuffers.add(null); ++ } ++ for (int i = 0; i < 1024; i++) { ++ zstdDataStream.writeInt(this.bufferUncompressedSize[i]); // Write uncompressed size ++ zstdDataStream.writeInt(this.chunkTimestamps[i]); // Write timestamp ++ } ++ for (int i = 0; i < 1024; i++) { ++ if (byteBuffers.get(i) != null) ++ zstdDataStream.write(byteBuffers.get(i), 0, byteBuffers.get(i).length); ++ } ++ zstdDataStream.close(); ++ ++ dataStream.writeShort(chunkCount); + -+ dataStream.writeInt(compressed.length); -+ dataStream.writeLong(0); ++ byte[] compressed = zstdByteArray.toByteArray(); + -+ dataStream.write(compressed, 0, compressed.length); -+ dataStream.writeLong(SUPERBLOCK); ++ dataStream.writeInt(compressed.length); ++ dataStream.writeLong(0); + -+ dataStream.flush(); -+ fileStream.getFD().sync(); -+ fileStream.getChannel().force(true); // Ensure atomicity on Btrfs -+ dataStream.close(); ++ dataStream.write(compressed, 0, compressed.length); ++ dataStream.writeLong(SUPERBLOCK); + -+ fileStream.close(); -+ Files.move(tempFile.toPath(), this.regionFile, StandardCopyOption.REPLACE_EXISTING); ++ dataStream.flush(); ++ fileStream.getFD().sync(); ++ fileStream.getChannel().force(true); // Ensure atomicity on Btrfs ++ } ++ Files.move(tempFile.toPath(), this.path, StandardCopyOption.REPLACE_EXISTING); + } + ++ + public void setStatus(int x, int z, ChunkStatus status) { + this.statuses[getChunkIndex(x, z)] = status; + } @@ -360,7 +371,7 @@ index 0000000000000000000000000000000000000000..b7ce89429675bde7f037793305275f4b + this.chunkTimestamps[index] = getTimestamp(); + this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize; + } catch (IOException e) { -+ LOGGER.error("Chunk write IOException " + e + " " + this.regionFile); ++ LOGGER.error("Chunk write IOException " + e + " " + this.path); + } + markToSave(); + } @@ -370,7 +381,6 @@ index 0000000000000000000000000000000000000000..b7ce89429675bde7f037793305275f4b + } + + private class ChunkBuffer extends ByteArrayOutputStream { -+ + private final ChunkPos pos; + + public ChunkBuffer(ChunkPos chunkcoordintpair) { @@ -423,12 +433,9 @@ index 0000000000000000000000000000000000000000..b7ce89429675bde7f037793305275f4b + } + + public void close() throws IOException { -+ close = true; -+ try { -+ flush(); -+ } catch(IOException e) { -+ throw new IOException("Region flush IOException " + e + " " + this.regionFile); -+ } ++ if (closed) return; ++ closed = true; ++ flush(); // sync + } + + private static int getChunkIndex(int x, int z) { @@ -446,13 +453,64 @@ index 0000000000000000000000000000000000000000..b7ce89429675bde7f037793305275f4b + public void setOversized(int x, int z, boolean something) {} + + public CompoundTag getOversizedData(int x, int z) throws IOException { -+ throw new IOException("getOversizedData is a stub " + this.regionFile); ++ throw new IOException("getOversizedData is a stub " + this.path); + } + + public boolean isOversized(int x, int z) { + return false; + } +} +diff --git a/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFileFlusher.java b/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFileFlusher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a5731e37aa63004476dd6db67191d5be6ce480be +--- /dev/null ++++ b/src/main/java/dev/kaiijumc/kaiiju/region/LinearRegionFileFlusher.java +@@ -0,0 +1,45 @@ ++package dev.kaiijumc.kaiiju.region; ++ ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import java.util.Queue; ++import java.util.concurrent.*; ++import dev.kaiijumc.kaiiju.KaiijuConfig; ++import org.bukkit.Bukkit; ++ ++public class LinearRegionFileFlusher { ++ private final Queue savingQueue = new LinkedBlockingQueue<>(); ++ private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor( ++ new ThreadFactoryBuilder() ++ .setNameFormat("linear-flush-scheduler") ++ .build() ++ ); ++ private final ExecutorService executor = Executors.newFixedThreadPool( ++ KaiijuConfig.linearFlushThreads, ++ new ThreadFactoryBuilder() ++ .setNameFormat("linear-flusher-%d") ++ .build() ++ ); ++ ++ public LinearRegionFileFlusher() { ++ Bukkit.getLogger().info("Using " + KaiijuConfig.linearFlushThreads + " threads for linear region flushing."); ++ scheduler.scheduleAtFixedRate(this::pollAndFlush, 0L, KaiijuConfig.linearFlushFrequency, TimeUnit.SECONDS); ++ } ++ ++ public void scheduleSave(LinearRegionFile regionFile) { ++ if (savingQueue.contains(regionFile)) return; ++ savingQueue.add(regionFile); ++ } ++ ++ private void pollAndFlush() { ++ while (!savingQueue.isEmpty()) { ++ LinearRegionFile regionFile = savingQueue.poll(); ++ if (!regionFile.closed && regionFile.isMarkedToSave()) ++ executor.execute(regionFile::flushWrapper); ++ } ++ } ++ ++ public void shutdown() { ++ executor.shutdown(); ++ scheduler.shutdown(); ++ } ++} diff --git a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java b/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java index 8a11e10b01fa012b2f98b1c193c53251e848f909..61001e76b38f8647b33e73b5cc15b4b6785cf49a 100644 --- a/src/main/java/io/papermc/paper/chunk/system/io/RegionFileIOThread.java @@ -587,7 +645,7 @@ index 25fe439c8d1e88a86e85ac9a4761425d98ee6c4f..c4d28d887b4cc71dc713b1e3f46bc80f regionFile.setStatus(chunkPos.x, chunkPos.z, ChunkSerializer.getStatus(compound)); } diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 042ca6b3faae5249210567f2c26dff404974e1ff..7099a44a2322ab390c21e74668c8657dcc9e5e87 100644 +index 042ca6b3faae5249210567f2c26dff404974e1ff..1446db0111ba21514640e09e48a32d54b4a97a0c 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -426,8 +426,8 @@ public class ServerLevel extends Level implements WorldGenLevel { @@ -596,7 +654,7 @@ index 042ca6b3faae5249210567f2c26dff404974e1ff..7099a44a2322ab390c21e74668c8657d - public EntityRegionFileStorage(Path directory, boolean dsync) { - super(directory, dsync); -+ public EntityRegionFileStorage(String format, int linearCompression, Path directory, boolean dsync) { // Kaiiju ++ public EntityRegionFileStorage(dev.kaiijumc.kaiiju.region.RegionFileFormat format, int linearCompression, Path directory, boolean dsync) { // Kaiiju + super(format, linearCompression, directory, dsync); // Kaiiju } @@ -754,7 +812,7 @@ index dcfe090c269d4cbcc2eb1b6f85392848bb34656c..d42c320179ae055b8675d1ce6ce1788e try (DataInputStream out = new DataInputStream(new java.io.BufferedInputStream(new InflaterInputStream(Files.newInputStream(file))))) { return NbtIo.read((java.io.DataInput) out); 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 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823f4b63a3a 100644 +index 81554c321a78258ff78da3801f00d0fb90b9e113..e91e8aef8b63ea9d94e0ecb3e4a62655a8c77ce1 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 @@ -22,9 +22,13 @@ public class RegionFileStorage implements AutoCloseable { @@ -836,7 +894,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 } // Paper end return regionfile; -@@ -126,28 +134,49 @@ public class RegionFileStorage implements AutoCloseable { +@@ -126,28 +134,45 @@ public class RegionFileStorage implements AutoCloseable { } // Paper end - cache regionfile does not exist state if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - configurable @@ -874,10 +932,6 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 + // Kaiiju end this.createRegionFile(regionPos); } -+ // Kaiiju start - Polyglot -+ if (dev.kaiijumc.kaiiju.KaiijuConfig.regionFormatDebug) -+ org.bukkit.Bukkit.getLogger().info("Opening file " + path1 + " with format " + this.format + " (existingOnly = " + existingOnly + ")"); -+ // Kaiiju end + // Paper end - cache regionfile does not exist state FileUtil.createDirectoriesSafe(this.folder); // Paper - only create directory if not existing only - moved from above @@ -893,7 +947,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 } // Paper end return regionfile1; -@@ -175,7 +204,7 @@ public class RegionFileStorage implements AutoCloseable { +@@ -175,7 +200,7 @@ public class RegionFileStorage implements AutoCloseable { } @@ -902,7 +956,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 synchronized (regionfile) { try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) { CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z); -@@ -222,14 +251,14 @@ public class RegionFileStorage implements AutoCloseable { +@@ -222,14 +247,14 @@ public class RegionFileStorage implements AutoCloseable { @Nullable public CompoundTag read(ChunkPos pos) throws IOException { // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing @@ -919,7 +973,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 // 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 -@@ -239,7 +268,7 @@ public class RegionFileStorage implements AutoCloseable { +@@ -239,7 +264,7 @@ public class RegionFileStorage implements AutoCloseable { // Paper start if (regionfile.isOversized(pos.x, pos.z)) { @@ -928,7 +982,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 return readOversizedChunk(regionfile, pos); } // Paper end -@@ -253,12 +282,12 @@ public class RegionFileStorage implements AutoCloseable { +@@ -253,12 +278,12 @@ public class RegionFileStorage implements AutoCloseable { if (this.isChunkData) { ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound); if (!chunkPos.equals(pos)) { @@ -944,7 +998,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 return null; } } -@@ -292,13 +321,13 @@ public class RegionFileStorage implements AutoCloseable { +@@ -292,13 +317,13 @@ public class RegionFileStorage implements AutoCloseable { return nbttagcompound; } finally { // Paper start @@ -960,7 +1014,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 if (regionfile == null) { return; } -@@ -328,7 +357,7 @@ public class RegionFileStorage implements AutoCloseable { +@@ -328,7 +353,7 @@ public class RegionFileStorage implements AutoCloseable { } protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { @@ -969,7 +1023,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 if (nbt == null && regionfile == null) { return; } -@@ -378,7 +407,7 @@ public class RegionFileStorage implements AutoCloseable { +@@ -378,7 +403,7 @@ public class RegionFileStorage implements AutoCloseable { } // Paper end } finally { // Paper start @@ -978,7 +1032,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 } // Paper end } -@@ -387,7 +416,7 @@ public class RegionFileStorage implements AutoCloseable { +@@ -387,7 +412,7 @@ public class RegionFileStorage implements AutoCloseable { ObjectIterator objectiterator = this.regionCache.values().iterator(); while (objectiterator.hasNext()) { @@ -987,7 +1041,7 @@ index 81554c321a78258ff78da3801f00d0fb90b9e113..b35a6a7daf768af3bc453fe18deec823 try { regionfile.close(); -@@ -403,7 +432,7 @@ public class RegionFileStorage implements AutoCloseable { +@@ -403,7 +428,7 @@ public class RegionFileStorage implements AutoCloseable { ObjectIterator objectiterator = this.regionCache.values().iterator(); while (objectiterator.hasNext()) { diff --git a/patches/server/0005-Network-configuration.patch b/patches/server/0005-Network-configuration.patch index 191343d..67cad84 100644 --- a/patches/server/0005-Network-configuration.patch +++ b/patches/server/0005-Network-configuration.patch @@ -5,12 +5,12 @@ Subject: [PATCH] Network configuration diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -index b86c90cc3601e666998cfa12f44515f605bb53eb..7c6d43d8a360530344ef296f4477750c8a298607 100644 +index b7f43cce80742aa0cd523e930772ff84946f3eef..fa829cef4033625470dfae29ddf777e6c5ab8c55 100644 --- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java +++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -@@ -198,4 +198,7 @@ public class KaiijuConfig { - private static void regionFormatSettings() { - regionFormatDebug = getBoolean("region-format.debug", regionFormatDebug); +@@ -203,4 +203,7 @@ public class KaiijuConfig { + else + linearFlushThreads = Math.max(linearFlushThreads, 1); } + + private static void networkSettings() { diff --git a/patches/server/0006-Send-null-entity-packets.patch b/patches/server/0006-Send-null-entity-packets.patch index 6bd89e1..3e583b5 100644 --- a/patches/server/0006-Send-null-entity-packets.patch +++ b/patches/server/0006-Send-null-entity-packets.patch @@ -6,12 +6,12 @@ Subject: [PATCH] Send null entity packets This is from Purpur. Don't send null entity packets. diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -index 7c6d43d8a360530344ef296f4477750c8a298607..ab08e11f13921163b8ff1ff51ff9e9b86d2b47c7 100644 +index fa829cef4033625470dfae29ddf777e6c5ab8c55..47e23a196ae5e44600a64184b69141c00235baca 100644 --- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java +++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -@@ -198,7 +198,10 @@ public class KaiijuConfig { - private static void regionFormatSettings() { - regionFormatDebug = getBoolean("region-format.debug", regionFormatDebug); +@@ -203,7 +203,10 @@ public class KaiijuConfig { + else + linearFlushThreads = Math.max(linearFlushThreads, 1); } + + public static boolean sendNullEntityPackets = true; @@ -21,7 +21,7 @@ index 7c6d43d8a360530344ef296f4477750c8a298607..ab08e11f13921163b8ff1ff51ff9e9b8 } } diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 6670e657e08e130f7e0368f418379fd1ece00cdf..5558d87ef182f82be7877455dd027082c6a80632 100644 +index 81d0b2933040a451441f660f9e46199ae3b111e3..cdbc4be679d7e096c1005eaf84b74c4877479c43 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -201,6 +201,11 @@ public class ServerEntity { diff --git a/patches/server/0007-Alternate-Keepalive.patch b/patches/server/0007-Alternate-Keepalive.patch index 3e0bcd3..654fcbe 100644 --- a/patches/server/0007-Alternate-Keepalive.patch +++ b/patches/server/0007-Alternate-Keepalive.patch @@ -7,10 +7,10 @@ Don't kick players because 1 keepalive is lost. This patch is from Purpur. diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -index ab08e11f13921163b8ff1ff51ff9e9b86d2b47c7..b42a3466f145a92608c8746fd4beb529b4a60b01 100644 +index 47e23a196ae5e44600a64184b69141c00235baca..95b53b50606ea0ad47f407bebbb9e2bd445c0e66 100644 --- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java +++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -@@ -200,8 +200,10 @@ public class KaiijuConfig { +@@ -205,8 +205,10 @@ public class KaiijuConfig { } public static boolean sendNullEntityPackets = true; diff --git a/patches/server/0008-Optimization-Configuration.patch b/patches/server/0008-Optimization-Configuration.patch index f16e451..a8992bc 100644 --- a/patches/server/0008-Optimization-Configuration.patch +++ b/patches/server/0008-Optimization-Configuration.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Optimization Configuration diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -index b42a3466f145a92608c8746fd4beb529b4a60b01..7b56cc1275319cf07a4a25280e0ff900bdca8afa 100644 +index 95b53b50606ea0ad47f407bebbb9e2bd445c0e66..8aab072a21b0775338b8235a84f5f675f385cf37 100644 --- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java +++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -@@ -206,4 +206,7 @@ public class KaiijuConfig { +@@ -211,4 +211,7 @@ public class KaiijuConfig { sendNullEntityPackets = getBoolean("network.send-null-entity-packets", sendNullEntityPackets); alternateKeepAlive = getBoolean("network.alternate-keepalive", alternateKeepAlive); } diff --git a/patches/server/0009-Gameplay-Configuration.patch b/patches/server/0009-Gameplay-Configuration.patch index 90e3612..1ca5062 100644 --- a/patches/server/0009-Gameplay-Configuration.patch +++ b/patches/server/0009-Gameplay-Configuration.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Gameplay Configuration diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -index 7b56cc1275319cf07a4a25280e0ff900bdca8afa..fdc4cdbf34ed10f6523dceac7c1931d3ca4eb522 100644 +index 8aab072a21b0775338b8235a84f5f675f385cf37..46ae5b84ee5b87b0ed0b93e920689e44288a50d2 100644 --- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java +++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -@@ -209,4 +209,7 @@ public class KaiijuConfig { +@@ -214,4 +214,7 @@ public class KaiijuConfig { private static void optimizationSettings() { } diff --git a/patches/server/0011-Server-mod-name.patch b/patches/server/0011-Server-mod-name.patch index 60347c7..7045518 100644 --- a/patches/server/0011-Server-mod-name.patch +++ b/patches/server/0011-Server-mod-name.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Server mod name diff --git a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -index fdc4cdbf34ed10f6523dceac7c1931d3ca4eb522..e1f50adfa216320bc53b460d11666064cb58c969 100644 +index 46ae5b84ee5b87b0ed0b93e920689e44288a50d2..778de435ca3c13ccc2f2d86030e3529436a2d945 100644 --- a/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java +++ b/src/main/java/dev/kaiijumc/kaiiju/KaiijuConfig.java -@@ -210,6 +210,9 @@ public class KaiijuConfig { +@@ -215,6 +215,9 @@ public class KaiijuConfig { private static void optimizationSettings() { } @@ -19,7 +19,7 @@ index fdc4cdbf34ed10f6523dceac7c1931d3ca4eb522..e1f50adfa216320bc53b460d11666064 } } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 06233380f7580726753de34c1fb23d6347c9ac94..1b3f895b51b634ee9cf69cdbbfd7a1e600de16f0 100644 +index bc9204d2c925437e9ff5c5d62d9faf38c2938e48..5168d16b7e9ed08bdedd0e386f9671a63b6859be 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1812,7 +1812,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop