9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00

backport from dev/1.21.6

73ffcb09fa optimize mob spawning
333373d204 fix async mob spawning data race
7c9f88e4f8 [ci skip] cleanup
bf9486f0f0 remove hash lookup in optimize random tick
915ac01cd3 cleanup
b90c1cd527 fix playermobcaps command
fd34d9f626 cleanup
5d663b4d36 optimize collectSpawningChunks (#382)
This commit is contained in:
hayanesuru
2025-06-29 23:58:26 +09:00
parent 186a317f5d
commit 34d9673f3e
12 changed files with 1015 additions and 235 deletions

View File

@@ -1,11 +1,11 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Taiyou06 <kaandindar21@gmail.com> From: Taiyou06 <kaandindar21@gmail.com>
Date: Sun, 2 Mar 2025 21:23:20 +0100 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 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 --- a/ca/spottedleaf/moonrise/patches/chunk_system/player/RegionizedPlayerChunkLoader.java
+++ b/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 { @@ -436,7 +436,15 @@ public final class RegionizedPlayerChunkLoader {
@@ -13,7 +13,7 @@ index 32608df3da169159c070f37cb55407f4f6187744..3a78e7512772fd3f7cf8f221e3a72474
((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager ((ChunkSystemChunkHolder)((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager
.getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$removeReceivedChunk(this.player); .getChunkHolder(chunkX, chunkZ).vanillaChunkHolder).moonrise$removeReceivedChunk(this.player);
- this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))); - 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) { + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) {
+ org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( + org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute(
+ () -> this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))) + () -> this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ)))
@@ -21,26 +21,26 @@ index 32608df3da169159c070f37cb55407f4f6187744..3a78e7512772fd3f7cf8f221e3a72474
+ } else { + } else {
+ this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ))); + this.player.connection.send(new ClientboundForgetLevelChunkPacket(new ChunkPos(chunkX, chunkZ)));
+ } + }
+ // Leaf end - Async chunk send + // Leaf end - Async chunk sending
// Paper start - PlayerChunkUnloadEvent // Paper start - PlayerChunkUnloadEvent
if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { 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(); 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 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 --- a/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
+++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java +++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData.java
@@ -75,6 +75,45 @@ public class ClientboundLevelChunkPacketData { @@ -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<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo, BlockEntity[] blockEntities, Map<Heightmap.Types, long[]> heightmaps) { + public ClientboundLevelChunkPacketData(LevelChunk levelChunk, io.papermc.paper.antixray.ChunkPacketInfo<net.minecraft.world.level.block.state.BlockState> chunkPacketInfo, BlockEntity[] blockEntities, Map<Heightmap.Types, long[]> heightmaps) {
+ this.heightmaps = heightmaps; + this.heightmaps = heightmaps;
+ +
+ if (Thread.currentThread() instanceof org.dreeam.leaf.async.chunk.AsyncChunkSendThread) { + 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); + 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) { + if (chunkPacketInfo != null) {
+ chunkPacketInfo.setBuffer(array); + chunkPacketInfo.setBuffer(array);
+ } + }
@@ -70,20 +70,20 @@ index 526c117e0d53ad527eb610c79cdc46ec16b18c0c..9151e580b4840fddab04e487d723130a
+ this.blockEntitiesData.add(ClientboundLevelChunkPacketData.BlockEntityInfo.create(blockEntity)); + 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) { public ClientboundLevelChunkPacketData(RegistryFriendlyByteBuf buffer, int x, int z) {
this.heightmaps = HEIGHTMAPS_STREAM_CODEC.decode(buffer); this.heightmaps = HEIGHTMAPS_STREAM_CODEC.decode(buffer);
int varInt = buffer.readVarInt(); int varInt = buffer.readVarInt();
diff --git a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java 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 --- a/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
+++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java +++ b/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
@@ -44,6 +44,17 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa @@ -44,6 +44,17 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight); this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight);
chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks
} }
+ // Leaf start - Async chunk send + // Leaf start - Async chunk sending
+ public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightEngine, @Nullable BitSet skyLight, @Nullable BitSet blockLight, boolean modifyBlocks, net.minecraft.world.level.block.entity.BlockEntity[] blockEntities, java.util.Map<net.minecraft.world.level.levelgen.Heightmap.Types, long[]> heightmaps) { + public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightEngine, @Nullable BitSet skyLight, @Nullable BitSet blockLight, boolean modifyBlocks, net.minecraft.world.level.block.entity.BlockEntity[] blockEntities, java.util.Map<net.minecraft.world.level.levelgen.Heightmap.Types, long[]> heightmaps) {
+ ChunkPos pos = chunk.getPos(); + ChunkPos pos = chunk.getPos();
+ this.x = pos.x; + this.x = pos.x;
@@ -93,12 +93,12 @@ index 8578d1f78ddd1bb75f3230f04bfaa35af9f5f822..4f54c4c8e49c1e0352ab2c5c23277b41
+ this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight); + this.lightData = new ClientboundLightUpdatePacketData(pos, lightEngine, skyLight, blockLight);
+ chunk.getLevel().chunkPacketBlockController.modifyBlocks(this, chunkPacketInfo); // Paper - Anti-Xray - Modify blocks + 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) { private ClientboundLevelChunkWithLightPacket(RegistryFriendlyByteBuf buffer) {
this.x = buffer.readInt(); this.x = buffer.readInt();
diff --git a/net/minecraft/server/network/PlayerChunkSender.java b/net/minecraft/server/network/PlayerChunkSender.java 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 --- a/net/minecraft/server/network/PlayerChunkSender.java
+++ b/net/minecraft/server/network/PlayerChunkSender.java +++ b/net/minecraft/server/network/PlayerChunkSender.java
@@ -64,13 +64,29 @@ public class PlayerChunkSender { @@ -64,13 +64,29 @@ public class PlayerChunkSender {
@@ -106,7 +106,7 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9
ServerGamePacketListenerImpl serverGamePacketListenerImpl = player.connection; ServerGamePacketListenerImpl serverGamePacketListenerImpl = player.connection;
this.unacknowledgedBatches++; this.unacknowledgedBatches++;
- serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE); - serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE);
+ // Leaf start - Async chunk send + // Leaf start - Async chunk sending
+ if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) { + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) {
+ org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( + org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute(
+ () -> serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE) + () -> serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE)
@@ -114,14 +114,14 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9
+ } else { + } else {
+ serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE); + serverGamePacketListenerImpl.send(ClientboundChunkBatchStartPacket.INSTANCE);
+ } + }
+ // Leaf end - Async chunk send + // Leaf end - Async chunk sending
for (LevelChunk levelChunk : list) { for (LevelChunk levelChunk : list) {
sendChunk(serverGamePacketListenerImpl, serverLevel, levelChunk); sendChunk(serverGamePacketListenerImpl, serverLevel, levelChunk);
} }
- serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size())); - 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) { + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) {
+ org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute( + org.dreeam.leaf.async.chunk.AsyncChunkSend.POOL.execute(
+ () -> serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size())) + () -> serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size()))
@@ -129,7 +129,7 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9
+ } else { + } else {
+ serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size())); + serverGamePacketListenerImpl.send(new ClientboundChunkBatchFinishedPacket(list.size()));
+ } + }
+ // Leaf end - Async chunk send + // Leaf end - Async chunk sending
this.batchQuota = this.batchQuota - list.size(); this.batchQuota = this.batchQuota - list.size();
} }
} }
@@ -138,7 +138,7 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9
public static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) { public static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) {
final boolean shouldModify = level.chunkPacketBlockController.shouldModify(packetListener.player, chunk); final boolean shouldModify = level.chunkPacketBlockController.shouldModify(packetListener.player, chunk);
- packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null, shouldModify)); - 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) { + if (org.dreeam.leaf.config.modules.async.AsyncChunkSend.enabled) {
+ var blockEntities = chunk.blockEntities.values().toArray(new net.minecraft.world.level.block.entity.BlockEntity[0]); + var blockEntities = chunk.blockEntities.values().toArray(new net.minecraft.world.level.block.entity.BlockEntity[0]);
+ java.util.Map<net.minecraft.world.level.levelgen.Heightmap.Types, long[]> heightmaps = new java.util.concurrent.ConcurrentHashMap<>(); + java.util.Map<net.minecraft.world.level.levelgen.Heightmap.Types, long[]> heightmaps = new java.util.concurrent.ConcurrentHashMap<>();
@@ -154,12 +154,12 @@ index 14878690a88fd4de3e2c127086607e6c819c636c..69581890ab34af20f9c608678f378ec9
+ } else { + } else {
+ packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null, shouldModify)); + 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 end - Anti-Xray
// Paper start - PlayerChunkLoadEvent // Paper start - PlayerChunkLoadEvent
if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { 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 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 --- a/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/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_ @@ -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 SECTION_SIZE = 4096;
public static final int BIOME_CONTAINER_BITS = 2; public static final int BIOME_CONTAINER_BITS = 2;
- short nonEmptyBlockCount; // Paper - package private - 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 tickingBlockCount;
private short tickingFluidCount; private short tickingFluidCount;
private boolean isRandomlyTickingBlocksStatus; // Leaf - Cache random tick block status private boolean isRandomlyTickingBlocksStatus; // Leaf - Cache random tick block status

View File

@@ -1,11 +1,11 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp> From: hayanesuru <hayanesuru@outlook.jp>
Date: Tue, 3 Jun 2025 15:20:40 +0900 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 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 --- a/net/minecraft/advancements/critereon/LocationPredicate.java
+++ b/net/minecraft/advancements/critereon/LocationPredicate.java +++ b/net/minecraft/advancements/critereon/LocationPredicate.java
@@ -49,7 +49,7 @@ public record LocationPredicate( @@ -49,7 +49,7 @@ public record LocationPredicate(
@@ -13,12 +13,12 @@ index a26a5311f87873e0d4d26fda9cb8956a32ee81e8..65e4315cce35814c60b21bbd5baea2ff
BlockPos blockPos = BlockPos.containing(x, y, z); BlockPos blockPos = BlockPos.containing(x, y, z);
boolean isLoaded = level.isLoaded(blockPos); 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(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.structures.isPresent() || isLoaded && level.structureManager().getStructureWithPieceAt(blockPos, this.structures.get()).isValid())
&& (!this.smokey.isPresent() || isLoaded && this.smokey.get() == CampfireBlock.isSmokeyPos(level, blockPos)) && (!this.smokey.isPresent() || isLoaded && this.smokey.get() == CampfireBlock.isSmokeyPos(level, blockPos))
&& (!this.light.isPresent() || this.light.get().matches(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 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 --- a/net/minecraft/world/level/LevelReader.java
+++ b/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 @@ -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 + // Leaf start - cache getBiome
+ default Holder<Biome> getBiomeCached(BlockPos pos) { + default Holder<Biome> getBiomeCached(@Nullable net.minecraft.world.level.chunk.LevelChunk chunk, BlockPos pos) {
+ return this.getBiomeManager().getBiomeCached(pos); + return this.getBiomeManager().getBiomeCached(chunk, pos);
+ } + }
+ // Leaf end - cache getBiome + // Leaf end - cache getBiome
+ +
@@ -35,7 +35,7 @@ index 0842fd6488c8b27d98c4344e1244996b4c0e9912..c6ddb49648c55443ae880c1adba9887a
int floor = Mth.floor(aabb.minX); int floor = Mth.floor(aabb.minX);
int floor1 = Mth.floor(aabb.maxX); int floor1 = Mth.floor(aabb.maxX);
diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java 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 --- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java
@@ -443,7 +443,7 @@ public final class NaturalSpawner { @@ -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 ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, RandomSource random, BlockPos pos
) { ) {
- Holder<Biome> biome = level.getBiome(pos); - Holder<Biome> biome = level.getBiome(pos);
+ Holder<Biome> biome = org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(pos) : level.getBiome(pos); // Leaf - cache getBiome + Holder<Biome> 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 return category == MobCategory.WATER_AMBIENT && biome.is(BiomeTags.REDUCED_WATER_AMBIENT_SPAWNS) && random.nextFloat() < 0.98F
? Optional.empty() ? Optional.empty()
: mobsAt(level, structureManager, generator, category, pos, biome).getRandom(random); : mobsAt(level, structureManager, generator, category, pos, biome).getRandom(random);
@@ -52,12 +52,12 @@ index a2da4fce50f31d56036d04041c4f80ed90c18b27..f242941ce06d356a025e306efe78c688
return isInNetherFortressBounds(pos, level, cetagory, structureManager) return isInNetherFortressBounds(pos, level, cetagory, structureManager)
? NetherFortressStructure.FORTRESS_ENEMIES ? NetherFortressStructure.FORTRESS_ENEMIES
- : generator.getMobsAt(biome != null ? biome : level.getBiome(pos), structureManager, cetagory, pos); - : 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) { 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 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 --- a/net/minecraft/world/level/biome/BiomeManager.java
+++ b/net/minecraft/world/level/biome/BiomeManager.java +++ b/net/minecraft/world/level/biome/BiomeManager.java
@@ -15,10 +15,23 @@ public class BiomeManager { @@ -15,10 +15,23 @@ public class BiomeManager {
@@ -84,12 +84,12 @@ index a48175a7ebb1788ace46395621ed78d910178a53..00122472991ba0c1a0ea77053aad71cd
} }
public static long obfuscateSeed(long seed) { 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); return new BiomeManager(newSource, this.biomeZoomSeed);
} }
+ // Leaf start - cache getBiome + // Leaf start - cache getBiome
+ public Holder<Biome> getBiomeCached(BlockPos pos) { + public Holder<Biome> getBiomeCached(@org.jetbrains.annotations.Nullable net.minecraft.world.level.chunk.LevelChunk chunk, BlockPos pos) {
+ if (biomeCache == null) { + if (biomeCache == null) {
+ return getBiome(pos); + return getBiome(pos);
+ } + }
@@ -113,19 +113,84 @@ index a48175a7ebb1788ace46395621ed78d910178a53..00122472991ba0c1a0ea77053aad71cd
+ } + }
+ } + }
+ +
+ Holder<Biome> biome = getBiome(pos); + Holder<Biome> biome = getBiomeCachedChunk(chunk, pos);
+ +
+ biomeCache[(int) hash] = biome; + biomeCache[(int) hash] = biome;
+ biomeCachePos[(int) hash] = packedPos; + biomeCachePos[(int) hash] = packedPos;
+ +
+ return biome; + return biome;
+ } + }
+ private Holder<Biome> 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 + // Leaf end - cache getBiome
+ +
public Holder<Biome> getBiome(BlockPos pos) { public Holder<Biome> getBiome(BlockPos pos) {
// Leaf start - Carpet-Fixes - Optimized getBiome method // Leaf start - Carpet-Fixes - Optimized getBiome method
int xMinus2 = pos.getX() - 2; 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); return Mth.square(zNoise + fiddle2) + Mth.square(yNoise + fiddle1) + Mth.square(xNoise + fiddle);
} }

