From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Kevin Raneri Date: Wed, 10 Nov 2021 00:37:03 -0500 Subject: [PATCH] Pufferfish: Optimize mob spawning Original license: GPL v3 Original project: https://github.com/pufferfish-gg/Pufferfish Co-authored-by: booky10 This patch aims to reduce the main-thread impact of mob spawning by offloading as much work as possible to other threads. It is possible for inconsistencies to come up, but when they happen they never interfere with the server's operation (they don't produce errors), and side effects are limited to more or less mobs being spawned in any particular tick. It is possible to disable this optimization if it is not required or if it interferes with any plugins. On servers with thousands of entities, this can result in performance gains of up to 15%, which is significant and, in my opinion, worth the low risk of minor mob-spawning-related inconsistencies. diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java index 4ba85d704ffebae38f7a76a97a182e3674730c6f..a76b67a846b12a7b3d0c41b6ac4833d4f0372531 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -286,6 +286,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation + public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning public static S spin(Function threadFunction) { ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java index bd7f792c45059f0652e530608ef0c77c5caf7cfa..649403ef1d5d898052412d6d47783769f291b94f 100644 --- a/net/minecraft/server/dedicated/DedicatedServer.java +++ b/net/minecraft/server/dedicated/DedicatedServer.java @@ -363,6 +363,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface LOGGER.info("JMX monitoring enabled"); } + if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) mobSpawnExecutor.start(); // Pufferfish + return true; } } diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java index 7a18b11782b2524280fddf20e6b1cabdddf07c49..55f708438e5d71cf14f4e632fc20a65b4bfb7d25 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -179,6 +179,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } // Paper end - chunk tick iteration optimisations + public boolean firstRunSpawnCounts = true; // Pufferfish + public final java.util.concurrent.atomic.AtomicBoolean _pufferfish_spawnCountsReady = new java.util.concurrent.atomic.AtomicBoolean(false); // Pufferfish - optimize countmobs public ServerChunkCache( ServerLevel level, @@ -503,7 +505,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon this.collectTickingChunks(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) + 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 this.tickChunks(l, list); // Gale - Purpur - remove vanilla profiler } finally { @@ -513,6 +516,54 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon this.broadcastChangedChunks(); // Gale - Purpur - remove vanilla profiler } + + // Pufferfish start - optimize mob spawning + if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) { + 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 + } + if (firstRunSpawnCounts) { + firstRunSpawnCounts = false; + _pufferfish_spawnCountsReady.set(true); + } + if (_pufferfish_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); + // Fix: Use proper mob cap calculator based on configuration + LocalMobCapCalculator mobCapCalculator = !level.paperConfig().entities.spawning.perPlayerMobSpawns ? + new LocalMobCapCalculator(chunkMap) : null; + + // This ensures the caps are properly enforced by using the correct calculator + lastSpawnState = NaturalSpawner.createState( + mapped, + wrappedIterator, + ServerChunkCache.this::getFullChunk, + mobCapCalculator, // This is the key fix - was previously null + level.paperConfig().entities.spawning.perPlayerMobSpawns + ); + } finally { + objectiterator.finishedIterating(); + } + _pufferfish_spawnCountsReady.set(true); + }); + } + } + // Pufferfish end } private void broadcastChangedChunks() { // Gale - Purpur - remove vanilla profiler @@ -560,6 +611,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount(); // Paper start - Optional per player mob spawns if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled + if (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) { // Pufferfish - moved down when async processing // re-set mob counts for (ServerPlayer player : this.level.players) { // Paper start - per player mob spawning backoff @@ -574,12 +626,16 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } // Paper end - per player mob spawning backoff } - spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); + lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); // Pufferfish - async mob spawning + } // Pufferfish - (endif) moved down when async processing } else { - spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); + // Pufferfish start - async mob spawning + lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); + _pufferfish_spawnCountsReady.set(true); + // Pufferfish end } // Paper end - Optional per player mob spawns - this.lastSpawnState = spawnState; + //this.lastSpawnState = spawnState; // Pufferfish - this is managed asynchronously // Gale start - MultiPaper - skip unnecessary mob spawning computations } else { spawnState = null; @@ -597,7 +653,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 // Pufferfish } else { filteredSpawningCategories = List.of(); } @@ -605,8 +661,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon for (LevelChunk levelChunk : chunks) { ChunkPos pos = levelChunk.getPos(); levelChunk.incrementInhabitedTime(timeInhabited); - if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos, true)) { // Spigot - NaturalSpawner.spawnForChunk(this.level, levelChunk, spawnState, filteredSpawningCategories); + if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get()) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos, true)) { // Spigot // Pufferfish + NaturalSpawner.spawnForChunk(this.level, levelChunk, lastSpawnState, filteredSpawningCategories); // Pufferfish } if (true) { // Paper - rewrite chunk system diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java index 423779a2b690f387a4f0bd07b97b50e0baefda76..dec51066fc3f57b7bdc56195313c219f45a7fbee 100644 --- a/net/minecraft/world/level/entity/EntityTickList.java +++ b/net/minecraft/world/level/entity/EntityTickList.java @@ -9,7 +9,7 @@ import javax.annotation.Nullable; import net.minecraft.world.entity.Entity; public class EntityTickList { - private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system + public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system // Pufferfish - private->public private void ensureActiveIsNotIterated() { // Paper - rewrite chunk system