diff --git a/patches/server/0040-Luminol-Add-configurable-region-format-framework-lin.patch b/patches/server/0040-Luminol-Add-configurable-region-format-framework-lin.patch index cb16db7..34df248 100644 --- a/patches/server/0040-Luminol-Add-configurable-region-format-framework-lin.patch +++ b/patches/server/0040-Luminol-Add-configurable-region-format-framework-lin.patch @@ -68,17 +68,16 @@ index 0000000000000000000000000000000000000000..d92f1d549c7e01daa6b5bba7d405e462 +} diff --git a/src/main/java/abomination/LinearRegionFile.java b/src/main/java/abomination/LinearRegionFile.java new file mode 100644 -index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc002b13107 +index 0000000000000000000000000000000000000000..6067391445509c0d4e7d3d7970034065dfbb66a2 --- /dev/null +++ b/src/main/java/abomination/LinearRegionFile.java -@@ -0,0 +1,608 @@ +@@ -0,0 +1,560 @@ +package abomination; + +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 net.edenor.foldenor.config.FoldenorConfig; +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4FastDecompressor; @@ -95,18 +94,23 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; -+import java.util.concurrent.ScheduledFuture; -+import java.util.concurrent.atomic.AtomicInteger; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.List; ++import java.util.concurrent.TimeUnit; ++import java.util.concurrent.atomic.AtomicBoolean; ++import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; + +// LinearRegionFile_implementation_version_0_5byXymb +// Just gonna use this string to inform other forks about updates ;-) -+public class LinearRegionFile implements IRegionFile{ ++public class LinearRegionFile 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 static final LinearRegionFileFlusher linearRegionFileFlusher = new LinearRegionFileFlusher(); + + private byte[][] bucketBuffers; + private final byte[][] buffer = new byte[1024][]; @@ -118,8 +122,8 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + private final LZ4Compressor compressor; + private final LZ4FastDecompressor decompressor; + -+ private boolean markedToSave = false; -+ private boolean close = false; ++ private AtomicBoolean markedToSave = new AtomicBoolean(false); ++ public boolean close = false; + + public final ReentrantLock fileLock = new ReentrantLock(true); + public Path regionFile; @@ -128,9 +132,6 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + private int gridSize = 8; + private int bucketSize = 4; + -+ private final AtomicInteger modifiedChunkCount = new AtomicInteger(0); -+ private ScheduledFuture ioTask = null; -+ + public Path getRegionFile() { + return this.regionFile; + } @@ -198,8 +199,7 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + + File regionFile = new File(this.regionFile.toString()); + -+ if(!regionFile.canRead()) { -+ //this.start(); ++ if (!regionFile.canRead()) { + return; + } + @@ -220,7 +220,6 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + 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); + } @@ -331,61 +330,30 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); + } + -+ private synchronized void markToSave() { -+ synchronized(markedToSaveLock) { -+ markedToSave = true; -+ } ++ private void markToSave() { ++ linearRegionFileFlusher.scheduleSave(this); ++ markedToSave.set(true); + } + -+ private synchronized boolean isMarkedToSave() { -+ synchronized(markedToSaveLock) { -+ if(markedToSave) { -+ markedToSave = false; -+ return true; -+ } -+ return false; -+ } ++ public boolean isMarkedToSave() { ++ return markedToSave.getAndSet(false); + } + -+ /* -+ private static final int SAVE_THREAD_MAX_COUNT = 6; -+ private static final Object saveLock = new Object(); -+ private static int activeSaveThreads = 0; -+ */ -+ -+ /*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 void flushWrapper() { ++ try { ++ flush(); ++ } catch (IOException e) { ++ throw new RuntimeException(e); ++ } ++ } ++ + public synchronized void flush() throws IOException { -+ if(!isMarkedToSave()) return; ++ if (!isMarkedToSave()) return; + + openRegionFile(); + @@ -491,8 +459,7 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + + 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); ++ //System.out.println("writeStart REGION FILE FLUSH " + (System.nanoTime() - writeStart) + " misses: " + bucketMisses); + } + + private void writeNBTFeatures(DataOutputStream dataStream) throws IOException { @@ -535,17 +502,6 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + LOGGER.error("Chunk write IOException " + e + " " + this.regionFile); + } + markToSave(); -+ this.modifiedChunkCount.getAndIncrement(); -+ -+ this.ioTask = FoldenorConfig.linearFlusher.claimTask(this.ioTask, this); -+ } -+ -+ public synchronized boolean isClosed() { -+ return this.close; -+ } -+ -+ public double getLoadPercent() { -+ return ((double)this.modifiedChunkCount.get()) / (32 * 32); + } + + public DataOutputStream getChunkDataOutputStream(ChunkPos pos) { @@ -559,8 +515,8 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + 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() ++ data, ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE, ++ out, regionFile -> out.close() + ); + } + @@ -596,7 +552,7 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + openRegionFile(); + openBucket(pos.x, pos.z); + -+ if(this.bufferUncompressedSize[getChunkIndex(pos.x, pos.z)] != 0) { ++ 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)); @@ -623,12 +579,7 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + public synchronized void close() throws IOException { + openRegionFile(); + close = true; -+ try { -+ if (this.ioTask != null) this.ioTask.cancel(false); -+ flush(); -+ } catch(IOException e) { -+ throw new IOException("Region flush IOException " + e + " " + this.regionFile); -+ } ++ markToSave(); + } + + private static int getChunkIndex(int x, int z) { @@ -643,7 +594,8 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + return false; + } + -+ public void setOversized(int x, int z, boolean something) {} ++ 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); @@ -680,65 +632,596 @@ index 0000000000000000000000000000000000000000..3b37fec479c716047d6ad0ab5d4d6dc0 + } + } +} -diff --git a/src/main/java/abomination/LinearRegionFileFlusher.java b/src/main/java/abomination/LinearRegionFileFlusher.java +diff --git a/src/main/java/abomination/LinearRegionFile1.java b/src/main/java/abomination/LinearRegionFile1.java new file mode 100644 -index 0000000000000000000000000000000000000000..b4e4b4a7fb8f37715b6a3099ec2c7f48d65f9390 +index 0000000000000000000000000000000000000000..a19850915299e7aa3fcb691053a6ddcca60dc2f4 --- /dev/null -+++ b/src/main/java/abomination/LinearRegionFileFlusher.java -@@ -0,0 +1,55 @@ -+package abomination; ++++ b/src/main/java/abomination/LinearRegionFile1.java +@@ -0,0 +1,534 @@ ++/*package abomination; + -+import com.google.common.util.concurrent.ThreadFactoryBuilder; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; ++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 net.jpountz.lz4.LZ4Compressor; ++import net.jpountz.lz4.LZ4Factory; ++import net.jpountz.lz4.LZ4FastDecompressor; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.world.level.ChunkPos; ++import net.minecraft.world.level.chunk.storage.RegionFileVersion; ++import net.minecraft.world.level.chunk.storage.RegionStorageInfo; ++import net.openhft.hashing.LongHashFunction; ++import org.slf4j.Logger; + -+import java.util.concurrent.ScheduledFuture; -+import java.util.concurrent.ScheduledThreadPoolExecutor; -+import java.util.concurrent.TimeUnit; ++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.atomic.AtomicBoolean; + -+public class LinearRegionFileFlusher { -+ private final ScheduledThreadPoolExecutor delayedIoSchedule; -+ private final long baseDelay; -+ -+ public LinearRegionFileFlusher(int nThreads, long baseDelay) { -+ this.delayedIoSchedule = new ScheduledThreadPoolExecutor(nThreads, -+ new ThreadFactoryBuilder() -+ .setPriority(3) -+ .setNameFormat("Linear IO Thread - %d") -+ .build() -+ ); -+ this.baseDelay = baseDelay; ++// LinearRegionFile_implementation_version_0_5byXymb ++// Just going to use this string to inform other forks about updates ;-) ++public class LinearRegionFile1 implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.storage.ChunkSystemRegionFile, IRegionFile{ ++ private static final long SUPERBLOCK = 0xc3ff13183cca9d9aL; ++ private static final byte VERSION = 3; ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ public static final int MAX_CHUNK_SIZE = 500 * 1024 * 1024; // Abomination - prevent chunk dupe ++ private static final LinearRegionFileFlusher linearRegionFileFlusher = new LinearRegionFileFlusher(); ++ ++ private byte[][] bucketBuffers; ++ private final ChunkBucket[] buckets = new ChunkBucket[1024]; ++ ++ private final long[] chunkTimestamps = new long[1024]; ++ ++ private final LZ4Compressor compressor; ++ private final LZ4FastDecompressor decompressor; ++ ++ private final AtomicBoolean markedToSave = new AtomicBoolean(false); ++ public final AtomicBoolean close = new AtomicBoolean(false); ++ ++ public Path regionFile; ++ ++ private final int compressionLevel; ++ private int gridSize = 8; ++ private int bucketSize = 4; ++ ++ public boolean regionFileOpen = false; ++ ++ public LinearRegionFile1(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync, int compressionLevel) { ++ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync, compressionLevel); + } + -+ public void shutdown() { -+ this.delayedIoSchedule.shutdown(); -+ while (true) { ++ public LinearRegionFile1(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync, int compressionLevel) { ++ this.regionFile = path; ++ this.compressionLevel = compressionLevel; ++ ++ this.compressor = LZ4Factory.fastestInstance().fastCompressor(); ++ this.decompressor = LZ4Factory.fastestInstance().fastDecompressor(); ++ } ++ ++ private ChunkBucket openBucket(ChunkPos pos) { ++ return openBucket(pos.x, pos.z); ++ } ++ ++ private ChunkBucket openBucket(int chunkX, int chunkZ) { ++ chunkX = Math.floorMod(chunkX, 32); ++ chunkZ = Math.floorMod(chunkZ, 32); ++ int bx = chunkX / bucketSize, bz = chunkZ / bucketSize; ++ int idx = bx * gridSize + bz; ++ ++ if (bucketBuffers == null) return null; ++ if (bucketBuffers[idx] != null) { + try { -+ if (this.delayedIoSchedule.awaitTermination(1000, TimeUnit.MILLISECONDS)) break; -+ } catch (InterruptedException e) { -+ throw new RuntimeException(e); ++ ByteArrayInputStream bucketByteStream = new ByteArrayInputStream(bucketBuffers[idx]); ++ ZstdInputStream zstdStream = new ZstdInputStream(bucketByteStream); ++ ByteBuffer bucketBuffer = ByteBuffer.wrap(zstdStream.readAllBytes()); ++ ++ 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); ++ ++ this.buckets[chunkIndex] = new ChunkBucket(finalCompressed, chunkData.length); ++ return this.buckets[chunkIndex]; ++ } ++ } ++ } ++ } ++ catch (IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ bucketBuffers[idx] = null; ++ } ++ ++ return null; ++ } ++ ++ private void openRegionFile() { ++ if (regionFileOpen) return; ++ regionFileOpen = true; ++ ++ File regionFile = new File(this.regionFile.toString()); ++ ++ if(!regionFile.canRead()) { ++ 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) { ++ try { ++ parseLinearV1(buffer); ++ } catch (IOException e) { ++ throw new RuntimeException(e); ++ } ++ ++ } else if (version == 3) { ++ try { ++ parseLinearV2(buffer); ++ } catch (IOException e) { ++ throw new RuntimeException(e); ++ } ++ } else { ++ throw new RuntimeException("Invalid version: " + version + " file " + this.regionFile); ++ } ++ } 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.buckets[i] = new ChunkBucket(finalCompressed, size); ++ this.chunkTimestamps[i] = getTimestamp(); // Use current timestamp as we don't have the original + } + } + } + -+ public ScheduledFuture claimTask(@Nullable ScheduledFuture parent, @NotNull LinearRegionFile file){ -+ if (parent != null) { -+ if (!parent.isCancelled() && !parent.isDone()) { -+ parent.cancel(false); ++ 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) ++ ++ while (true) { ++ byte featureNameLength = buffer.get(); ++ if (featureNameLength == 0) break; ++ byte[] featureNameBytes = new byte[featureNameLength]; ++ buffer.get(featureNameBytes); ++ // System.out.println("NBT Feature: " + featureName + " = " + featureValue); ++ } ++ ++ int[] bucketSizes = new int[gridSize * gridSize]; ++ long[] bucketHashes = new long[gridSize * gridSize]; ++ for (int i = 0; i < gridSize * gridSize; i++) { ++ bucketSizes[i] = buffer.getInt(); ++ 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 + "\t" + rawHash + "\t" + bucketHashes[i]); + } + } + -+ final double loadPercent = file.getLoadPercent() * 100; -+ final double delayOffset = Math.max(this.baseDelay, loadPercent * this.baseDelay); ++ long footerSuperBlock = buffer.getLong(); ++ if (footerSuperBlock != SUPERBLOCK) ++ throw new IOException("Footer superblock invalid " + this.regionFile); ++ } + -+ final long actualDelay = (long) (this.baseDelay - delayOffset); -+ return this.delayedIoSchedule.schedule(() -> { -+ try { -+ file.flush(); -+ }catch (Exception e) { -+ throw new RuntimeException(e); ++ private void markToSave() { ++ linearRegionFileFlusher.scheduleSave(this); ++ markedToSave.set(true); ++ } ++ ++ public boolean isMarkedToSave() { ++ return markedToSave.getAndSet(false); ++ } ++ ++ public boolean doesChunkExist(ChunkPos pos) { ++ return hasChunk(pos); ++ } ++ ++ public void flushWrapper() { ++ try { ++ flush(); ++ } catch (IOException e) { ++ LOGGER.error("Failed to flush region file {}", regionFile.toAbsolutePath(), e); ++ } ++ } ++ ++ public void flush() throws IOException { ++ if(!isMarkedToSave()) return; ++ ++ openRegionFile(); ++ ++ long timestamp = getTimestamp(); ++ ++ 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++) { ++ int size = this.buckets[i] != null ? this.buckets[i].size : 0; ++ chunkExistenceBitmap[i] = size > 0; ++ } ++ writeSerializedExistenceBitmap(dataStream, chunkExistenceBitmap); ++ ++ writeNBTFeatures(dataStream); ++ ++ 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; ++ } ++ ++ 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.buckets[chunkIndex] != null ? this.buckets[chunkIndex].size : 0) > 0) { ++ hasData = true; ++ byte[] chunkData = new byte[(this.buckets[chunkIndex] != null ? this.buckets[chunkIndex].size : 0)]; ++ this.decompressor.decompress((this.buckets[chunkIndex] != null ? this.buckets[chunkIndex].data : null), 0, ++ chunkData, 0, (this.buckets[chunkIndex] != null ? this.buckets[chunkIndex].size : 0)); ++ 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); ++ } ++ ++ 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 synchronized void write(ChunkPos pos, ByteBuffer buffer) { ++ openRegionFile(); ++ openBucket(pos); ++ 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.chunkTimestamps[index] = getTimestamp(); ++ this.buckets[index] = new ChunkBucket(b, uncompressedSize); ++ } ++ } catch (IOException e) { ++ LOGGER.error("Chunk write IOException {} {}", e, this.regionFile); ++ } ++ markToSave(); ++ } ++ ++ public DataOutputStream getChunkDataOutputStream(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos); ++ return new DataOutputStream(new BufferedOutputStream(new LinearRegionFile1.ChunkBuffer(pos))); ++ } ++ ++ @Override ++ public MoonriseRegionFileIO.RegionDataController.WriteData moonrise$startWrite(CompoundTag data, ChunkPos pos) throws IOException { ++ final DataOutputStream out = this.getChunkDataOutputStream(pos); ++ ++ return new MoonriseRegionFileIO.RegionDataController.WriteData( ++ data, MoonriseRegionFileIO.RegionDataController.WriteData.WriteResult.WRITE, ++ out, regionFile -> out.close() ++ ); ++ } ++ ++ 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(); ++ ChunkBucket bucket = openBucket(pos); ++ ++ if(bucket != null && bucket.size != 0) { ++ byte[] content = new byte[bucket.size]; ++ this.decompressor.decompress(bucket.data, 0, content, 0, bucket.size); ++ return new DataInputStream(new ByteArrayInputStream(content)); ++ } ++ return null; ++ } ++ ++ public void clear(ChunkPos pos) { ++ openRegionFile(); ++ openBucket(pos); ++ int i = getChunkIndex(pos); ++ this.buckets[i] = new ChunkBucket(null, 0); ++ this.chunkTimestamps[i] = 0; ++ markToSave(); ++ } ++ ++ public boolean hasChunk(ChunkPos pos) { ++ openRegionFile(); ++ ChunkBucket bucket = openBucket(pos); ++ return bucket != null && bucket.isExists(); ++ } ++ ++ public void close() throws IOException { ++ openRegionFile(); ++ close.set(true); ++ markToSave(); ++ } ++ ++ private static int getChunkIndex(ChunkPos pos) { ++ return getChunkIndex(pos.x, pos.z); ++ } ++ ++ 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 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 |= (byte) (1 << (7 - j)); ++ } ++ } ++ out.writeByte(b); ++ } ++ } ++ ++ private class ChunkBuffer extends ByteArrayOutputStream { ++ ++ private final ChunkPos pos; ++ ++ public ChunkBuffer(ChunkPos chunkcoordintpair) { ++ super(); ++ this.pos = chunkcoordintpair; ++ } ++ ++ public void close() { ++ ByteBuffer bytebuffer = ByteBuffer.wrap(this.buf, 0, this.count); ++ LinearRegionFile1.this.write(this.pos, bytebuffer); ++ } ++ } ++ ++ private record ChunkBucket(byte[] data, int size) { ++ public boolean isExists() { ++ return this.size > 0 && this.data != null; + } -+ }, actualDelay, java.util.concurrent.TimeUnit.MILLISECONDS); ++ } ++}*/ +diff --git a/src/main/java/abomination/LinearRegionFileFlusher.java b/src/main/java/abomination/LinearRegionFileFlusher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4c4e70891b360db5f94210f5d4be384489aa59ad +--- /dev/null ++++ b/src/main/java/abomination/LinearRegionFileFlusher.java +@@ -0,0 +1,46 @@ ++package abomination; ++ ++import com.google.common.util.concurrent.ThreadFactoryBuilder; ++import net.edenor.foldenor.config.FoldenorConfig; ++import org.bukkit.Bukkit; ++ ++import java.util.Queue; ++import java.util.concurrent.*; ++ ++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( ++ FoldenorConfig.linearIoThreadCount, ++ new ThreadFactoryBuilder() ++ .setNameFormat("linear-flusher-%d") ++ .build() ++ ); ++ ++ public LinearRegionFileFlusher() { ++ Bukkit.getLogger().info("Using " + FoldenorConfig.linearIoThreadCount + " threads for linear region flushing."); ++ scheduler.scheduleAtFixedRate(this::pollAndFlush, 0L, FoldenorConfig.linearIoFlushDelayMs, 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.close && regionFile.isMarkedToSave()) ++ executor.execute(regionFile::flushWrapper); ++ } ++ } ++ ++ public void shutdown() { ++ executor.shutdown(); ++ scheduler.shutdown(); + } +} 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 @@ -856,13 +1339,13 @@ index 0000000000000000000000000000000000000000..5af068489646ed70330d8c6242ec88f5 + +public record RegionCreatorInfo (RegionStorageInfo info, Path filePath, Path folder, boolean sync) {} diff --git a/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java b/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java -index 4224de6dd0aa9ce934384ba357dfc17791c93e7d..2015cfc902c5f2027b26951361be43265cdb2979 100644 +index 4224de6dd0aa9ce934384ba357dfc17791c93e7d..15f6c81e41c3d1f047147c28e4b6b491fde7e057 100644 --- a/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java +++ b/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java @@ -1,7 +1,10 @@ package net.edenor.foldenor.config; -+import abomination.LinearRegionFileFlusher; ++import abomination.LinearRegionFile; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; +import me.earthme.luminol.utils.EnumRegionFormat; @@ -889,7 +1372,7 @@ index 4224de6dd0aa9ce934384ba357dfc17791c93e7d..2015cfc902c5f2027b26951361be4326 + public static int linearCompressionLevel = 1; + public static int linearIoThreadCount = 6; + public static int linearIoFlushDelayMs = 100; -+ public static LinearRegionFileFlusher linearFlusher; ++ public static boolean linearUseVirtualThread = true; + public static EnumRegionFormat regionFormat; + // Luminol End - Configurable region file format + @@ -905,15 +1388,16 @@ index 4224de6dd0aa9ce934384ba357dfc17791c93e7d..2015cfc902c5f2027b26951361be4326 try { config.save(CONFIG_FILE); } catch (IOException ex) { -@@ -125,6 +136,34 @@ public class FoldenorConfig { +@@ -125,6 +136,31 @@ public class FoldenorConfig { EVERYONE } + private static void readRegionSettings() { -+ format = getString("region-format.type", format, "Available names: MCA, LINEAR"); ++ format = getString("region-format.type", format, "Available names: MCA, LINEAR_V2"); + linearCompressionLevel = getInt("region-format.linear.compression-level", linearCompressionLevel); + linearIoThreadCount = getInt("region-format.linear.flush-max-threads", linearIoThreadCount); + linearIoFlushDelayMs = getInt("region-format.linear.flush-frequency", linearIoFlushDelayMs); ++ linearUseVirtualThread = getBoolean("region-format.linear.use-virtual-thread", linearUseVirtualThread); + regionFormat = EnumRegionFormat.fromString(format.toUpperCase()); + + if (regionFormat == null) { @@ -926,22 +1410,18 @@ index 4224de6dd0aa9ce934384ba357dfc17791c93e7d..2015cfc902c5f2027b26951361be4326 + else + linearIoThreadCount = Math.max(linearIoThreadCount, 1); + -+ if (linearCompressionLevel > 23 || linearCompressionLevel < 1) { ++ if (regionFormat == EnumRegionFormat.LINEAR_V2 && (linearCompressionLevel > 23 || linearCompressionLevel < 1)) { + MinecraftServer.LOGGER.error("Linear region compression level should be between 1 and 22 in config: {}", linearCompressionLevel); + MinecraftServer.LOGGER.error("Falling back to compression level 1."); + linearCompressionLevel = 1; + } -+ -+ if (regionFormat == EnumRegionFormat.LINEAR_V2) { -+ linearFlusher = new LinearRegionFileFlusher(linearIoThreadCount, linearIoFlushDelayMs); -+ } + } + protected static void set(String path, Object val) { config.addDefault(path, val); config.set(path, val); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 85e167e74a94fa9749350010c2de3fc914742d8d..50b2599c6e340a45b1626d079cce517bb825bdee 100644 +index 85e167e74a94fa9749350010c2de3fc914742d8d..cc8939f97b4d36051c342092922eeb533e666f18 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -57,6 +57,8 @@ import java.util.stream.Collectors; @@ -966,14 +1446,6 @@ index 85e167e74a94fa9749350010c2de3fc914742d8d..50b2599c6e340a45b1626d079cce517b } return flag3; -@@ -1180,6 +1182,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop exceptionCollector = new ExceptionCollector<>(); @@ -1278,7 +1782,7 @@ index e40665cead218502b44dd49051a53326ed94f061..c085ad2c5a30fbe06cb0e46178d23125 try { regionFile.close(); } catch (final IOException ex) { -@@ -482,7 +503,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise +@@ -482,7 +494,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise // Paper start - rewrite chunk system synchronized (this) { final ExceptionCollector exceptionCollector = new ExceptionCollector<>(); diff --git a/patches/server/0041-Pufferfish-Optimize-random-calls-in-chunk-ticking.patch b/patches/server/0041-Pufferfish-Optimize-random-calls-in-chunk-ticking.patch new file mode 100644 index 0000000..27d4975 --- /dev/null +++ b/patches/server/0041-Pufferfish-Optimize-random-calls-in-chunk-ticking.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AltronMaxX +Date: Sat, 4 Jan 2025 14:37:23 +0400 +Subject: [PATCH] Pufferfish: Optimize random calls in chunk ticking + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 0bbff929f55371e0f11369dd621b0b27b807e8fb..8f743959197fab8a516105b4ee11237bca3a5146 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -994,7 +994,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("thunder"); +- if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && simpleRandom.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking ++ if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && chunk.shouldDoLightning(this.simpleRandom)) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking // Pufferfish // Mizi + BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); + + if (this.isRainingAt(blockposition)) { +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 6003e3495e61073c39928918b9d9f4c2e20f3a49..644101f1a3518c2928522fd2295661970090be33 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -96,6 +96,18 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + private final LevelChunkTicks fluidTicks; + private LevelChunk.UnsavedListener unsavedListener; + ++ // Pufferfish start - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively ++ private int lightningTick; ++ // shouldDoLightning compiles down to 29 bytes, which with the default of 35 byte inlining should guarantee an inline ++ public final boolean shouldDoLightning(net.minecraft.util.RandomSource random) { ++ if (this.lightningTick-- <= 0) { ++ this.lightningTick = random.nextInt(this.level.spigotConfig.thunderChance) << 1; ++ return true; ++ } ++ return false; ++ } ++ // Pufferfish end ++ + public LevelChunk(Level world, ChunkPos pos) { + this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null); + } +@@ -129,6 +141,8 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p + this.debug = !empty && this.level.isDebug(); + this.defaultBlockState = empty ? VOID_AIR_BLOCKSTATE : AIR_BLOCKSTATE; + // Paper end - get block chunk optimisation ++ ++ this.lightningTick = new java.util.Random().nextInt(100000) << 1; // Pufferfish - initialize lightning tick + } + + // CraftBukkit start diff --git a/patches/server/0042-Lithium-fast-entity-retrieval.patch b/patches/server/0042-Lithium-fast-entity-retrieval.patch new file mode 100644 index 0000000..738e4bc --- /dev/null +++ b/patches/server/0042-Lithium-fast-entity-retrieval.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AltronMaxX +Date: Sat, 4 Jan 2025 14:42:42 +0400 +Subject: [PATCH] Lithium fast entity retrieval + + +diff --git a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +index af92a3ae38df4df02a5028d1d7568b674a60da51..9fade710ed998a29edda2791c78b134dc19f0a81 100644 +--- a/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java ++++ b/src/main/java/net/minecraft/world/level/entity/EntitySectionStorage.java +@@ -35,6 +35,8 @@ public class EntitySectionStorage { + } + + public void forEachAccessibleNonEmptySection(AABB box, AbortableIterationConsumer> consumer) { ++ // Mizi start - lithium fast entity retrieval ++ /* + int i = SectionPos.posToSectionCoord(box.minX - 2.0); + int j = SectionPos.posToSectionCoord(box.minY - 4.0); + int k = SectionPos.posToSectionCoord(box.minZ - 2.0); +@@ -60,10 +62,57 @@ public class EntitySectionStorage { + return; + } + } ++ */ ++ int minX = SectionPos.posToSectionCoord(box.minX - 2.0D); ++ int minY = SectionPos.posToSectionCoord(box.minY - 4.0D); ++ int minZ = SectionPos.posToSectionCoord(box.minZ - 2.0D); ++ int maxX = SectionPos.posToSectionCoord(box.maxX + 2.0D); ++ int maxY = SectionPos.posToSectionCoord(box.maxY + 0.0D); ++ int maxZ = SectionPos.posToSectionCoord(box.maxZ + 2.0D); ++ ++ if (maxX >= minX + 4 || maxZ >= minZ + 4) { ++ return; ++ } ++ for (int x = minX; x <= maxX; x++) { ++ for (int z = Math.max(minZ, 0); z <= maxZ; z++) { ++ if (this.forEachInColumn(x, minY, maxY, z, consumer).shouldAbort()) { ++ return; ++ } ++ } ++ int bound = Math.min(-1, maxZ); ++ for (int z = minZ; z <= bound; z++) { ++ if (this.forEachInColumn(x, minY, maxY, z, consumer).shouldAbort()) { ++ return; ++ } + } + } + } + ++ private AbortableIterationConsumer.Continuation forEachInColumn(int x, int minY, int maxY, int z, AbortableIterationConsumer> consumer) { ++ AbortableIterationConsumer.Continuation ret = AbortableIterationConsumer.Continuation.CONTINUE; ++ //y from negative to positive, but y is treated as unsigned ++ for (int y = Math.max(minY, 0); y <= maxY; y++) { ++ if ((ret = this.consumeSection(SectionPos.asLong(x, y, z), consumer)).shouldAbort()) { ++ return ret; ++ } ++ } ++ int bound = Math.min(-1, maxY); ++ for (int y = minY; y <= bound; y++) { ++ if ((ret = this.consumeSection(SectionPos.asLong(x, y, z), consumer)).shouldAbort()) { ++ return ret; ++ } ++ } ++ return ret; ++ } ++ ++ private AbortableIterationConsumer.Continuation consumeSection(long sectionPos, AbortableIterationConsumer> consumer) { ++ EntitySection section = this.getSection(sectionPos); ++ if (section != null && 0 != section.size() && section.getStatus().isAccessible()) { ++ return consumer.accept(section); ++ } ++ return AbortableIterationConsumer.Continuation.CONTINUE; ++ } // Mizi end ++ + public LongStream getExistingSectionPositionsInChunk(long chunkPos) { + int i = ChunkPos.getX(chunkPos); + int j = ChunkPos.getZ(chunkPos); diff --git a/patches/server/0043-Pufferfish-Remove-lambda-from-ticking-guard.patch b/patches/server/0043-Pufferfish-Remove-lambda-from-ticking-guard.patch new file mode 100644 index 0000000..3f9cb63 --- /dev/null +++ b/patches/server/0043-Pufferfish-Remove-lambda-from-ticking-guard.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AltronMaxX +Date: Sat, 4 Jan 2025 14:49:52 +0400 +Subject: [PATCH] Pufferfish: Remove lambda from ticking guard + + +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 8f743959197fab8a516105b4ee11237bca3a5146..36ef59f82f3e8bda974962fd2cddbc8b4b8c35f3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -834,7 +834,21 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + gameprofilerfiller.push("tick"); +- this.guardEntityTick(this::tickNonPassenger, entity); ++ // this.guardEntityTick(this::tickNonPassenger, entity); ++ // Pufferfish start - copied from this.guardEntityTick ++ try { ++ this.tickNonPassenger(entity); // Pufferfish - changed ++ } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; // Paper ++ // Paper start - Prevent tile entity and entity crashes ++ final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); ++ MinecraftServer.LOGGER.error(msg, throwable); ++ getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); ++ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); ++ // Paper end ++ } ++ this.moonrise$midTickTasks(); // Paper - rewrite chunk system ++ // Pufferfish end + gameprofilerfiller.pop(); + } + } diff --git a/patches/server/0044-Leaf-Remove-stream-in-Brain.patch b/patches/server/0044-Leaf-Remove-stream-in-Brain.patch new file mode 100644 index 0000000..712e1d2 --- /dev/null +++ b/patches/server/0044-Leaf-Remove-stream-in-Brain.patch @@ -0,0 +1,80 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AltronMaxX +Date: Sat, 4 Jan 2025 14:52:34 +0400 +Subject: [PATCH] Leaf: Remove stream in Brain + + +diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java +index d4e6198fdfbefe54e374479a1f1d835ab98ce93a..23331820dfc947b4e4065d52f0832d592858a1f9 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/Brain.java ++++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java +@@ -69,13 +69,22 @@ public class Brain { + mutableObject.setValue( + (new MapCodec>() { + public Stream keys(DynamicOps dynamicOps) { +- return memoryModules.stream() +- .flatMap( +- memoryType -> memoryType.getCodec() +- .map(codec -> BuiltInRegistries.MEMORY_MODULE_TYPE.getKey((MemoryModuleType)memoryType)) +- .stream() +- ) +- .map(id -> dynamicOps.createString(id.toString())); ++ // Leaf start - Remove stream in Brain ++ List results = new java.util.ArrayList<>(); ++ ++ for (MemoryModuleType memoryType : memoryModules) { ++ final Optional codec = memoryType.getCodec(); ++ ++ if (codec.isPresent()) { ++ final net.minecraft.resources.ResourceLocation id = BuiltInRegistries.MEMORY_MODULE_TYPE.getKey(memoryType); ++ final T ops = dynamicOps.createString(id.toString()); ++ ++ results.add(ops); ++ } ++ } ++ ++ return results.stream(); ++ // Leaf end - Remove stream in Brain + } + + public DataResult> decode(DynamicOps dynamicOps, MapLike mapLike) { +@@ -110,7 +119,8 @@ public class Brain { + } + + public RecordBuilder encode(Brain brain, DynamicOps dynamicOps, RecordBuilder recordBuilder) { +- brain.memories().forEach(entry -> entry.serialize(dynamicOps, recordBuilder)); ++ brain.serializeMemories(dynamicOps, recordBuilder); // Leaf - Remove stream in Brain ++ + return recordBuilder; + } + }) +@@ -152,8 +162,28 @@ public class Brain { + } + + Stream> memories() { +- return this.memories.entrySet().stream().map(entry -> Brain.MemoryValue.createUnchecked(entry.getKey(), entry.getValue())); ++ // Leaf start - Remove stream in Brain ++ return memoriesList().stream(); ++ } ++ ++ List> memoriesList() { ++ List> result = new java.util.ArrayList<>(); ++ ++ for (Entry, Optional>> entry : this.memories.entrySet()) { ++ result.add(Brain.MemoryValue.createUnchecked(entry.getKey(), entry.getValue())); ++ } ++ ++ return result; ++ } ++ ++ void serializeMemories(DynamicOps dynamicOps, RecordBuilder recordBuilder) { ++ for (Entry, Optional>> entry : this.memories.entrySet()) { ++ final Brain.MemoryValue result = Brain.MemoryValue.createUnchecked(entry.getKey(), entry.getValue()); ++ ++ result.serialize(dynamicOps, recordBuilder); ++ } + } ++ // Leaf end - Remove stream in Brain + + public boolean hasMemoryValue(MemoryModuleType type) { + return this.checkMemory(type, MemoryStatus.VALUE_PRESENT); diff --git a/patches/server/0045-Pufferfish-DAB.patch b/patches/server/0045-Pufferfish-DAB.patch new file mode 100644 index 0000000..8b75c37 --- /dev/null +++ b/patches/server/0045-Pufferfish-DAB.patch @@ -0,0 +1,398 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AltronMaxX +Date: Sat, 4 Jan 2025 14:58:29 +0400 +Subject: [PATCH] Pufferfish: DAB + + +diff --git a/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java b/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java +index 15f6c81e41c3d1f047147c28e4b6b491fde7e057..8b3481286976b4ef60c236d16f380d9b2ebe3996 100644 +--- a/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java ++++ b/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java +@@ -4,7 +4,9 @@ import abomination.LinearRegionFile; + import com.google.common.base.Throwables; + import com.google.common.collect.ImmutableMap; + import me.earthme.luminol.utils.EnumRegionFormat; ++import net.minecraft.core.registries.BuiltInRegistries; + import net.minecraft.server.MinecraftServer; ++import net.minecraft.world.entity.EntityType; + import org.bukkit.Bukkit; + import org.bukkit.configuration.ConfigurationSection; + import org.bukkit.configuration.InvalidConfigurationException; +@@ -12,6 +14,7 @@ import org.bukkit.configuration.file.YamlConfiguration; + + import java.io.File; + import java.io.IOException; ++import java.util.Collections; + import java.util.List; + import java.util.Map; + import java.util.logging.Level; +@@ -46,6 +49,12 @@ public class FoldenorConfig { + public static EnumRegionFormat regionFormat; + // Luminol End - Configurable region file format + ++ public static boolean dearEnabled; ++ public static int startDistance; ++ public static int startDistanceSquared; ++ public static int maximumActivationPrio; ++ public static int activationDistanceMod; ++ + public static void init(File configFile) { + init(configFile, true); + } +@@ -161,6 +170,30 @@ public class FoldenorConfig { + } + } + ++ private static void dynamicActivationOfBrains() throws IOException { ++ dearEnabled = getBoolean("dab.enabled", true); ++ startDistance = getInt("dab.start-distance", 12, ++ "This value determines how far away an entity has to be", ++ "from the player to start being effected by DEAR."); ++ startDistanceSquared = startDistance * startDistance; ++ maximumActivationPrio = getInt("dab.max-tick-freq", 20, ++ "This value defines how often in ticks, the furthest entity", ++ "will get their pathfinders and behaviors ticked. 20 = 1s"); ++ activationDistanceMod = getInt("dab.activation-dist-mod", 8, ++ "This value defines how much distance modifies an entity's", ++ "tick frequency. freq = (distanceToPlayer^2) / (2^value)", ++ "If you want further away entities to tick less often, use 7.", ++ "If you want further away entities to tick more often, try 9."); ++ ++ for (EntityType entityType : BuiltInRegistries.ENTITY_TYPE) { ++ entityType.dabEnabled = true; // reset all, before setting the ones to true ++ } ++ getStringList("dab.blacklisted-entities", Collections.emptyList(), "A list of entities to ignore for activation") ++ .forEach(name -> EntityType.byString(name).ifPresentOrElse(entityType -> { ++ entityType.dabEnabled = false; ++ }, () -> MinecraftServer.LOGGER.warn("Unknown entity \"" + name + "\""))); ++ } ++ + protected static void set(String path, Object val) { + config.addDefault(path, val); + config.set(path, val); +diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java +index 36ef59f82f3e8bda974962fd2cddbc8b4b8c35f3..317013cd4452d5ebb6dfd0ce1c7cbcd2a02d4e42 100644 +--- a/src/main/java/net/minecraft/server/level/ServerLevel.java ++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java +@@ -816,6 +816,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } finally { profiler.stopTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ACTIVATE_ENTITIES); } // Folia - profiler + profiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.ENTITY_TICK); try { // Folia - profiler + regionizedWorldData.forEachTickingEntity((entity) -> { // Folia - regionised ticking ++ entity.activatedPriorityReset = false; // Pufferfish - DAB + if (!entity.isRemoved()) { + if (!tickratemanager.isEntityFrozen(entity)) { + gameprofilerfiller.push("checkDespawn"); +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index c5d0adb53e9cb8f57ee7f2bed67f532ed1be3424..6c52db7a89105e46229af8dbbad1348b0768fc94 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -391,6 +391,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + public boolean freezeLocked = false; // Paper - Freeze Tick Lock API + public boolean fixedPose = false; // Paper - Expand Pose API + private final int despawnTime; // Paper - entity despawn time limit ++ public boolean activatedPriorityReset = false; // Pufferfish - DAB ++ public int activatedPriority = FoldenorConfig.maximumActivationPrio; // Pufferfish - DAB (golf score) + + public void setOrigin(@javax.annotation.Nonnull Location location) { + this.origin = location.toVector(); +diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java +index 635c9c7a8c8307c2bc845a8e1f24aacb526a3c92..2e880d2068dc5e7ea0b6d0922d53818183e5c2b1 100644 +--- a/src/main/java/net/minecraft/world/entity/EntityType.java ++++ b/src/main/java/net/minecraft/world/entity/EntityType.java +@@ -384,6 +384,7 @@ public class EntityType implements FeatureElement, EntityTypeT + private final boolean canSpawnFarFromPlayer; + private final int clientTrackingRange; + private final int updateInterval; ++ public boolean dabEnabled = false; // Pufferfish + private final String descriptionId; + @Nullable + private Component description; +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 8e16b867c412458867b7deba853b4061beb8ea1d..34895cefe32e2698ef20b1656dc8b40491c33027 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -235,10 +235,10 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + @Override + public void inactiveTick() { + super.inactiveTick(); +- if (this.goalSelector.inactiveTick()) { ++ if (this.goalSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priroity + this.goalSelector.tick(); + } +- if (this.targetSelector.inactiveTick()) { ++ if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priroity + this.targetSelector.tick(); + } + } +@@ -939,16 +939,20 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + + if (i % 2 != 0 && this.tickCount > 1) { + gameprofilerfiller.push("targetSelector"); ++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.targetSelector.tickRunningGoals(false); + gameprofilerfiller.pop(); + gameprofilerfiller.push("goalSelector"); ++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.goalSelector.tickRunningGoals(false); + gameprofilerfiller.pop(); + } else { + gameprofilerfiller.push("targetSelector"); ++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.targetSelector.tick(); + gameprofilerfiller.pop(); + gameprofilerfiller.push("goalSelector"); ++ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking + this.goalSelector.tick(); + gameprofilerfiller.pop(); + } +diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +index 29ae74339a4831ccef3d01e8054931715ba192ad..7d1fb7a5639e9acd2e7eb8e9a429d5681bfc896c 100644 +--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java ++++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +@@ -7,6 +7,8 @@ import java.util.EnumSet; + import java.util.Map; + import java.util.Set; + import java.util.function.Predicate; ++ ++import net.edenor.foldenor.config.FoldenorConfig; + import net.minecraft.util.profiling.Profiler; + import net.minecraft.util.profiling.ProfilerFiller; + +@@ -38,7 +40,9 @@ public class GoalSelector { + } + + // Paper start - EAR 2 +- public boolean inactiveTick() { ++ public boolean inactiveTick(int tickRate, boolean inactive) { // Pufferfish start ++ if (inactive && !FoldenorConfig.dearEnabled) tickRate = 4; // reset to Paper's ++ tickRate = Math.min(tickRate, 3); + this.curRate++; + return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct + } +diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java +index b86f638390d386c838318a4d9b6571ac5514df8f..f9290f65dd0d56a794f2841f22534cb8bbf75a28 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java ++++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java +@@ -222,12 +222,13 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS + public float getSoundVolume() { + return 0.4F; + } +- ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel world) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("allayBrain"); ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(world, this); + gameprofilerfiller.pop(); + gameprofilerfiller.push("allayActivityUpdate"); +diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +index 31b10cd404b672d7ce21c2107d8f83e32de26ef4..784599bfb03fc5ee45163cd1f8a75ef6c30f01ce 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java ++++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +@@ -291,12 +291,13 @@ public class Axolotl extends Animal implements VariantHolder, B + public boolean canBeLeashed() { + return true; + } +- ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel world) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("axolotlBrain"); ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(world, this); + gameprofilerfiller.pop(); + gameprofilerfiller.push("axolotlActivityUpdate"); +diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java +index ca04e5d829331551a2c2f44e223ff05c6ce04e76..a04160f895da0cc4b56684a34ef6621b52297950 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java ++++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java +@@ -183,11 +183,12 @@ public class Frog extends Animal implements VariantHolder> { + .flatMap(BuiltInRegistries.FROG_VARIANT::get) + .ifPresent(this::setVariant); + } +- ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel world) { + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("frogBrain"); ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(world, this); + profilerFiller.pop(); + profilerFiller.push("frogActivityUpdate"); +diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +index 48ac8c3f6e00c3c2dc67b6c994be7c0ac6dfcf81..c8dd51d98722a36c86f2e5d07a4b96cc1a2cdc54 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java ++++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +@@ -82,12 +82,13 @@ public class Tadpole extends AbstractFish { + protected SoundEvent getFlopSound() { + return SoundEvents.TADPOLE_FLOP; + } +- ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel world) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("tadpoleBrain"); ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(world, this); + gameprofilerfiller.pop(); + gameprofilerfiller.push("tadpoleActivityUpdate"); +diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +index 76aca47d8638d5c37c57d3a59fa7f8ceaa5a53b4..13465c0295a4a00cbf817e59264d312b51ef9914 100644 +--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java ++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +@@ -191,12 +191,13 @@ public class Goat extends Animal { + public Brain getBrain() { + return (Brain) super.getBrain(); // CraftBukkit - decompile error + } +- ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel world) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("goatBrain"); ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(world, this); + gameprofilerfiller.pop(); + gameprofilerfiller.push("goatActivityUpdate"); +diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java +index 92270912ef26924f611a1df7cb3d5b485b0a262d..743dc5b50bd97b66480b0d61dc61b3f5de4c3097 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java +@@ -137,12 +137,13 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { + public Brain getBrain() { + return (Brain) super.getBrain(); // CraftBukkit - decompile error + } +- ++ private int behaviorTick; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel world) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("hoglinBrain"); ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(world, this); + gameprofilerfiller.pop(); + HoglinAi.updateActivity(this); +diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java +index 2121d2a2e1aa1d0f0390cc515317096431f6dcb0..df3631fa33801f3dd7b075d8a04553485f61d819 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java ++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java +@@ -306,12 +306,13 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento + protected boolean canHunt() { + return !this.cannotHunt; + } +- ++ private int behaviorTick; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel world) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("piglinBrain"); ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(world, this); + gameprofilerfiller.pop(); + PiglinAi.updateActivity(this); +diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java +index c47ed605f0822effd58df4f875297ed015e1e57e..03852e4656da198747395399f7cc56ec82e96bfc 100644 +--- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java ++++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java +@@ -274,12 +274,13 @@ public class Warden extends Monster implements VibrationSystem { + } + + } +- ++ private int behaviorTick = 0; // Pufferfish + @Override + protected void customServerAiStep(ServerLevel world) { + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("wardenBrain"); ++ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + this.getBrain().tick(world, this); + gameprofilerfiller.pop(); + super.customServerAiStep(world); +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 07f50048e9748b28178847ad470b8b2ce37e0eea..7cc9eb0e5bc27bd6b3c7f2b3089248ccd4ed93e1 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -142,6 +142,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + return holder.is(PoiTypes.MEETING); + }); + ++ public long nextGolemPanic = -1; // Pufferfish ++ + public Villager(EntityType entityType, Level world) { + this(entityType, world, VillagerType.PLAINS); + } +@@ -245,6 +247,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + } + // Spigot End + ++ private int behaviorTick = 0; // Pufferfish ++ + @Override + protected void customServerAiStep(ServerLevel world) { + // Paper start - EAR 2 +@@ -255,7 +259,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + ProfilerFiller gameprofilerfiller = Profiler.get(); + + gameprofilerfiller.push("villagerBrain"); +- if (!inactive) this.getBrain().tick(world, this); ++ // Pufferfish start ++ if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) { ++ this.getBrain().tick((ServerLevel) this.level(), this); // Paper ++ } ++ // Pufferfish end + gameprofilerfiller.pop(); + if (this.assignProfessionWhenSpawned) { + this.assignProfessionWhenSpawned = false; +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index f1b7ac4663e8304e754ff0b00b112e4a18621d95..9495a93980bb14680f80e089a4316bbb5483b747 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -38,6 +38,10 @@ import net.minecraft.world.entity.projectile.ThrownTrident; + import net.minecraft.world.entity.raid.Raider; + import net.minecraft.world.level.Level; + import net.minecraft.world.phys.AABB; ++// Pufferfish start ++import net.minecraft.world.phys.Vec3; ++import java.util.List; ++// Pufferfish end + + public class ActivationRange + { +@@ -233,6 +237,25 @@ public class ActivationRange + } + // Paper end - Configurable marker ticking + ActivationRange.activateEntity(entity, bbByType); // Folia - threaded regions ++ ++ // Pufferfish start ++ if (FoldenorConfig.dearEnabled && entity.getType().dabEnabled) { ++ if (!entity.activatedPriorityReset) { ++ entity.activatedPriorityReset = true; ++ entity.activatedPriority = FoldenorConfig.maximumActivationPrio; ++ } ++ Vec3 playerVec = player.position(); ++ Vec3 entityVec = entity.position(); ++ double diffX = playerVec.x - entityVec.x, diffY = playerVec.y - entityVec.y, diffZ = playerVec.z - entityVec.z; ++ int squaredDistance = (int) (diffX * diffX + diffY * diffY + diffZ * diffZ); ++ entity.activatedPriority = squaredDistance > FoldenorConfig.startDistanceSquared ? ++ Math.max(1, Math.min(squaredDistance >> FoldenorConfig.activationDistanceMod, entity.activatedPriority)) : ++ 1; ++ } else { ++ entity.activatedPriority = 1; ++ } ++ // Pufferfish end ++ + } + // Paper end + } diff --git a/patches/server/0046-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch b/patches/server/0046-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch new file mode 100644 index 0000000..64efd42 --- /dev/null +++ b/patches/server/0046-Paper-PR-Skip-AI-during-inactive-ticks-for-non-aware.patch @@ -0,0 +1,96 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: AltronMaxX +Date: Sat, 4 Jan 2025 15:02:02 +0400 +Subject: [PATCH] Paper PR: Skip AI during inactive ticks for non-aware mobs + + +diff --git a/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java b/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java +index 8b3481286976b4ef60c236d16f380d9b2ebe3996..e2472260256ed02f00384a23942bce1cd86538be 100644 +--- a/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java ++++ b/src/main/java/net/edenor/foldenor/config/FoldenorConfig.java +@@ -55,6 +55,8 @@ public class FoldenorConfig { + public static int maximumActivationPrio; + public static int activationDistanceMod; + ++ public static boolean skipAIForNonAwareMob = true; ++ + public static void init(File configFile) { + init(configFile, true); + } +@@ -105,6 +107,12 @@ public class FoldenorConfig { + + readRegionSettings(); + ++ try { ++ dynamicActivationOfBrains(); ++ } catch (IOException e) { ++ throw new RuntimeException(e); ++ } ++ + try { + config.save(CONFIG_FILE); + } catch (IOException ex) { +@@ -123,6 +131,7 @@ public class FoldenorConfig { + "Reduces piglin spawn in portal, by reducing change to spawn"); + skipMapItemUpdatesIfNoBukkitRender = getBoolean("optimizations.skip_map_item_updates_if_no_bukkit_render", skipMapItemUpdatesIfNoBukkitRender); + entityActivationCheckFrequency = getInt("optimizations.entity-activation-check-frequency", 20); ++ skipAIForNonAwareMob = getBoolean("optimizations.skip-ai-for-non-aware-mob", skipAIForNonAwareMob); + } + + private static void readMiscSettings() { +diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java +index 34895cefe32e2698ef20b1656dc8b40491c33027..81a3fa36ed7c35d7d3ac924933c3e38bcf07b663 100644 +--- a/src/main/java/net/minecraft/world/entity/Mob.java ++++ b/src/main/java/net/minecraft/world/entity/Mob.java +@@ -14,6 +14,8 @@ import java.util.Optional; + import java.util.Set; + import java.util.function.Predicate; + import javax.annotation.Nullable; ++ ++import net.edenor.foldenor.config.FoldenorConfig; + import net.minecraft.core.BlockPos; + import net.minecraft.core.Holder; + import net.minecraft.core.NonNullList; +@@ -235,6 +237,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + @Override + public void inactiveTick() { + super.inactiveTick(); ++ // Paper start - Skip AI during inactive ticks for non-aware mobs ++ if (FoldenorConfig.skipAIForNonAwareMob && !(this.isEffectiveAi() && this.aware)) { ++ return; ++ } ++ // Paper end - Skip AI during inactive ticks for non-aware mobs + if (this.goalSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priroity + this.goalSelector.tick(); + } +diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java +index 7cc9eb0e5bc27bd6b3c7f2b3089248ccd4ed93e1..521791d4449f1220ee44e5ae7f5e7cc09f90dd67 100644 +--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java ++++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java +@@ -16,6 +16,8 @@ import java.util.Objects; + import java.util.Optional; + import java.util.function.BiPredicate; + import javax.annotation.Nullable; ++ ++import net.edenor.foldenor.config.FoldenorConfig; + import net.minecraft.core.BlockPos; + import net.minecraft.core.GlobalPos; + import net.minecraft.core.Holder; +@@ -234,14 +236,14 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + if (this.getUnhappyCounter() > 0) { + this.setUnhappyCounter(this.getUnhappyCounter() - 1); + } +- if (this.isEffectiveAi()) { ++ if (this.isEffectiveAi() && (!FoldenorConfig.skipAIForNonAwareMob || this.aware)) { // Paper - Skip AI during inactive ticks for non-aware mobs + if (this.level().spigotConfig.tickInactiveVillagers) { + this.customServerAiStep(this.level().getMinecraftWorld()); + } else { + this.customServerAiStep(this.level().getMinecraftWorld(), true); + } +- } +- maybeDecayGossip(); ++ } ++ maybeDecayGossip(); + // Paper end + super.inactiveTick(); + }