From 73ffcb09fa9a846b15688001714a662c1d5325c7 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Thu, 26 Jun 2025 01:52:40 +0900 Subject: [PATCH] optimize mob spawning --- .../features/0191-Cache-chunk-key.patch | 9 +- .../features/0264-optimize-mob-spawning.patch | 441 +++++++++++++++++- .../features/0266-throttle-mob-spawning.patch | 4 +- .../features/0273-optimize-random-tick.patch | 4 +- .../features/0276-Paw-optimization.patch | 4 +- .../features/0059-optimize-despawn.patch | 19 + 6 files changed, 471 insertions(+), 10 deletions(-) create mode 100644 leaf-server/paper-patches/features/0059-optimize-despawn.patch diff --git a/leaf-server/minecraft-patches/features/0191-Cache-chunk-key.patch b/leaf-server/minecraft-patches/features/0191-Cache-chunk-key.patch index 2050860f..f422f348 100644 --- a/leaf-server/minecraft-patches/features/0191-Cache-chunk-key.patch +++ b/leaf-server/minecraft-patches/features/0191-Cache-chunk-key.patch @@ -128,7 +128,7 @@ index c5949a0e852ca6de84e8dd12e3d4ed8527b60e25..0f311e603c8df175576a33d5d20369cb // Paper end - rewrite chunk system } diff --git a/net/minecraft/world/level/ChunkPos.java b/net/minecraft/world/level/ChunkPos.java -index 6e2b2d258e47dcca30a5ad9f4f492598f2bc21fb..f9af074e833a6dab96414750314a27b35ec07bfc 100644 +index 6e2b2d258e47dcca30a5ad9f4f492598f2bc21fb..b48f429320db9f440f65b5032a952d9b050af323 100644 --- a/net/minecraft/world/level/ChunkPos.java +++ b/net/minecraft/world/level/ChunkPos.java @@ -54,19 +54,19 @@ public class ChunkPos { @@ -154,7 +154,7 @@ index 6e2b2d258e47dcca30a5ad9f4f492598f2bc21fb..f9af074e833a6dab96414750314a27b3 } public static ChunkPos minFromRegion(int chunkX, int chunkZ) { -@@ -82,7 +82,7 @@ public class ChunkPos { +@@ -82,11 +82,11 @@ public class ChunkPos { } public static long asLong(int x, int z) { @@ -163,3 +163,8 @@ index 6e2b2d258e47dcca30a5ad9f4f492598f2bc21fb..f9af074e833a6dab96414750314a27b3 } public static long asLong(BlockPos pos) { +- return asLong(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())); ++ return ((pos.getX() >> 4) & 4294967295L) | (((pos.getZ() >> 4) & 4294967295L) << 32); // Leaf - Cache chunk key - diff on change - inline + } + + public static int getX(long chunkAsLong) { diff --git a/leaf-server/minecraft-patches/features/0264-optimize-mob-spawning.patch b/leaf-server/minecraft-patches/features/0264-optimize-mob-spawning.patch index c8b32c9d..cac42df2 100644 --- a/leaf-server/minecraft-patches/features/0264-optimize-mob-spawning.patch +++ b/leaf-server/minecraft-patches/features/0264-optimize-mob-spawning.patch @@ -4,14 +4,358 @@ Date: Tue, 3 Jun 2025 15:20:59 +0900 Subject: [PATCH] optimize mob spawning +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index 79674f4bd7a12c42dec19a4175012d7a2dc88b84..9f4ba5b7e054bbe70a820068f22fe8a6b9d72554 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 - fixme: not available in /paper playermobcaps with async 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 46e171ca454253c32e22c0c18587e9a7ba19f331..fd7f38558a598192703a8d79fc4d3818abb2551c 100644 +--- a/net/minecraft/server/level/ServerChunkCache.java ++++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -70,11 +70,11 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + private final long[] lastChunkPos = new long[4]; + private final ChunkStatus[] lastChunkStatus = new ChunkStatus[4]; + private final ChunkAccess[] lastChunk = new ChunkAccess[4]; +- private final List spawningChunks = new ObjectArrayList<>(); ++ private final List spawningChunks = it.unimi.dsi.fastutil.objects.ReferenceArrayList.wrap(new LevelChunk[0]); // Leaf + private final Set chunkHoldersToBroadcast = new ReferenceOpenHashSet<>(); + @Nullable + @VisibleForDebug +- private NaturalSpawner.SpawnState lastSpawnState; ++ private volatile NaturalSpawner.SpawnState lastSpawnState; // Leaf + // Paper start + public final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); + public int getFullChunksCount() { +@@ -542,10 +542,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( + mapped, + wrappedIterator, +- ServerChunkCache.this::getFullChunk, ++ this.level, + mobCapCalculator, // This is the key fix - was previously null + level.paperConfig().entities.spawning.perPlayerMobSpawns + ); +@@ -627,9 +627,32 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon + 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 + // Paper end - chunk tick iteration optimisation + +- for (LevelChunk levelChunk : list) { +- this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, lastSpawnState); // Pufferfish ++ // Leaf start ++ var lastSpawnState1 = lastSpawnState; ++ if ((!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get()) && lastSpawnState1 != null) { ++ if (!lastSpawnState1.playerCap.isEmpty()) { ++ for (final var iterator = lastSpawnState1.playerCap.reference2ReferenceEntrySet().fastIterator(); iterator.hasNext(); ) { ++ final var entry = iterator.next(); ++ final var entryKey = entry.getKey().mobCounts; ++ final var entryVal = entry.getValue(); ++ for (int i = 0; i < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; i++) { ++ entryKey[i] += entryVal[i]; ++ } ++ } ++ } ++ if (list instanceof it.unimi.dsi.fastutil.objects.ReferenceArrayList arrayList) { ++ LevelChunk[] levelChunks = arrayList.elements(); ++ int size = arrayList.size(); ++ for (int i = 0; i < size; i++) { ++ this.tickSpawningChunk(levelChunks[i], timeInhabited, filteredSpawningCategories, lastSpawnState1); // Pufferfish ++ } ++ } else { ++ for (LevelChunk levelChunk : list) { ++ this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, lastSpawnState1); // Pufferfish ++ } ++ } + } ++ // Leaf end + } finally { + list.clear(); + } diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java -index f242941ce06d356a025e306efe78c688e9b755c4..38a6a11c05c0a0a2f59c1477e516431263dce101 100644 +index f242941ce06d356a025e306efe78c688e9b755c4..8d50517a4c6c4f14dcb14ea18904749f7bebabf0 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java -@@ -472,6 +472,17 @@ public final class NaturalSpawner { +@@ -1,5 +1,7 @@ + package net.minecraft.world.level; + ++import ca.spottedleaf.moonrise.common.list.ReferenceList; ++import ca.spottedleaf.moonrise.common.misc.NearbyPlayers; + import com.mojang.logging.LogUtils; + import it.unimi.dsi.fastutil.objects.Object2IntMap; + import it.unimi.dsi.fastutil.objects.Object2IntMaps; +@@ -68,6 +70,7 @@ public final class NaturalSpawner { + return createState(spawnableChunkCount, entities, chunkGetter, calculator, false); + } + ++ @Deprecated(forRemoval = true) + public static NaturalSpawner.SpawnState createState( + int spawnableChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator, final boolean countMobs + ) { +@@ -108,7 +111,61 @@ 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.objects.Reference2ReferenceOpenHashMap<>()); ++ } ++ ++ public static NaturalSpawner.SpawnState createState1( ++ int spawnableChunkCount, Iterable entities, ServerLevel level, LocalMobCapCalculator calculator, final boolean countMobs ++ ) { ++ // Paper end - Optional per player mob spawns ++ PotentialCalculator potentialCalculator = new PotentialCalculator(); ++ Object2IntOpenHashMap map = new Object2IntOpenHashMap<>(); ++ it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap playerCap = new it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap<>(); ++ 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(); ++ var 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(); ++ 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, playerCap); + } + + static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) { +@@ -265,28 +322,67 @@ public final class NaturalSpawner { + MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer trackEntity, final boolean nothing + // Paper end - throttle failed spawn attempts + ) { ++ // Leaf start ++ if (!(chunk instanceof LevelChunk levelChunk)) { ++ // unreachable ++ return 0; ++ } ++ // Leaf end + // Paper end - Optional per player mob spawns + StructureManager structureManager = level.structureManager(); + ChunkGenerator generator = level.getChunkSource().getGenerator(); + int y = pos.getY(); ++ // Leaf start ++ 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 + mutableBlockPos.set(x, y, z); + double d = x + 0.5; + double d1 = z + 0.5; +@@ -295,8 +391,8 @@ public final class NaturalSpawner { + double d2 = nearestPlayer.distanceToSqr(d, y, d1); + if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn + if (spawnerData == null) { +- Optional randomSpawnMobAt = getRandomSpawnMobAt( +- level, structureManager, generator, category, level.random, mutableBlockPos ++ Optional randomSpawnMobAt = getRandomSpawnMobAtWithChunk( // Leaf ++ level, structureManager, generator, category, level.random, mutableBlockPos, levelChunk // Leaf + ); + if (randomSpawnMobAt.isEmpty()) { + break; +@@ -307,7 +403,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 +510,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 ++ 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 + + @Nullable + private static Mob getMobForSpawn(ServerLevel level, EntityType entityType) { +@@ -449,6 +583,17 @@ public final class NaturalSpawner { + : mobsAt(level, structureManager, generator, category, pos, biome).getRandom(random); + } + ++ // Leaf start ++ private static Optional getRandomSpawnMobAtWithChunk( ++ ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, RandomSource random, BlockPos pos, LevelChunk chunk ++ ) { ++ Holder biome = org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(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 ++ + private static boolean canSpawnMobAt( + ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory category, MobSpawnSettings.SpawnerData data, BlockPos pos + ) { +@@ -463,6 +608,16 @@ public final class NaturalSpawner { + : 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 + } + ++ // Leaf start ++ private static WeightedList mobsAtWithChunk( ++ ServerLevel level, StructureManager structureManager, ChunkGenerator generator, MobCategory cetagory, BlockPos pos, @Nullable Holder biome, LevelChunk chunk ++ ) { ++ return isInNetherFortressBoundsChunk(pos, level, cetagory, structureManager, chunk) ++ ? NetherFortressStructure.FORTRESS_ENEMIES ++ : generator.getMobsAtChunk(biome != null ? biome : (org.dreeam.leaf.config.modules.opt.OptimizeBiome.mobSpawn ? level.getBiomeCached(pos) : level.getBiome(pos)), structureManager, cetagory, pos, chunk); // Leaf - cache getBiome ++ } ++ // Leaf end ++ + 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 +627,28 @@ public final class NaturalSpawner { } } ++ // Leaf start ++ public static boolean isInNetherFortressBoundsChunk(BlockPos pos, ServerLevel level, MobCategory category, StructureManager structureManager, LevelChunk chunk) { ++ if (category == MobCategory.MONSTER && ((chunk.getPos().longKey == ChunkPos.asLong(pos) ? chunk.getBlockStateFinal(pos.getX(), pos.getY() - 1, pos.getZ()).is(Blocks.NETHER_BRICKS) : level.getBlockState(pos.below()).is(Blocks.NETHER_BRICKS)))) { ++ Structure structure = structureManager.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(BuiltinStructures.FORTRESS); ++ return structure != null && structureManager.getStructureAt(pos, structure).isValid(); ++ } else { ++ return false; ++ } ++ } ++ // Leaf end ++ + // Leaf start - optimize mob spawning + private static void mutableRandomPosWithin(BlockPos.MutableBlockPos pos1, Level level, LevelChunk chunk) { + ChunkPos pos = chunk.getPos(); @@ -26,3 +370,96 @@ index f242941ce06d356a025e306efe78c688e9b755c4..38a6a11c05c0a0a2f59c1477e5164312 private static BlockPos getRandomPosWithin(Level level, LevelChunk chunk) { ChunkPos pos = chunk.getPos(); int i = pos.getMinBlockX() + level.random.nextInt(16); +@@ -612,18 +789,21 @@ public final class NaturalSpawner { + @Nullable + private EntityType lastCheckedType; + private double lastCharge; ++ public final it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap playerCap; // Leaf + + SpawnState( + int spawnableChunkCount, + Object2IntOpenHashMap mobCategoryCounts, + PotentialCalculator spawnPotential, +- LocalMobCapCalculator localMobCapCalculator ++ LocalMobCapCalculator localMobCapCalculator, ++ it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap playerCap + ) { + this.spawnableChunkCount = spawnableChunkCount; + this.mobCategoryCounts = mobCategoryCounts; + this.spawnPotential = spawnPotential; + this.localMobCapCalculator = localMobCapCalculator; + this.unmodifiableMobCategoryCounts = Object2IntMaps.unmodifiable(mobCategoryCounts); ++ this.playerCap = playerCap; + } + + private boolean canSpawn(EntityType entityType, BlockPos pos, ChunkAccess chunk) { +diff --git a/net/minecraft/world/level/StructureManager.java b/net/minecraft/world/level/StructureManager.java +index fbe93098ce0366054a6da857cd808af1431b6612..a7cadf708bd79abb96fed0d41b4dbc00b265a9a6 100644 +--- a/net/minecraft/world/level/StructureManager.java ++++ b/net/minecraft/world/level/StructureManager.java +@@ -181,6 +181,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 ++ public Map getAllStructuresAtChunk(net.minecraft.world.level.chunk.ChunkAccess chunk) { ++ //SectionPos sectionPos = SectionPos.of(pos); // Leaf - optimise ChunkGenerator#getMobsAt ++ return chunk.getAllReferences(); // Leaf - optimise ChunkGenerator#getMobsAt ++ } ++ // Leaf end + + 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..68e53989c13070c27c78abeda8d9dd94f155aa0b 100644 +--- a/net/minecraft/world/level/biome/MobSpawnSettings.java ++++ b/net/minecraft/world/level/biome/MobSpawnSettings.java +@@ -52,7 +52,7 @@ public class MobSpawnSettings { + Map, MobSpawnSettings.MobSpawnCost> mobSpawnCosts + ) { + this.creatureGenerationProbability = creatureGenerationProbability; +- this.spawners = ImmutableMap.copyOf(spawners); ++ this.spawners = com.google.common.collect.Maps.immutableEnumMap(spawners); // Leaf + 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..7ca5f1b09162daf5d588468da101bced9dad7e4e 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 ++ public WeightedList getMobsAtChunk(Holder biome, StructureManager structureManager, MobCategory category, BlockPos pos, LevelChunk chunk) { ++ Map allStructuresAt = structureManager.getAllStructuresAtChunk(ChunkPos.asLong(pos) == chunk.getPos().longKey ? chunk : structureManager.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, ChunkStatus.STRUCTURE_REFERENCES)); ++ ++ for (Entry entry : allStructuresAt.entrySet()) { ++ Structure structure = entry.getKey(); ++ StructureSpawnOverride structureSpawnOverride = structure.spawnOverrides().get(category); ++ if (structureSpawnOverride != null) { ++ // Leaf start - optimise ChunkGenerator#getMobsAt ++ for (long l : entry.getValue()) { ++ StructureStart startForStructure = structureManager.getStartForStructure( ++ null, structure, 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 ++ + public void createStructures( + RegistryAccess registryAccess, + ChunkGeneratorStructureState structureState, diff --git a/leaf-server/minecraft-patches/features/0266-throttle-mob-spawning.patch b/leaf-server/minecraft-patches/features/0266-throttle-mob-spawning.patch index b5d60f04..dbc89401 100644 --- a/leaf-server/minecraft-patches/features/0266-throttle-mob-spawning.patch +++ b/leaf-server/minecraft-patches/features/0266-throttle-mob-spawning.patch @@ -5,10 +5,10 @@ Subject: [PATCH] throttle mob spawning diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java -index 38a6a11c05c0a0a2f59c1477e516431263dce101..a55abb08093847a8bfec446659f9af5fb1df2824 100644 +index 8d50517a4c6c4f14dcb14ea18904749f7bebabf0..896868b34ba5ec76be693aca1c3b75c2afbcc72e 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java -@@ -154,6 +154,17 @@ public final class NaturalSpawner { +@@ -211,6 +211,17 @@ public final class NaturalSpawner { // Paper start - Optional per player mob spawns final boolean canSpawn; int maxSpawns = Integer.MAX_VALUE; diff --git a/leaf-server/minecraft-patches/features/0273-optimize-random-tick.patch b/leaf-server/minecraft-patches/features/0273-optimize-random-tick.patch index 53be9ce4..e88f11e5 100644 --- a/leaf-server/minecraft-patches/features/0273-optimize-random-tick.patch +++ b/leaf-server/minecraft-patches/features/0273-optimize-random-tick.patch @@ -5,10 +5,10 @@ Subject: [PATCH] optimize random tick diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 46e171ca454253c32e22c0c18587e9a7ba19f331..b0f4ef60832d2e301a9a31716d888638d9250c14 100644 +index fd7f38558a598192703a8d79fc4d3818abb2551c..f2fccba5ce54cfed9ac76555e8d2860a3070fe1c 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -634,7 +634,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon +@@ -657,7 +657,13 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon list.clear(); } diff --git a/leaf-server/minecraft-patches/features/0276-Paw-optimization.patch b/leaf-server/minecraft-patches/features/0276-Paw-optimization.patch index a55755fe..c35a3fac 100644 --- a/leaf-server/minecraft-patches/features/0276-Paw-optimization.patch +++ b/leaf-server/minecraft-patches/features/0276-Paw-optimization.patch @@ -100,7 +100,7 @@ index 4535858701b2bb232b9d2feb2af6551526232ddc..e65c62dbe4c1560ae153e4c4344e9194 - // Paper end - detailed watchdog information } diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index b0f4ef60832d2e301a9a31716d888638d9250c14..bfce7501918a120f0fa84699ab6f70bacfe92ec9 100644 +index f2fccba5ce54cfed9ac76555e8d2860a3070fe1c..2763eed217b65d75c59e8c9049e7225bcf5cca05 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -623,8 +623,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -115,7 +115,7 @@ index b0f4ef60832d2e301a9a31716d888638d9250c14..bfce7501918a120f0fa84699ab6f70ba + } // Paper end - chunk tick iteration optimisation - for (LevelChunk levelChunk : list) { + // Leaf start diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 35fb0770eb385e3837cb29711905c41b899bac8f..fd6fe51ccac5163e70569484239bebeb79348501 100644 --- a/net/minecraft/server/level/ServerLevel.java diff --git a/leaf-server/paper-patches/features/0059-optimize-despawn.patch b/leaf-server/paper-patches/features/0059-optimize-despawn.patch new file mode 100644 index 00000000..1be60e74 --- /dev/null +++ b/leaf-server/paper-patches/features/0059-optimize-despawn.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: hayanesuru +Date: Thu, 26 Jun 2025 00:42:27 +0900 +Subject: [PATCH] optimize despawn + + +diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +index ed687b0ab589fd2ddb8bf77f42ba42cf8b1c2ea7..5d075f51baeaf775a458f459b762bf629e695f2e 100644 +--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java ++++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java +@@ -178,7 +178,7 @@ public class WorldConfiguration extends ConfigurationPart { + @MergeMap + public Reference2IntMap spawnLimits = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1))); + @MergeMap +- public Map despawnRanges = Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault())); ++ public Map despawnRanges = new java.util.EnumMap<>(Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()))); // Leaf - replace EnumMap optimize despawn + public DespawnRange.Shape despawnRangeShape = DespawnRange.Shape.ELLIPSOID; + @MergeMap + public Reference2IntMap ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));