View File

@@ -3,26 +3,594 @@ From: hayanesuru <hayanesuru@outlook.jp>
Date: Tue, 3 Jun 2025 15:20:59 +0900 Date: Tue, 3 Jun 2025 15:20:59 +0900
Subject: [PATCH] optimize mob spawning 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<LevelChunk> spawningChunks = new ObjectArrayList<>();
+ private final List<LevelChunk> spawningChunks = it.unimi.dsi.fastutil.objects.ReferenceArrayList.wrap(new LevelChunk[0]); // Leaf - optimize mob spawning
private final Set<ChunkHolder> 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<net.minecraft.world.level.chunk.LevelChunk> 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<Entity> 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<LevelChunk> 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<LevelChunk> 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 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 --- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/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<Entity> 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<Entity> entities, ServerLevel level, LocalMobCapCalculator calculator, final boolean countMobs
+ ) {
+ // Paper end - Optional per player mob spawns
+ PotentialCalculator potentialCalculator = new PotentialCalculator();
+ Object2IntOpenHashMap<MobCategory> map = new Object2IntOpenHashMap<>();
+ it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<int[]> 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<Entity> 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<MobSpawnSettings.SpawnerData> randomSpawnMobAt = getRandomSpawnMobAt(
- level, structureManager, generator, category, level.random, mutableBlockPos
+ Optional<MobSpawnSettings.SpawnerData> 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<MobSpawnSettings.SpawnerData> getRandomSpawnMobAtWithChunk(
+ ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, RandomSource random, BlockPos pos, LevelChunk chunk
+ ) {
+ Holder<Biome> 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<MobSpawnSettings.SpawnerData> mobsAtWithChunk(
+ ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory cetagory, BlockPos pos, @Nullable Holder<Biome> 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 + // Leaf start - optimize mob spawning
+ private static void mutableRandomPosWithin(BlockPos.MutableBlockPos pos1, Level level, LevelChunk chunk) { + public static boolean isInNetherFortressBoundsChunk(BlockPos pos, ServerLevel level, MobCategory category, StructureManager structureManager, LevelChunk chunk) {
+ ChunkPos pos = chunk.getPos(); + 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)))) {
+ int randomX = pos.getMinBlockX() + level.random.nextInt(16); + Structure structure = structureManager.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(BuiltinStructures.FORTRESS);
+ int randomZ = pos.getMinBlockZ() + level.random.nextInt(16); + return structure != null && structureManager.getStructureAt(pos, structure).isValid();
+ int surfaceY = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, randomX, randomZ) + 1; + } else {
+ int randomY = Mth.randomBetweenInclusive(level.random, level.getMinY(), surfaceY); + return false;
+ pos1.set(randomX, randomY, randomZ); + }
+ } + }
+ // Leaf end - optimize mob spawning + // Leaf end - optimize mob spawning
+ +
private static BlockPos getRandomPosWithin(Level level, LevelChunk chunk) { private static BlockPos getRandomPosWithin(Level level, LevelChunk chunk) {
ChunkPos pos = chunk.getPos(); ChunkPos pos = chunk.getPos();
int i = pos.getMinBlockX() + level.random.nextInt(16); 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<int[]> chunkCap; // Leaf - optimize mob spawning
SpawnState(
int spawnableChunkCount,
Object2IntOpenHashMap<MobCategory> mobCategoryCounts,
PotentialCalculator spawnPotential,
- LocalMobCapCalculator localMobCapCalculator
+ LocalMobCapCalculator localMobCapCalculator,
+ it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<int[]> 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<net.minecraft.server.level.ServerPlayer> 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<Structure, LongSet> 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<EntityType<?>, 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<MobSpawnSettings.SpawnerData> getMobsAtChunk(Holder<Biome> biome, StructureManager structureManager, MobCategory category, BlockPos pos, LevelChunk chunk) {
+ Map<Structure, LongSet> allStructuresAt = structureManager.getAllStructuresAtChunk(ChunkPos.asLong(pos) == chunk.getPos().longKey ? chunk : structureManager.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_REFERENCES));
+
+ for (Entry<Structure, LongSet> 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,

View File

@@ -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 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 --- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/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 // Paper start - Optional per player mob spawns
final boolean canSpawn; final boolean canSpawn;
int maxSpawns = Integer.MAX_VALUE; int maxSpawns = Integer.MAX_VALUE;

View File

@@ -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 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 --- a/net/minecraft/server/level/ServerChunkCache.java
+++ b/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.iterateTickingChunksFaster(); // Paper - chunk tick iteration optimisations
+ this.level.randomTickSystem.tick(this.level); // Leaf - optimize random tick + // 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) { if (_boolean) {
this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
} }
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java 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 --- a/net/minecraft/server/level/ServerLevel.java
+++ b/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 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) { 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 final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting
ChunkPos pos = chunk.getPos(); 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 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 --- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/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 // 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 + // Leaf start - optimize random tick
+ private boolean leaf$tickingBlocksDirty = true; + public boolean leaf$tickingBlocksDirty = true;
+ private int leaf$tickingBlocksCount; + public int[] leaf$tickingCount = {};
+ 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();
+ }
+ // Leaf end - optimize random tick + // Leaf end - optimize random tick
public LevelChunk(Level level, ChunkPos pos) { public LevelChunk(Level level, ChunkPos pos) {
this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null); 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) { if (blockState == state) {
return null; return null;
} else { } else {

View File

@@ -99,25 +99,8 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..e65c62dbe4c1560ae153e4c4344e9194
- } - }
- // Paper end - detailed watchdog information - // 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 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 --- a/net/minecraft/server/level/ServerLevel.java
+++ b/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 @@ -1510,13 +1510,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe

View File

@@ -1,11 +1,11 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <mc@jvavav.com> From: hayanesuru <mc@jvavav.com>
Date: Fri, 2 May 2025 18:22:24 -0700 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 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 --- a/src/main/java/io/papermc/paper/antixray/ChunkPacketBlockControllerAntiXray.java
+++ b/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 @@ -185,7 +185,7 @@ public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockCo
@@ -13,7 +13,7 @@ index ca24f4cd7a50f0156d84e263c60f841cca95c669..7e15038e8fceab1e97c2245c2e9111de
} }
- if (!Bukkit.isPrimaryThread()) { - 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? // Plugins?
MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo)); MinecraftServer.getServer().scheduleOnMain(() -> modifyBlocks(chunkPacket, chunkPacketInfo));
return; return;

View File

@@ -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 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 --- a/src/main/java/org/bukkit/craftbukkit/util/DelegatedGeneratorAccess.java
+++ b/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 { @@ -285,6 +285,13 @@ public abstract class DelegatedGeneratorAccess implements WorldGenLevel {
@@ -14,8 +14,8 @@ index 0e14f962b26823e49b192a4f97ec6c1f477ef0ff..cb6cd899fd8af069534f45a4a9e81137
+ // Leaf start - cache getBiome + // Leaf start - cache getBiome
+ @Override + @Override
+ public Holder<Biome> getBiomeCached(BlockPos pos) { + public Holder<Biome> getBiomeCached(@Nullable net.minecraft.world.level.chunk.LevelChunk chunk, BlockPos pos) {
+ return this.delegate.getBiomeCached(pos); + return this.delegate.getBiomeCached(chunk, pos);
+ } + }
+ // Leaf end - cache getBiome + // Leaf end - cache getBiome
+ +

View File

@@ -0,0 +1,19 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
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<MobCategory> spawnLimits = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
@MergeMap
- public Map<MobCategory, DespawnRangePair> despawnRanges = Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()));
+ public Map<MobCategory, DespawnRangePair> 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<MobCategory> ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));

View File

@@ -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<LevelChunk> chunks, List<LevelChunk> 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;
}
}

