9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00
Files
Leaf/patches/server/0006-Pufferfish-Optimize-mob-spawning.patch
2024-05-19 09:03:01 -04:00

216 lines
15 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] Pufferfish: Optimize mob spawning
Original license: GPL v3
Original project: https://github.com/pufferfish-gg/Pufferfish
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/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 17af0e5be9245573bfcd2ead2be3b6434cd8f576..a976002437a5d9c761835691213e11b14d9a7d45 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -303,6 +303,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public volatile boolean abnormalExit = false; // Paper
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 extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
AtomicReference<S> atomicreference = new AtomicReference();
Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 8c74652ffa2c1263c835381bc27ffdc6d272dbf7..05e214ca14240ba635927c9f2a71d19ba1e9aa45 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -364,6 +364,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
DedicatedServer.LOGGER.info("JMX monitoring enabled");
}
+ if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) 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 7bac7112b520286ebe9e8d36dc0932900e76eb52..54c3e32c7ae869d55408d77ea2aa1635f980a39b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -246,7 +246,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end
// Paper start - optimise chunk tick iteration
public final it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<ChunkHolder> needsChangeBroadcasting = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>();
- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap = new gg.pufferfish.pufferfish.util.AsyncPlayerAreaMap(this.pooledLinkedPlayerHashSets); // Pufferfish
// Paper end - optimise chunk tick iteration
public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index 94689992b4d159ab996e00ae20afa8fef0e84db2..af4fe64e190e73dcc5f2495d0b533547d8f57f1d 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -74,6 +74,9 @@ public class ServerChunkCache extends ChunkSource {
private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4];
// Paper end
+ 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 world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory) {
this.level = world;
this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(world);
@@ -508,6 +511,7 @@ public class ServerChunkCache extends ChunkSource {
// Paper start - Optional per player mob spawns
int naturalSpawnChunkCount = k;
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
@@ -522,14 +526,18 @@ public class ServerChunkCache extends ChunkSource {
}
// Paper end - per player mob spawning backoff
}
- spawnercreature_d = 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 {
- spawnercreature_d = 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.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
- this.lastSpawnState = spawnercreature_d;
+ //this.lastSpawnState = spawnercreature_d; // Pufferfish - this is managed asynchronously
// Gale start - MultiPaper - skip unnecessary mob spawning computations
} else {
spawnercreature_d = null;
@@ -622,8 +630,8 @@ public class ServerChunkCache extends ChunkSource {
if (tick && chunk1.chunkStatus.isOrAfter(net.minecraft.server.level.FullChunkStatus.ENTITY_TICKING)) {
// Paper end - optimise chunk tick iteration
chunk1.incrementInhabitedTime(j);
- if (spawn && flagAndHasNaturalSpawn && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration // Gale - MultiPaper - skip unnecessary mob spawning computations
- NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
+ if (spawn && flagAndHasNaturalSpawn && (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get()) && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration // Gale - MultiPaper - skip unnecessary mob spawning computations // Pufferfish
+ NaturalSpawner.spawnForChunk(this.level, chunk1, lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag1); // Pufferfish
}
if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration
@@ -666,6 +674,40 @@ public class ServerChunkCache extends ChunkSource {
this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
// Paper - optimise chunk tick iteration
}
+
+ // 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();
+ 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
}
// Gale start - MultiPaper - skip unnecessary mob spawning computations
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 83a39f900551e39d5af6f17a339a386ddee4feef..0c8c534fc69172387f188af5282accfed7597ac7 100644
--- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
+++ b/src/main/java/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 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
diff --git a/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java
new file mode 100644
index 0000000000000000000000000000000000000000..67c575439f7d60046586972dfc3212bb36a3d033
--- /dev/null
+++ b/src/main/java/org/dreeam/leaf/config/modules/async/AsyncMobSpawning.java
@@ -0,0 +1,41 @@
+package org.dreeam.leaf.config.modules.async;
+
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import org.dreeam.leaf.config.ConfigInfo;
+import org.dreeam.leaf.config.DoNotLoad;
+import org.dreeam.leaf.config.EnumConfigCategory;
+import org.dreeam.leaf.config.IConfigModule;
+
+public class AsyncMobSpawning implements IConfigModule {
+
+ @Override
+ public EnumConfigCategory getCategory() {
+ return EnumConfigCategory.ASYNC;
+ }
+
+ @Override
+ public String getBaseName() {
+ return "async_mob_spawning";
+ }
+
+ @ConfigInfo(baseName = "enabled")
+ public static boolean enabled = true;
+ @DoNotLoad
+ public static boolean asyncMobSpawningInitialized;
+
+ @Override
+ public void onLoaded(CommentedFileConfig config) {
+ config.setComment("async.async_mob_spawning", """
+ 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;
+ this.get("async.async_mob_spawning.enabled", enabled, config);
+ }
+ }
+}