From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: froobynooby Date: Wed, 17 Jul 2024 18:46:11 +0930 Subject: [PATCH] Paper PR: Throttle failed spawn attempts Original license: GPLv3 Original project: https://github.com/PaperMC/Paper Paper pull request: https://github.com/PaperMC/Paper/pull/11099 For example config in paper-world-defaults.yml ``` spawning-throttle: failed-attempts-threshold: 1200 throttled-ticks-per-spawn: ambient: 10 # default value in bukkit.yml tickers-per * 10 axolotls: 10 creature: 4000 monster: 10 underground_water_creature: 10 water_ambient: 10 water_creature: 10 ``` This patch adds the option to use longer ticks-per-spawn for a given mob type in chunks where spawn attempts are consecutively failing. This behaviour is particularly useful on servers where players build mob farms. Mob farm designs often require making surrounding chunks spawnproof, which causes the server to waste CPU cycles trying to spawn mobs in vain. Throttling spawn attempts in suspected spawnproof chunks improves performance without noticeably advantaging or disadvantaging the mob farm. diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java index 345d4b80bd4383e0fb66d744d87bc8ef4100fd32..a2da4fce50f31d56036d04041c4f80ed90c18b27 100644 --- a/net/minecraft/world/level/NaturalSpawner.java +++ b/net/minecraft/world/level/NaturalSpawner.java @@ -158,10 +158,21 @@ public final class NaturalSpawner { // Copied from getFilteredSpawningCategories int limit = mobCategory.getMaxInstancesPerChunk(); org.bukkit.entity.SpawnCategory spawnCategory = org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(mobCategory); + // Paper start - throttle failed spawn attempts + boolean spawnThisTick = true; + long ticksPerSpawn = level.ticksPerSpawnCategory.getLong(spawnCategory); + long ticksPerSpawnTmp = ticksPerSpawn; + io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.SpawningThrottle spawningThrottle = level.paperConfig().entities.spawning.spawningThrottle; + if (spawningThrottle.failedAttemptsThreshold.test(threshold -> chunk.failedSpawnAttempts[mobCategory.ordinal()] > threshold)) { + ticksPerSpawn = Math.max(ticksPerSpawn, spawningThrottle.throttledTicksPerSpawn.getOrDefault(mobCategory, -1)); + } + // Paper end - throttle failed spawn attempts if (org.bukkit.craftbukkit.util.CraftSpawnCategory.isValidForLimits(spawnCategory)) { + spawnThisTick = ticksPerSpawnTmp != 0 && level.getGameTime() % ticksPerSpawn == 0; // Paper - throttle failed spawn attempts limit = level.getWorld().getSpawnLimit(spawnCategory); } + if (!spawningThrottle.failedAttemptsThreshold.enabled() || spawnThisTick) { // Paper - throttle failed spawn attempts // Apply per-player limit int minDiff = Integer.MAX_VALUE; final ca.spottedleaf.moonrise.common.list.ReferenceList inRange = @@ -175,12 +186,20 @@ public final class NaturalSpawner { maxSpawns = (minDiff == Integer.MAX_VALUE) ? 0 : minDiff; canSpawn = maxSpawns > 0; + } else { canSpawn = false; } // Paper - throttle failed spawn attempts } else { canSpawn = spawnState.canSpawnForCategoryLocal(mobCategory, chunk.getPos()); } if (canSpawn) { - spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn, - maxSpawns, level.paperConfig().entities.spawning.perPlayerMobSpawns ? level.getChunkSource().chunkMap::updatePlayerMobTypeMap : null); + // Paper start - throttle failed spawn attempts + int spawnCount = spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn, + maxSpawns, level.paperConfig().entities.spawning.perPlayerMobSpawns ? level.getChunkSource().chunkMap::updatePlayerMobTypeMap : null, false); + if (spawnCount == 0) { + chunk.failedSpawnAttempts[mobCategory.ordinal()]++; + } else { + chunk.failedSpawnAttempts[mobCategory.ordinal()] = 0; + } + // Paper end - throttle failed spawn attempts // Paper end - Optional per player mob spawns } } @@ -204,12 +223,21 @@ public final class NaturalSpawner { } public static void spawnCategoryForChunk( MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final Consumer trackEntity + // Paper start - throttle failed spawn attempts + ) { + spawnCategoryForChunk(category, level, chunk, filter, callback, maxSpawns, trackEntity, false); + } + public static int spawnCategoryForChunk( + MobCategory category, ServerLevel level, LevelChunk chunk, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final Consumer trackEntity, final boolean nothing + // Paper end - throttle failed spawn attempts ) { // Paper end - Optional per player mob spawns BlockPos randomPosWithin = getRandomPosWithin(level, chunk); if (randomPosWithin.getY() >= level.getMinY() + 1) { - spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback, maxSpawns, trackEntity); // Paper - Optional per player mob spawns + return spawnCategoryForPosition(category, level, chunk, randomPosWithin, filter, callback, maxSpawns, trackEntity, false); // Paper - Optional per player mob spawns // Paper - throttle failed spawn attempts } + + return 0; // Paper - throttle failed spawn attempts } @VisibleForDebug @@ -229,15 +257,23 @@ public final class NaturalSpawner { } public static void spawnCategoryForPosition( MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer trackEntity + // Paper start - throttle failed spawn attempts + ) { + spawnCategoryForPosition(category, level, chunk, pos, filter, callback, maxSpawns, trackEntity, false); + } + public static int spawnCategoryForPosition( + MobCategory category, ServerLevel level, ChunkAccess chunk, BlockPos pos, NaturalSpawner.SpawnPredicate filter, NaturalSpawner.AfterSpawnCallback callback, final int maxSpawns, final @Nullable Consumer trackEntity, final boolean nothing + // Paper end - throttle failed spawn attempts ) { // Paper end - Optional per player mob spawns StructureManager structureManager = level.structureManager(); ChunkGenerator generator = level.getChunkSource().getGenerator(); int y = pos.getY(); + 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(); - int i = 0; + //int i = 0; // Paper - throttle failed spawn attempts - move up for (int i1 = 0; i1 < 3; i1++) { int x = pos.getX(); @@ -278,14 +314,14 @@ public final class NaturalSpawner { } // Paper end - per player mob count backoff if (doSpawning == PreSpawnStatus.ABORT) { - return; + return i; // Paper - throttle failed spawn attempts } if (doSpawning == PreSpawnStatus.SUCCESS // Paper end - PreCreatureSpawnEvent && filter.test(spawnerData.type(), mutableBlockPos, chunk)) { Mob mobForSpawn = getMobForSpawn(level, spawnerData.type()); if (mobForSpawn == null) { - return; + return i; // Paper - throttle failed spawn attempts } mobForSpawn.snapTo(d, y, d1, level.random.nextFloat() * 360.0F, 0.0F); @@ -308,7 +344,7 @@ public final class NaturalSpawner { } // CraftBukkit end if (i >= mobForSpawn.getMaxSpawnClusterSize() || i >= maxSpawns) { // Paper - Optional per player mob spawns - return; + return i; // Paper - throttle failed spawn attempts } if (mobForSpawn.isMaxGroupSizeReached(i3)) { @@ -321,6 +357,8 @@ public final class NaturalSpawner { } } } + + return i; // Paper - throttle failed spawn attempts } private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel level, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double distance) { diff --git a/net/minecraft/world/level/chunk/ChunkAccess.java b/net/minecraft/world/level/chunk/ChunkAccess.java index 686c031ec73acc80683aaa39a78fe9221f0215a6..d7dbe7d24a6697be6b5db729f940b16b74838d11 100644 --- a/net/minecraft/world/level/chunk/ChunkAccess.java +++ b/net/minecraft/world/level/chunk/ChunkAccess.java @@ -92,6 +92,7 @@ public abstract class ChunkAccess implements BiomeManager.NoiseBiomeSource, Ligh private boolean slimeChunk; private boolean hasComputedSlimeChunk; // Leaf end - Matter - Secure Seed + public final long[] failedSpawnAttempts = new long[net.minecraft.world.entity.MobCategory.values().length]; // Paper - throttle failed spawn attempts // Paper start - rewrite chunk system private volatile ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray[] blockNibbles; diff --git a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java index e04d3479383cd480cf35ed7ac3c82e7f6fb69e28..ab1fc7ca0639f9e71933f623258bb8d123988262 100644 --- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java +++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java @@ -94,6 +94,7 @@ public record SerializableChunkData( List blockEntities, CompoundTag structureData , @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer + , @Nullable long[] failedSpawnAttempts // Paper - throttle failed spawn attempts ) { public static final Codec> BLOCK_STATE_CODEC = PalettedContainer.codecRW( Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState(), null // Paper - Anti-Xray @@ -190,6 +191,19 @@ public record SerializableChunkData( lists[i] = list2; } + // Paper start - throttle failed spawn attempts + long[] failedSpawnAttemptsData = null; + if (tag.contains("Paper.FailedSpawnAttempts")) { + failedSpawnAttemptsData = new long[net.minecraft.world.entity.MobCategory.values().length]; + CompoundTag failedSpawnAttemptsTag = tag.getCompoundOrEmpty("Paper.FailedSpawnAttempts"); + for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.level.NaturalSpawner.SPAWNING_CATEGORIES) { + if (failedSpawnAttemptsTag.contains(mobCategory.getSerializedName())) { + failedSpawnAttemptsData[mobCategory.ordinal()] = failedSpawnAttemptsTag.getLongOr(mobCategory.getSerializedName(), 0); + } + } + } + // Paper end - throttle failed spawn attempts + List list3 = tag.getList("entities").stream().flatMap(ListTag::compoundStream).toList(); List list4 = tag.getList("block_entities").stream().flatMap(ListTag::compoundStream).toList(); CompoundTag compoundOrEmpty = tag.getCompoundOrEmpty("structures"); @@ -270,6 +284,7 @@ public record SerializableChunkData( list4, compoundOrEmpty , tag.get("ChunkBukkitValues") // CraftBukkit - ChunkBukkitValues + , failedSpawnAttemptsData // Paper - throttle failed spawn attempts ); } } @@ -426,6 +441,15 @@ public record SerializableChunkData( chunkAccess.addPackedPostProcess(this.postProcessingSections[i], i); } + // Paper start - throttle failed spawn attempts + long[] failedSpawnAttemptsData = this.failedSpawnAttempts; + if (failedSpawnAttemptsData != null) { + for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.entity.MobCategory.values()) { + System.arraycopy(failedSpawnAttemptsData, 0, chunkAccess.failedSpawnAttempts, 0, failedSpawnAttemptsData.length); + } + } + // Paper end - throttle failed spawn attempts + if (chunkType == ChunkType.LEVELCHUNK) { return this.loadStarlightLightData(level, new ImposterProtoChunk((LevelChunk)chunkAccess, false)); // Paper - starlight } else { @@ -556,6 +580,7 @@ public record SerializableChunkData( persistentDataContainer = chunk.persistentDataContainer.toTagCompound(); } // CraftBukkit end + final long[] failedSpawnAttemptsData = chunk.failedSpawnAttempts; // Paper - throttle failed spawn attempts return new SerializableChunkData( level.registryAccess().lookupOrThrow(Registries.BIOME), pos, @@ -576,6 +601,7 @@ public record SerializableChunkData( list1, compoundTag , persistentDataContainer // CraftBukkit - persistentDataContainer + , failedSpawnAttemptsData // Paper - throttle failed spawn attempts ); } } @@ -660,6 +686,21 @@ public record SerializableChunkData( compoundTag.put("ChunkBukkitValues", this.persistentDataContainer); } // CraftBukkit end + // Paper start - throttle failed spawn attempts + CompoundTag failedSpawnAttemptsTag = new CompoundTag(); + long[] failedSpawnAttemptsData = this.failedSpawnAttempts; + if (failedSpawnAttemptsData != null) { + for (net.minecraft.world.entity.MobCategory mobCategory : net.minecraft.world.entity.MobCategory.values()) { + long failedAttempts = failedSpawnAttemptsData[mobCategory.ordinal()]; + if (failedAttempts > 0) { + failedSpawnAttemptsTag.putLong(mobCategory.getSerializedName(), failedAttempts); + } + } + } + if (!failedSpawnAttemptsTag.isEmpty()) { + compoundTag.put("Paper.FailedSpawnAttempts", failedSpawnAttemptsTag); + } + // Paper end - throttle failed spawn attempts // Paper start - starlight if (this.lightCorrect && !this.chunkStatus.isBefore(net.minecraft.world.level.chunk.status.ChunkStatus.LIGHT)) { // clobber vanilla value to force vanilla to relight @@ -875,4 +916,50 @@ public record SerializableChunkData( } // Paper end - starlight - convert from record } + + // Paper start - throttle failed spawn attempts + // For plugin compatibility + public SerializableChunkData( + Registry biomeRegistry, + ChunkPos chunkPos, + int minSectionY, + long lastUpdateTime, + long inhabitedTime, + ChunkStatus chunkStatus, + @Nullable BlendingData.Packed blendingData, + @Nullable BelowZeroRetrogen belowZeroRetrogen, + UpgradeData upgradeData, + @Nullable long[] carvingMask, + Map heightmaps, + ChunkAccess.PackedTicks packedTicks, + ShortList[] postProcessingSections, + boolean lightCorrect, + List sectionData, + List entities, + List blockEntities, + CompoundTag structureData, + @Nullable net.minecraft.nbt.Tag persistentDataContainer // CraftBukkit - persistentDataContainer + ) { + this(biomeRegistry, + chunkPos, + minSectionY, + lastUpdateTime, + inhabitedTime, + chunkStatus, + blendingData, + belowZeroRetrogen, + upgradeData, + carvingMask, + heightmaps, + packedTicks, + postProcessingSections, + lightCorrect, + sectionData, + entities, + blockEntities, + structureData, + persistentDataContainer, + null); + } + // Paper end - throttle failed spawn attempts }