From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Wed, 19 Mar 2025 23:24:32 +0300 Subject: [PATCH] Pufferfish: Optimize mob spawning Original license: GPL v3 Original project: https://github.com/pufferfish-gg/Pufferfish This patch reduces the main-thread impact of mob spawning by moving spawning work to other threads diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java index 7dbefd83d164a7d97a56b02862fef3b2f17d5aab..2f4be37e1ef4ea550bd6acd2c4647e5a7ed77648 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -289,6 +289,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function threadFunction) { ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java index 75c8ce32e68f92e20201e9c243f46f2be716eac8..a76f7ce474cc1d6ff918737e845666a7529bd4a3 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -182,6 +182,10 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } // Paper end - chunk tick iteration optimisations + // DivineMC start - Async mob spawning + public boolean firstRunSpawnCounts = true; + public final java.util.concurrent.atomic.AtomicBoolean spawnCountsReady = new java.util.concurrent.atomic.AtomicBoolean(false); + // DivineMC end - Async mob spawning public ServerChunkCache( ServerLevel level, @@ -506,6 +510,47 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon this.broadcastChangedChunks(); } + + // DivineMC start - Async mob spawning + if (org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableAsyncSpawning) { + for (ServerPlayer player : this.level.players) { + for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { + player.mobCounts[ii] = 0; + + int newBackoff = Math.max(0, player.mobBackoffCounts[ii] - 1); + player.mobBackoffCounts[ii] = newBackoff; + } + } + + if (firstRunSpawnCounts) { + firstRunSpawnCounts = false; + spawnCountsReady.set(true); + } + + if (spawnCountsReady.getAndSet(false)) { + net.minecraft.server.MinecraftServer.getServer().mobSpawnExecutor.submit(() -> { + int mapped = distanceManager.getNaturalSpawnChunkCount(); + ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator objectiterator = level.entityTickList.entities.iterator(ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); + + try { + gg.pufferfish.pufferfish.util.IterableWrapper wrappedIterator = new gg.pufferfish.pufferfish.util.IterableWrapper<>(objectiterator); + LocalMobCapCalculator mobCapCalculator = !level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(chunkMap) : null; + + lastSpawnState = NaturalSpawner.createState( + mapped, + wrappedIterator, + ServerChunkCache.this::getFullChunk, + mobCapCalculator, + level.paperConfig().entities.spawning.perPlayerMobSpawns + ); + } finally { + objectiterator.finishedIterating(); + } + spawnCountsReady.set(true); + }); + } + } + // DivineMC end - Async mob spawning } private void broadcastChangedChunks() { @@ -523,27 +568,31 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount(); // Paper start - Optional per player mob spawns NaturalSpawner.SpawnState spawnState; + // DivineMC start - Async mob spawning if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled - // re-set mob counts - 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; + if (!org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableAsyncSpawning) { + // re-set mob counts + 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; } - player.mobBackoffCounts[ii] = newBackoff; + // Paper end - per player mob spawning backoff } - // Paper end - per player mob spawning backoff + lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap), true); } - spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); } else { - spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); + lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap), false); + spawnCountsReady.set(true); } + // DivineMC end - Async mob spawning // Paper end - Optional per player mob spawns - this.lastSpawnState = spawnState; boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); List filteredSpawningCategories; @@ -557,7 +606,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } // 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 - filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag, this.level); // CraftBukkit + filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag, this.level); // CraftBukkit // DivineMC - Async mob spawning } else { filteredSpawningCategories = List.of(); } @@ -572,7 +621,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon // Paper end - chunk tick iteration optimisation for (LevelChunk levelChunk : list) { - this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, spawnState); + this.tickSpawningChunk(levelChunk, timeInhabited, filteredSpawningCategories, lastSpawnState); // DivineMC - Async mob spawning } } finally { list.clear(); @@ -591,11 +640,11 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon this.level.tickThunder(chunk); } - if (!spawnCategories.isEmpty()) { - if (this.level.getWorldBorder().isWithinBounds(pos)) { // Paper - rewrite chunk system - NaturalSpawner.spawnForChunk(this.level, chunk, spawnState, spawnCategories); - } + // DivineMC start - Async mob spawning + if (!spawnCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && (!org.bxteam.divinemc.config.DivineConfig.AsyncCategory.enableAsyncSpawning || spawnCountsReady.get())) { // Paper - rewrite chunk system + NaturalSpawner.spawnForChunk(this.level, chunk, spawnState, spawnCategories); } + // DivineMC end - Async mob spawning } private void getFullChunk(long chunkPos, Consumer fullChunkGetter) {