mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-19 15:09:25 +00:00
216 lines
15 KiB
Diff
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);
|
|
+ }
|
|
+ }
|
|
+}
|