diff --git a/leaf-server/minecraft-patches/features/0214-Async-chunk-send.patch b/leaf-server/minecraft-patches/features/0214-Async-chunk-sending.patch similarity index 88% rename from leaf-server/minecraft-patches/features/0214-Async-chunk-send.patch rename to leaf-server/minecraft-patches/features/0214-Async-chunk-sending.patch index e648120f..15822eaf 100644 --- a/leaf-server/minecraft-patches/features/0214-Async-chunk-send.patch +++ b/leaf-server/minecraft-patches/features/0214-Async-chunk-sending.patch @@ -1,11 +1,11 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Sun, 2 Mar 2025 21:23:20 +0100 -Subject: [PATCH] Async chunk send +Subject: [PATCH] Async chunk sending diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java -index 32608df3da169159c070f37cb55407f4f6187744..3a78e7512772fd3f7cf8f221e3a72474def14bea 100644 +index 32608df3da169159c070f37cb55407f4f6187744..fc3901acdfcdad85fbd435ca21869388a90b2207 100644 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java +++ b/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java @@ -436,7 +436,15 @@ public final class RegionizedPlayerChunkLoader { @@ -13,7 +13,7 @@ index 32608df3da169159c070f37cb55407f4f6187744..3a78e7512772fd3f7cf8f221e3a72474 ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$removeReceivedChunk(this.player); - this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))); -+ // Leaf start - Async chunk send ++ // Leaf start - Async chunk sending + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { + org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( + () -> this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))) @@ -21,26 +21,26 @@ index 32608df3da169159c070f37cb55407f4f6187744..3a78e7512772fd3f7cf8f221e3a72474 + } else { + this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))); + } -+ // Leaf end - Async chunk send ++ // Leaf end - Async chunk sending // Paper start - PlayerChunkUnloadEvent if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(new ChunkPos(chunkX, chunkZ).longKey), player.getBukkitEntity()).callEvent(); diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java -index 526c117e0d53ad527eb610c79cdc46ec16b18c0c..9151e580b4840fddab04e487d723130a5a769a1a 100644 +index 526c117e0d53ad527eb610c79cdc46ec16b18c0c..9e5edbb05900fdaa8b9deed0fc65e9ca31fe6d61 100644 --- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java @@ -75,6 +75,45 @@ public class ClientboundLevelChunkPacketData { } } -+ // Leaf start - Async chunk send ++ // Leaf start - Async chunk sending + public ClientboundLevelChunkPacketData(LevelChunk levelChunk, io.papermc.paper.antixray.ChunkPacketInfo chunkPacketInfo, BlockEntity[] blockEntities, Map heightmaps) { + this.heightmaps = heightmaps; + + if (Thread.currentThread() instanceof org.dreeam.leaf.async.chunk.AsyncChunkSendThread) { -+ var buffer = new io.netty.buffer.UnpooledByteBufAllocator(false).buffer(calculateChunkSize(levelChunk)); ++ ByteBuf buffer = Unpooled.buffer(calculateChunkSize(levelChunk)); + extractChunkData(new FriendlyByteBuf(buffer), levelChunk, chunkPacketInfo); -+ var array = it.unimi.dsi.fastutil.bytes.ByteArrays.trim(buffer.array(), buffer.writerIndex()); ++ byte[] array = it.unimi.dsi.fastutil.bytes.ByteArrays.setLength(buffer.array(), buffer.writerIndex()); + if (chunkPacketInfo != null) { + chunkPacketInfo.setBuffer(array); + } @@ -70,20 +70,20 @@ index 526c117e0d53ad527eb610c79cdc46ec16b18c0c..9151e580b4840fddab04e487d723130a + this.blockEntitiesData.add(ClientboundLevelChunkPacketData.BlockEntityInfo.create(blockEntity)); + } + } -+ // Leaf end - Async chunk send ++ // Leaf end - Async chunk sending + public ClientboundLevelChunkPacketData(RegistryFriendlyByteBuf buffer, int x, int z) { this.heightmaps = HEIGHTMAPS_STREAM_CODEC.decode(buffer); int varInt = buffer.readVarInt(); diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java -index 8578d1f78ddd1bb75f3230f04bfaa35af9f5f822..4f54c4c8e49c1e0352ab2c5c23277b4103504c55 100644 +index 8578d1f78ddd1bb75f3230f04bfaa35af9f5f822..d8e938abf5123b092cec80feb6468e3d91ae823e 100644 --- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java @@ -44,6 +44,17 @@ public class ClientboundLevelChunkWithLightPacket implements Packet heightmaps) { + ChunkPos pos = chunk.getPos(); + this.x = pos.x; @@ -93,12 +93,12 @@ index 8578d1f78ddd1bb75f3230f04bfaa35af9f5f822..4f54c4c8e49c1e0352ab2c5c23277b41 + this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight); + chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks + } -+ // Leaf end - Async chunk send ++ // Leaf end - Async chunk sending private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) { this.x = buffer.readInt(); diff --git a/net/minecraft/server/network/PlayerChunkSender.java b/net/minecraft/server/network/PlayerChunkSender.java -index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9fe3ec775 100644 +index 14878690a88fd4de3e2c127086607e6c819c636c..62e1dabd84b80d5e2e4a8fcb308934dce2d8c74b 100644 --- a/net/minecraft/server/network/PlayerChunkSender.java +++ b/net/minecraft/server/network/PlayerChunkSender.java @@ -64,13 +64,29 @@ public class PlayerChunkSender { @@ -106,7 +106,7 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9 ServerGamePacketListenerImpl serverGamePacketListenerImpl = player.connection; this.unacknowledgedBatches++; - serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE); -+ // Leaf start - Async chunk send ++ // Leaf start - Async chunk sending + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { + org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( + () -> serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE) @@ -114,14 +114,14 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9 + } else { + serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE); + } -+ // Leaf end - Async chunk send ++ // Leaf end - Async chunk sending for (LevelChunk levelChunk : list) { sendChunk(serverGamePacketListenerImpl, serverLevel, levelChunk); } - serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size())); -+ // Leaf start - Async chunk send ++ // Leaf start - Async chunk sending + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { + org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( + () -> serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size())) @@ -129,7 +129,7 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9 + } else { + serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size())); + } -+ // Leaf end - Async chunk send ++ // Leaf end - Async chunk sending this.batchQuota = this.batchQuota - list.size(); } } @@ -138,7 +138,7 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9 public static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) { final boolean shouldModify = level.chunkPacketBlockController.shouldModify(packetListener.player, chunk); - packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null, shouldModify)); -+ // Leaf start - Async chunk send ++ // Leaf start - Async chunk sending + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { + var blockEntities = chunk.blockEntities.values().toArray(new net.minecraft.world.level.block.entity.BlockEntity[0]); + java.util.Map heightmaps = new java.util.concurrent.ConcurrentHashMap<>(); @@ -154,12 +154,12 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9 + } else { + packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null, shouldModify)); + } -+ // Leaf end - Async chunk send ++ // Leaf end - Async chunk sending // Paper end - Anti-Xray // Paper start - PlayerChunkLoadEvent if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { diff --git a/net/minecraft/world/level/chunk/LevelChunkSection.java b/net/minecraft/world/level/chunk/LevelChunkSection.java -index 36c033b0ee63dfc273d721fb4b614733e8fdef19..1cc33a038060aaf5258ee4f1deb19b4a1be59a29 100644 +index 36c033b0ee63dfc273d721fb4b614733e8fdef19..4d06df242ab73411bdefc4770e131b27a6ea668a 100644 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/net/minecraft/world/level/chunk/LevelChunkSection.java @@ -18,7 +18,7 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.block_ @@ -167,7 +167,7 @@ index 36c033b0ee63dfc273d721fb4b614733e8fdef19..1cc33a038060aaf5258ee4f1deb19b4a public static final int SECTION_SIZE = 4096; public static final int BIOME_CONTAINER_BITS = 2; - short nonEmptyBlockCount; // Paper - package private -+ volatile short nonEmptyBlockCount; // Paper - package private // Leaf - Async chunk send - volatile ++ volatile short nonEmptyBlockCount; // Paper - package private // Leaf - Async chunk sending - volatile private short tickingBlockCount; private short tickingFluidCount; private boolean isRandomlyTickingBlocksStatus; // Leaf - Cache random tick block status diff --git a/leaf-server/minecraft-patches/features/0263-optimise-getBiome.patch b/leaf-server/minecraft-patches/features/0263-cache-getBiome.patch similarity index 56% rename from leaf-server/minecraft-patches/features/0263-optimise-getBiome.patch rename to leaf-server/minecraft-patches/features/0263-cache-getBiome.patch index d52b0083..592665d7 100644 --- a/leaf-server/minecraft-patches/features/0263-optimise-getBiome.patch +++ b/leaf-server/minecraft-patches/features/0263-cache-getBiome.patch @@ -1,11 +1,11 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Tue, 3 Jun 2025 15:20:40 +0900 -Subject: [PATCH] optimise getBiome +Subject: [PATCH] cache getBiome diff --git a/net/minecraft/advancements/critereon/LocationPredicate.java b/net/minecraft/advancements/critereon/LocationPredicate.java -index a26a5311f87873e0d4d26fda9cb8956a32ee81e8..65e4315cce35814c60b21bbd5baea2ffac82162c 100644 +index a26a5311f87873e0d4d26fda9cb8956a32ee81e8..7ba5c85bbce8528a4df072e63948673300630a9d 100644 --- a/net/minecraft/advancements/critereon/LocationPredicate.java +++ b/net/minecraft/advancements/critereon/LocationPredicate.java @@ -49,7 +49,7 @@ public record LocationPredicate( @@ -13,12 +13,12 @@ index a26a5311f87873e0d4d26fda9cb8956a32ee81e8..65e4315cce35814c60b21bbd5baea2ff BlockPos blockPos = BlockPos.containing(x, y, z); boolean isLoaded = level.isLoaded(blockPos); - return (!this.biomes.isPresent() || isLoaded && this.biomes.get().contains(level.getBiome(blockPos))) -+ return (!this.biomes.isPresent() || isLoaded && this.biomes.get().contains(org.dreeam.leaf.config.modules.opt.OptimizeBiome.advancement ? level.getBiomeCached(blockPos) : level.getBiome(blockPos))) // Leaf - cache getBiome ++ return (!this.biomes.isPresent() || isLoaded && this.biomes.get().contains(org.dreeam.leaf.config.modules.opt.OptimizeBiome.advancement ? level.getBiomeCached(null, blockPos) : level.getBiome(blockPos))) // Leaf - cache getBiome && (!this.structures.isPresent() || isLoaded && level.structureManager().getStructureWithPieceAt(blockPos, this.structures.get()).isValid()) && (!this.smokey.isPresent() || isLoaded && this.smokey.get() == CampfireBlock.isSmokeyPos(level, blockPos)) && (!this.light.isPresent() || this.light.get().matches(level, blockPos)) diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java -index 0842fd6488c8b27d98c4344e1244996b4c0e9912..c6ddb49648c55443ae880c1adba9887ab0681c82 100644 +index 0842fd6488c8b27d98c4344e1244996b4c0e9912..55c7f7486c293d4434b7e3facdbef034d105aa19 100644 --- a/net/minecraft/world/level/LevelReader.java +++ b/net/minecraft/world/level/LevelReader.java @@ -57,6 +57,12 @@ public interface LevelReader extends ca.spottedleaf.moonrise.patches.chunk_syste @@ -26,8 +26,8 @@ index 0842fd6488c8b27d98c4344e1244996b4c0e9912..c6ddb49648c55443ae880c1adba9887a } + // Leaf start - cache getBiome -+ default Holder getBiomeCached(BlockPos pos) { -+ return this.getBiomeManager().getBiomeCached(pos); ++ default Holder getBiomeCached(@Nullable net.minecraft.world.level.chunk.LevelChunk chunk, BlockPos pos) { ++ return this.getBiomeManager().getBiomeCached(chunk, pos); + } + // Leaf end - cache getBiome + @@ -35,7 +35,7 @@ index 0842fd6488c8b27d98c4344e1244996b4c0e9912..c6ddb49648c55443ae880c1adba9887a int floor = Mth.floor(aabb.minX); int floor1 = Mth.floor(aabb.maxX); diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java -index a2da4fce50f31d56036d04041c4f80ed90c18b27..f242941ce06d356a025e306efe78c688e9b755c4 100644 +index a2da4fce50f31d56036d04041c4f80ed90c18b27..81e176d17fb072f9ee531639abfe42134ae833a9 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java @@ -443,7 +443,7 @@ public final class NaturalSpawner { @@ -43,7 +43,7 @@ index a2da4fce50f31d56036d04041c4f80ed90c18b27..f242941ce06d356a025e306efe78c688 ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, RandomSource random, BlockPos pos ) { - Holder biome = level.getBiome(pos); -+ Holder biome = org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(pos) : level.getBiome(pos); // Leaf - cache getBiome ++ Holder biome = org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(null, pos) : level.getBiome(pos); // Leaf - cache getBiome return category == MobCategory.WATER_AMBIENT && biome.is(BiomeTags.REDUCED_WATER_AMBIENT_SPAWNS) && random.nextFloat() < 0.98F ? Optional.empty() : mobsAt(level, structureManager, generator, category, pos, biome).getRandom(random); @@ -52,12 +52,12 @@ index a2da4fce50f31d56036d04041c4f80ed90c18b27..f242941ce06d356a025e306efe78c688 return isInNetherFortressBounds(pos, level, cetagory, structureManager) ? NetherFortressStructure.FORTRESS_ENEMIES - : generator.getMobsAt(biome != null ? biome : level.getBiome(pos), structureManager, cetagory, pos); -+ : generator.getMobsAt(biome != null ? biome : (org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(pos) : level.getBiome(pos)), structureManager, cetagory, pos); // Leaf - cache getBiome ++ : generator.getMobsAt(biome != null ? biome : (org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(null, pos) : level.getBiome(pos)), structureManager, cetagory, pos); // Leaf - cache getBiome } public static boolean isInNetherFortressBounds(BlockPos pos, ServerLevel level, MobCategory category, StructureManager structureManager) { diff --git a/net/minecraft/world/level/biome/BiomeManager.java b/net/minecraft/world/level/biome/BiomeManager.java -index a48175a7ebb1788ace46395621ed78d910178a53..00122472991ba0c1a0ea77053aad71cdfa92a7bd 100644 +index a48175a7ebb1788ace46395621ed78d910178a53..bda44479d1537d9fa2d23f519049aeb96685fbe6 100644 --- a/net/minecraft/world/level/biome/BiomeManager.java +++ b/net/minecraft/world/level/biome/BiomeManager.java @@ -15,10 +15,23 @@ public class BiomeManager { @@ -84,12 +84,12 @@ index a48175a7ebb1788ace46395621ed78d910178a53..00122472991ba0c1a0ea77053aad71cd } public static long obfuscateSeed(long seed) { -@@ -29,6 +42,40 @@ public class BiomeManager { +@@ -29,6 +42,105 @@ public class BiomeManager { return new BiomeManager(newSource, this.biomeZoomSeed); } + // Leaf start - cache getBiome -+ public Holder getBiomeCached(BlockPos pos) { ++ public Holder getBiomeCached(@org.jetbrains.annotations.Nullable net.minecraft.world.level.chunk.LevelChunk chunk, BlockPos pos) { + if (biomeCache == null) { + return getBiome(pos); + } @@ -113,19 +113,84 @@ index a48175a7ebb1788ace46395621ed78d910178a53..00122472991ba0c1a0ea77053aad71cd + } + } + -+ Holder biome = getBiome(pos); ++ Holder biome = getBiomeCachedChunk(chunk, pos); + + biomeCache[(int) hash] = biome; + biomeCachePos[(int) hash] = packedPos; + + return biome; + } ++ private Holder getBiomeCachedChunk(@org.jetbrains.annotations.Nullable net.minecraft.world.level.chunk.LevelChunk chunk, BlockPos pos) { ++ // Leaf start - Carpet-Fixes - Optimized getBiome method ++ int xMinus2 = pos.getX() - 2; ++ int yMinus2 = pos.getY() - 2; ++ int zMinus2 = pos.getZ() - 2; ++ int x = xMinus2 >> 2; // BlockPos to BiomePos ++ int y = yMinus2 >> 2; ++ int z = zMinus2 >> 2; ++ double quartX = (double) (xMinus2 & 3) / 4.0; // quartLocal divided by 4 ++ double quartY = (double) (yMinus2 & 3) / 4.0; // 0/4, 1/4, 2/4, 3/4 ++ double quartZ = (double) (zMinus2 & 3) / 4.0; // [0, 0.25, 0.5, 0.75] ++ int smallestX = 0; ++ double smallestDist = Double.POSITIVE_INFINITY; ++ for (int biomeX = 0; biomeX < 8; ++biomeX) { ++ boolean everyOtherQuad = (biomeX & 4) == 0; // 1 1 1 1 0 0 0 0 ++ boolean everyOtherPair = (biomeX & 2) == 0; // 1 1 0 0 1 1 0 0 ++ boolean everyOther = (biomeX & 1) == 0; // 1 0 1 0 1 0 1 0 ++ double quartXX = everyOtherQuad ? quartX : quartX - 1.0; //[-1.0,-0.75,-0.5,-0.25,0.0,0.25,0.5,0.75] ++ double quartYY = everyOtherPair ? quartY : quartY - 1.0; ++ double quartZZ = everyOther ? quartZ : quartZ - 1.0; ++ ++ //This code block is new ++ double maxQuartYY = 0.0, maxQuartZZ = 0.0; ++ if (biomeX != 0) { ++ maxQuartYY = Mth.square(Math.max(quartYY + maxOffset, Math.abs(quartYY - maxOffset))); ++ maxQuartZZ = Mth.square(Math.max(quartZZ + maxOffset, Math.abs(quartZZ - maxOffset))); ++ double maxQuartXX = Mth.square(Math.max(quartXX + maxOffset, Math.abs(quartXX - maxOffset))); ++ if (smallestDist < maxQuartXX + maxQuartYY + maxQuartZZ) continue; ++ } ++ int xx = everyOtherQuad ? x : x + 1; ++ int yy = everyOtherPair ? y : y + 1; ++ int zz = everyOther ? z : z + 1; ++ ++ //I transferred the code from method_38106 to here, so I could call continue halfway through ++ long seed = LinearCongruentialGenerator.next(this.biomeZoomSeed, xx); ++ seed = LinearCongruentialGenerator.next(seed, yy); ++ seed = LinearCongruentialGenerator.next(seed, zz); ++ seed = LinearCongruentialGenerator.next(seed, xx); ++ seed = LinearCongruentialGenerator.next(seed, yy); ++ seed = LinearCongruentialGenerator.next(seed, zz); ++ double offsetX = getFiddle(seed); ++ double sqrX = Mth.square(quartXX + offsetX); ++ if (biomeX != 0 && smallestDist < sqrX + maxQuartYY + maxQuartZZ) continue; //skip the rest of the loop ++ seed = LinearCongruentialGenerator.next(seed, this.biomeZoomSeed); ++ double offsetY = getFiddle(seed); ++ double sqrY = Mth.square(quartYY + offsetY); ++ if (biomeX != 0 && smallestDist < sqrX + sqrY + maxQuartZZ) continue; // skip the rest of the loop ++ seed = LinearCongruentialGenerator.next(seed, this.biomeZoomSeed); ++ double offsetZ = getFiddle(seed); ++ double biomeDist = sqrX + sqrY + Mth.square(quartZZ + offsetZ); ++ ++ if (smallestDist > biomeDist) { ++ smallestX = biomeX; ++ smallestDist = biomeDist; ++ } ++ } ++ int x1 = (smallestX & 4) == 0 ? x : x + 1; ++ int y1 = (smallestX & 2) == 0 ? y : y + 1; ++ int z1 = (smallestX & 1) == 0 ? z : z + 1; ++ if (chunk != null && chunk.locX == x >> 2 && chunk.locZ == z >> 2) { ++ return chunk.getNoiseBiome(x1, y1, z1); ++ } ++ return this.noiseBiomeSource.getNoiseBiome(x1, y1, z1); ++ // Leaf end - Carpet-Fixes - Optimized getBiome method ++ } + // Leaf end - cache getBiome + public Holder getBiome(BlockPos pos) { // Leaf start - Carpet-Fixes - Optimized getBiome method int xMinus2 = pos.getX() - 2; -@@ -126,9 +173,18 @@ public class BiomeManager { +@@ -126,9 +238,18 @@ public class BiomeManager { return Mth.square(zNoise + fiddle2) + Mth.square(yNoise + fiddle1) + Mth.square(xNoise + fiddle); } diff --git a/leaf-server/minecraft-patches/features/0264-optimize-mob-spawning.patch b/leaf-server/minecraft-patches/features/0264-optimize-mob-spawning.patch index c8b32c9d..5578e8f4 100644 --- a/leaf-server/minecraft-patches/features/0264-optimize-mob-spawning.patch +++ b/leaf-server/minecraft-patches/features/0264-optimize-mob-spawning.patch @@ -3,26 +3,594 @@ From: hayanesuru Date: Tue, 3 Jun 2025 15:20:59 +0900 Subject: [PATCH] optimize mob spawning +Avoid getChunk calls if its position is same as the chunk used for mob spawning +Fix data race in async mob spawning +by adding chunk position to the mob count map +then apply result on server thread. + +Generally faster than the non-async approach + +iterate over all entities, get their chunk, and increment the count + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index 083b49c8fbdbde96b5be3e5531c21b2c96371b60..ca707979e221ea0500efad42350994b90aac3d52 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -287,6 +287,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + // Paper end - per player mob count backoff + public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { ++ // Leaf - diff - async mob spawning - optimize mob spawning + return player.mobCounts[mobCategory.ordinal()] + player.mobBackoffCounts[mobCategory.ordinal()]; // Paper - per player mob count backoff + } + // Paper end - Optional per player mob spawns +diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java +index fc86e900e41305287a6cc6d766184c6e28d6189b..56516dd07433161f5afd448aaf535c59d14c2528 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -70,11 +70,11 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + private final long[] lastChunkPos = new long[4]; + private final ChunkStatus[] lastChunkStatus = new ChunkStatus[4]; + private final ChunkAccess[] lastChunk = new ChunkAccess[4]; +- private final List spawningChunks = new ObjectArrayList<>(); ++ private final List spawningChunks = it.unimi.dsi.fastutil.objects.ReferenceArrayList.wrap(new LevelChunk[0]); // Leaf - optimize mob spawning + private final Set chunkHoldersToBroadcast = new ReferenceOpenHashSet<>(); + @Nullable + @VisibleForDebug +- private NaturalSpawner.SpawnState lastSpawnState; ++ private volatile NaturalSpawner.SpawnState lastSpawnState; // Leaf - optimize mob spawning + // Paper start + public final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); + public int getFullChunksCount() { +@@ -497,6 +497,23 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + private void tickChunks() { ++ // Leaf start - optimize mob spawning ++ if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled && this.level.tickRateManager().runsNormally()) { ++ for (ServerPlayer player : this.level.players) { ++ // Paper start - per player mob spawning backoff ++ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { ++ player.mobCounts[ii] = 0; ++ ++ int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm? ++ if (newBackoff < 0) { ++ newBackoff = 0; ++ } ++ player.mobBackoffCounts[ii] = newBackoff; ++ } ++ // Paper end - per player mob spawning backoff ++ } ++ } ++ // Leaf end - optimize mob spawning + long gameTime = this.level.getGameTime(); + long l = gameTime - this.lastInhabitedUpdate; + this.lastInhabitedUpdate = gameTime; +@@ -510,8 +527,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + // Pufferfish start - optimize mob spawning +- if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) { +- for (ServerPlayer player : this.level.players) { ++ if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled && this.level.tickRateManager().runsNormally()) { ++ /*for (ServerPlayer player : this.level.players) { + // Paper start - per player mob spawning backoff + for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { + player.mobCounts[ii] = 0; +@@ -523,14 +540,14 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + player.mobBackoffCounts[ii] = newBackoff; + } + // Paper end - per player mob spawning backoff +- } ++ }*/ + if (firstRunSpawnCounts) { + firstRunSpawnCounts = false; + _pufferfish_spawnCountsReady.set(true); + } + if (_pufferfish_spawnCountsReady.getAndSet(false)) { ++ int mapped = distanceManager.getNaturalSpawnChunkCount(); + net.minecraft.server.MinecraftServer.getServer().mobSpawnExecutor.submit(() -> { +- int mapped = distanceManager.getNaturalSpawnChunkCount(); + ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator objectiterator = + level.entityTickList.entities.iterator(ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); + try { +@@ -541,10 +558,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + new LocalMobCapCalculator(chunkMap) : null; + + // This ensures the caps are properly enforced by using the correct calculator +- lastSpawnState = NaturalSpawner.createState( ++ lastSpawnState = NaturalSpawner.createState1( // Leaf - optimize mob spawning + mapped, + wrappedIterator, +- ServerChunkCache.this::getFullChunk, ++ this.level, // Leaf - optimize mob spawning + mobCapCalculator, // This is the key fix - was previously null + level.paperConfig().entities.spawning.perPlayerMobSpawns + ); +@@ -609,6 +626,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + chunkRange = Math.min(chunkRange, 8); + entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); + entityPlayer.playerNaturallySpawnedEvent.callEvent(); ++ this.level.natureSpawnChunkMap.addPlayer(entityPlayer); // Leaf - optimize mob spawning + } + // Paper end - PlayerNaturallySpawnCreaturesEvent + boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit +@@ -620,16 +638,40 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + List list = this.spawningChunks; + + try { +- this.chunkMap.collectSpawningChunks(list); ++ // Leaf start - optimize mob spawning ++ // this.chunkMap.collectSpawningChunks(list); ++ this.level.natureSpawnChunkMap.build(); + // Paper start - chunk tick iteration optimisation +- this.shuffleRandom.setSeed(this.level.random.nextLong()); +- if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled ++ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ this.level.natureSpawnChunkMap.collectSpawningChunks(this.level.moonrise$getPlayerTickingChunks(), list); ++ this.shuffleRandom.setSeed(this.level.random.nextLong()); // Leaf - paw optimization - Only set seed if is really used ++ Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled ++ } + // Paper end - chunk tick iteration optimisation + +- for (LevelChunk levelChunk : list) { +- this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, lastSpawnState); // Pufferfish ++ if (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get()) { ++ NaturalSpawner.SpawnState currentState = lastSpawnState; ++ if (currentState != null) { ++ currentState.applyPerPlayerMobCount(level); ++ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { ++ for (LevelChunk levelChunk : list) { ++ this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, currentState); // Pufferfish ++ } ++ } else { ++ ca.spottedleaf.moonrise.common.list.ReferenceList chunks = this.level.moonrise$getPlayerTickingChunks(); ++ LevelChunk[] raw = chunks.getRawDataUnchecked(); ++ for (int i = 0, length = chunks.size(); i < length; i++) { ++ LevelChunk levelChunk = raw[i]; ++ if (level.natureSpawnChunkMap.contains(levelChunk.locX, levelChunk.locZ)) { ++ this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, currentState); // Pufferfish ++ } ++ } ++ } ++ } + } ++ // Leaf end - optimize mob spawning + } finally { ++ this.level.natureSpawnChunkMap.clear(); // Leaf - optimize mob spawning + list.clear(); + } + +@@ -647,7 +689,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + } + + if (!spawnCategories.isEmpty()) { +- if (this.level.getWorldBorder().isWithinBounds(pos) && (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get())) { // Paper - rewrite chunk system // Pufferfish ++ if (this.level.getWorldBorder().isWithinBounds(pos)) { // Paper - rewrite chunk system + NaturalSpawner.spawnForChunk(this.level, chunk, spawnState, spawnCategories); + } + } +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 224a032e8992f104ad9380182ed67c316c93274e..983b03c9cf1c91e9954da0ed8be99c2dfaa5a9c6 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -1110,6 +1110,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + + private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.simpleRandom.nextInt(16); } // Gale - Airplane - optimize random calls in chunk ticking + ++ public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning + public void tickChunk(LevelChunk chunk, int randomTickSpeed) { + final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting + ChunkPos pos = chunk.getPos(); diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java -index f242941ce06d356a025e306efe78c688e9b755c4..38a6a11c05c0a0a2f59c1477e516431263dce101 100644 +index 81e176d17fb072f9ee531639abfe42134ae833a9..ab6fa7ed111ef16a0b6774c21988589ee2110c66 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java -@@ -472,6 +472,17 @@ public final class NaturalSpawner { +@@ -68,6 +68,7 @@ public final class NaturalSpawner { + return createState(spawnableChunkCount, entities, chunkGetter, calculator, false); + } + ++ @Deprecated // Leaf - optimize mob spawning + public static NaturalSpawner.SpawnState createState( + int spawnableChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator, final boolean countMobs + ) { +@@ -108,9 +109,69 @@ public final class NaturalSpawner { + } + } + +- return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator); ++ return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator, new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>()); // Leaf - optimize mob spawning + } + ++ // Leaf start - optimize mob spawning ++ public static NaturalSpawner.SpawnState createState1( ++ int spawnableChunkCount, Iterable entities, ServerLevel level, LocalMobCapCalculator calculator, final boolean countMobs ++ ) { ++ // Paper end - Optional per player mob spawns ++ PotentialCalculator potentialCalculator = new PotentialCalculator(); ++ Object2IntOpenHashMap map = new Object2IntOpenHashMap<>(); ++ it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap chunkCap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); ++ boolean countAllMobsForSpawning = level.paperConfig().entities.spawning.countAllMobsForSpawning; ++ for (Entity entity : entities) { ++ if (!(entity instanceof Mob mob && (mob.isPersistenceRequired() || mob.requiresCustomPersistence()))) { ++ MobCategory category = entity.getType().getCategory(); ++ if (category != MobCategory.MISC) { ++ // Paper start - Only count natural spawns ++ if (!countAllMobsForSpawning && ++ !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL || ++ entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { ++ continue; ++ } ++ // Paper end - Only count natural spawns ++ BlockPos blockPos = entity.blockPosition(); ++ LevelChunk chunk = level.chunkSource.fullChunks.get(ChunkPos.asLong(blockPos)); ++ MobSpawnSettings.MobSpawnCost mobSpawnCost = getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entity.getType()); ++ if (mobSpawnCost != null) { ++ potentialCalculator.addCharge(entity.blockPosition(), mobSpawnCost.charge()); ++ } ++ ++ if (calculator != null && entity instanceof Mob) { // Paper - Optional per player mob spawns ++ calculator.addMob(chunk.getPos(), category); ++ } ++ ++ map.addTo(category, 1); ++ // Paper start - Optional per player mob spawns ++ if (countMobs) { ++ final int index = entity.getType().getCategory().ordinal(); ++ ++chunkCap.computeIfAbsent(chunk.getPos().longKey, k -> new int[net.minecraft.server.level.ServerPlayer.MOBCATEGORY_TOTAL_ENUMS])[index]; ++ /* ++ final int index = entity.getType().getCategory().ordinal(); ++ final var inRange = level.moonrise$getNearbyPlayers().getPlayers(entity.chunkPosition(), NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); ++ if (inRange == null) { ++ continue; ++ } ++ ++ final net.minecraft.server.level.ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); ++ for (int i = 0, len = inRange.size(); i < len; i++) { ++ final net.minecraft.server.level.ServerPlayer player = backingSet[i]; ++ if (player == null) continue; ++ ++playerCap.computeIfAbsent(player, k -> new int[net.minecraft.server.level.ServerPlayer.MOBCATEGORY_TOTAL_ENUMS])[index]; ++ } ++ */ ++ } ++ // Paper end - Optional per player mob spawns ++ } ++ } ++ } ++ ++ return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator, chunkCap); ++ } ++ // Leaf end - optimize mob spawning ++ + static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) { + return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value(); + } +@@ -265,28 +326,67 @@ public final class NaturalSpawner { + MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer trackEntity, final boolean nothing + // Paper end - throttle failed spawn attempts + ) { ++ // Leaf start - optimize mob spawning ++ if (!(chunk instanceof LevelChunk levelChunk)) { ++ // unreachable ++ return 0; ++ } ++ // Leaf end - optimize mob spawning + // Paper end - Optional per player mob spawns + StructureManager structureManager = level.structureManager(); + ChunkGenerator generator = level.getChunkSource().getGenerator(); + int y = pos.getY(); ++ // Leaf start - optimize mob spawning ++ int posX = pos.getX(); ++ int posZ = pos.getZ(); + int i = 0; // Paper - throttle failed spawn attempts +- BlockState blockState = level.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn ++ BlockState blockState = level.getWorldBorder().isWithinBounds(pos) ? (ChunkPos.asLong(pos) == levelChunk.getPos().longKey ? levelChunk.getBlockStateFinal(posX, y, posZ) : level.getBlockStateIfLoaded(pos)) : null; // Paper - don't load chunks for mob spawn // Leaf + if (blockState != null && !blockState.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn + BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + //int i = 0; // Paper - throttle failed spawn attempts - move up +- ++ // 4 * (3 * ([1, 4] * 4)) + 3 * 1 * 2 ++ long rand = level.random.nextLong(); ++ int bits = 0; + for (int i1 = 0; i1 < 3; i1++) { +- int x = pos.getX(); +- int z = pos.getZ(); +- int i2 = 6; ++ int x = posX; ++ int z = posZ; ++ //int i2 = 6; + MobSpawnSettings.SpawnerData spawnerData = null; + SpawnGroupData spawnGroupData = null; +- int ceil = Mth.ceil(level.random.nextFloat() * 4.0F); ++ //int ceil = Mth.ceil(level.random.nextFloat() * 4.0F); ++ int ceil = (int) (((rand >>> bits) & 0x3L) + 1); ++ bits += 2; ++ if (bits >= 62) { ++ rand = level.random.nextInt(); ++ bits = 0; ++ } + int i3 = 0; + + for (int i4 = 0; i4 < ceil; i4++) { +- x += level.random.nextInt(6) - level.random.nextInt(6); +- z += level.random.nextInt(6) - level.random.nextInt(6); ++ int rand1=0,rand2=0,rand3=0,rand4=0,valuesNeeded=4; ++ while (valuesNeeded > 0) { ++ // [0, 61] 3 remains ++ int threeBits = (int) ((rand >>> bits) & 0x7L); ++ bits += 3; ++ if (threeBits != 7 && threeBits != 6) { ++ switch (valuesNeeded) { ++ case 1 -> rand4 = threeBits; ++ case 2 -> rand3 = threeBits; ++ case 3 -> rand2 = threeBits; ++ case 4 -> rand1 = threeBits; ++ } ++ valuesNeeded--; ++ } ++ if (bits >= 62) { ++ rand = level.random.nextInt(); ++ bits = 0; ++ } ++ } ++ x += rand1 - rand2; ++ z += rand3 - rand4; ++ // x += level.random.nextInt(6) - level.random.nextInt(6); ++ // z += level.random.nextInt(6) - level.random.nextInt(6); ++ // Leaf end - optimize mob spawning + mutableBlockPos.set(x, y, z); + double d = x + 0.5; + double d1 = z + 0.5; +@@ -295,8 +395,8 @@ public final class NaturalSpawner { + double d2 = nearestPlayer.distanceToSqr(d, y, d1); + if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn + if (spawnerData == null) { +- Optional randomSpawnMobAt = getRandomSpawnMobAt( +- level, structureManager, generator, category, level.random, mutableBlockPos ++ Optional randomSpawnMobAt = getRandomSpawnMobAtWithChunk( // Leaf - optimize mob spawning ++ level, structureManager, generator, category, level.random, mutableBlockPos, levelChunk // Leaf - optimize mob spawning + ); + if (randomSpawnMobAt.isEmpty()) { + break; +@@ -307,7 +407,7 @@ public final class NaturalSpawner { + } + + // Paper start - PreCreatureSpawnEvent +- PreSpawnStatus doSpawning = isValidSpawnPostitionForType(level, category, structureManager, generator, spawnerData, mutableBlockPos, d2); ++ PreSpawnStatus doSpawning = isValidSpawnPostitionForTypeWithChunk(level, category, structureManager, generator, spawnerData, mutableBlockPos, d2, levelChunk); // Leaf + // Paper start - per player mob count backoff + if (doSpawning == PreSpawnStatus.ABORT || doSpawning == PreSpawnStatus.CANCELLED) { + level.getChunkSource().chunkMap.updateFailurePlayerMobTypeMap(mutableBlockPos.getX() >> 4, mutableBlockPos.getZ() >> 4, category); +@@ -414,6 +514,44 @@ public final class NaturalSpawner { + && level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5)); + return success ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL; // Paper - PreCreatureSpawnEvent + } ++ // Leaf start - optimize mob spawning ++ private static PreSpawnStatus isValidSpawnPostitionForTypeWithChunk( ++ // Paper end - PreCreatureSpawnEvent ++ ServerLevel level, ++ MobCategory category, ++ StructureManager structureManager, ++ ChunkGenerator generator, ++ MobSpawnSettings.SpawnerData data, ++ BlockPos.MutableBlockPos pos, ++ double distance, ++ LevelChunk chunk ++ ) { ++ EntityType entityType = data.type(); ++ // Paper start - PreCreatureSpawnEvent ++ com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent event = new com.destroystokyo.paper.event.entity.PreCreatureSpawnEvent( ++ org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level), ++ org.bukkit.craftbukkit.entity.CraftEntityType.minecraftToBukkit(entityType), org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL ++ ); ++ if (!event.callEvent()) { ++ if (event.shouldAbortSpawn()) { ++ return PreSpawnStatus.ABORT; ++ } ++ return PreSpawnStatus.CANCELLED; ++ } ++ final boolean success = entityType.getCategory() != MobCategory.MISC ++ // Paper end - PreCreatureSpawnEvent ++ && ( ++ entityType.canSpawnFarFromPlayer() ++ || !(distance > entityType.getCategory().getDespawnDistance() * entityType.getCategory().getDespawnDistance()) ++ ) ++ && entityType.canSummon() ++ && mobsAtWithChunk(level, structureManager, generator, category, pos, null, chunk).contains(data) ++ && SpawnPlacements.isSpawnPositionOk(entityType, level, pos) ++ && SpawnPlacements.checkSpawnRules(entityType, level, EntitySpawnReason.NATURAL, pos, level.random) ++ && level.noCollision(entityType.getSpawnAABB(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5)); ++ return success ? PreSpawnStatus.SUCCESS : PreSpawnStatus.FAIL; // Paper - PreCreatureSpawnEvent ++ } ++ // Leaf end - optimize mob spawning + + @Nullable + private static Mob getMobForSpawn(ServerLevel level, EntityType entityType) { +@@ -449,6 +587,17 @@ public final class NaturalSpawner { + : mobsAt(level, structureManager, generator, category, pos, biome).getRandom(random); + } + ++ // Leaf start - optimize mob spawning ++ private static Optional getRandomSpawnMobAtWithChunk( ++ ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, RandomSource random, BlockPos pos, LevelChunk chunk ++ ) { ++ Holder biome = org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(chunk, pos) : level.getBiome(pos); // Leaf - cache getBiome ++ return category == MobCategory.WATER_AMBIENT && biome.is(BiomeTags.REDUCED_WATER_AMBIENT_SPAWNS) && random.nextFloat() < 0.98F ++ ? Optional.empty() ++ : mobsAtWithChunk(level, structureManager, generator, category, pos, biome, chunk).getRandom(random); ++ } ++ // Leaf end - optimize mob spawning ++ + private static boolean canSpawnMobAt( + ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, MobSpawnSettings.SpawnerData data, BlockPos pos + ) { +@@ -463,6 +612,16 @@ public final class NaturalSpawner { + : generator.getMobsAt(biome != null ? biome : (org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(null, pos) : level.getBiome(pos)), structureManager, cetagory, pos); // Leaf - cache getBiome + } + ++ // Leaf start - optimize mob spawning ++ private static WeightedList mobsAtWithChunk( ++ ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory cetagory, BlockPos pos, @Nullable Holder biome, LevelChunk chunk ++ ) { ++ return isInNetherFortressBoundsChunk(pos, level, cetagory, structureManager, chunk) ++ ? NetherFortressStructure.FORTRESS_ENEMIES ++ : generator.getMobsAtChunk(biome != null ? biome : (org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(chunk, pos) : level.getBiome(pos)), structureManager, cetagory, pos, chunk); // Leaf - cache getBiome ++ } ++ // Leaf end - optimize mob spawning ++ + public static boolean isInNetherFortressBounds(BlockPos pos, ServerLevel level, MobCategory category, StructureManager structureManager) { + if (category == MobCategory.MONSTER && level.getBlockState(pos.below()).is(Blocks.NETHER_BRICKS)) { + Structure structure = structureManager.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(BuiltinStructures.FORTRESS); +@@ -472,6 +631,17 @@ public final class NaturalSpawner { } } + // Leaf start - optimize mob spawning -+ private static void mutableRandomPosWithin(BlockPos.MutableBlockPos pos1, Level level, LevelChunk chunk) { -+ ChunkPos pos = chunk.getPos(); -+ int randomX = pos.getMinBlockX() + level.random.nextInt(16); -+ int randomZ = pos.getMinBlockZ() + level.random.nextInt(16); -+ int surfaceY = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, randomX, randomZ) + 1; -+ int randomY = Mth.randomBetweenInclusive(level.random, level.getMinY(), surfaceY); -+ pos1.set(randomX, randomY, randomZ); ++ public static boolean isInNetherFortressBoundsChunk(BlockPos pos, ServerLevel level, MobCategory category, StructureManager structureManager, LevelChunk chunk) { ++ if (category == MobCategory.MONSTER && ((chunk.getPos().longKey == ChunkPos.asLong(pos) ? chunk.getBlockStateFinal(pos.getX(), pos.getY() - 1, pos.getZ()).is(Blocks.NETHER_BRICKS) : level.getBlockState(pos.below()).is(Blocks.NETHER_BRICKS)))) { ++ Structure structure = structureManager.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(BuiltinStructures.FORTRESS); ++ return structure != null && structureManager.getStructureAt(pos, structure).isValid(); ++ } else { ++ return false; ++ } + } + // Leaf end - optimize mob spawning + private static BlockPos getRandomPosWithin(Level level, LevelChunk chunk) { ChunkPos pos = chunk.getPos(); int i = pos.getMinBlockX() + level.random.nextInt(16); +@@ -612,18 +782,21 @@ public final class NaturalSpawner { + @Nullable + private EntityType lastCheckedType; + private double lastCharge; ++ public final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap chunkCap; // Leaf - optimize mob spawning + + SpawnState( + int spawnableChunkCount, + Object2IntOpenHashMap mobCategoryCounts, + PotentialCalculator spawnPotential, +- LocalMobCapCalculator localMobCapCalculator ++ LocalMobCapCalculator localMobCapCalculator, ++ it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap playerCap // Leaf - optimize mob spawning + ) { + this.spawnableChunkCount = spawnableChunkCount; + this.mobCategoryCounts = mobCategoryCounts; + this.spawnPotential = spawnPotential; + this.localMobCapCalculator = localMobCapCalculator; + this.unmodifiableMobCategoryCounts = Object2IntMaps.unmodifiable(mobCategoryCounts); ++ this.chunkCap = playerCap; // Leaf - optimize mob spawning + } + + private boolean canSpawn(EntityType entityType, BlockPos pos, ChunkAccess chunk) { +@@ -680,5 +853,32 @@ public final class NaturalSpawner { + boolean canSpawnForCategoryLocal(MobCategory category, ChunkPos chunkPos) { + return this.localMobCapCalculator.canSpawn(category, chunkPos); + } ++ ++ // Leaf start - optimize mob spawning ++ public void applyPerPlayerMobCount(ServerLevel level) { ++ if (chunkCap.isEmpty()) { ++ return; ++ } ++ final var iterator = chunkCap.long2ObjectEntrySet().fastIterator(); ++ final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); ++ while (iterator.hasNext()) { ++ var entry = iterator.next(); ++ long chunk = entry.getLongKey(); ++ int[] cap = entry.getValue(); ++ ca.spottedleaf.moonrise.common.list.ReferenceList players = nearbyPlayers.getPlayersByChunk(ChunkPos.getX(chunk), ChunkPos.getZ(chunk), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); ++ if (players == null) { ++ continue; ++ } ++ int playersSize = players.size(); ++ net.minecraft.server.level.ServerPlayer[] playersRawDataUnchecked = players.getRawDataUnchecked(); ++ for (int i = 0; i < playersSize; i++) { ++ int[] p = playersRawDataUnchecked[i].mobCounts; ++ for (int j = 0; j < net.minecraft.server.level.ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; j++) { ++ p[j] += cap[j]; ++ } ++ } ++ } ++ } ++ // Leaf end - optimize mob spawning + } + } +diff --git a/net/minecraft/world/level/StructureManager.java b/net/minecraft/world/level/StructureManager.java +index fbe93098ce0366054a6da857cd808af1431b6612..57de70773d2766a8f3a41e61efc16ceb7a9f80c8 100644 +--- a/net/minecraft/world/level/StructureManager.java ++++ b/net/minecraft/world/level/StructureManager.java +@@ -90,6 +90,7 @@ public class StructureManager { + + @Nullable + public StructureStart getStartForStructure(SectionPos sectionPos, Structure structure, StructureAccess structureAccess) { ++ // Leaf - optimize mob spawning - diff + return structureAccess.getStartForStructure(structure); + } + +@@ -181,6 +182,12 @@ public class StructureManager { + //SectionPos sectionPos = SectionPos.of(pos); // Leaf - optimise ChunkGenerator#getMobsAt + return this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences(); // Leaf - optimise ChunkGenerator#getMobsAt + } ++ // Leaf start - optimize mob spawning ++ public Map getAllStructuresAtChunk(net.minecraft.world.level.chunk.ChunkAccess chunk) { ++ //SectionPos sectionPos = SectionPos.of(pos); // Leaf - optimise ChunkGenerator#getMobsAt ++ return chunk.getAllReferences(); // Leaf - optimise ChunkGenerator#getMobsAt ++ } ++ // Leaf end - optimize mob spawning + + public StructureCheckResult checkStructurePresence(ChunkPos chunkPos, Structure structure, StructurePlacement placement, boolean skipKnownStructures) { + return this.structureCheck.checkStart(chunkPos, structure, placement, skipKnownStructures); +diff --git a/net/minecraft/world/level/biome/MobSpawnSettings.java b/net/minecraft/world/level/biome/MobSpawnSettings.java +index db3b8a237d63255aa9ffd70c88a093002a6bd770..4a69f404eee00d8972e9501a76031d4339136b6f 100644 +--- a/net/minecraft/world/level/biome/MobSpawnSettings.java ++++ b/net/minecraft/world/level/biome/MobSpawnSettings.java +@@ -52,7 +52,7 @@ public class MobSpawnSettings { + Map, MobSpawnSettings.MobSpawnCost> mobSpawnCosts + ) { + this.creatureGenerationProbability = creatureGenerationProbability; +- this.spawners = ImmutableMap.copyOf(spawners); ++ this.spawners = com.google.common.collect.Maps.immutableEnumMap(spawners); // Leaf - optimize mob spawning + this.mobSpawnCosts = ImmutableMap.copyOf(mobSpawnCosts); + } + +diff --git a/net/minecraft/world/level/chunk/ChunkGenerator.java b/net/minecraft/world/level/chunk/ChunkGenerator.java +index 11c7c299d4affb9e78488590e7db939efe6e3dd9..f1675db9942751235bb31634d5b99fdc30fb2950 100644 +--- a/net/minecraft/world/level/chunk/ChunkGenerator.java ++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java +@@ -516,6 +516,35 @@ public abstract class ChunkGenerator { + return biome.value().getMobSettings().getMobs(category); + } + ++ // Leaf start - optimize mob spawning ++ public WeightedList getMobsAtChunk(Holder biome, StructureManager structureManager, MobCategory category, BlockPos pos, LevelChunk chunk) { ++ Map allStructuresAt = structureManager.getAllStructuresAtChunk(ChunkPos.asLong(pos) == chunk.getPos().longKey ? chunk : structureManager.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_REFERENCES)); ++ ++ for (Entry entry : allStructuresAt.entrySet()) { ++ Structure structure = entry.getKey(); ++ StructureSpawnOverride structureSpawnOverride = structure.spawnOverrides().get(category); ++ if (structureSpawnOverride != null) { ++ // Leaf start - optimise ChunkGenerator#getMobsAt ++ for (long l : entry.getValue()) { ++ StructureStart startForStructure = structureManager.getStartForStructure( ++ null, structure, chunk.getPos().longKey == l ? chunk : structureManager.level.getChunk(ChunkPos.getX(l), ChunkPos.getZ(l), ChunkStatus.STRUCTURE_STARTS) ++ ); ++ if (startForStructure != null && startForStructure.isValid()) { ++ if (structureSpawnOverride.boundingBox() == StructureSpawnOverride.BoundingBoxType.PIECE ++ ? structureManager.structureHasPieceAt(pos, startForStructure) ++ : startForStructure.getBoundingBox().isInside(pos)) { ++ return structureSpawnOverride.spawns(); ++ } ++ } ++ } ++ // Leaf end - optimise ChunkGenerator#getMobsAt ++ } ++ } ++ ++ return biome.value().getMobSettings().getMobs(category); ++ } ++ // Leaf end - optimize mob spawning ++ + public void createStructures( + RegistryAccess registryAccess, + ChunkGeneratorStructureState structureState, diff --git a/leaf-server/minecraft-patches/features/0266-throttle-mob-spawning.patch b/leaf-server/minecraft-patches/features/0266-throttle-mob-spawning.patch index b5d60f04..de72975d 100644 --- a/leaf-server/minecraft-patches/features/0266-throttle-mob-spawning.patch +++ b/leaf-server/minecraft-patches/features/0266-throttle-mob-spawning.patch @@ -5,10 +5,10 @@ Subject: [PATCH] throttle mob spawning diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java -index 38a6a11c05c0a0a2f59c1477e516431263dce101..a55abb08093847a8bfec446659f9af5fb1df2824 100644 +index ab6fa7ed111ef16a0b6774c21988589ee2110c66..f3bed4b372f858656dbf51fb0140260bad4a41be 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java -@@ -154,6 +154,17 @@ public final class NaturalSpawner { +@@ -215,6 +215,17 @@ public final class NaturalSpawner { // Paper start - Optional per player mob spawns final boolean canSpawn; int maxSpawns = Integer.MAX_VALUE; diff --git a/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch index bc3d86cd..54adb079 100644 --- a/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch +++ b/leaf-server/minecraft-patches/features/0275-optimize-random-tick.patch @@ -5,93 +5,52 @@ Subject: [PATCH] optimize random tick diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index fc86e900e41305287a6cc6d766184c6e28d6189b..3f3450e79f768f9c9fa4f2c0fe485b7179419680 100644 +index 56516dd07433161f5afd448aaf535c59d14c2528..9992eeda9535e6304e7019e7eb7e5c1458ddd89e 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -634,6 +634,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -675,7 +675,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + list.clear(); } - this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations -+ this.level.randomTickSystem.tick(this.level); // Leaf - optimize random tick +- this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations ++ // Leaf start - optimize random tick ++ if (org.dreeam.leaf.config.modules.opt.OptimizeRandomTick.enabled) { ++ this.level.randomTickSystem.tick(this.level); ++ } else { ++ this.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations ++ } ++ // Leaf end - optimize random tick if (_boolean) { this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 224a032e8992f104ad9380182ed67c316c93274e..07f6da45a88e1630ca4249bb134fce1f95d2c39c 100644 +index 983b03c9cf1c91e9954da0ed8be99c2dfaa5a9c6..4bd5e0268d52d63e160ccfe3d1509bbda421bc0f 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java -@@ -1110,6 +1110,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - +@@ -1111,6 +1111,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.simpleRandom.nextInt(16); } // Gale - Airplane - optimize random calls in chunk ticking -+ public org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick + public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning ++ public final org.dreeam.leaf.world.RandomTickSystem randomTickSystem = new org.dreeam.leaf.world.RandomTickSystem(); // Leaf - optimize random tick public void tickChunk(LevelChunk chunk, int randomTickSpeed) { final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting ChunkPos pos = chunk.getPos(); -@@ -1125,7 +1126,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - } // Paper - Option to disable ice and snow - - if (randomTickSpeed > 0) { -- this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking -+ if (org.dreeam.leaf.config.modules.opt.OptimizeRandomTick.enabled) randomTickSystem.tickChunk(this.simpleRandom, chunk, randomTickSpeed); // Leaf - optimize random tick -+ else this.optimiseRandomTick(chunk, randomTickSpeed); // Paper - optimise random ticking // Leaf - optimize random tick - } - } - diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java -index 31f19dfe16e270b55f3b44754c97ed8d9fa422cf..31c6c035aca5400a5c0a030bfe5334545e2a4bca 100644 +index 31f19dfe16e270b55f3b44754c97ed8d9fa422cf..174da710d2b86de98cbf6499d4a954f9476a225a 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java -@@ -150,6 +150,48 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p +@@ -150,6 +150,10 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p } // Gale end - Airplane - optimize random calls in chunk ticking - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively + // Leaf start - optimize random tick -+ private boolean leaf$tickingBlocksDirty = true; -+ private int leaf$tickingBlocksCount; -+ private int leaf$firstTickingSectionIndex = -1; -+ public final int leaf$tickingBlocksCount() { -+ if (!leaf$tickingBlocksDirty) { -+ return leaf$tickingBlocksCount; -+ } -+ leaf$tickingBlocksDirty = false; -+ int sum = 0; -+ leaf$firstTickingSectionIndex = -1; -+ for (int i = 0; i < sections.length; i++) { -+ LevelChunkSection section = sections[i]; -+ int size = section.moonrise$getTickingBlockList().size(); -+ if (size != 0 && leaf$firstTickingSectionIndex == -1) { -+ leaf$firstTickingSectionIndex = i; -+ } -+ sum += size; -+ } -+ leaf$tickingBlocksCount = sum; -+ return sum; -+ } -+ public final java.util.OptionalLong leaf$getTickingPos(int idx) { -+ if (leaf$firstTickingSectionIndex != -1) { -+ for (int i = leaf$firstTickingSectionIndex; i < sections.length; i++) { -+ LevelChunkSection section = sections[i]; -+ var l = section.moonrise$getTickingBlockList(); -+ int size = l.size(); -+ if (idx < size) { -+ short loc = l.getRaw(idx); -+ int x = (loc & 15) | (chunkPos.x << 4); -+ int y = (loc >>> 8) | ((getMinSectionY() + i) << 4); -+ int z = ((loc >>> 4) & 15) | (chunkPos.z << 4); -+ return java.util.OptionalLong.of(BlockPos.asLong(x, y, z)); -+ } -+ idx -= size; -+ } -+ } -+ leaf$tickingBlocksDirty = true; -+ return java.util.OptionalLong.empty(); -+ } ++ public boolean leaf$tickingBlocksDirty = true; ++ public int[] leaf$tickingCount = {}; + // Leaf end - optimize random tick public LevelChunk(Level level, ChunkPos pos) { this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); } -@@ -414,6 +456,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p +@@ -414,6 +418,11 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p if (blockState == state) { return null; } else { diff --git a/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch index bc1e6458..874b98df 100644 --- a/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0278-Paw-optimization.patch @@ -99,25 +99,8 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..e65c62dbe4c1560ae153e4c4344e9194 - } - // Paper end - detailed watchdog information } -diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 3f3450e79f768f9c9fa4f2c0fe485b7179419680..f16760c8817d0220f0a44ed620859ba910bf63a9 100644 ---- a/net/minecraft/server/level/ServerChunkCache.java -+++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -622,8 +622,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - try { - this.chunkMap.collectSpawningChunks(list); - // Paper start - chunk tick iteration optimisation -- this.shuffleRandom.setSeed(this.level.random.nextLong()); -- if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled -+ if (!this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { -+ this.shuffleRandom.setSeed(this.level.random.nextLong()); // Leaf - paw optimization - Only set seed if is really used -+ Util.shuffle(list, this.shuffleRandom); // Paper - Optional per player mob spawns; do not need this when per-player is enabled -+ } - // Paper end - chunk tick iteration optimisation - - for (LevelChunk levelChunk : list) { diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index 07f6da45a88e1630ca4249bb134fce1f95d2c39c..9fbaafaf5df6e003742cdea55da732cc7e602866 100644 +index 4bd5e0268d52d63e160ccfe3d1509bbda421bc0f..36138a2fc86f5e0fe0ea3d40061c54f8a6096c4d 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -1510,13 +1510,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe diff --git a/leaf-server/paper-patches/features/0054-Async-chunk-send.patch b/leaf-server/paper-patches/features/0054-Async-chunk-sending.patch similarity index 85% rename from leaf-server/paper-patches/features/0054-Async-chunk-send.patch rename to leaf-server/paper-patches/features/0054-Async-chunk-sending.patch index 453ba318..81ffdd8c 100644 --- a/leaf-server/paper-patches/features/0054-Async-chunk-send.patch +++ b/leaf-server/paper-patches/features/0054-Async-chunk-sending.patch @@ -1,11 +1,11 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Fri, 2 May 2025 18:22:24 -0700 -Subject: [PATCH] Async chunk send +Subject: [PATCH] Async chunk sending diff --git a/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java b/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java -index ca24f4cd7a50f0156d84e263c60f841cca95c669..7e15038e8fceab1e97c2245c2e9111deed6455fb 100644 +index ca24f4cd7a50f0156d84e263c60f841cca95c669..e98a7edc4001d020d238a44a96fda00f389234eb 100644 --- a/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java +++ b/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java @@ -185,7 +185,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo @@ -13,7 +13,7 @@ index ca24f4cd7a50f0156d84e263c60f841cca95c669..7e15038e8fceab1e97c2245c2e9111de } - if (!Bukkit.isPrimaryThread()) { -+ if (!Bukkit.isPrimaryThread() && !(Thread.currentThread() instanceof org.dreeam.leaf.async.chunk.AsyncChunkSendThread)) { // Leaf - Async chunk send ++ if (!Bukkit.isPrimaryThread() && !(Thread.currentThread() instanceof org.dreeam.leaf.async.chunk.AsyncChunkSendThread)) { // Leaf - Async chunk sending // Plugins? MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); return; diff --git a/leaf-server/paper-patches/features/0057-cache-getBiome.patch b/leaf-server/paper-patches/features/0057-cache-getBiome.patch index 9ef4307b..8b14ac78 100644 --- a/leaf-server/paper-patches/features/0057-cache-getBiome.patch +++ b/leaf-server/paper-patches/features/0057-cache-getBiome.patch @@ -5,7 +5,7 @@ Subject: [PATCH] cache getBiome diff --git a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java -index 0e14f962b26823e49b192a4f97ec6c1f477ef0ff..cb6cd899fd8af069534f45a4a9e81137f941e250 100644 +index 0e14f962b26823e49b192a4f97ec6c1f477ef0ff..757dd75474b134be8e432d64e1c11d52ecbb0587 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java +++ b/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java @@ -285,6 +285,13 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel { @@ -14,8 +14,8 @@ index 0e14f962b26823e49b192a4f97ec6c1f477ef0ff..cb6cd899fd8af069534f45a4a9e81137 + // Leaf start - cache getBiome + @Override -+ public Holder getBiomeCached(BlockPos pos) { -+ return this.delegate.getBiomeCached(pos); ++ public Holder getBiomeCached(@Nullable net.minecraft.world.level.chunk.LevelChunk chunk, BlockPos pos) { ++ return this.delegate.getBiomeCached(chunk, pos); + } + // Leaf end - cache getBiome + diff --git a/leaf-server/paper-patches/features/0062-optimize-despawn.patch b/leaf-server/paper-patches/features/0062-optimize-despawn.patch new file mode 100644 index 00000000..7ffee8c9 --- /dev/null +++ b/leaf-server/paper-patches/features/0062-optimize-despawn.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Thu, 26 Jun 2025 00:42:27 +0900 +Subject: [PATCH] optimize despawn + + +diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +index 6c298304f842612d0e063b578f274eed04b32960..dc401da92deb55ed59197c6b1a1f6996f39dc9d0 100644 +--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +@@ -177,7 +177,7 @@ public class WorldConfiguration extends ConfigurationPart { + @MergeMap + public Reference2IntMap spawnLimits = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1))); + @MergeMap +- public Map despawnRanges = Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault())); ++ public Map despawnRanges = new java.util.EnumMap<>(Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()))); // Leaf - replace EnumMap optimize despawn + public DespawnRange.Shape despawnRangeShape = DespawnRange.Shape.ELLIPSOID; + @MergeMap + public Reference2IntMap ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1))); diff --git a/leaf-server/paper-patches/features/0062-Paw-optimization.patch b/leaf-server/paper-patches/features/0063-Paw-optimization.patch similarity index 100% rename from leaf-server/paper-patches/features/0062-Paw-optimization.patch rename to leaf-server/paper-patches/features/0063-Paw-optimization.patch diff --git a/leaf-server/src/main/java/org/dreeam/leaf/world/NatureSpawnChunkMap.java b/leaf-server/src/main/java/org/dreeam/leaf/world/NatureSpawnChunkMap.java new file mode 100644 index 00000000..354d71c9 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/NatureSpawnChunkMap.java @@ -0,0 +1,154 @@ +package org.dreeam.leaf.world; + +import ca.spottedleaf.moonrise.common.list.ReferenceList; +import com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent; +import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; + +import java.util.List; + +public class NatureSpawnChunkMap { + /// breadth-first search + /// + /// 0 4 12 28 48 80 112 148 196 + private static final long[][] TABLE_BFS = new long[][]{ + {0L}, + {0L, 4294967295L, -4294967296L, 4294967296L, 1L}, + {0L, -1L, 4294967295L, 8589934591L, -4294967296L, 4294967296L, -4294967295L, 1L, 4294967297L, 4294967294L, -8589934592L, 8589934592L, 2L}, + {0L, -1L, 4294967295L, 8589934591L, -4294967296L, 4294967296L, -4294967295L, 1L, 4294967297L, -4294967298L, -2L, 4294967294L, -4294967297L, -8589934592L, 8589934590L, 12884901886L, 12884901887L, 8589934592L, -8589934591L, 8589934593L, -8589934590L, -4294967294L, 2L, 4294967298L, 8589934594L, 4294967293L, -12884901888L, 12884901888L, 3L}, + {0L, -1L, 4294967295L, 8589934591L, -4294967296L, 4294967296L, -4294967295L, 1L, 4294967297L, -4294967298L, -2L, 4294967294L, -4294967297L, -8589934592L, 8589934590L, 12884901886L, 12884901887L, 8589934592L, -8589934591L, 8589934593L, -8589934590L, -4294967294L, 2L, 4294967298L, 8589934594L, -4294967299L, -3L, -8589934594L, -8589934593L, 4294967293L, 8589934589L, -12884901888L, -12884901887L, 12884901885L, 17179869182L, 17179869183L, 12884901888L, 12884901889L, -12884901886L, 12884901890L, -8589934589L, -4294967293L, 3L, 4294967299L, 8589934595L, 4294967292L, -17179869184L, 17179869184L, 4L}, + {0L, -1L, 4294967295L, 8589934591L, -4294967296L, 4294967296L, -4294967295L, 1L, 4294967297L, -4294967298L, -2L, 4294967294L, -4294967297L, -8589934592L, 8589934590L, 12884901886L, 12884901887L, 8589934592L, -8589934591L, 8589934593L, -8589934590L, -4294967294L, 2L, 4294967298L, 8589934594L, -8589934595L, -4294967299L, -3L, -8589934594L, -8589934593L, 4294967293L, 8589934589L, -12884901888L, -12884901887L, 12884901885L, 17179869181L, 17179869182L, 17179869183L, 12884901888L, 12884901889L, -12884901886L, 12884901890L, -12884901885L, -8589934589L, -4294967293L, 3L, 4294967299L, 8589934595L, 12884901891L, -8589934596L, -4294967300L, -12884901891L, -12884901890L, -4L, 4294967292L, -12884901889L, -17179869184L, 8589934588L, 12884901884L, -17179869183L, -17179869182L, 17179869180L, 21474836477L, 21474836478L, 21474836479L, 17179869184L, 17179869185L, 17179869186L, -17179869181L, 17179869187L, -12884901884L, -8589934588L, -4294967292L, 4L, 4294967300L, 8589934596L, 12884901892L, 4294967291L, -21474836480L, 21474836480L, 5L}, + {0L, -1L, 4294967295L, 8589934591L, -4294967296L, 4294967296L, -4294967295L, 1L, 4294967297L, -4294967298L, -2L, 4294967294L, -4294967297L, -8589934592L, 8589934590L, 12884901886L, 12884901887L, 8589934592L, -8589934591L, 8589934593L, -8589934590L, -4294967294L, 2L, 4294967298L, 8589934594L, -8589934595L, -4294967299L, -3L, -8589934594L, -8589934593L, 4294967293L, 8589934589L, -12884901888L, -12884901887L, 12884901885L, 17179869181L, 17179869182L, 17179869183L, 12884901888L, 12884901889L, -12884901886L, 12884901890L, -12884901885L, -8589934589L, -4294967293L, 3L, 4294967299L, 8589934595L, 12884901891L, -12884901892L, -8589934596L, -4294967300L, -12884901891L, -12884901890L, -4L, 4294967292L, -12884901889L, -17179869184L, 8589934588L, 12884901884L, -17179869183L, -17179869182L, 17179869180L, 21474836476L, 21474836477L, 21474836478L, 21474836479L, 17179869184L, 17179869185L, 17179869186L, -17179869181L, 17179869187L, -17179869180L, -12884901884L, -8589934588L, -4294967292L, 4L, 4294967300L, 8589934596L, 12884901892L, 17179869188L, -8589934597L, -17179869187L, -4294967301L, -5L, -17179869186L, -17179869185L, 4294967291L, 8589934587L, -21474836480L, -21474836479L, 12884901883L, 17179869179L, -21474836478L, -21474836477L, 25769803773L, 25769803774L, 25769803775L, 21474836480L, 21474836481L, 21474836482L, 21474836483L, -12884901883L, -8589934587L, -4294967291L, 5L, 4294967301L, 8589934597L, 12884901893L, 4294967290L, -25769803776L, 25769803776L, 6L}, + {0L, -1L, 4294967295L, 8589934591L, -4294967296L, 4294967296L, -4294967295L, 1L, 4294967297L, -4294967298L, -2L, 4294967294L, -4294967297L, -8589934592L, 8589934590L, 12884901886L, 12884901887L, 8589934592L, -8589934591L, 8589934593L, -8589934590L, -4294967294L, 2L, 4294967298L, 8589934594L, -8589934595L, -4294967299L, -3L, -8589934594L, -8589934593L, 4294967293L, 8589934589L, -12884901888L, -12884901887L, 12884901885L, 17179869181L, 17179869182L, 17179869183L, 12884901888L, 12884901889L, -12884901886L, 12884901890L, -12884901885L, -8589934589L, -4294967293L, 3L, 4294967299L, 8589934595L, 12884901891L, -12884901892L, -8589934596L, -4294967300L, -12884901891L, -12884901890L, -4L, 4294967292L, -12884901889L, -17179869184L, 8589934588L, 12884901884L, -17179869183L, -17179869182L, 17179869180L, 21474836476L, 21474836477L, 21474836478L, 21474836479L, 17179869184L, 17179869185L, 17179869186L, -17179869181L, 17179869187L, -17179869180L, -12884901884L, -8589934588L, -4294967292L, 4L, 4294967300L, 8589934596L, 12884901892L, 17179869188L, -12884901893L, -8589934597L, -17179869188L, -17179869187L, -4294967301L, -5L, -17179869186L, -17179869185L, 4294967291L, 8589934587L, -21474836480L, -21474836479L, 12884901883L, 17179869179L, -21474836478L, -21474836477L, 21474836475L, 25769803772L, 25769803773L, 25769803774L, 25769803775L, 21474836480L, 21474836481L, 21474836482L, 21474836483L, -21474836476L, 21474836484L, -17179869179L, -12884901883L, -8589934587L, -4294967291L, 5L, 4294967301L, 8589934597L, 12884901893L, 17179869189L, -8589934598L, -4294967302L, -21474836483L, -21474836482L, -6L, 4294967290L, -21474836481L, -25769803776L, 8589934586L, 12884901882L, -25769803775L, -25769803774L, 17179869178L, -25769803773L, 30064771069L, 30064771070L, 30064771071L, 25769803776L, 25769803777L, 25769803778L, 25769803779L, -12884901882L, -8589934586L, -4294967290L, 6L, 4294967302L, 8589934598L, 12884901894L, 4294967289L, -30064771072L, 30064771072L, 7L}, + {0L, -1L, 4294967295L, 8589934591L, -4294967296L, 4294967296L, -4294967295L, 1L, 4294967297L, -4294967298L, -2L, 4294967294L, -4294967297L, -8589934592L, 8589934590L, 12884901886L, 12884901887L, 8589934592L, -8589934591L, 8589934593L, -8589934590L, -4294967294L, 2L, 4294967298L, 8589934594L, -8589934595L, -4294967299L, -3L, -8589934594L, -8589934593L, 4294967293L, 8589934589L, -12884901888L, -12884901887L, 12884901885L, 17179869181L, 17179869182L, 17179869183L, 12884901888L, 12884901889L, -12884901886L, 12884901890L, -12884901885L, -8589934589L, -4294967293L, 3L, 4294967299L, 8589934595L, 12884901891L, -12884901892L, -8589934596L, -4294967300L, -12884901891L, -12884901890L, -4L, 4294967292L, -12884901889L, -17179869184L, 8589934588L, 12884901884L, -17179869183L, -17179869182L, 17179869180L, 21474836476L, 21474836477L, 21474836478L, 21474836479L, 17179869184L, 17179869185L, 17179869186L, -17179869181L, 17179869187L, -17179869180L, -12884901884L, -8589934588L, -4294967292L, 4L, 4294967300L, 8589934596L, 12884901892L, 17179869188L, -17179869189L, -12884901893L, -8589934597L, -17179869188L, -17179869187L, -4294967301L, -5L, -17179869186L, -17179869185L, 4294967291L, 8589934587L, -21474836480L, -21474836479L, 12884901883L, 17179869179L, -21474836478L, -21474836477L, 21474836475L, 25769803771L, 25769803772L, 25769803773L, 25769803774L, 25769803775L, 21474836480L, 21474836481L, 21474836482L, 21474836483L, -21474836476L, 21474836484L, -21474836475L, -17179869179L, -12884901883L, -8589934587L, -4294967291L, 5L, 4294967301L, 8589934597L, 12884901893L, 17179869189L, 21474836485L, -17179869190L, -12884901894L, -21474836485L, -21474836484L, -8589934598L, -4294967302L, -21474836483L, -21474836482L, -6L, 4294967290L, -21474836481L, -25769803776L, 8589934586L, 12884901882L, -25769803775L, -25769803774L, 17179869178L, 21474836474L, -25769803773L, -25769803772L, 25769803770L, 30064771067L, 30064771068L, 30064771069L, 30064771070L, 30064771071L, 25769803776L, 25769803777L, 25769803778L, 25769803779L, 25769803780L, -25769803771L, 25769803781L, -21474836474L, -17179869178L, -12884901882L, -8589934586L, -4294967290L, 6L, 4294967302L, 8589934598L, 12884901894L, 17179869190L, 21474836486L, -8589934599L, -25769803779L, -4294967303L, -7L, -25769803778L, -25769803777L, 4294967289L, 8589934585L, -30064771072L, -30064771071L, 12884901881L, 17179869177L, -30064771070L, -30064771069L, 34359738365L, 34359738366L, 34359738367L, 30064771072L, 30064771073L, 30064771074L, 30064771075L, -12884901881L, -8589934585L, -4294967289L, 7L, 4294967303L, 8589934599L, 12884901895L, 4294967288L, -34359738368L, 34359738368L, 8L} + }; + private static final int MAX_RADIUS = 8; + private static final int SIZE_RADIUS = 9; + private static final int REGION_MASK = 15; + private static final int REGION_SHIFT = 4; + + private final LongArrayList[] centersByRadius; + private final Long2LongOpenHashMap regionBitSets; + + public NatureSpawnChunkMap() { + this.centersByRadius = new LongArrayList[SIZE_RADIUS]; + for (int i = 0; i < SIZE_RADIUS; i++) { + this.centersByRadius[i] = new LongArrayList(); + } + this.regionBitSets = new Long2LongOpenHashMap(); + } + + public void clear() { + for (LongArrayList chunkPosition : this.centersByRadius) { + chunkPosition.clear(); + } + this.regionBitSets.clear(); + } + + public void addPlayer(ServerPlayer player) { + if (player.isSpectator()) { + return; + } + PlayerNaturallySpawnCreaturesEvent event = player.playerNaturallySpawnedEvent; + if (event == null || event.isCancelled()) { + return; + } + int range = event.getSpawnRadius(); + if (range > MAX_RADIUS) { + range = MAX_RADIUS; + } else if (range < 0) { + return; + } + this.centersByRadius[range].add(player.chunkPosition().longKey); + } + + public void build() { + for (int index = 0; index < SIZE_RADIUS; index++) { + buildBy(index); + } + } + + private void buildBy(int index) { + LongArrayList list = this.centersByRadius[index]; + int centersSize = deduplicate(list); + if (centersSize == 0) { + return; + } + long[] centersRaw = list.elements(); + long cachedKey = ChunkPos.asLong(ChunkPos.getX(centersRaw[0]) >> REGION_SHIFT, ChunkPos.getZ(centersRaw[0]) >> REGION_SHIFT); + long cachedVal = this.regionBitSets.get(cachedKey); + long[] offsets = TABLE_BFS[index]; + for (int i = 0; i < centersSize; i++) { + long center = centersRaw[i]; + int cx = ChunkPos.getX(center); + int cz = ChunkPos.getZ(center); + + for (long packedOffset : offsets) { + int dx = ChunkPos.getX(packedOffset); + int dz = ChunkPos.getZ(packedOffset); + int chunkX = cx + dx; + int chunkZ = cz + dz; + + int regionX = chunkX >> REGION_SHIFT; + int regionZ = chunkZ >> REGION_SHIFT; + long regionKey = ChunkPos.asLong(regionX, regionZ); + + int localX = chunkX & REGION_MASK; + int localZ = chunkZ & REGION_MASK; + int bitIndex = (localZ << REGION_SHIFT) | localX; + long bitMask = 1L << bitIndex; + + if (regionKey != cachedKey) { + this.regionBitSets.put(cachedKey, cachedVal); + cachedKey = regionKey; + cachedVal = this.regionBitSets.get(regionKey); + } + + cachedVal |= bitMask; + } + } + + if (cachedVal != 0L) { + this.regionBitSets.put(cachedKey, cachedVal); + } + } + + private int deduplicate(LongArrayList list) { + int n = list.size(); + if (n == 0) { + return 0; + } + list.unstableSort(null); + long[] centersRaw = list.elements(); + int size = 0; + for (int i = 1; i < n; i++) { + long current = centersRaw[i]; + long last = centersRaw[size]; + if (current != last) { + size++; + centersRaw[size] = current; + } + } + return size + 1; + } + + public void collectSpawningChunks(ReferenceList chunks, List out) { + LevelChunk[] raw = chunks.getRawDataUnchecked(); + for (int i = 0, length = chunks.size(); i < length; i++) { + LevelChunk chunk = raw[i]; + if (contains(chunk.locX, chunk.locZ)) { + out.add(chunk); + } + } + } + + public boolean contains(int chunkX, int chunkZ) { + int regionX = chunkX >> REGION_SHIFT; + int regionZ = chunkZ >> REGION_SHIFT; + long bitset = this.regionBitSets.get(ChunkPos.asLong(regionX, regionZ)); + return bitset != 0 && (bitset & (1L << (((chunkZ & REGION_MASK) << REGION_SHIFT) | (chunkX & REGION_MASK)))) != 0L; + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java b/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java index 23967287..07a84775 100644 --- a/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java +++ b/leaf-server/src/main/java/org/dreeam/leaf/world/RandomTickSystem.java @@ -1,115 +1,162 @@ package org.dreeam.leaf.world; +import ca.spottedleaf.moonrise.common.list.ReferenceList; +import ca.spottedleaf.moonrise.common.list.ShortList; import it.unimi.dsi.fastutil.longs.LongArrayList; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.RandomSource; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.GameRules; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.levelgen.BitRandomSource; import net.minecraft.world.level.material.FluidState; -import java.util.OptionalLong; - public final class RandomTickSystem { private static final long SCALE = 0x100000L; - private static final long CHUNK_BLOCKS = 4096L; - - /// reduce unnecessary sampling and block counting - private static final long TICK_MASK = 0b11L; - private static final long TICK_MUL = 4L; + private static final long TICK_FILTER_MASK = 0b11L; + private static final long CHUNK_BLOCKS = 4096L / 4L; private static final int BITS_STEP = 2; private static final int BITS_MAX = 60; private final LongArrayList queue = new LongArrayList(); private final LongArrayList samples = new LongArrayList(); private final LongArrayList weights = new LongArrayList(); - private long weightsSum = 0L; - - private int bits = 60; - private long cacheRandom = 0L; public void tick(ServerLevel world) { - if (weights.isEmpty() || samples.isEmpty()) { + queue.clear(); + samples.clear(); + weights.clear(); + + final BitRandomSource random = world.simpleRandom; + final ReferenceList entityTickingChunks = world.moonrise$getEntityTickingChunks(); + final int randomTickSpeed = world.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); + final LevelChunk[] raw = entityTickingChunks.getRawDataUnchecked(); + final int size = entityTickingChunks.size(); + final boolean disableIceAndSnow = world.paperConfig().environment.disableIceAndSnow; + if (randomTickSpeed <= 0) { return; } - - final var random = world.simpleRandom; - final long chosen; - if (((weightsSum % SCALE) >= boundedNextLong(random, SCALE))) { - chosen = weightsSum / SCALE + 1L; - } else { - chosen = weightsSum / SCALE; + if (!disableIceAndSnow) { + iceSnow(world, size, randomTickSpeed, random, raw); } + final long weightsSum = collectTickingChunks(size, random, raw, randomTickSpeed); + if (samples.isEmpty() || weightsSum == 0L) { + return; + } + sampling(random, weightsSum); + + final long[] q = queue.elements(); + final int minY = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(world) << 4; + for (int k = 0, len = queue.size(); k < len; ++k) { + final long packed = q[k]; + final LevelChunk chunk = raw[(int) (packed >>> 16)]; + tickBlock(world, chunk, (int) (packed & 0xFFFF), random, minY); + } + } + + private void sampling(BitRandomSource random, long weightsSum) { + final long chosen = ((weightsSum % SCALE) >= boundedNextLong(random, SCALE)) + ? (weightsSum / SCALE + 1L) + : (weightsSum / SCALE); if (chosen == 0L) { return; } + final long[] w = weights.elements(); + final long[] s = samples.elements(); + long accumulated = w[0]; final long spoke = weightsSum / chosen; - if (spoke == 0L) { - return; - } + if (spoke == 0L) return; - final long[] weightsRaw = weights.elements(); - final long[] samplesRaw = samples.elements(); - - long accumulated = weightsRaw[0]; long current = boundedNextLong(random, spoke); int i = 0; while (current < weightsSum) { while (accumulated < current) { - i += 1; - accumulated += weightsRaw[i]; + i++; + accumulated += w[i]; } - queue.add(samplesRaw[i]); + queue.add(s[i]); current += spoke; } - while (queue.size() < chosen) { - queue.add(samplesRaw[i]); - } - - long[] queueRaw = queue.elements(); - int j = 0; - int k; - for (k = queue.size() - 3; j < k; j += 4) { - final long packed1 = queueRaw[j]; - final long packed2 = queueRaw[j + 1]; - final long packed3 = queueRaw[j + 2]; - final long packed4 = queueRaw[j + 3]; - final LevelChunk chunk1 = getChunk(world, packed1); - final LevelChunk chunk2 = packed1 != packed2 ? getChunk(world, packed2) : chunk1; - final LevelChunk chunk3 = packed2 != packed3 ? getChunk(world, packed3) : chunk2; - final LevelChunk chunk4 = packed3 != packed4 ? getChunk(world, packed4) : chunk3; - if (chunk1 != null) tickBlock(world, chunk1, random); - if (chunk2 != null) tickBlock(world, chunk2, random); - if (chunk3 != null) tickBlock(world, chunk3, random); - if (chunk4 != null) tickBlock(world, chunk4, random); - } - for (k = queue.size(); j < k; j++) { - final LevelChunk chunk = getChunk(world, queueRaw[j]); - if (chunk != null) tickBlock(world, chunk, random); - } - - weightsSum = 0L; - queue.clear(); - weights.clear(); - samples.clear(); } - private static LevelChunk getChunk(ServerLevel world, long packed) { - return world.chunkSource.getChunkAtIfLoadedImmediately((int) packed, (int) (packed >> 32)); + private long collectTickingChunks(int size, BitRandomSource random, LevelChunk[] raw, long randomTickSpeed) { + int bits = 0; + long cacheRandom = random.nextLong(); + long weightsSum = 0L; + + for (int i = 0; i < size; i++) { + if (bits != BITS_MAX) { + bits += BITS_STEP; + } else { + bits = 0; + cacheRandom = random.nextLong(); + } + if ((cacheRandom & (TICK_FILTER_MASK << bits)) != 0L) { + continue; + } + final LevelChunk chunk = raw[i]; + if (chunk.leaf$tickingBlocksDirty) { + populateChunkTickingCount(chunk); + } + int[] data = chunk.leaf$tickingCount; + for (int packed : data) { + int count = packed >>> 16; + int idx = packed & 0xFFFF; + samples.add((((long) i) << 16 | idx)); + long weight = (randomTickSpeed * count * SCALE) / CHUNK_BLOCKS; + weights.add(weight); + weightsSum += weight; + } + } + return weightsSum; } - private static void tickBlock(ServerLevel world, LevelChunk chunk, RandomSource random) { - int count = chunk.leaf$tickingBlocksCount(); - if (count == 0) { - return; + private static void populateChunkTickingCount(LevelChunk chunk) { + chunk.leaf$tickingBlocksDirty = false; + int sum = 0; + for (LevelChunkSection section : chunk.getSections()) { + sum += (section.moonrise$getTickingBlockList().size() == 0) ? 0 : 1; } - OptionalLong optionalPos = chunk.leaf$getTickingPos(random.nextInt(count)); - if (optionalPos.isEmpty()) { - return; + + if (chunk.leaf$tickingCount.length != sum) { + chunk.leaf$tickingCount = new int[sum]; } - BlockPos pos = BlockPos.of(optionalPos.getAsLong()); - BlockState state = chunk.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ()); + + int k = 0; + LevelChunkSection[] sections = chunk.getSections(); + for (int j = 0; j < sections.length; j++) { + ShortList list = sections[j].moonrise$getTickingBlockList(); + int n = list.size(); + if (n != 0) { + chunk.leaf$tickingCount[k++] = (n << 16) | (j & 0xFFFF); + } + } + } + + private static void iceSnow(ServerLevel world, int size, int randomTickSpeed, BitRandomSource random, LevelChunk[] raw) { + int currentIceAndSnowTick = random.nextInt(48 * 16); + for (int i = 0; i < size; i++) { + currentIceAndSnowTick -= randomTickSpeed; + if (currentIceAndSnowTick <= 0) { + currentIceAndSnowTick = random.nextInt(48 * 16); + LevelChunk chunk = raw[i]; + ChunkPos pos = chunk.getPos(); + world.tickPrecipitation(world.getBlockRandomPos(pos.getMinBlockX(), 0, pos.getMinBlockZ(), 15)); + } + } + } + + private static void tickBlock(ServerLevel world, LevelChunk chunk, int sectionIdx, BitRandomSource random, int minSection) { + LevelChunkSection section = chunk.getSection(sectionIdx); + ShortList list = section.moonrise$getTickingBlockList(); + int size = list.size(); + if (size == 0) return; + short location = list.getRaw(boundedNextInt(random, size)); + BlockState state = section.states.get(location); + final BlockPos pos = new BlockPos((location & 15) | (chunk.locX << 4), (location >>> 8) | (minSection + (sectionIdx << 4)), ((location >>> 4) & 15) | (chunk.locZ << 4)); state.randomTick(world, pos, random); final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); @@ -121,43 +168,13 @@ public final class RandomTickSystem { } } - public void tickChunk( - RandomSource random, - LevelChunk chunk, - long tickSpeed - ) { - if (this.bits == BITS_MAX) { - this.bits = 0; - this.cacheRandom = random.nextLong(); - } else { - this.bits += BITS_STEP; - } - if ((this.cacheRandom & (TICK_MASK << bits)) == 0L) { - long count = chunk.leaf$tickingBlocksCount(); - if (count != 0L) { - long weight = (TICK_MUL * tickSpeed * count * SCALE) / CHUNK_BLOCKS; - samples.add(chunk.getPos().longKey); - weights.add(weight); - weightsSum += weight; - } - } - } - - /** - * @param rng a random number generator to be used as a - * source of pseudorandom {@code long} values - * @param bound the upper bound (exclusive); must be greater than zero - * - * @return a pseudorandomly chosen {@code long} value - * - * @see java.util.random.RandomGenerator#nextLong(long) nextLong(bound) - */ - public static long boundedNextLong(RandomSource rng, long bound) { - final long m = bound - 1; + private static long boundedNextLong(BitRandomSource rng, long bound) { + final long m = bound - 1L; long r = rng.nextLong(); if ((bound & m) == 0L) { r &= m; } else { + //noinspection StatementWithEmptyBody for (long u = r >>> 1; u + m - (r = u % bound) < 0L; u = rng.nextLong() >>> 1) @@ -165,4 +182,19 @@ public final class RandomTickSystem { } return r; } + + private static int boundedNextInt(BitRandomSource rng, int bound) { + final int m = bound - 1; + int r = rng.nextInt(); + if ((bound & m) == 0) { + r &= m; + } else { + //noinspection StatementWithEmptyBody + for (int u = r >>> 1; + u + m - (r = u % bound) < 0; + u = rng.nextInt() >>> 1) + ; + } + return r; + } }