View File

@@ -1,115 +1,162 @@
package org.dreeam.leaf.world; 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 it.unimi.dsi.fastutil.longs.LongArrayList;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel; 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.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk; 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 net.minecraft.world.level.material.FluidState;
import java.util.OptionalLong;
public final class RandomTickSystem { public final class RandomTickSystem {
private static final long SCALE = 0x100000L; private static final long SCALE = 0x100000L;
private static final long CHUNK_BLOCKS = 4096L; private static final long TICK_FILTER_MASK = 0b11L;
private static final long CHUNK_BLOCKS = 4096L / 4L;
/// reduce unnecessary sampling and block counting
private static final long TICK_MASK = 0b11L;
private static final long TICK_MUL = 4L;
private static final int BITS_STEP = 2; private static final int BITS_STEP = 2;
private static final int BITS_MAX = 60; private static final int BITS_MAX = 60;
private final LongArrayList queue = new LongArrayList(); private final LongArrayList queue = new LongArrayList();
private final LongArrayList samples = new LongArrayList(); private final LongArrayList samples = new LongArrayList();
private final LongArrayList weights = 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) { public void tick(ServerLevel world) {
if (weights.isEmpty() || samples.isEmpty()) { queue.clear();
samples.clear();
weights.clear();
final BitRandomSource random = world.simpleRandom;
final ReferenceList<LevelChunk> 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; return;
} }
if (!disableIceAndSnow) {
final var random = world.simpleRandom; iceSnow(world, size, randomTickSpeed, random, raw);
final long chosen;
if (((weightsSum % SCALE) >= boundedNextLong(random, SCALE))) {
chosen = weightsSum / SCALE + 1L;
} else {
chosen = weightsSum / SCALE;
} }
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) { if (chosen == 0L) {
return; return;
} }
final long[] w = weights.elements();
final long[] s = samples.elements();
long accumulated = w[0];
final long spoke = weightsSum / chosen; final long spoke = weightsSum / chosen;
if (spoke == 0L) { if (spoke == 0L) return;
return;
}
final long[] weightsRaw = weights.elements();
final long[] samplesRaw = samples.elements();
long accumulated = weightsRaw[0];
long current = boundedNextLong(random, spoke); long current = boundedNextLong(random, spoke);
int i = 0; int i = 0;
while (current < weightsSum) { while (current < weightsSum) {
while (accumulated < current) { while (accumulated < current) {
i += 1; i++;
accumulated += weightsRaw[i]; accumulated += w[i];
} }
queue.add(samplesRaw[i]); queue.add(s[i]);
current += spoke; 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) { private long collectTickingChunks(int size, BitRandomSource random, LevelChunk[] raw, long randomTickSpeed) {
return world.chunkSource.getChunkAtIfLoadedImmediately((int) packed, (int) (packed >> 32)); 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) { private static void populateChunkTickingCount(LevelChunk chunk) {
int count = chunk.leaf$tickingBlocksCount(); chunk.leaf$tickingBlocksDirty = false;
if (count == 0) { int sum = 0;
return; for (LevelChunkSection section : chunk.getSections()) {
sum += (section.moonrise$getTickingBlockList().size() == 0) ? 0 : 1;
} }
OptionalLong optionalPos = chunk.leaf$getTickingPos(random.nextInt(count));
if (optionalPos.isEmpty()) { if (chunk.leaf$tickingCount.length != sum) {
return; 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); state.randomTick(world, pos, random);
final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294(); final boolean doubleTickFluids = !ca.spottedleaf.moonrise.common.PlatformHooks.get().configFixMC224294();
@@ -121,43 +168,13 @@ public final class RandomTickSystem {
} }
} }
public void tickChunk( private static long boundedNextLong(BitRandomSource rng, long bound) {
RandomSource random, final long m = bound - 1L;
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;
long r = rng.nextLong(); long r = rng.nextLong();
if ((bound & m) == 0L) { if ((bound & m) == 0L) {
r &= m; r &= m;
} else { } else {
//noinspection StatementWithEmptyBody
for (long u = r >>> 1; for (long u = r >>> 1;
u + m - (r = u % bound) < 0L; u + m - (r = u % bound) < 0L;
u = rng.nextLong() >>> 1) u = rng.nextLong() >>> 1)
@@ -165,4 +182,19 @@ public final class RandomTickSystem {
} }
return r; 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;
}
} }