From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: hayanesuru Date: Tue, 3 Jun 2025 15:20:59 +0900 Subject: [PATCH] optimize mob spawning diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java index f3be481a92b4f5403809c38d3b3431f4096d7a2e..2a5a2b6c28c61fe5cc3c89676472b9695fd3f188 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -280,6 +280,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } // Paper end - per player mob count backoff public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { + // Leaf - diff - async mob spawning - optimize mob spawning return player.mobCounts[mobCategory.ordinal()] + player.mobBackoffCounts[mobCategory.ordinal()]; // Paper - per player mob count backoff } // Paper end - Optional per player mob spawns diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java index 52a2b993bbd1ad4851b3273af6ecbc069beb5b84..f66f16332bd1af89a44b71bc015d52a2aeda09de 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -70,7 +70,9 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon private final Set chunkHoldersToBroadcast = new ReferenceOpenHashSet<>(); @Nullable @VisibleForDebug - private NaturalSpawner.SpawnState lastSpawnState; + private volatile NaturalSpawner.SpawnState lastSpawnState; // Leaf - optimize mob spawning + private long delayTimeInhabited = 0L; // Leaf - optimize mob spawning + private long delaySpawn = -1L; // Leaf - optimize mob spawning // Paper start public final ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable fullChunks = new ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable<>(); public int getFullChunksCount() { @@ -496,6 +498,23 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon long gameTime = this.level.getGameTime(); long l = gameTime - this.lastInhabitedUpdate; this.lastInhabitedUpdate = gameTime; + // Leaf start - optimize mob spawning + if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled && level.paperConfig().entities.spawning.perPlayerMobSpawns) { + 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 + } + } + // Leaf end - optimize mob spawning if (!this.level.isDebug()) { this.level.resetIceAndSnowTick(); // Gale - Airplane - optimize random calls in chunk ticking - reset ice & snow tick random if (this.level.tickRateManager().runsNormally()) { @@ -518,8 +537,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } // Pufferfish start - optimize mob spawning - if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) { - for (ServerPlayer player : this.level.players) { + if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled && level.paperConfig().entities.spawning.perPlayerMobSpawns) { + /*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; @@ -531,34 +550,21 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon player.mobBackoffCounts[ii] = newBackoff; } // Paper end - per player mob spawning backoff - } + }*/ if (firstRunSpawnCounts) { firstRunSpawnCounts = false; _pufferfish_spawnCountsReady.set(true); } if (_pufferfish_spawnCountsReady.getAndSet(false)) { + final int mapped = distanceManager.getNaturalSpawnChunkCount(); + final Iterable entities = this.level.getAllEntities(); 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(); - } + lastSpawnState = NaturalSpawner.createState1( + mapped, + entities, + this.level + ); + // Leaf end - optimize mob spawning _pufferfish_spawnCountsReady.set(true); }); } @@ -658,13 +664,38 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon filteredSpawningCategories = List.of(); } - for (LevelChunk levelChunk : chunks) { - ChunkPos pos = levelChunk.getPos(); - levelChunk.incrementInhabitedTime(timeInhabited); - if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && lastSpawnState != null && (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || _pufferfish_spawnCountsReady.get()) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos, true)) { // Spigot // Pufferfish // Leaf - Don't spawn if lastSpawnState is null - NaturalSpawner.spawnForChunk(this.level, levelChunk, lastSpawnState, filteredSpawningCategories); // Pufferfish + // Leaf start - optimize mob spawning + var lastSpawnState1 = this.lastSpawnState; + if (lastSpawnState1 != null && (!org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled || !level.paperConfig().entities.spawning.perPlayerMobSpawns || _pufferfish_spawnCountsReady.get())) { + lastSpawnState1.applyPerPlayerMobCount(level); // Leaf - optimize mob spawning + long sumTimeInhabited = timeInhabited + delayTimeInhabited; + long time = level.getGameTime(); + for (LevelChunk levelChunk : chunks) { + ChunkPos pos = levelChunk.getPos(); + levelChunk.incrementInhabitedTime(sumTimeInhabited); + if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos, true)) { // Spigot + NaturalSpawner.spawnForChunk(this.level, levelChunk, lastSpawnState1, filteredSpawningCategories, time); // Pufferfish + } } + if (delaySpawn != -1L) { + time = delaySpawn; + for (LevelChunk levelChunk : chunks) { + ChunkPos pos = levelChunk.getPos(); + if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos, true)) { // Spigot + NaturalSpawner.spawnForChunk(this.level, levelChunk, lastSpawnState1, filteredSpawningCategories, time); // Pufferfish + } + } + } + delaySpawn = -1L; + delayTimeInhabited = 0L; + } else { + // unlikely + delayTimeInhabited += timeInhabited; + delaySpawn = level.getGameTime(); + } + // Leaf end - optimize mob spawning + for (LevelChunk levelChunk : chunks) { // Leaf - optimize mob spawning - split to 2 loop if (true) { // Paper - rewrite chunk system this.level.tickChunk(levelChunk, _int); } diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java index 9b37b763c6555705f3e256010f508b5a0c2cdb66..418fd9db5a06af5369da597762c0757453929f83 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java @@ -74,6 +74,7 @@ public final class NaturalSpawner { return createState(spawnableChunkCount, entities, chunkGetter, calculator, false); } + @Deprecated // Leaf - optimize mob spawning public static NaturalSpawner.SpawnState createState( int spawnableChunkCount, Iterable entities, NaturalSpawner.ChunkGetter chunkGetter, LocalMobCapCalculator calculator, final boolean countMobs ) { @@ -114,9 +115,65 @@ public final class NaturalSpawner { } } - return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator); + return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, calculator, new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>()); // Leaf - optimize mob spawning } + // Leaf start - optimize mob spawning + public static NaturalSpawner.SpawnState createState1( + int spawnableChunkCount, Iterable entities, ServerLevel level + ) { + // Paper end - Optional per player mob spawns + PotentialCalculator potentialCalculator = new PotentialCalculator(); + Object2IntOpenHashMap map = new Object2IntOpenHashMap<>(); + it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap chunkCap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); + boolean countAllMobsForSpawning = level.paperConfig().entities.spawning.countAllMobsForSpawning; + for (Entity entity : entities) { + if (!(entity instanceof Mob mob && (mob.isPersistenceRequired() || mob.requiresCustomPersistence()))) { + MobCategory category = entity.getType().getCategory(); + if (category != MobCategory.MISC) { + // Paper start - Only count natural spawns + if (!countAllMobsForSpawning && + !(entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL || + entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { + continue; + } + // Paper end - Only count natural spawns + BlockPos blockPos = entity.blockPosition(); + LevelChunk chunk = level.getChunkSource().getChunkNow(blockPos.getX() >> 4, blockPos.getZ() >> 4); + if (chunk != null) { + MobSpawnSettings.MobSpawnCost mobSpawnCost = getRoughBiome(blockPos, chunk).getMobSettings().getMobSpawnCost(entity.getType()); + if (mobSpawnCost != null) { + potentialCalculator.addCharge(entity.blockPosition(), mobSpawnCost.charge()); + } + + map.addTo(category, 1); + // Paper start - Optional per player mob spawns + final int index = entity.getType().getCategory().ordinal(); + ++chunkCap.computeIfAbsent(chunk.getPos().toLong(), k -> new int[net.minecraft.server.level.ServerPlayer.MOBCATEGORY_TOTAL_ENUMS])[index]; + /* + final int index = entity.getType().getCategory().ordinal(); + final var inRange = level.moonrise$getNearbyPlayers().getPlayers(entity.chunkPosition(), NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); + if (inRange == null) { + continue; + } + + final net.minecraft.server.level.ServerPlayer[] backingSet = inRange.getRawDataUnchecked(); + for (int i = 0, len = inRange.size(); i < len; i++) { + final net.minecraft.server.level.ServerPlayer player = backingSet[i]; + if (player == null) continue; + ++playerCap.computeIfAbsent(player, k -> new int[net.minecraft.server.level.ServerPlayer.MOBCATEGORY_TOTAL_ENUMS])[index]; + } + */ + // Paper end - Optional per player mob spawns + } + } + } + } + + return new NaturalSpawner.SpawnState(spawnableChunkCount, map, potentialCalculator, null, chunkCap); + } + // Leaf end - optimize mob spawning + static Biome getRoughBiome(BlockPos pos, ChunkAccess chunk) { return chunk.getNoiseBiome(QuartPos.fromBlock(pos.getX()), QuartPos.fromBlock(pos.getY()), QuartPos.fromBlock(pos.getZ())).value(); } @@ -155,7 +212,13 @@ public final class NaturalSpawner { return list; } + @Deprecated(forRemoval = true) // Leaf - optimize mob spawning public static void spawnForChunk(ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnState spawnState, List categories) { + // Leaf start - optimize mob spawning + spawnForChunk(level, chunk, spawnState, categories, level.getGameTime()); + } + public static void spawnForChunk(ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnState spawnState, List categories, long gameTime) { + // Leaf end - optimize mob spawning for (MobCategory mobCategory : categories) { // Paper start - Optional per player mob spawns final boolean canSpawn; @@ -174,7 +237,7 @@ public final class NaturalSpawner { } // Paper end - throttle failed spawn attempts if (CraftSpawnCategory.isValidForLimits(spawnCategory)) { - spawnThisTick = ticksPerSpawnTmp != 0 && level.getGameTime() % ticksPerSpawn == 0; // Paper - throttle failed spawn attempts + spawnThisTick = ticksPerSpawnTmp != 0 && gameTime % ticksPerSpawn == 0; // Paper - throttle failed spawn attempts // Leaf - optimize mob spawning limit = level.getWorld().getSpawnLimit(spawnCategory); } @@ -238,12 +301,14 @@ public final class NaturalSpawner { // Paper end - throttle failed spawn attempts ) { // Paper end - Optional per player mob spawns - BlockPos randomPosWithin = getRandomPosWithin(level, chunk); - if (randomPosWithin.getY() >= level.getMinY() + 1) { - return spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback, maxSpawns, trackEntity, false); // Paper - Optional per player mob spawns // Paper - throttle failed spawn attempts + // Leaf start - optimize mob spawning + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + mutableRandomPosWithin(pos, level, chunk); + if (pos.getY() < level.getMinY() + 1) { + return 0; } - - return 0; // Paper - throttle failed spawn attempts + return spawnCategoryForPosition(category, level, chunk, pos, filter, callback, maxSpawns, trackEntity, false); // Paper - Optional per player mob spawns // Paper - throttle failed spawn attempts + // Leaf end - optimize mob spawning } @VisibleForDebug @@ -275,31 +340,59 @@ public final class NaturalSpawner { StructureManager structureManager = level.structureManager(); ChunkGenerator generator = level.getChunkSource().getGenerator(); int y = pos.getY(); + int posX = pos.getX(); // Leaf - optimize mob spawning + int posZ = pos.getZ(); // Leaf - optimize mob spawning int i = 0; // Paper - throttle failed spawn attempts BlockState blockState = level.getBlockStateIfLoadedAndInBounds(pos); // Paper - don't load chunks for mob spawn if (blockState != null && !blockState.isRedstoneConductor(chunk, pos)) { // Paper - don't load chunks for mob spawn - BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + BlockPos.MutableBlockPos mutableBlockPos = pos instanceof BlockPos.MutableBlockPos pos2 ? pos2 : new BlockPos.MutableBlockPos(); // Leaf - optimize mob spawning //int i = 0; // Paper - throttle failed spawn attempts - move up + // Leaf start - optimize mob spawning + long rand = level.random.nextLong(); + int bits = 0; for (int i1 = 0; i1 < 3; i1++) { - int x = pos.getX(); - int z = pos.getZ(); - int i2 = 6; + int x = posX; + int z = posZ; MobSpawnSettings.SpawnerData spawnerData = null; SpawnGroupData spawnGroupData = null; - int ceil = Mth.ceil(level.random.nextFloat() * 4.0F); + int ceil = (int) ((rand & 0x3L) + 1L); + bits += 2; + if (bits >= 62) { + rand = level.random.nextLong(); + bits = 0; + } int i3 = 0; for (int i4 = 0; i4 < ceil; i4++) { - x += level.random.nextInt(6) - level.random.nextInt(6); - z += level.random.nextInt(6) - level.random.nextInt(6); + int rand1=0,rand2=0,rand3=0,rand4=0,valuesNeeded=4; + while (valuesNeeded > 0) { + int threeBits = (int) ((rand >>> bits) & 0x7L); + bits += 3; + if (threeBits != 7 && threeBits != 6) { + switch (valuesNeeded) { + case 1 -> rand4 = threeBits; + case 2 -> rand3 = threeBits; + case 3 -> rand2 = threeBits; + case 4 -> rand1 = threeBits; + } + valuesNeeded--; + } + if (bits >= 62) { + rand = level.random.nextLong(); + bits = 0; + } + } + x += rand1 - rand2; + z += rand3 - rand4; + // Leaf end - optimize mob spawning mutableBlockPos.set(x, y, z); double d = x + 0.5; double d1 = z + 0.5; Player nearestPlayer = level.getNearestPlayer(d, y, d1, -1.0, level.purpurConfig.mobSpawningIgnoreCreativePlayers); // Purpur - mob spawning option to ignore creative players if (nearestPlayer != null) { double d2 = nearestPlayer.distanceToSqr(d, y, d1); - if (level.isLoadedAndInBounds(mutableBlockPos) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn + if (level.getWorldBorder().isWithinBounds(mutableBlockPos) && (chunk.getPos().longKey == ChunkPos.asLong(mutableBlockPos) || level.getChunkIfLoadedImmediately(mutableBlockPos.getX() >> 4, mutableBlockPos.getZ() >> 4) != null) && isRightDistanceToPlayerAndSpawnPoint(level, chunk, mutableBlockPos, d2)) { // Paper - don't load chunks for mob spawn // Leaf - optimize mob spawning if (spawnerData == null) { Optional randomSpawnMobAt = getRandomSpawnMobAt( level, structureManager, generator, category, level.random, mutableBlockPos @@ -368,8 +461,8 @@ public final class NaturalSpawner { private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel level, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double distance) { return !(distance <= 576.0) - && !level.getSharedSpawnPos().closerToCenterThan(new Vec3(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5), 24.0) - && (Objects.equals(new ChunkPos(pos), chunk.getPos()) || level.isNaturalSpawningAllowed(pos)); + && !(level.getSharedSpawnPos().distToCenterSqr(pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5) < 576.0) // Leaf - optimize mob spawning + && (ChunkPos.asLong(pos) == chunk.getPos().longKey || level.isNaturalSpawningAllowed(pos)); // Leaf - optimize mob spawning } // Paper start - PreCreatureSpawnEvent @@ -474,6 +567,17 @@ public final class NaturalSpawner { } } + // Leaf start - optimize mob spawning + private static void mutableRandomPosWithin(BlockPos.MutableBlockPos pos1, Level level, LevelChunk chunk) { + ChunkPos pos = chunk.getPos(); + int randomX = pos.getMinBlockX() + level.random.nextInt(16); + int randomZ = pos.getMinBlockZ() + level.random.nextInt(16); + int surfaceY = chunk.getHeight(Heightmap.Types.WORLD_SURFACE, randomX, randomZ) + 1; + int randomY = Mth.randomBetweenInclusive(level.random, level.getMinY(), surfaceY); + pos1.set(randomX, randomY, randomZ); + } + // Leaf end - optimize mob spawning + private static BlockPos getRandomPosWithin(Level level, LevelChunk chunk) { ChunkPos pos = chunk.getPos(); int i = pos.getMinBlockX() + level.random.nextInt(16); @@ -614,18 +718,21 @@ public final class NaturalSpawner { @Nullable private EntityType lastCheckedType; private double lastCharge; + public final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap chunkCap; // Leaf - optimize mob spawning SpawnState( int spawnableChunkCount, Object2IntOpenHashMap mobCategoryCounts, PotentialCalculator spawnPotential, - LocalMobCapCalculator localMobCapCalculator + LocalMobCapCalculator localMobCapCalculator, + it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap playerCap // Leaf - optimize mob spawning ) { this.spawnableChunkCount = spawnableChunkCount; this.mobCategoryCounts = mobCategoryCounts; this.spawnPotential = spawnPotential; this.localMobCapCalculator = localMobCapCalculator; this.unmodifiableMobCategoryCounts = Object2IntMaps.unmodifiable(mobCategoryCounts); + this.chunkCap = playerCap; // Leaf - optimize mob spawning } private boolean canSpawn(EntityType entityType, BlockPos pos, ChunkAccess chunk) { @@ -682,5 +789,32 @@ public final class NaturalSpawner { boolean canSpawnForCategoryLocal(MobCategory category, ChunkPos chunkPos) { return this.localMobCapCalculator.canSpawn(category, chunkPos); } + + // Leaf start - optimize mob spawning + public void applyPerPlayerMobCount(ServerLevel level) { + if (chunkCap.isEmpty()) { + return; + } + final var iterator = chunkCap.long2ObjectEntrySet().fastIterator(); + final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = level.moonrise$getNearbyPlayers(); + while (iterator.hasNext()) { + var entry = iterator.next(); + long chunk = entry.getLongKey(); + int[] cap = entry.getValue(); + ca.spottedleaf.moonrise.common.list.ReferenceList players = nearbyPlayers.getPlayersByChunk(net.minecraft.world.level.ChunkPos.getX(chunk), net.minecraft.world.level.ChunkPos.getZ(chunk), ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.TICK_VIEW_DISTANCE); + if (players == null) { + continue; + } + int playersSize = players.size(); + net.minecraft.server.level.ServerPlayer[] playersRawDataUnchecked = players.getRawDataUnchecked(); + for (int i = 0; i < playersSize; i++) { + int[] p = playersRawDataUnchecked[i].mobCounts; + for (int j = 0; j < net.minecraft.server.level.ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; j++) { + p[j] += cap[j]; + } + } + } + } + // Leaf end - optimize mob spawning } } diff --git a/net/minecraft/world/level/entity/EntityTickList.java b/net/minecraft/world/level/entity/EntityTickList.java index 5a90b3bffeeb08a168b370e49d18c5f8b257a980..ba173bc1751c495e6fa497566b5ed3c7a9547364 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 { - public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled); // Paper - rewrite chunk system // Pufferfish - private->public and do thread check + private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system // Leaf start - SparklyPaper - parallel world ticking mod // preserve original constructor