9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-28 11:29:11 +00:00
Files
Leaf/leaf-server/minecraft-patches/features/0261-optimize-mob-spawning.patch
Dreeam 66887e3367 Update Leaf 1.21.6 (#385)
* Init 1.21.6

* Finish API patches

* Apply purpur patches & all paper server patches

* Apply some

* Apply all

* Updated Upstream (Paper/Purpur)

Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@6aafe29e 1.21.6 dev
PaperMC/Paper@ae7c74ec tweak mob goal keys, generate missing api
PaperMC/Paper@9ea1460b Revert "fix sponge-absorb deleting chest content (#12647)"

Purpur Changes:
PurpurMC/Purpur@eb0ba67d Updated Upstream (Paper)
PurpurMC/Purpur@7c6502dc Updated Upstream (Paper)
PurpurMC/Purpur@aa289e2c Updated Upstream (Paper)
PurpurMC/Purpur@b1d412fb Updated Upstream (Paper)
PurpurMC/Purpur@293e28a0 use empty registryaccess where context is not needed, closes #1676
PurpurMC/Purpur@452bb319 port PaperMC/Paper#12654, closes #1665
PurpurMC/Purpur@849bc79c register test subcommands used for debugging, closes #1675

* Fix build & diff

* Update vanilla attribute size

* [ci/skip] Forget to remove this

* Updated Upstream (Paper/Purpur)

Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@a24f9b20 1.21.6 dev
PaperMC/Paper@457a0a69 Bump version string and protocol version to 1.21.6 release
PaperMC/Paper@245b5473 Update base to 1.21.6
PaperMC/Paper@b28d77b3 Update Moonrise to 1.21.6
PaperMC/Paper@8318dcb7 Update DataConverter to 1.21.6-rc1

Purpur Changes:
PurpurMC/Purpur@b228e316 Updated Upstream (Paper)
PurpurMC/Purpur@d6562a57 make happy ghast takes-damage-from-water and always-drop-exp options work
PurpurMC/Purpur@08d989b6 inline import
PurpurMC/Purpur@61d7f559 Updated Upstream (Paper)
PurpurMC/Purpur@469a4de5 Merge branch 'ver/1.21.5' into ver/1.21.6

* Updated Upstream (Paper)

Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@d2b1158e Set correct world access on BSL snapshots

* Updated Upstream (Paper)

Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@023e6c29 Ensure waypoints are per world
PaperMC/Paper@40d5d31c Fix recursively converting JSON Text Components

* Update spigot mapping to remap reobf mapping to mojamp for plugins

* Updated Upstream (Paper/Purpur)

Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@1fcc4162 Implement FeatureHooks#isChunkSent for Moonrise chunk system
PaperMC/Paper@e4d7178a Implement WaypointTransmitter#isChunkVisible
PaperMC/Paper@8980ead7 Directly walk text component after converting from JSON
PaperMC/Paper@5613ed6d Provide env environment variable and copy spigots sys prop for overriding default repository
PaperMC/Paper@62b7f86d Avoid and discourage use of Maven Central as a CDN (#12689)
PaperMC/Paper@f5534cb7 [ci/skip] Run generators (#12691)
PaperMC/Paper@4781d28b Re-add Log4j Javadocs (#12693)
PaperMC/Paper@74fbcce5 Check type of Material in get/set stats (#12607)
PaperMC/Paper@4b3f967e Improve Fix MC-44654 (#12703)
PaperMC/Paper@a7dd2635 Enable spigot obfuscation support (#12695)
PaperMC/Paper@6a51c44e Cleanup Primed TNT Fix (#12704)
PaperMC/Paper@839c6a18 Fix #11169 (#12706)
PaperMC/Paper@c77d5f99 Fix MC-297591
PaperMC/Paper@219f86ee Implement chunk unload delay config option
PaperMC/Paper@e4eb69b8 Do not allow ticket level decreases to be processed asynchronously
PaperMC/Paper@71b0c768 Adds support for vanilla negative explosions (#12705)
PaperMC/Paper@3750927a [ci/skip] Fix PermissionManager#clearPermissions() docs bad wording (#12657)
PaperMC/Paper@d61a51e8 Add ItemType#getBurnDuration() (#12604)
PaperMC/Paper@29fc8532 Allow empty paths in namespaced keys (#12687)
PaperMC/Paper@4419afb9 fix: Safely handle nanosecond overflow in ClickCallback (#12686)
PaperMC/Paper@56528821 Add `isProxyEnabled` method to ServerConfiguration (#12664)
PaperMC/Paper@c0dda0ea Add `isForceDefaultGameMode` method (#12673)
PaperMC/Paper@e714de63 Fix excess slot updates
PaperMC/Paper@6d0821d2 [ci/skip] Fix docs for Spawner class and cleanup (#12710)
PaperMC/Paper@cceffe3d Release ItemType and BlockType (#12708)
PaperMC/Paper@186e9e33 Relocate CommandMap#registerServerAliases() call to after lifecycle events have been run (#12601)
PaperMC/Paper@5edcf6dd Cleanup/command dispatching (#12713)

Purpur Changes:
PurpurMC/Purpur@baa196f5 Updated Upstream (Paper)
PurpurMC/Purpur@fdd1e980 Updated Upstream (Paper)
PurpurMC/Purpur@439f15db Updated Upstream (Paper)
PurpurMC/Purpur@46a28b93 [ci/skip] update version in README
PurpurMC/Purpur@162bd288 Updated Upstream (Paper)
PurpurMC/Purpur@afcdf9bb Updated Upstream (Paper)

* fix random tick order

Signed-off-by: hayanesuru <hayanesuru@outlook.jp>

* update optimize random tick

* fix mob spawning over limit

* optimize mob spawning

* fix FastBitRadixSort memory leak

* optimize brain collections

* [ci/skip] update comments

* Revert "fix FastBitRadixSort memory leak"

This reverts commit bdea683525.

* optimize brain collections

* fix async mob spawning data race

* [ci/skip] cleanup

* remove hash lookup in optimize random tick

* Updated Upstream (Paper/Purpur)

Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@803baf0b Support hidden entities in Waypoints (#12715)
PaperMC/Paper@1814d8b4 build: publish to fill (#12717)
PaperMC/Paper@e454fef4 Add support for private constructors in plugin main classes (#12652)
PaperMC/Paper@d0e808f4 Move player to correct position in vehicle move packet handler
PaperMC/Paper@46b4b0b8 Improve keepalive ping system
PaperMC/Paper@38c1ddb5 Add and use FeatureHooks.getAllEntities
PaperMC/Paper@2f083acb Ensure player entity schedulers are ticked when they are dead
PaperMC/Paper@7c90c7c4 Optimise EntityScheduler ticking
PaperMC/Paper@aa6ee44a Re-add global region scheduler tick erroneously removed in last commit
PaperMC/Paper@d7510efc Fix #12722 (#12726)
PaperMC/Paper@0caf75f8 Fix #12721 (#12725)
PaperMC/Paper@bee28792 Adventure 4.23.0 (#12690)
PaperMC/Paper@692e93a9 Fix MC-299110
PaperMC/Paper@ea10fa4a Don't mutate the position of Items for MC-4 Fix (#12702)
PaperMC/Paper@aa6cd74c Remove unnecesary item check for ServerboundPlayerActionPacket RELEASE_USE_ITEM (#12668)
PaperMC/Paper@c9e89f49 Expose arrow velocity in EntityShootBowEvent for mobs (#12688)
PaperMC/Paper@7ec3174a Jump out of experimental phase
PaperMC/Paper@4e1a2555 Update try catch for command handling
PaperMC/Paper@e382e687 Fix 0 yield on explosion events
PaperMC/Paper@35b2c6ec Use dropped item for stats info (#12747)
PaperMC/Paper@bd79e20c [ci/skip] PluginManager#getPlugin and PluginManager#isPluginEnabled are case-insensitive (#12723)

Purpur Changes:
PurpurMC/Purpur@4a5974cf Updated Upstream (Paper)
PurpurMC/Purpur@3893bba0 this is important...
PurpurMC/Purpur@47e758fb Updated Upstream (Paper)

* Update PWT patch

* cleanup

* fix playermobcaps command

* fix Didn't fill chunk buffer in async chunk sending

* cleanup

* fix HurtByTargetGoal npe

* optimize collectSpawningChunks (#382)

* optimize collectSpawningChunks

* add bit set

* cleanup

* cleanup

* cleanup

* cleanup

* Updated Upstream (Paper/Purpur)

Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@57c202e0 Allow adventure nbt codec to parse all tags

Purpur Changes:
PurpurMC/Purpur@a4776d7b Updated Upstream (Paper)
PurpurMC/Purpur@dfd8a7ad Fix phantom going at high speed when ridden (#1680)

* Fix diff

---------

Signed-off-by: hayanesuru <hayanesuru@outlook.jp>
Co-authored-by: hayanesuru <hayanesuru@outlook.jp>
2025-06-30 05:17:33 +08:00

597 lines
36 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: hayanesuru <hayanesuru@outlook.jp>
Date: Tue, 3 Jun 2025 15:20:59 +0900
Subject: [PATCH] optimize mob spawning
Avoid getChunk calls if its position is same as the chunk used for mob spawning
Fix data race in async mob spawning
by adding chunk position to the mob count map
then apply result on server thread.
Generally faster than the non-async approach
iterate over all entities, get their chunk, and increment the count
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index 79674f4bd7a12c42dec19a4175012d7a2dc88b84..0a97a491737807d59815b75635fa3d8c94901ba8 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 46e171ca454253c32e22c0c18587e9a7ba19f331..1c7e2e5124c7d4c1b86039b0327bfd92c49df9b1 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() {
@@ -498,6 +498,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;
@@ -511,8 +528,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;
@@ -524,14 +541,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 {
@@ -542,10 +559,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
);
@@ -610,6 +627,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
@@ -621,16 +639,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();
}
@@ -648,7 +690,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 fc61a7083232148397a0b330ce2a4d70ba38543d..d319e4d7e12c4bae63893ad25dcdc3ed59c88d97 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1114,6 +1114,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.simpleRandom.nextInt(16); } // Gale - Airplane - optimize random calls in chunk ticking
+ public final org.dreeam.leaf.world.NatureSpawnChunkMap natureSpawnChunkMap = new org.dreeam.leaf.world.NatureSpawnChunkMap(); // Leaf - optimize mob spawning
public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting
ChunkPos pos = chunk.getPos();
diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java
index 81e176d17fb072f9ee531639abfe42134ae833a9..ab6fa7ed111ef16a0b6774c21988589ee2110c66 100644
--- a/net/minecraft/world/level/NaturalSpawner.java
+++ b/net/minecraft/world/level/NaturalSpawner.java
@@ -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
+ public static boolean isInNetherFortressBoundsChunk(BlockPos pos, ServerLevel level, MobCategory category, StructureManager structureManager, LevelChunk chunk) {
+ if (category == MobCategory.MONSTER && ((chunk.getPos().longKey == ChunkPos.asLong(pos) ? chunk.getBlockStateFinal(pos.getX(), pos.getY() - 1, pos.getZ()).is(Blocks.NETHER_BRICKS) : level.getBlockState(pos.below()).is(Blocks.NETHER_BRICKS)))) {
+ Structure structure = structureManager.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(BuiltinStructures.FORTRESS);
+ return structure != null && structureManager.getStructureAt(pos, structure).isValid();
+ } else {
+ return false;
+ }
+ }
+ // Leaf end - optimize mob spawning
+
private static BlockPos getRandomPosWithin(Level level, LevelChunk chunk) {
ChunkPos pos = chunk.getPos();
int i = pos.getMinBlockX() + level.random.nextInt(16);
@@ -612,18 +782,21 @@ public final class NaturalSpawner {
@Nullable
private EntityType<?> lastCheckedType;
private double lastCharge;
+ public final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<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,