From 2b5bedf14be2596915464d889c023e34a637688d Mon Sep 17 00:00:00 2001 From: MrHua269 Date: Sun, 15 Dec 2024 12:16:23 +0800 Subject: [PATCH] Add configurable region format framework & linear v2 region format support --- ...-region-format-framework-linear-v2-r.patch | 1159 +++++++++++++++++ ...-fixing-folia-spector-teleportation.patch} | 2 +- ...-entity-was-moving-to-another-regio.patch} | 2 +- ...olia-off-region-POI-accessing-issue.patch} | 0 ...-Force-disable-builtin-spark-plugin.patch} | 12 +- ...aiiju-Don-t-pathfind-outside-region.patch} | 0 ...ju-Vanilla-end-portal-teleportation.patch} | 2 +- ...iju-Entity-tick-and-removal-limiter.patch} | 2 +- ...ch => 0026-Petal-Reduce-sensor-work.patch} | 0 ...Cache-climbing-check-for-activation.patch} | 0 ...erfish-Reduce-chunk-loading-lookups.patch} | 0 ...per-6045-block-goal-shouldn-t-load-.patch} | 0 ...ish-Reduce-projectile-chunk-loading.patch} | 0 ...heck-for-spooky-season-once-an-hour.patch} | 0 ...032-Pufferfish-Optimize-suffocation.patch} | 0 ...ferfish-Dynamic-Activation-of-Brain.patch} | 2 +- ...le-goal-selector-during-inactive-ti.patch} | 2 +- ...h => 0035-Pufferfish-SIMD-Utilities.patch} | 4 +- ...le-Variable-entity-wake-up-duration.patch} | 0 ...=> 0037-Gale-Optimize-sun-burn-tick.patch} | 0 ...38-Gale-Use-platform-math-functions.patch} | 0 ...0039-Gale-Optimize-noise-generation.patch} | 0 ...a-and-Optional-allocation-in-Entity.patch} | 0 ...ttle-tracker-map-with-optimized-col.patch} | 0 ...kip-entity-move-if-movement-is-zero.patch} | 2 +- ...parkly-Paper-Optimize-canSee-checks.patch} | 0 ...-MapItem-update-if-the-map-does-not.patch} | 0 ...-distanceToSqr-call-in-ServerEntity.patch} | 0 ...I-and-display-of-chunkhot-in-tpsbar.patch} | 8 +- ...047-Leaf-Skip-event-if-no-listeners.patch} | 0 ...8-Purpur-use-alternative-keep-alive.patch} | 0 ...-start-tick-and-finished-tick-event.patch} | 0 ...x-MC-2025.patch => 0050-Fix-MC-2025.patch} | 2 +- ...=> 0051-FoliaPR-Add-TPS-From-Region.patch} | 2 +- 34 files changed, 1180 insertions(+), 21 deletions(-) create mode 100644 patches/server/0018-Add-configurable-region-format-framework-linear-v2-r.patch rename patches/server/{0018-Try-fixing-folia-spector-teleportation.patch => 0019-Try-fixing-folia-spector-teleportation.patch} (97%) rename patches/server/{0019-Teleport-async-if-entity-was-moving-to-another-regio.patch => 0020-Teleport-async-if-entity-was-moving-to-another-regio.patch} (97%) rename patches/server/{0020-Try-fixing-folia-off-region-POI-accessing-issue.patch => 0021-Try-fixing-folia-off-region-POI-accessing-issue.patch} (100%) rename patches/server/{0021-Force-disable-builtin-spark-plugin.patch => 0022-Force-disable-builtin-spark-plugin.patch} (96%) rename patches/server/{0022-Kaiiju-Don-t-pathfind-outside-region.patch => 0023-Kaiiju-Don-t-pathfind-outside-region.patch} (100%) rename patches/server/{0023-Kaiiju-Vanilla-end-portal-teleportation.patch => 0024-Kaiiju-Vanilla-end-portal-teleportation.patch} (97%) rename patches/server/{0024-Kaiiju-Entity-tick-and-removal-limiter.patch => 0025-Kaiiju-Entity-tick-and-removal-limiter.patch} (99%) rename patches/server/{0025-Petal-Reduce-sensor-work.patch => 0026-Petal-Reduce-sensor-work.patch} (100%) rename patches/server/{0026-Pufferfish-Cache-climbing-check-for-activation.patch => 0027-Pufferfish-Cache-climbing-check-for-activation.patch} (100%) rename patches/server/{0027-Pufferfish-Reduce-chunk-loading-lookups.patch => 0028-Pufferfish-Reduce-chunk-loading-lookups.patch} (100%) rename patches/server/{0028-Pufferfish-Fix-Paper-6045-block-goal-shouldn-t-load-.patch => 0029-Pufferfish-Fix-Paper-6045-block-goal-shouldn-t-load-.patch} (100%) rename patches/server/{0029-Pufferfish-Reduce-projectile-chunk-loading.patch => 0030-Pufferfish-Reduce-projectile-chunk-loading.patch} (100%) rename patches/server/{0030-Pufferfish-Only-check-for-spooky-season-once-an-hour.patch => 0031-Pufferfish-Only-check-for-spooky-season-once-an-hour.patch} (100%) rename patches/server/{0031-Pufferfish-Optimize-suffocation.patch => 0032-Pufferfish-Optimize-suffocation.patch} (100%) rename patches/server/{0032-Pufferfish-Dynamic-Activation-of-Brain.patch => 0033-Pufferfish-Dynamic-Activation-of-Brain.patch} (99%) rename patches/server/{0033-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch => 0034-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch} (96%) rename patches/server/{0034-Pufferfish-SIMD-Utilities.patch => 0035-Pufferfish-SIMD-Utilities.patch} (96%) rename patches/server/{0035-Gale-Variable-entity-wake-up-duration.patch => 0036-Gale-Variable-entity-wake-up-duration.patch} (100%) rename patches/server/{0036-Gale-Optimize-sun-burn-tick.patch => 0037-Gale-Optimize-sun-burn-tick.patch} (100%) rename patches/server/{0037-Gale-Use-platform-math-functions.patch => 0038-Gale-Use-platform-math-functions.patch} (100%) rename patches/server/{0038-Gale-Optimize-noise-generation.patch => 0039-Gale-Optimize-noise-generation.patch} (100%) rename patches/server/{0039-Gale-Reduce-lambda-and-Optional-allocation-in-Entity.patch => 0040-Gale-Reduce-lambda-and-Optional-allocation-in-Entity.patch} (100%) rename patches/server/{0040-Gale-Replace-throttle-tracker-map-with-optimized-col.patch => 0041-Gale-Replace-throttle-tracker-map-with-optimized-col.patch} (100%) rename patches/server/{0041-Gale-Skip-entity-move-if-movement-is-zero.patch => 0042-Gale-Skip-entity-move-if-movement-is-zero.patch} (95%) rename patches/server/{0042-Sparkly-Paper-Optimize-canSee-checks.patch => 0043-Sparkly-Paper-Optimize-canSee-checks.patch} (100%) rename patches/server/{0043-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch => 0044-SparklyPaper-Skip-MapItem-update-if-the-map-does-not.patch} (100%) rename patches/server/{0044-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch => 0045-SparklyPaper-Skip-distanceToSqr-call-in-ServerEntity.patch} (100%) rename patches/server/{0045-KioCG-Chunk-API-and-display-of-chunkhot-in-tpsbar.patch => 0046-KioCG-Chunk-API-and-display-of-chunkhot-in-tpsbar.patch} (98%) rename patches/server/{0046-Leaf-Skip-event-if-no-listeners.patch => 0047-Leaf-Skip-event-if-no-listeners.patch} (100%) rename patches/server/{0047-Purpur-use-alternative-keep-alive.patch => 0048-Purpur-use-alternative-keep-alive.patch} (100%) rename patches/server/{0048-Threaded-region-start-tick-and-finished-tick-event.patch => 0049-Threaded-region-start-tick-and-finished-tick-event.patch} (100%) rename patches/server/{0049-Fix-MC-2025.patch => 0050-Fix-MC-2025.patch} (95%) rename patches/server/{0050-FoliaPR-Add-TPS-From-Region.patch => 0051-FoliaPR-Add-TPS-From-Region.patch} (96%) diff --git a/patches/server/0018-Add-configurable-region-format-framework-linear-v2-r.patch b/patches/server/0018-Add-configurable-region-format-framework-linear-v2-r.patch new file mode 100644 index 0000000..e4bab57 --- /dev/null +++ b/patches/server/0018-Add-configurable-region-format-framework-linear-v2-r.patch @@ -0,0 +1,1159 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrHua269 +Date: Sun, 15 Dec 2024 12:11:39 +0800 +Subject: [PATCH] Add configurable region format framework & linear v2 region + format support + + +diff --git a/build.gradle.kts b/build.gradle.kts +index 017dc1ff0a7c6f7c50a57bf615fc31947ed49639..8f23bf19618382ccf5fd10a0b17b57cd445dea58 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -82,6 +82,11 @@ dependencies { + implementation("me.lucko:spark-api:0.1-20240720.200737-2") + implementation("me.lucko:spark-paper:1.10.119-SNAPSHOT") + // Paper end - spark ++ // Abomination start ++ implementation("com.github.luben:zstd-jni:1.5.4-1") ++ implementation("org.lz4:lz4-java:1.8.0") ++ implementation("net.openhft:zero-allocation-hashing:0.16") ++ // Abomination end + } + + paperweight { +diff --git a/src/main/java/abomination/IRegionFile.java b/src/main/java/abomination/IRegionFile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d92f1d549c7e01daa6b5bba7d405e462a9d57e27 +--- /dev/null ++++ b/src/main/java/abomination/IRegionFile.java +@@ -0,0 +1,39 @@ ++package abomination; ++ ++import ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.level.ChunkPos; ++ ++import java.io.DataInputStream; ++import java.io.DataOutputStream; ++import java.io.IOException; ++import java.nio.ByteBuffer; ++import java.nio.file.Path; ++ ++public interface IRegionFile extends ChunkSystemRegionFile { ++ Path getPath(); ++ ++ DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException; ++ ++ boolean doesChunkExist(ChunkPos pos) throws Exception; ++ ++ DataOutputStream getChunkDataOutputStream(ChunkPos pos) throws IOException; ++ ++ void flush() throws IOException; ++ ++ void clear(ChunkPos pos) throws IOException; ++ ++ boolean hasChunk(ChunkPos pos); ++ ++ void close() throws IOException; ++ ++ void write(ChunkPos pos, ByteBuffer buf) throws IOException; ++ ++ CompoundTag getOversizedData(int x, int z) throws IOException; ++ ++ boolean isOversized(int x, int z); ++ ++ boolean recalculateHeader() throws IOException; ++ ++ void setOversized(int x, int z, boolean oversized) throws IOException; ++} +diff --git a/src/main/java/abomination/LinearRegionFile.java b/src/main/java/abomination/LinearRegionFile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4b4a0f9b514909debf36e0937338f27eb2f99239 +--- /dev/null ++++ b/src/main/java/abomination/LinearRegionFile.java +@@ -0,0 +1,668 @@ ++package abomination; ++ ++import ca.spottedleaf.concurrentutil.executor.PrioritisedExecutor; ++import ca.spottedleaf.concurrentutil.executor.thread.PrioritisedThreadPool; ++import ca.spottedleaf.concurrentutil.util.Priority; ++import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO; ++import com.github.luben.zstd.ZstdInputStream; ++import com.github.luben.zstd.ZstdOutputStream; ++import com.mojang.logging.LogUtils; ++import me.earthme.luminol.config.modules.misc.RegionFormatConfig; ++import net.jpountz.lz4.LZ4Compressor; ++import net.jpountz.lz4.LZ4Factory; ++import net.jpountz.lz4.LZ4FastDecompressor; ++import net.minecraft.server.MinecraftServer; ++import net.openhft.hashing.LongHashFunction; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.level.chunk.storage.RegionStorageInfo; ++import net.minecraft.world.level.chunk.storage.RegionFileVersion; ++import net.minecraft.world.level.ChunkPos; ++import org.slf4j.Logger; ++ ++import javax.annotation.Nullable; ++import java.io.*; ++import java.nio.ByteBuffer; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.nio.file.StandardCopyOption; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.Executor; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.locks.ReentrantLock; ++import java.util.stream.IntStream; ++ ++// LinearRegionFile_implementation_version_0_5byXymb ++// Just gonna use this string to inform other forks about updates ;-) ++public class LinearRegionFile extends Thread implements IRegionFile{ ++ private static final long SUPERBLOCK = 0xc3ff13183cca9d9aL; ++ private static final byte VERSION = 3; ++ private static final int HEADER_SIZE = 27; ++ private static final int FOOTER_SIZE = 8; ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ private byte[][] bucketBuffers; ++ private final byte[][] buffer = new byte[1024][]; ++ private final int[] bufferUncompressedSize = new int[1024]; ++ ++ private final long[] chunkTimestamps = new long[1024]; ++ private final Object markedToSaveLock = new Object(); ++ ++ 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; ++ private int gridSize = 8; ++ private int bucketSize = 4; ++ ++ private final AtomicInteger modifiedChunkCount = new AtomicInteger(0); ++ private PrioritisedExecutor.PrioritisedTask ioTask = null; ++ ++ public Path getRegionFile() { ++ return this.regionFile; ++ } ++ ++ public ReentrantLock getFileLock() { ++ return this.fileLock; ++ } ++ ++ private int chunkToBucketIdx(int chunkX, int chunkZ) { ++ int bx = chunkX / bucketSize, bz = chunkZ / bucketSize; ++ return bx * gridSize + bz; ++ } ++ ++ private void openBucket(int chunkX, int chunkZ) { ++ chunkX = Math.floorMod(chunkX, 32); ++ chunkZ = Math.floorMod(chunkZ, 32); ++ int idx = chunkToBucketIdx(chunkX, chunkZ); ++ ++ if (bucketBuffers == null) return; ++ if (bucketBuffers[idx] != null) { ++ try { ++ ByteArrayInputStream bucketByteStream = new ByteArrayInputStream(bucketBuffers[idx]); ++ ZstdInputStream zstdStream = new ZstdInputStream(bucketByteStream); ++ ByteBuffer bucketBuffer = ByteBuffer.wrap(zstdStream.readAllBytes()); ++ ++ int bx = chunkX / bucketSize, bz = chunkZ / bucketSize; ++ ++ for (int cx = 0; cx < 32 / gridSize; cx++) { ++ for (int cz = 0; cz < 32 / gridSize; cz++) { ++ int chunkIndex = (bx * (32 / gridSize) + cx) + (bz * (32 / gridSize) + cz) * 32; ++ ++ int chunkSize = bucketBuffer.getInt(); ++ long timestamp = bucketBuffer.getLong(); ++ this.chunkTimestamps[chunkIndex] = timestamp; ++ ++ if (chunkSize > 0) { ++ byte[] chunkData = new byte[chunkSize - 8]; ++ bucketBuffer.get(chunkData); ++ ++ int maxCompressedLength = this.compressor.maxCompressedLength(chunkData.length); ++ byte[] compressed = new byte[maxCompressedLength]; ++ int compressedLength = this.compressor.compress(chunkData, 0, chunkData.length, compressed, 0, maxCompressedLength); ++ byte[] finalCompressed = new byte[compressedLength]; ++ System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength); ++ ++ // TODO: Optimization - return the requested chunk immediately to save on one LZ4 decompression ++ this.buffer[chunkIndex] = finalCompressed; ++ this.bufferUncompressedSize[chunkIndex] = chunkData.length; ++ } ++ } ++ } ++ } catch (IOException ex) { ++ throw new RuntimeException("Region file corrupted: " + regionFile + " bucket: " + idx); ++ // TODO: Make sure the server crashes instead of corrupting the world ++ } ++ bucketBuffers[idx] = null; ++ } ++ } ++ ++ public boolean regionFileOpen = false; ++ ++ private synchronized void openRegionFile() { ++ if (regionFileOpen) return; ++ regionFileOpen = true; ++ ++ File regionFile = new File(this.regionFile.toString()); ++ ++ if(!regionFile.canRead()) { ++ //this.start(); ++ return; ++ } ++ ++ try { ++ byte[] fileContent = Files.readAllBytes(this.regionFile); ++ ByteBuffer buffer = ByteBuffer.wrap(fileContent); ++ ++ long superBlock = buffer.getLong(); ++ if (superBlock != SUPERBLOCK) ++ throw new RuntimeException("Invalid superblock: " + superBlock + " file " + this.regionFile); ++ ++ byte version = buffer.get(); ++ if (version == 1 || version == 2) { ++ parseLinearV1(buffer); ++ } else if (version == 3) { ++ parseLinearV2(buffer); ++ } else { ++ throw new RuntimeException("Invalid version: " + version + " file " + this.regionFile); ++ } ++ ++ //this.start(); ++ } catch (IOException e) { ++ throw new RuntimeException("Failed to open region file " + this.regionFile, e); ++ } ++ } ++ ++ private void parseLinearV1(ByteBuffer buffer) throws IOException { ++ final int HEADER_SIZE = 32; ++ final int FOOTER_SIZE = 8; ++ ++ // Skip newestTimestamp (Long) + Compression level (Byte) + Chunk count (Short): Unused. ++ buffer.position(buffer.position() + 11); ++ ++ int dataCount = buffer.getInt(); ++ long fileLength = this.regionFile.toFile().length(); ++ if (fileLength != HEADER_SIZE + dataCount + FOOTER_SIZE) { ++ throw new IOException("Invalid file length: " + this.regionFile + " " + fileLength + " " + (HEADER_SIZE + dataCount + FOOTER_SIZE)); ++ } ++ ++ buffer.position(buffer.position() + 8); // Skip data hash (Long): Unused. ++ ++ byte[] rawCompressed = new byte[dataCount]; ++ buffer.get(rawCompressed); ++ ++ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(rawCompressed); ++ ZstdInputStream zstdInputStream = new ZstdInputStream(byteArrayInputStream); ++ ByteBuffer decompressedBuffer = ByteBuffer.wrap(zstdInputStream.readAllBytes()); ++ ++ int[] starts = new int[1024]; ++ for (int i = 0; i < 1024; i++) { ++ starts[i] = decompressedBuffer.getInt(); ++ decompressedBuffer.getInt(); // Skip timestamps (Int): Unused. ++ } ++ ++ for (int i = 0; i < 1024; i++) { ++ if (starts[i] > 0) { ++ int size = starts[i]; ++ byte[] chunkData = new byte[size]; ++ decompressedBuffer.get(chunkData); ++ ++ int maxCompressedLength = this.compressor.maxCompressedLength(size); ++ byte[] compressed = new byte[maxCompressedLength]; ++ int compressedLength = this.compressor.compress(chunkData, 0, size, compressed, 0, maxCompressedLength); ++ byte[] finalCompressed = new byte[compressedLength]; ++ System.arraycopy(compressed, 0, finalCompressed, 0, compressedLength); ++ ++ this.buffer[i] = finalCompressed; ++ this.bufferUncompressedSize[i] = size; ++ this.chunkTimestamps[i] = getTimestamp(); // Use current timestamp as we don't have the original ++ } ++ } ++ } ++ ++ private void parseLinearV2(ByteBuffer buffer) throws IOException { ++ buffer.getLong(); // Skip newestTimestamp (Long) ++ gridSize = buffer.get(); ++ if (gridSize != 1 && gridSize != 2 && gridSize != 4 && gridSize != 8 && gridSize != 16 && gridSize != 32) ++ throw new RuntimeException("Invalid grid size: " + gridSize + " file " + this.regionFile); ++ bucketSize = 32 / gridSize; ++ ++ buffer.getInt(); // Skip region_x (Int) ++ buffer.getInt(); // Skip region_z (Int) ++ ++ boolean[] chunkExistenceBitmap = deserializeExistenceBitmap(buffer); ++ ++ while (true) { ++ byte featureNameLength = buffer.get(); ++ if (featureNameLength == 0) break; ++ byte[] featureNameBytes = new byte[featureNameLength]; ++ buffer.get(featureNameBytes); ++ String featureName = new String(featureNameBytes); ++ int featureValue = buffer.getInt(); ++ // System.out.println("NBT Feature: " + featureName + " = " + featureValue); ++ } ++ ++ int[] bucketSizes = new int[gridSize * gridSize]; ++ byte[] bucketCompressionLevels = new byte[gridSize * gridSize]; ++ long[] bucketHashes = new long[gridSize * gridSize]; ++ for (int i = 0; i < gridSize * gridSize; i++) { ++ bucketSizes[i] = buffer.getInt(); ++ bucketCompressionLevels[i] = buffer.get(); ++ bucketHashes[i] = buffer.getLong(); ++ } ++ ++ bucketBuffers = new byte[gridSize * gridSize][]; ++ for (int i = 0; i < gridSize * gridSize; i++) { ++ if (bucketSizes[i] > 0) { ++ bucketBuffers[i] = new byte[bucketSizes[i]]; ++ buffer.get(bucketBuffers[i]); ++ long rawHash = LongHashFunction.xx().hashBytes(bucketBuffers[i]); ++ if (rawHash != bucketHashes[i]) throw new IOException("Region file hash incorrect " + this.regionFile); ++ } ++ } ++ ++ long footerSuperBlock = buffer.getLong(); ++ if (footerSuperBlock != SUPERBLOCK) ++ throw new IOException("Footer superblock invalid " + this.regionFile); ++ } ++ ++ public LinearRegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, int compressionLevel) throws IOException { ++ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, compressionLevel); ++ } ++ ++ public LinearRegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, int compressionLevel) throws IOException { ++ this.regionFile = path; ++ this.compressionLevel = compressionLevel; ++ ++ this.compressor = LZ4Factory.fastestInstance().fastCompressor(); ++ this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); ++ } ++ ++ private synchronized void markToSave() { ++ synchronized(markedToSaveLock) { ++ markedToSave = true; ++ } ++ } ++ ++ private synchronized boolean isMarkedToSave() { ++ synchronized(markedToSaveLock) { ++ if(markedToSave) { ++ markedToSave = false; ++ return true; ++ } ++ return false; ++ } ++ } ++ ++ private static final AtomicInteger ioThreadId = new AtomicInteger(); ++ private static final PrioritisedThreadPool linearIOThreadPool = new PrioritisedThreadPool(thread -> { ++ thread.setName("Linear RegionFile IO Thread- " + ioThreadId.getAndIncrement()); ++ thread.setContextClassLoader(MinecraftServer.class.getClassLoader()); ++ }); ++ public static final long WORKER_QUEUE_HOLD_TIME = (long)(20.0e6); // 20ms ++ private static PrioritisedExecutor linearIOExecutor; ++ ++ /* ++ private static final int SAVE_THREAD_MAX_COUNT = 6; ++ private static final Object saveLock = new Object(); ++ private static int activeSaveThreads = 0; ++ */ ++ ++ public static void initIOExecutor() { ++ linearIOExecutor = linearIOThreadPool.createExecutorGroup(1, 0).createExecutor(RegionFormatConfig.linearIoThreadCount, WORKER_QUEUE_HOLD_TIME, 0); ++ } ++ ++ public static void shutdownIOExecutor() { ++ linearIOThreadPool.shutdown(true); ++ } ++ ++ /*public void run() { ++ try { ++ while (!close) { ++ synchronized (saveLock) { ++ if (markedToSave && activeSaveThreads < SAVE_THREAD_MAX_COUNT) { ++ activeSaveThreads++; ++ Thread saveThread = new Thread(() -> { ++ try { ++ flush(); ++ } catch (IOException ex) { ++ LOGGER.error("Region file " + this.regionFile.toAbsolutePath() + " flush failed", ex); ++ } finally { ++ synchronized (saveLock) { ++ activeSaveThreads--; ++ } ++ } ++ }, "RegionFileFlush"); ++ saveThread.setPriority(Thread.NORM_PRIORITY - 3); ++ saveThread.start(); ++ } ++ } ++ Thread.sleep(100); ++ } ++ } catch(InterruptedException ignored) {} ++ }*/ ++ ++ public synchronized boolean doesChunkExist(ChunkPos pos) throws Exception { ++ openRegionFile(); ++ throw new Exception("doesChunkExist is a stub"); ++ } ++ ++ public synchronized void flush() throws IOException { ++ if(!isMarkedToSave()) return; ++ ++ openRegionFile(); ++ ++ long timestamp = getTimestamp(); ++ ++ long writeStart = System.nanoTime(); ++ File tempFile = new File(regionFile.toString() + ".tmp"); ++ FileOutputStream fileStream = new FileOutputStream(tempFile); ++ DataOutputStream dataStream = new DataOutputStream(fileStream); ++ ++ dataStream.writeLong(SUPERBLOCK); ++ dataStream.writeByte(VERSION); ++ dataStream.writeLong(timestamp); ++ dataStream.writeByte(gridSize); ++ ++ String fileName = regionFile.getFileName().toString(); ++ String[] parts = fileName.split("\\."); ++ int regionX = 0; ++ int regionZ = 0; ++ try { ++ if (parts.length >= 4) { ++ regionX = Integer.parseInt(parts[1]); ++ regionZ = Integer.parseInt(parts[2]); ++ } else { ++ LOGGER.warn("Unexpected file name format: " + fileName); ++ } ++ } catch (NumberFormatException e) { ++ LOGGER.error("Failed to parse region coordinates from file name: " + fileName, e); ++ } ++ ++ dataStream.writeInt(regionX); ++ dataStream.writeInt(regionZ); ++ ++ boolean[] chunkExistenceBitmap = new boolean[1024]; ++ for (int i = 0; i < 1024; i++) { ++ chunkExistenceBitmap[i] = (this.bufferUncompressedSize[i] > 0); ++ } ++ writeSerializedExistenceBitmap(dataStream, chunkExistenceBitmap); ++ ++ writeNBTFeatures(dataStream); ++ ++ int bucketMisses = 0; ++ byte[][] buckets = new byte[gridSize * gridSize][]; ++ for (int bx = 0; bx < gridSize; bx++) { ++ for (int bz = 0; bz < gridSize; bz++) { ++ if (bucketBuffers != null && bucketBuffers[bx * gridSize + bz] != null) { ++ buckets[bx * gridSize + bz] = bucketBuffers[bx * gridSize + bz]; ++ continue; ++ } ++ bucketMisses++; ++ ++ ByteArrayOutputStream bucketStream = new ByteArrayOutputStream(); ++ ZstdOutputStream zstdStream = new ZstdOutputStream(bucketStream, this.compressionLevel); ++ DataOutputStream bucketDataStream = new DataOutputStream(zstdStream); ++ ++ boolean hasData = false; ++ for (int cx = 0; cx < 32 / gridSize; cx++) { ++ for (int cz = 0; cz < 32 / gridSize; cz++) { ++ int chunkIndex = (bx * 32 / gridSize + cx) + (bz * 32 / gridSize + cz) * 32; ++ if (this.bufferUncompressedSize[chunkIndex] > 0) { ++ hasData = true; ++ byte[] chunkData = new byte[this.bufferUncompressedSize[chunkIndex]]; ++ this.decompressor.decompress(this.buffer[chunkIndex], 0, chunkData, 0, this.bufferUncompressedSize[chunkIndex]); ++ bucketDataStream.writeInt(chunkData.length + 8); ++ bucketDataStream.writeLong(this.chunkTimestamps[chunkIndex]); ++ bucketDataStream.write(chunkData); ++ } else { ++ bucketDataStream.writeInt(0); ++ bucketDataStream.writeLong(this.chunkTimestamps[chunkIndex]); ++ } ++ } ++ } ++ bucketDataStream.close(); ++ ++ if (hasData) { ++ buckets[bx * gridSize + bz] = bucketStream.toByteArray(); ++ } ++ } ++ } ++ ++ for (int i = 0; i < gridSize * gridSize; i++) { ++ dataStream.writeInt(buckets[i] != null ? buckets[i].length : 0); ++ dataStream.writeByte(this.compressionLevel); ++ long rawHash = 0; ++ if (buckets[i] != null) { ++ rawHash = LongHashFunction.xx().hashBytes(buckets[i]); ++ } ++ dataStream.writeLong(rawHash); ++ } ++ ++ for (int i = 0; i < gridSize * gridSize; i++) { ++ if (buckets[i] != null) { ++ dataStream.write(buckets[i]); ++ } ++ } ++ ++ dataStream.writeLong(SUPERBLOCK); ++ ++ dataStream.flush(); ++ fileStream.getFD().sync(); ++ fileStream.getChannel().force(true); // Ensure atomicity on Btrfs ++ dataStream.close(); ++ ++ fileStream.close(); ++ Files.move(tempFile.toPath(), this.regionFile, StandardCopyOption.REPLACE_EXISTING); ++//System.out.println("writeStart REGION FILE FLUSH " + (System.nanoTime() - writeStart) + " misses: " + bucketMisses); ++ this.modifiedChunkCount.set(0); ++ } ++ ++ private void writeNBTFeatures(DataOutputStream dataStream) throws IOException { ++ // writeNBTFeature(dataStream, "example", 1); ++ dataStream.writeByte(0); // End of NBT features ++ } ++ ++ private void writeNBTFeature(DataOutputStream dataStream, String featureName, int featureValue) throws IOException { ++ byte[] featureNameBytes = featureName.getBytes(); ++ dataStream.writeByte(featureNameBytes.length); ++ dataStream.write(featureNameBytes); ++ dataStream.writeInt(featureValue); ++ } ++ ++ public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Abomination - prevent chunk dupe ++ ++ public synchronized void write(ChunkPos pos, ByteBuffer buffer) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ try { ++ byte[] b = toByteArray(new ByteArrayInputStream(buffer.array())); ++ int uncompressedSize = b.length; ++ ++ if (uncompressedSize > MAX_CHUNK_SIZE) { ++ LOGGER.error("Chunk dupe attempt " + this.regionFile); ++ clear(pos); ++ } else { ++ int maxCompressedLength = this.compressor.maxCompressedLength(b.length); ++ byte[] compressed = new byte[maxCompressedLength]; ++ int compressedLength = this.compressor.compress(b, 0, b.length, compressed, 0, maxCompressedLength); ++ b = new byte[compressedLength]; ++ System.arraycopy(compressed, 0, b, 0, compressedLength); ++ ++ int index = getChunkIndex(pos.x, pos.z); ++ this.buffer[index] = b; ++ this.chunkTimestamps[index] = getTimestamp(); ++ this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] = uncompressedSize; ++ } ++ } catch (IOException e) { ++ LOGGER.error("Chunk write IOException " + e + " " + this.regionFile); ++ } ++ markToSave(); ++ this.modifiedChunkCount.getAndIncrement(); ++ ++ if (ioTask == null) { ++ this.scheduleSave(); ++ }else { ++ if (this.ioTask.getPriority() == Priority.COMPLETING) { ++ this.scheduleSave(); ++ return; ++ } ++ ++ this.ioTask.raisePriority(this.computeSavePriority()); ++ } ++ } ++ ++ private Priority computeSavePriority() { ++ final int currentModifiedChunkCount = this.modifiedChunkCount.get(); ++ ++ final int[] ordinals = new int[]{2, 3, 4, 5, 6}; ++ final int[] thresholds = new int[]{20, 40, 60, 80, 100}; ++ final int coverPercent = (currentModifiedChunkCount / 1024) * 100; ++ ++ int actualOrdinal = 6; ++ for (int i = 0; i < thresholds.length; i++) { ++ if (coverPercent >= thresholds[i]) { ++ actualOrdinal = ordinals[i]; ++ } ++ } ++ ++ return Priority.values()[actualOrdinal]; ++ } ++ ++ private void scheduleSave() { ++ final PrioritisedExecutor.PrioritisedTask created = linearIOExecutor.createTask(() -> { ++ try { ++ synchronized (this) { ++ if (!this.isMarkedToSave() && !this.close) { ++ return; ++ } ++ } ++ ++ this.flush(); ++ } catch (IOException e) { ++ throw new RuntimeException(e); ++ } ++ }, this.computeSavePriority()); ++ ++ created.queue(); ++ ++ this.ioTask = created; ++ } ++ ++ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ return new DataOutputStream(new BufferedOutputStream(new LinearRegionFile.ChunkBuffer(pos))); ++ } ++ ++ @Override ++ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(CompoundTag data, ChunkPos pos) throws IOException { ++ final DataOutputStream out = this.getChunkDataOutputStream(pos); ++ ++ return new ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData( ++ data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE, ++ out, regionFile -> out.close() ++ ); ++ } ++ ++ private class ChunkBuffer extends ByteArrayOutputStream { ++ ++ private final ChunkPos pos; ++ ++ public ChunkBuffer(ChunkPos chunkcoordintpair) { ++ super(); ++ this.pos = chunkcoordintpair; ++ } ++ ++ public void close() throws IOException { ++ ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); ++ LinearRegionFile.this.write(this.pos, bytebuffer); ++ } ++ } ++ ++ private byte[] toByteArray(InputStream in) throws IOException { ++ ByteArrayOutputStream out = new ByteArrayOutputStream(); ++ byte[] tempBuffer = new byte[4096]; ++ ++ int length; ++ while ((length = in.read(tempBuffer)) >= 0) { ++ out.write(tempBuffer, 0, length); ++ } ++ ++ return out.toByteArray(); ++ } ++ ++ @Nullable ++ public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ ++ if(this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) { ++ byte[] content = new byte[bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]]; ++ this.decompressor.decompress(this.buffer[getChunkIndex(pos.x, pos.z)], 0, content, 0, bufferUncompressedSize[getChunkIndex(pos.x, pos.z)]); ++ return new DataInputStream(new ByteArrayInputStream(content)); ++ } ++ return null; ++ } ++ ++ public synchronized void clear(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ int i = getChunkIndex(pos.x, pos.z); ++ this.buffer[i] = null; ++ this.bufferUncompressedSize[i] = 0; ++ this.chunkTimestamps[i] = 0; ++ markToSave(); ++ } ++ ++ public synchronized boolean hasChunk(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos.x, pos.z); ++ return this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] > 0; ++ } ++ ++ public synchronized void close() throws IOException { ++ openRegionFile(); ++ close = true; ++ try { ++ flush(); ++ } catch(IOException e) { ++ throw new IOException("Region flush IOException " + e + " " + this.regionFile); ++ } ++ } ++ ++ private static int getChunkIndex(int x, int z) { ++ return (x & 31) + ((z & 31) << 5); ++ } ++ ++ private static int getTimestamp() { ++ return (int) (System.currentTimeMillis() / 1000L); ++ } ++ ++ public boolean recalculateHeader() { ++ return false; ++ } ++ ++ 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); ++ } ++ ++ public boolean isOversized(int x, int z) { ++ return false; ++ } ++ ++ public Path getPath() { ++ return this.regionFile; ++ } ++ ++ private boolean[] deserializeExistenceBitmap(ByteBuffer buffer) { ++ boolean[] result = new boolean[1024]; ++ for (int i = 0; i < 128; i++) { ++ byte b = buffer.get(); ++ for (int j = 0; j < 8; j++) { ++ result[i * 8 + j] = ((b >> (7 - j)) & 1) == 1; ++ } ++ } ++ return result; ++ } ++ ++ private void writeSerializedExistenceBitmap(DataOutputStream out, boolean[] bitmap) throws IOException { ++ for (int i = 0; i < 128; i++) { ++ byte b = 0; ++ for (int j = 0; j < 8; j++) { ++ if (bitmap[i * 8 + j]) { ++ b |= (1 << (7 - j)); ++ } ++ } ++ out.writeByte(b); ++ } ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java +index a814512fcfb85312474ae2c2c21443843bf57831..2e084a5b28cbe4737f48c25e10af589213525362 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/ChunkSystemRegionFileStorage.java +@@ -8,9 +8,9 @@ public interface ChunkSystemRegionFileStorage { + + public boolean moonrise$doesRegionFileNotExistNoIO(final int chunkX, final int chunkZ); + +- public RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); ++ public abomination.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ); // Luminol - Configurable region file format + +- public RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; ++ public abomination.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException; // Luminol - Configurable region file format + + public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite( + final int chunkX, final int chunkZ, final CompoundTag compound +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java +index 1acea58838f057ab87efd103cbecb6f5aeaef393..f9b89684208b9fe2c93f0368f7df5a400061f6c7 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/io/MoonriseRegionFileIO.java +@@ -1462,7 +1462,7 @@ public final class MoonriseRegionFileIO { + + public static interface IORunnable { + +- public void run(final RegionFile regionFile) throws IOException; ++ public void run(final abomination.IRegionFile regionFile) throws IOException; + + } + } +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java +index 51c126735ace8fdde89ad97b5cab62f244212db0..c7d4d944eb198ac53a3eeae717a25c7d5815c8c1 100644 +--- a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/storage/ChunkSystemChunkBuffer.java +@@ -8,5 +8,5 @@ public interface ChunkSystemChunkBuffer { + + public void moonrise$setWriteOnClose(final boolean value); + +- public void moonrise$write(final RegionFile regionFile) throws IOException; ++ public void moonrise$write(final abomination.IRegionFile regionFile) throws IOException; // Luminol - Configurable region file format + } +diff --git a/src/main/java/me/earthme/luminol/config/modules/misc/RegionFormatConfig.java b/src/main/java/me/earthme/luminol/config/modules/misc/RegionFormatConfig.java +new file mode 100644 +index 0000000000000000000000000000000000000000..d755934d41ed7cbcf26bc9f5682984a732edd18a +--- /dev/null ++++ b/src/main/java/me/earthme/luminol/config/modules/misc/RegionFormatConfig.java +@@ -0,0 +1,50 @@ ++package me.earthme.luminol.config.modules.misc; ++ ++import abomination.LinearRegionFile; ++import com.electronwill.nightconfig.core.file.CommentedFileConfig; ++import me.earthme.luminol.config.ConfigInfo; ++import me.earthme.luminol.config.DoNotLoad; ++import me.earthme.luminol.config.EnumConfigCategory; ++import me.earthme.luminol.config.IConfigModule; ++import me.earthme.luminol.utils.EnumRegionFormat; ++import net.minecraft.server.MinecraftServer; ++ ++public class RegionFormatConfig implements IConfigModule { ++ @ConfigInfo(baseName = "format") ++ public static String format = "MCA"; ++ @ConfigInfo(baseName = "linear_compression_level") ++ public static int linearCompressionLevel = 1; ++ @ConfigInfo(baseName = "linear_io_thread_count") ++ public static int linearIoThreadCount = 6; ++ @ConfigInfo(baseName = "linear_io_signal_check_delay_ms") ++ public static int linearIoQueueSize = 100; ++ ++ @DoNotLoad ++ public static EnumRegionFormat regionFormat; ++ ++ @Override ++ public EnumConfigCategory getCategory() { ++ return EnumConfigCategory.MISC; ++ } ++ ++ @Override ++ public String getBaseName() { ++ return "region_format"; ++ } ++ ++ @Override ++ public void onLoaded(CommentedFileConfig configInstance) { ++ LinearRegionFile.initIOExecutor(); ++ regionFormat = EnumRegionFormat.fromString(format.toUpperCase()); ++ ++ if (regionFormat == null) { ++ throw new RuntimeException("Invalid region format: " + format); ++ } ++ ++ if (RegionFormatConfig.linearCompressionLevel > 23 || RegionFormatConfig.linearCompressionLevel < 1) { ++ MinecraftServer.LOGGER.error("Linear region compression level should be between 1 and 22 in config: {}", RegionFormatConfig.linearCompressionLevel); ++ MinecraftServer.LOGGER.error("Falling back to compression level 1."); ++ RegionFormatConfig.linearCompressionLevel = 1; ++ } ++ } ++} +diff --git a/src/main/java/me/earthme/luminol/utils/EnumRegionFormat.java b/src/main/java/me/earthme/luminol/utils/EnumRegionFormat.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f8e9ebd95312e94a07dc18c3435471810f0b5b2a +--- /dev/null ++++ b/src/main/java/me/earthme/luminol/utils/EnumRegionFormat.java +@@ -0,0 +1,40 @@ ++package me.earthme.luminol.utils; ++ ++import abomination.LinearRegionFile; ++import me.earthme.luminol.config.modules.misc.RegionFormatConfig; ++import net.minecraft.world.level.chunk.storage.RegionFile; ++import org.jetbrains.annotations.Nullable; ++ ++public enum EnumRegionFormat { ++ MCA("mca", ".mca" , (info) -> new RegionFile(info.info(), info.filePath(), info.folder(), info.sync())), ++ LINEAR_V2("linear_v2", ".linear" ,(info) -> new LinearRegionFile(info.info(), info.filePath(), info.folder(), info.sync(), RegionFormatConfig.linearCompressionLevel)); ++ ++ private final String name; ++ private final String argument; ++ private final IRegionCreateFunction creator; ++ ++ EnumRegionFormat(String name, String argument, IRegionCreateFunction creator) { ++ this.name = name; ++ this.argument = argument; ++ this.creator = creator; ++ } ++ ++ @Nullable ++ public static EnumRegionFormat fromString(String string) { ++ for (EnumRegionFormat format : values()) { ++ if (format.name.equalsIgnoreCase(string)) { ++ return format; ++ } ++ } ++ ++ return null; ++ } ++ ++ public IRegionCreateFunction getCreator() { ++ return this.creator; ++ } ++ ++ public String getArgument() { ++ return this.argument; ++ } ++} +diff --git a/src/main/java/me/earthme/luminol/utils/IRegionCreateFunction.java b/src/main/java/me/earthme/luminol/utils/IRegionCreateFunction.java +new file mode 100644 +index 0000000000000000000000000000000000000000..fb87ef13803122aa5a2e7f0c578de359140d4f31 +--- /dev/null ++++ b/src/main/java/me/earthme/luminol/utils/IRegionCreateFunction.java +@@ -0,0 +1,9 @@ ++package me.earthme.luminol.utils; ++ ++import abomination.IRegionFile; ++ ++import java.io.IOException; ++ ++public interface IRegionCreateFunction { ++ IRegionFile create(RegionCreatorInfo info) throws IOException; ++} +diff --git a/src/main/java/me/earthme/luminol/utils/RegionCreatorInfo.java b/src/main/java/me/earthme/luminol/utils/RegionCreatorInfo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5af068489646ed70330d8c6242ec88f536c4c289 +--- /dev/null ++++ b/src/main/java/me/earthme/luminol/utils/RegionCreatorInfo.java +@@ -0,0 +1,7 @@ ++package me.earthme.luminol.utils; ++ ++import net.minecraft.world.level.chunk.storage.RegionStorageInfo; ++ ++import java.nio.file.Path; ++ ++public record RegionCreatorInfo (RegionStorageInfo info, Path filePath, Path folder, boolean sync) {} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 8cc0c01a19fc71753d7c3ed4fa7e9992aaf93b5a..88be8a6232bc3311cc0bdb7c697f7a78ce33e38d 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1180,6 +1180,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop regionCache = new Long2ObjectLinkedOpenHashMap(); ++ public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap(); // Luminol - Configurable region file format + private final RegionStorageInfo info; + private final Path folder; + private final boolean sync; +@@ -35,6 +35,26 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + private static String getRegionFileName(final int chunkX, final int chunkZ) { + return "r." + (chunkX >> REGION_SHIFT) + "." + (chunkZ >> REGION_SHIFT) + ".mca"; + } ++ // Luminol start - Configurable region file format ++ protected abomination.IRegionFile createNew(RegionStorageInfo info, Path filePath, Path folder, boolean sync) throws IOException{ ++ final me.earthme.luminol.utils.EnumRegionFormat regionFormat = me.earthme.luminol.config.modules.misc.RegionFormatConfig.regionFormat; ++ final String fullFileName = filePath.getFileName().toString(); ++ final String argument; ++ ++ int lastDotIndex = fullFileName.lastIndexOf("."); ++ if (lastDotIndex != -1 && lastDotIndex < fullFileName.length() - 1) { ++ argument = fullFileName.substring(lastDotIndex + 1); ++ }else { ++ throw new IOException("Invalid region file name: " + fullFileName); ++ } ++ ++ if (!regionFormat.getArgument().equalsIgnoreCase(argument)) { ++ throw new IOException("Invalid region file format: " + argument + " expected " + regionFormat.getArgument()); ++ } ++ ++ return regionFormat.getCreator().create(new me.earthme.luminol.utils.RegionCreatorInfo(info, filePath, folder, sync)); ++ } ++ // Luminol end + + private boolean doesRegionFilePossiblyExist(final long position) { + synchronized (this.nonExistingRegionFiles) { +@@ -68,15 +88,15 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + @Override +- public synchronized final RegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { ++ public synchronized final abomination.IRegionFile moonrise$getRegionFileIfLoaded(final int chunkX, final int chunkZ) { // Luminol - Configurable region file format + return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT)); + } + + @Override +- public synchronized final RegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { ++ public synchronized final abomination.IRegionFile moonrise$getRegionFileIfExists(final int chunkX, final int chunkZ) throws IOException { // Luminol - Configurable region file format + final long key = ChunkPos.asLong(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); + +- RegionFile ret = this.regionCache.getAndMoveToFirst(key); ++ abomination.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // Luminol - Configurable region file format + if (ret != null) { + return ret; + } +@@ -100,7 +120,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + FileUtil.createDirectoriesSafe(this.folder); + +- ret = new RegionFile(this.info, regionPath, this.folder, this.sync); ++ ret = this.createNew(this.info, regionPath, this.folder, this.sync); // Luminol - Configurable region file format + + this.regionCache.putAndMoveToFirst(key, ret); + +@@ -119,7 +139,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); +- final RegionFile regionFile = this.getRegionFile(pos); ++ final abomination.IRegionFile regionFile = this.getRegionFile(pos); // Luminol - Configurable region file format + + // note: not required to keep regionfile loaded after this call, as the write param takes a regionfile as input + // (and, the regionfile parameter is unused for writing until the write call) +@@ -153,7 +173,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + ) throws IOException { + final ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (writeData.result() == ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.DELETE) { +- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); ++ final abomination.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // Luminol - Configurable region file format + if (regionFile != null) { + regionFile.clear(pos); + } // else: didn't exist +@@ -168,7 +188,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + public final ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.ReadData moonrise$readData( + final int chunkX, final int chunkZ + ) throws IOException { +- final RegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); ++ final abomination.IRegionFile regionFile = this.moonrise$getRegionFileIfExists(chunkX, chunkZ); // Luminol - Configurable region file format + + final DataInputStream input = regionFile == null ? null : regionFile.getChunkDataInputStream(new ChunkPos(chunkX, chunkZ)); + +@@ -250,12 +270,12 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + // Paper start - rewrite chunk system +- public RegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { ++ public abomination.IRegionFile getRegionFile(ChunkPos chunkcoordintpair) throws IOException { // Luminol - Configurable region file format + return this.getRegionFile(chunkcoordintpair, false); + } + // Paper end - rewrite chunk system + +- public RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - public ++ public abomination.IRegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit // Paper - public // Luminol - Configurable region file format + // Paper start - rewrite chunk system + if (existingOnly) { + return this.moonrise$getRegionFileIfExists(chunkcoordintpair.x, chunkcoordintpair.z); +@@ -263,7 +283,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + synchronized (this) { + final long key = ChunkPos.asLong(chunkcoordintpair.x >> REGION_SHIFT, chunkcoordintpair.z >> REGION_SHIFT); + +- RegionFile ret = this.regionCache.getAndMoveToFirst(key); ++ abomination.IRegionFile ret = this.regionCache.getAndMoveToFirst(key); // Luminol - Configurable region file format + if (ret != null) { + return ret; + } +@@ -292,7 +312,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); + } + +- private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { ++ private static CompoundTag readOversizedChunk(abomination.IRegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { // Luminol - Configurable region file format + synchronized (regionfile) { + try (DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkCoordinate)) { + CompoundTag oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z); +@@ -327,7 +347,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + @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 +- RegionFile regionfile = this.getRegionFile(pos, true); ++ abomination.IRegionFile regionfile = this.getRegionFile(pos, true); // Luminol - Configurable region file format + if (regionfile == null) { + return null; + } +@@ -391,7 +411,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + + public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException { + // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing +- RegionFile regionfile = this.getRegionFile(chunkPos, true); ++ abomination.IRegionFile regionfile = this.getRegionFile(chunkPos, true); // Luminol - Configurable region file format + if (regionfile == null) { + return; + } +@@ -421,7 +441,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + } + + public void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException { // Paper - rewrite chunk system - public +- RegionFile regionfile = this.getRegionFile(pos, nbt == null); // CraftBukkit // Paper - rewrite chunk system ++ abomination.IRegionFile regionfile = this.getRegionFile(pos, nbt == null); // CraftBukkit // Paper - rewrite chunk system // Luminol - Configurable region file format + // Paper start - rewrite chunk system + if (regionfile == null) { + // if the RegionFile doesn't exist, no point in deleting from it +@@ -465,7 +485,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + // Paper start - rewrite chunk system + synchronized (this) { + final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); +- for (final RegionFile regionFile : this.regionCache.values()) { ++ for (final abomination.IRegionFile regionFile : this.regionCache.values()) { // Luminol - Configurable region file format + try { + regionFile.close(); + } catch (final IOException ex) { +@@ -482,7 +502,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise + // Paper start - rewrite chunk system + synchronized (this) { + final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); +- for (final RegionFile regionFile : this.regionCache.values()) { ++ for (final abomination.IRegionFile regionFile : this.regionCache.values()) { // Luminol - Configurable region file format + try { + regionFile.flush(); + } catch (final IOException ex) { diff --git a/patches/server/0018-Try-fixing-folia-spector-teleportation.patch b/patches/server/0019-Try-fixing-folia-spector-teleportation.patch similarity index 97% rename from patches/server/0018-Try-fixing-folia-spector-teleportation.patch rename to patches/server/0019-Try-fixing-folia-spector-teleportation.patch index 09a4668..b6f0e57 100644 --- a/patches/server/0018-Try-fixing-folia-spector-teleportation.patch +++ b/patches/server/0019-Try-fixing-folia-spector-teleportation.patch @@ -36,7 +36,7 @@ index 0000000000000000000000000000000000000000..01f8c6ff3662569be5a4ff998bcd4fbb + } +} diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index e58e754534fc37f276e66cbd771d10f0d6d26706..76752263eba927fba482280680c011738247dbaa 100644 +index 93c038ba1fd216fd11ab8b5cec5807453f34e152..c72bda7413d9a7ce763743d0efbd85257262477e 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -1162,9 +1162,24 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple diff --git a/patches/server/0019-Teleport-async-if-entity-was-moving-to-another-regio.patch b/patches/server/0020-Teleport-async-if-entity-was-moving-to-another-regio.patch similarity index 97% rename from patches/server/0019-Teleport-async-if-entity-was-moving-to-another-regio.patch rename to patches/server/0020-Teleport-async-if-entity-was-moving-to-another-regio.patch index 339f51d..adf6acb 100644 --- a/patches/server/0019-Teleport-async-if-entity-was-moving-to-another-regio.patch +++ b/patches/server/0020-Teleport-async-if-entity-was-moving-to-another-regio.patch @@ -39,7 +39,7 @@ index 0000000000000000000000000000000000000000..0e51d465db3554ac80d00c6b85cc1f01 + } +} diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index eeb094265756e5440c0cdd0784f725f7de536493..4c95fb042b42e3acb7ec5fa93f5284434bf82196 100644 +index 8e1a75e56cc373a9ec9b563666af0864eee99479..4fdbdd1c5c937c20026afe555fa1c8371b4eaa16 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -1181,6 +1181,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess diff --git a/patches/server/0020-Try-fixing-folia-off-region-POI-accessing-issue.patch b/patches/server/0021-Try-fixing-folia-off-region-POI-accessing-issue.patch similarity index 100% rename from patches/server/0020-Try-fixing-folia-off-region-POI-accessing-issue.patch rename to patches/server/0021-Try-fixing-folia-off-region-POI-accessing-issue.patch diff --git a/patches/server/0021-Force-disable-builtin-spark-plugin.patch b/patches/server/0022-Force-disable-builtin-spark-plugin.patch similarity index 96% rename from patches/server/0021-Force-disable-builtin-spark-plugin.patch rename to patches/server/0022-Force-disable-builtin-spark-plugin.patch index 5bc57ae..c670264 100644 --- a/patches/server/0021-Force-disable-builtin-spark-plugin.patch +++ b/patches/server/0022-Force-disable-builtin-spark-plugin.patch @@ -18,7 +18,7 @@ index 48604e7f96adc9e226e034054c5e2bad0b024eb5..99f0c1e4d3437154a1062b0a8f94b7a0 return; } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 8cc0c01a19fc71753d7c3ed4fa7e9992aaf93b5a..1c052f730d834187c6645dd9530c8a6e7879b3d3 100644 +index 88be8a6232bc3311cc0bdb7c697f7a78ce33e38d..c7390e0341cd19fa5e0b21882f27943174f718d0 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -809,8 +809,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= j) { @@ -59,7 +59,7 @@ index 8cc0c01a19fc71753d7c3ed4fa7e9992aaf93b5a..1c052f730d834187c6645dd9530c8a6e if (this.emptyTicks == j) { MinecraftServer.LOGGER.info("Server empty for {} seconds, pausing", this.pauseWhileEmptySeconds()); this.autoSave(); -@@ -1747,7 +1747,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= MinecraftServer.STATUS_EXPIRE_TIME_NANOS) { // Folia - region threading -@@ -1809,6 +1832,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop