Files
Pufferfish/patches/server/0009-Optimize-mob-spawning.patch
T
2023-03-20 17:44:07 -04:00

184 lines
13 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Kevin Raneri <kevin.raneri@gmail.com>
Date: Wed, 10 Nov 2021 00:37:03 -0500
Subject: [PATCH] Optimize mob spawning
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/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
index 9a8896f51be5028724563cd5054782477868edef..b3dba80e30268f85befd2495cbf5bc5f59a57cde 100644
--- a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
@@ -181,4 +181,21 @@ public class PufferfishConfig {
"configuration option if the vanilla deviation is undesirable.");
}
+ public static boolean enableAsyncMobSpawning;
+ public static boolean asyncMobSpawningInitialized;
+ private static void asyncMobSpawning() {
+ boolean temp = getBoolean("enable-async-mob-spawning", true,
+ "Whether or not asynchronous mob spawning should be enabled.",
+ "On servers with many entities, this can improve performance by up to 15%. You must have",
+ "paper's per-player-mob-spawns setting set to true for this to work.",
+ "One quick note - this does not actually spawn mobs async (that would be very unsafe).",
+ "This just offloads some expensive calculations that are required for mob spawning.");
+
+ // This prevents us from changing the value during a reload.
+ if (!asyncMobSpawningInitialized) {
+ asyncMobSpawningInitialized = true;
+ enableAsyncMobSpawning = temp;
+ }
+ }
+
}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 2c9ff8990e5c7a03db22a1ad345c022ab0733801..70086d05b02bc0949317b03dddf6e246ae7249b7 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -312,6 +312,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public volatile Thread shutdownThread; // Paper
public volatile boolean abnormalExit = false; // Paper
public boolean isIteratingOverLevels = false; // Paper
+
+ public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
AtomicReference<S> atomicreference = new AtomicReference();
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 1556bb2b9c92f5f0e51cf35f3d53c04f2708dd15..ad4fdbdcf09f30d10e61ccf47f8fb9ce6bd92e73 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -341,6 +341,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
DedicatedServer.LOGGER.info("JMX monitoring enabled");
}
+ if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) mobSpawnExecutor.start(); // Pufferfish
return true;
}
}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index fbe209a66c77c47935ad026dd3e45e682af91fd8..02abc76333e8244cfcaa052a7df948b68079559a 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -345,7 +345,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
this.regionManagers.add(this.dataRegionManager);
// Paper end
- this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper
+ this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new gg.pufferfish.pufferfish.util.AsyncPlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper // Pufferfish
// Paper start - use distance map to optimise entity tracker
this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length];
this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length];
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index ca84eddbdb1e198b899750e5f6b3eafd25ce970f..be25c5b46eec3e60615404f58e49f6f611e76132 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -76,6 +76,9 @@ public class ServerChunkCache extends ChunkSource {
final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<LevelChunk> loadedChunkMap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(8192, 0.5f);
private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4];
+
+ public boolean firstRunSpawnCounts = true; // Pufferfish
+ public final java.util.concurrent.atomic.AtomicBoolean _pufferfish_spawnCountsReady = new java.util.concurrent.atomic.AtomicBoolean(false); // Pufferfish - optimize countmobs
private static int getChunkCacheKey(int x, int z) {
return x & 3 | ((z & 3) << 2);
@@ -712,18 +715,25 @@ public class ServerChunkCache extends ChunkSource {
// Paper start - per player mob spawning
NaturalSpawner.SpawnState spawnercreature_d; // moved down
if ((this.spawnFriendlies || this.spawnEnemies) && this.chunkMap.playerMobDistanceMap != null) { // don't count mobs when animals and monsters are disabled
- // re-set mob counts
- for (ServerPlayer player : this.level.players) {
- Arrays.fill(player.mobCounts, 0);
+ // Pufferfish start - moved down when async processing
+ if (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) {
+ // re-set mob counts
+ for (ServerPlayer player : this.level.players) {
+ Arrays.fill(player.mobCounts, 0);
+ }
+ lastSpawnState = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, null, true);
}
- spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, null, true);
+ // Pufferfish end
} else {
- spawnercreature_d = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, this.chunkMap.playerMobDistanceMap == null ? new LocalMobCapCalculator(this.chunkMap) : null, false);
+ // Pufferfish start - this is only implemented for per-player mob spawning so this makes everything work if this setting is disabled.
+ lastSpawnState = NaturalSpawner.createState(l, this.level.getAllEntities(), this::getFullChunk, this.chunkMap.playerMobDistanceMap == null ? new LocalMobCapCalculator(this.chunkMap) : null, false);
+ _pufferfish_spawnCountsReady.set(true);
+ // Pufferfish end
}
// Paper end
this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
- this.lastSpawnState = spawnercreature_d;
+ //this.lastSpawnState = spawnercreature_d; // Pufferfish - this is managed asynchronously
gameprofilerfiller.popPush("filteringLoadedChunks");
// Paper - moved down
this.level.timings.chunkTicks.startTiming(); // Paper
@@ -761,8 +771,8 @@ public class ServerChunkCache extends ChunkSource {
if ((true || this.level.isNaturalSpawningAllowed(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - the chunk is known ticking
chunk1.incrementInhabitedTime(j);
- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
- NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
+ if (flag2 && (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning || _pufferfish_spawnCountsReady.get()) && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
+ NaturalSpawner.spawnForChunk(this.level, chunk1, lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag1); // Pufferfish
}
if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - the chunk is known ticking
@@ -824,6 +834,30 @@ public class ServerChunkCache extends ChunkSource {
}
// Paper end - controlled flush for entity tracker packets
}
+
+ // Pufferfish start - optimize mob spawning
+ if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) {
+ for (ServerPlayer player : this.level.players) {
+ Arrays.fill(player.mobCounts, 0);
+ }
+ if (firstRunSpawnCounts) {
+ firstRunSpawnCounts = false;
+ _pufferfish_spawnCountsReady.set(true);
+ }
+ if (chunkMap.playerMobDistanceMap != null && _pufferfish_spawnCountsReady.getAndSet(false)) {
+ net.minecraft.server.MinecraftServer.getServer().mobSpawnExecutor.submit(() -> {
+ int mapped = distanceManager.getNaturalSpawnChunkCount();
+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Entity> objectiterator =
+ level.entityTickList.entities.iterator(io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS);
+ gg.pufferfish.pufferfish.util.IterableWrapper<Entity> wrappedIterator =
+ new gg.pufferfish.pufferfish.util.IterableWrapper<>(objectiterator);
+ lastSpawnState = NaturalSpawner.createState(mapped, wrappedIterator, this::getFullChunk, null, true);
+ objectiterator.finishedIterating();
+ _pufferfish_spawnCountsReady.set(true);
+ });
+ }
+ }
+ // Pufferfish end
}
private void getFullChunk(long pos, Consumer<LevelChunk> chunkConsumer) {
diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
index 4cdfc433df67afcd455422e9baf56f167dd712ae..57fcf3910f45ce371ac2e237b277b1034caaac4e 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
@@ -8,7 +8,7 @@ import javax.annotation.Nullable;
import net.minecraft.world.entity.Entity;
public class EntityTickList {
- private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Entity> entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking?
+ public final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Entity> entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? // Pufferfish - private->public
private void ensureActiveIsNotIterated() {
// Paper - replace with better logic, do not delay removals