Files
LuminolMC/patches/server/0041-Optimize-mob-spawning-Async-mob-spawn-state-calc.patch
2023-12-11 20:19:30 +08:00

484 lines
25 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: M2ke4U <79621885+MrHua269@users.noreply.github.com>
Date: Sat, 9 Dec 2023 22:19:49 +0800
Subject: [PATCH] Optimize mob spawning(Async mob spawn state calc)
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
index 41b9405d6759d865e0d14dd4f95163e9690e967d..091b1ae822e1c0517e59572e7a9bda11e998c0ee 100644
--- a/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
+++ b/src/main/java/com/destroystokyo/paper/util/misc/AreaMap.java
@@ -26,7 +26,7 @@ public abstract class AreaMap<E> {
// we use linked for better iteration.
// map of: coordinate to set of objects in coordinate
- protected final Long2ObjectOpenHashMap<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f);
+ protected Long2ObjectOpenHashMap<PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E>> areaMap = new Long2ObjectOpenHashMap<>(1024, 0.7f); // Pufferfish - not actually final
protected final PooledLinkedHashSets<E> pooledHashSets;
protected final ChangeCallback<E> addCallback;
@@ -160,7 +160,8 @@ public abstract class AreaMap<E> {
protected abstract PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<E> getEmptySetFor(final E object);
// expensive op, only for debug
- protected void validate(final E object, final int viewDistance) {
+ protected void validate0(final E object, final int viewDistance) { // Pufferfish - rename this thing just in case it gets used I'd rather a compile time error.
+ if (true) throw new UnsupportedOperationException(); // Pufferfish - not going to put in the effort to fix this if it doesn't ever get used.
int entiesGot = 0;
int expectedEntries = (2 * viewDistance + 1);
expectedEntries *= expectedEntries;
diff --git a/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
index 46954db7ecd35ac4018fdf476df7c8020d7ce6c8..1ad890a244bdf6df48a8db68cb43450e08c788a6 100644
--- a/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
+++ b/src/main/java/com/destroystokyo/paper/util/misc/PlayerAreaMap.java
@@ -5,7 +5,7 @@ import net.minecraft.server.level.ServerPlayer;
/**
* @author Spottedleaf
*/
-public final class PlayerAreaMap extends AreaMap<ServerPlayer> {
+public class PlayerAreaMap extends AreaMap<ServerPlayer> { // Pufferfish - not actually final
public PlayerAreaMap() {
super();
diff --git a/src/main/java/gg/pufferfish/pufferfish/util/AsyncPlayerAreaMap.java b/src/main/java/gg/pufferfish/pufferfish/util/AsyncPlayerAreaMap.java
new file mode 100644
index 0000000000000000000000000000000000000000..fdcb62d12164024a5f354d60cc863821a18d1b2a
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/util/AsyncPlayerAreaMap.java
@@ -0,0 +1,31 @@
+package gg.pufferfish.pufferfish.util;
+
+import com.destroystokyo.paper.util.misc.PlayerAreaMap;
+import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
+import java.util.concurrent.ConcurrentHashMap;
+import net.minecraft.server.level.ServerPlayer;
+
+public final class AsyncPlayerAreaMap extends PlayerAreaMap {
+
+ public AsyncPlayerAreaMap() {
+ super();
+ this.areaMap = new Long2ObjectOpenHashMapWrapper<>(new ConcurrentHashMap<>(1024, 0.7f));
+ }
+
+ public AsyncPlayerAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets) {
+ super(pooledHashSets);
+ this.areaMap = new Long2ObjectOpenHashMapWrapper<>(new ConcurrentHashMap<>(1024, 0.7f));
+ }
+
+ public AsyncPlayerAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets, final ChangeCallback<ServerPlayer> addCallback,
+ final ChangeCallback<ServerPlayer> removeCallback) {
+ this(pooledHashSets, addCallback, removeCallback, null);
+ }
+
+ public AsyncPlayerAreaMap(final PooledLinkedHashSets<ServerPlayer> pooledHashSets, final ChangeCallback<ServerPlayer> addCallback,
+ final ChangeCallback<ServerPlayer> removeCallback, final ChangeSourceCallback<ServerPlayer> changeSourceCallback) {
+ super(pooledHashSets, addCallback, removeCallback, changeSourceCallback);
+ this.areaMap = new Long2ObjectOpenHashMapWrapper<>(new ConcurrentHashMap<>(1024, 0.7f));
+ }
+
+}
diff --git a/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..facd55463d44cb7e3d2ca6892982f5497b8dded1
--- /dev/null
+++ b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java
@@ -0,0 +1,40 @@
+package gg.pufferfish.pufferfish.util;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import java.util.Map;
+import org.jetbrains.annotations.Nullable;
+
+public class Long2ObjectOpenHashMapWrapper<V> extends Long2ObjectOpenHashMap<V> {
+
+ private final Map<Long, V> backingMap;
+
+ public Long2ObjectOpenHashMapWrapper(Map<Long, V> map) {
+ backingMap = map;
+ }
+
+ @Override
+ public V put(Long key, V value) {
+ return backingMap.put(key, value);
+ }
+
+ @Override
+ public V get(Object key) {
+ return backingMap.get(key);
+ }
+
+ @Override
+ public V remove(Object key) {
+ return backingMap.remove(key);
+ }
+
+ @Nullable
+ @Override
+ public V putIfAbsent(Long key, V value) {
+ return backingMap.putIfAbsent(key, value);
+ }
+
+ @Override
+ public int size() {
+ return backingMap.size();
+ }
+}
diff --git a/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java b/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java
index 7ca275826609bcf96f103a8c50beaa47c3b4068b..dc5399bd5dba9dd33a7cfd644327c2568a6ad051 100644
--- a/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java
+++ b/src/main/java/io/papermc/paper/threadedregions/RegionizedWorldData.java
@@ -4,6 +4,7 @@ import com.destroystokyo.paper.util.maplist.ReferenceList;
import com.destroystokyo.paper.util.misc.PlayerAreaMap;
import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
import com.mojang.logging.LogUtils;
+import gg.pufferfish.pufferfish.util.AsyncPlayerAreaMap;
import io.papermc.paper.chunk.system.scheduling.ChunkHolderManager;
import io.papermc.paper.util.CoordinateUtils;
import io.papermc.paper.util.TickThread;
@@ -14,6 +15,7 @@ import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import me.earthme.luminol.LuminolConfig;
import net.minecraft.CrashReport;
import net.minecraft.ReportedException;
import net.minecraft.core.BlockPos;
@@ -58,6 +60,10 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -145,6 +151,10 @@ public final class RegionizedWorldData {
into.wanderingTraderSpawnDelay = Math.max(from.wanderingTraderSpawnDelay, into.wanderingTraderSpawnDelay);
into.wanderingTraderSpawnChance = Math.max(from.wanderingTraderSpawnChance, into.wanderingTraderSpawnChance);
}
+
+ //Luminol start - Async mob spawning
+ from.lastAsyncSpawnStateTask = null; //Discard the task currently processing
+ //Luminol end
}
@Override
@@ -302,6 +312,10 @@ public final class RegionizedWorldData {
regionizedWorldData.wanderingTraderSpawnDelay = from.wanderingTraderSpawnDelay;
regionizedWorldData.villageSiegeState = new VillageSiegeState(); // just re set it, as the spawn pos will be invalid
}
+
+ //Luminol start - Async mob spawning
+ from.lastAsyncSpawnStateTask = null; //Reset the task
+ //Luminol end
}
};
@@ -398,6 +412,22 @@ public final class RegionizedWorldData {
public java.util.ArrayDeque<net.minecraft.world.level.block.RedstoneTorchBlock.Toggle> redstoneUpdateInfos;
public final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap();
+ //Luminol start - Asnc mob spawning
+ public volatile CompletableFuture<NaturalSpawner.SpawnState> lastAsyncSpawnStateTask = null;
+ public static ThreadPoolExecutor ASYNC_MOB_SPAWNING_EXECUTOR;
+ public static void initMobSpawningExecutor(){
+ if (LuminolConfig.enableAsyncMobSpawning){
+ ASYNC_MOB_SPAWNING_EXECUTOR = new ThreadPoolExecutor(
+ 1,
+ Integer.MAX_VALUE,
+ 1,
+ TimeUnit.MINUTES,
+ new LinkedBlockingQueue<>()
+ );
+ }
+ }
+ //Luminol end
+
public static final class TempCollisionList<T> {
final UnsafeList<T> list = new UnsafeList<>(64);
boolean inUse;
@@ -430,7 +460,7 @@ public final class RegionizedWorldData {
// Mob spawning
private final PooledLinkedHashSets<ServerPlayer> pooledHashSets = new PooledLinkedHashSets<>();
- public final PlayerAreaMap mobSpawnMap = new PlayerAreaMap(this.pooledHashSets);
+ public final PlayerAreaMap mobSpawnMap = new AsyncPlayerAreaMap(this.pooledHashSets); //Luminol - Async mob spawning
public int catSpawnerNextTick = 0;
public int patrolSpawnerNextTick = 0;
public int phantomSpawnerNextTick = 0;
diff --git a/src/main/java/me/earthme/luminol/LuminolConfig.java b/src/main/java/me/earthme/luminol/LuminolConfig.java
index e8cd180bab5c196db09ded74aea676b4412fc6e9..534f54be1495b1a8f754bec0eb1aba8306c1d902 100644
--- a/src/main/java/me/earthme/luminol/LuminolConfig.java
+++ b/src/main/java/me/earthme/luminol/LuminolConfig.java
@@ -2,6 +2,7 @@ package me.earthme.luminol;
import dev.kaiijumc.kaiiju.region.RegionFileFormat;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
+import io.papermc.paper.threadedregions.RegionizedWorldData;
import me.earthme.luminol.commands.TpsBarCommand;
import me.earthme.luminol.functions.GlobalServerTpsBar;
import net.minecraft.core.registries.BuiltInRegistries;
@@ -62,6 +63,7 @@ public class LuminolConfig {
public static boolean asyncPathProcessing = false;
public static int asyncPathProcessingMaxThreads = 0;
public static int asyncPathProcessingKeepalive = 60;
+ public static boolean enableAsyncMobSpawning = false;
public static void init() throws IOException {
PARENT_FOLDER.mkdir();
@@ -188,6 +190,8 @@ public class LuminolConfig {
asyncPathProcessingMaxThreads = Math.max(Runtime.getRuntime().availableProcessors() / 4, 1);
if (!asyncPathProcessing)
asyncPathProcessingMaxThreads = 0;
+ enableAsyncMobSpawning = get("optimizations.enable_async_mob_spawning",enableAsyncMobSpawning);
+ RegionizedWorldData.initMobSpawningExecutor();
}
public static <T> T get(String key,T def){
diff --git a/src/main/java/me/earthme/luminol/utils/AsyncMobSpawnExecutor.java b/src/main/java/me/earthme/luminol/utils/AsyncMobSpawnExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..88d5b188ccfb17fe1ae4b08f32565f27569cad5c
--- /dev/null
+++ b/src/main/java/me/earthme/luminol/utils/AsyncMobSpawnExecutor.java
@@ -0,0 +1,71 @@
+package me.earthme.luminol.utils;
+
+import ca.spottedleaf.concurrentutil.collection.MultiThreadedQueue;
+import net.minecraft.server.MinecraftServer;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.LockSupport;
+
+public class AsyncMobSpawnExecutor implements Runnable, Executor {
+ private final MultiThreadedQueue<Runnable> allTasks = new MultiThreadedQueue<>();
+ private final Thread worker = new Thread(this);
+ private AtomicBoolean shouldRunNext = new AtomicBoolean(true);
+ private AtomicBoolean isRunning = new AtomicBoolean(false);
+ private AtomicBoolean isIdle = new AtomicBoolean(false);
+
+ public boolean isRunning(){
+ return this.isRunning.get();
+ }
+
+ public void startExecutor(){
+ this.worker.setDaemon(true);
+ this.worker.setContextClassLoader(MinecraftServer.class.getClassLoader());
+ this.worker.start();
+ }
+
+ public void forceTerminate(){
+ this.shouldRunNext.set(false);
+ if (this.isRunning.get()){
+ this.allTasks.clear();
+ LockSupport.unpark(this.worker);
+ }
+ }
+
+ public void dropAllTasks(){
+ this.allTasks.clear();
+ }
+
+ @Override
+ public void run() {
+ this.isRunning.set(true);
+ try {
+ while (this.shouldRunNext.get()){
+ final Runnable task = this.allTasks.poll();
+ if (task != null){
+ this.isIdle.set(false);
+
+ try {
+ task.run();
+ }catch (Exception e){
+ e.printStackTrace(); //TODO - Exception processing?
+ }
+
+ continue;
+ }
+
+ this.isIdle.set(true);
+ LockSupport.park();
+ }
+ }finally {
+ this.isRunning.set(false);
+ }
+ }
+
+ @Override
+ public void execute(@NotNull Runnable command) {
+ this.allTasks.offer(command);
+ LockSupport.unpark(this.worker); //Notify
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index b9b1dfe04eda8498f0ceff0aee66489d2a02b814..c470eafef884075b6e4d170adc2ced96a4beb517 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -11,13 +11,15 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.annotation.Nullable;
+
+import io.papermc.paper.threadedregions.RegionizedWorldData;
+import me.earthme.luminol.LuminolConfig;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
@@ -487,32 +489,38 @@ public class ServerChunkCache extends ChunkSource {
this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
int l = this.distanceManager.getNaturalSpawnChunkCount();
// Paper start - per player mob spawning
- NaturalSpawner.SpawnState spawnercreature_d; // moved down
+ NaturalSpawner.SpawnState spawnercreature_d = null; // moved down
profiler.startTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.MOB_SPAWN_ENTITY_COUNT); try { // Folia - profiler
- 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 : regionizedWorldData.getLocalPlayers()) { // Folia - region threading
- // 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 (!LuminolConfig.enableAsyncMobSpawning){ //Luminol
+ 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 : regionizedWorldData.getLocalPlayers()) { // Folia - region threading
+ // 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
+ spawnercreature_d = NaturalSpawner.createState(l, regionizedWorldData.getLoadedEntities(), this::getFullChunk, null, true); // Folia - region threading
+ } else {
+ spawnercreature_d = NaturalSpawner.createState(l, regionizedWorldData.getLoadedEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); // Folia - region threading
}
- spawnercreature_d = NaturalSpawner.createState(l, regionizedWorldData.getLoadedEntities(), this::getFullChunk, null, true); // Folia - region threading
- } else {
- spawnercreature_d = NaturalSpawner.createState(l, regionizedWorldData.getLoadedEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); // Folia - region threading
+ }//Luminol
+ //Luminol start - Async mob spawning
+ if (!LuminolConfig.enableAsyncMobSpawning){
+ regionizedWorldData.lastAsyncSpawnStateTask = CompletableFuture.completedFuture(spawnercreature_d);
}
} finally { profiler.stopTimer(ca.spottedleaf.leafprofiler.LProfilerRegistry.MOB_SPAWN_ENTITY_COUNT); } // Folia - profiler
// Paper end
this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
- regionizedWorldData.lastSpawnState = spawnercreature_d; // Folia - region threading
+ //regionizedWorldData.lastSpawnState = spawnercreature_d; // Folia - region threading //Luminol - Async mob spawning
gameprofilerfiller.popPush("filteringLoadedChunks");
// Paper - optimise chunk tick iteration
// Paper - optimise chunk tick iteration
@@ -610,7 +618,12 @@ public class ServerChunkCache extends ChunkSource {
chunk1.incrementInhabitedTime(j);
if (spawn && flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { // Spigot // Paper - optimise chunk tick iteration
++spawnChunkCount; // Folia - profiler
- NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
+
+ //Luminol start - Async mob spawning
+ if (regionizedWorldData.lastAsyncSpawnStateTask != null && regionizedWorldData.lastAsyncSpawnStateTask.isDone()){
+ NaturalSpawner.spawnForChunk(this.level, chunk1, regionizedWorldData.lastAsyncSpawnStateTask.join(), this.spawnFriendlies, this.spawnEnemies, flag1);
+ }
+ //Luminol end
}
if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - optimise chunk tick iteration
@@ -667,6 +680,36 @@ public class ServerChunkCache extends ChunkSource {
gameprofilerfiller.pop();
gameprofilerfiller.pop();
this.chunkMap.tick();
+
+ //Luminol start - Async mob spawning
+ if (LuminolConfig.enableAsyncMobSpawning){
+ //Luminol - Copied down
+ 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 : regionizedWorldData.getLocalPlayers()) { // Folia - region threading
+ // 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 (regionizedWorldData.lastAsyncSpawnStateTask == null || regionizedWorldData.lastAsyncSpawnStateTask.isDone()){
+ regionizedWorldData.lastAsyncSpawnStateTask = CompletableFuture.supplyAsync(() -> NaturalSpawner.createState(l, regionizedWorldData.getLoadedEntities(), this::getFullChunk, null, true),RegionizedWorldData.ASYNC_MOB_SPAWNING_EXECUTOR);
+ }
+ } else {
+ if (regionizedWorldData.lastAsyncSpawnStateTask == null || regionizedWorldData.lastAsyncSpawnStateTask.isDone()){
+ regionizedWorldData.lastAsyncSpawnStateTask = CompletableFuture.supplyAsync(() -> NaturalSpawner.createState(l, regionizedWorldData.getLoadedEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false), RegionizedWorldData.ASYNC_MOB_SPAWNING_EXECUTOR);
+ }
+ }
+ }
+ //Luminol end
}
}
@@ -809,7 +852,7 @@ public class ServerChunkCache extends ChunkSource {
@VisibleForDebug
public NaturalSpawner.SpawnState getLastSpawnState() {
io.papermc.paper.threadedregions.RegionizedWorldData worldData = this.level.getCurrentWorldData(); // Folia - region threading
- return worldData == null ? null : worldData.lastSpawnState; // Folia - region threading
+ return worldData.lastAsyncSpawnStateTask != null && worldData.lastAsyncSpawnStateTask.isDone() ? worldData.lastAsyncSpawnStateTask.join() : null; // Folia - region threading //Luminol - Async mob spawning
}
public void removeTicketsOnClosing() {
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index 5caca2a34849189ea42d2699f6d8672e0d7251cb..b21243d494fd1989e7d6c2b98b08e090dc2f38b7 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@@ -1,6 +1,9 @@
package net.minecraft.world.level;
+import ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor;
import com.mojang.logging.LogUtils;
+import io.papermc.paper.threadedregions.RegionizedServer;
+import io.papermc.paper.util.TickThread;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
@@ -117,6 +120,15 @@ public final class NaturalSpawner {
object2intopenhashmap.addTo(enumcreaturetype, 1);
// Paper start
if (countMobs) {
+ //Luminol start - Async mob spawning
+ if (!TickThread.isTickThread()){
+ RegionizedServer.getInstance().taskQueue.queueTickTaskQueue(chunk.level,chunk.locX,chunk.locZ,()->{
+ chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
+ });
+ return;
+ }
+ //Luminol end
+
chunk.level.getChunkSource().chunkMap.updatePlayerMobTypeMap(entity);
}
// Paper end