diff --git a/build-data/leaf.at b/build-data/leaf.at index f0c75d48..55828ad9 100644 --- a/build-data/leaf.at +++ b/build-data/leaf.at @@ -5,6 +5,8 @@ protected-f net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase$ public net.minecraft.util.Mth SIN public net.minecraft.world.entity.Entity updateInWaterStateAndDoWaterCurrentPushing()V public net.minecraft.world.entity.LivingEntity canGlide()Z +public net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities lineOfSightTest +public net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities nearbyEntities public net.minecraft.world.entity.decoration.ArmorStand noTickEquipmentDirty public net.minecraft.world.entity.monster.Shulker MAX_SCALE public net.minecraft.world.entity.player.Player canGlide()Z diff --git a/leaf-archived-patches/removed/hardfork/server/0120-Replace-criterion-listener-map-with-optimized-collec.patch b/leaf-archived-patches/removed/hardfork/server/0120-Replace-criterion-listener-map-with-optimized-collec.patch new file mode 100644 index 00000000..93d9ce96 --- /dev/null +++ b/leaf-archived-patches/removed/hardfork/server/0120-Replace-criterion-listener-map-with-optimized-collec.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 9 Feb 2025 01:36:24 +0100 +Subject: [PATCH] Replace criterion listener map with optimized collection + + +diff --git a/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java b/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +index 4b2ae046413146b11912e7aa4a9a3d643de6afd1..6a4889d4ba90c1995e15c0daa6bb52682ec87eb4 100644 +--- a/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java ++++ b/net/minecraft/advancements/critereon/SimpleCriterionTrigger.java +@@ -19,7 +19,7 @@ public abstract class SimpleCriterionTrigger listener) { +- playerAdvancements.criterionData.computeIfAbsent(this, managerx -> Sets.newHashSet()).add(listener); // Paper - fix PlayerAdvancements leak ++ playerAdvancements.criterionData.computeIfAbsent(this, managerx -> new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>()).add(listener); // Paper - fix PlayerAdvancements leak // Leaf - Replace criterion listener map with optimized collection + } + + @Override +@@ -43,7 +43,7 @@ public abstract class SimpleCriterionTrigger> set = (Set) advancements.criterionData.get(this); // Paper - fix PlayerAdvancements leak + if (set != null && !set.isEmpty()) { + LootContext lootContext = null; // EntityPredicate.createContext(player, player); // Paper - Perf: lazily create LootContext for criterions +- List> list = null; ++ it.unimi.dsi.fastutil.objects.ObjectArrayList> list = null; // Leaf - Replace criterion listener map with optimized collection + + for (CriterionTrigger.Listener listener : set) { + T simpleInstance = listener.trigger(); +@@ -51,7 +51,7 @@ public abstract class SimpleCriterionTrigger optional = simpleInstance.player(); + if (optional.isEmpty() || optional.get().matches(lootContext = (lootContext == null ? EntityPredicate.createContext(player, player) : lootContext))) { // Paper - Perf: lazily create LootContext for criterions + if (list == null) { +- list = Lists.newArrayList(); ++ list = new it.unimi.dsi.fastutil.objects.ObjectArrayList<>(); // Leaf - Replace criterion listener map with optimized collection + } + + list.add(listener); diff --git a/leaf-archived-patches/work/server/0110-Cache-ItemStack-max-stack-size.patch b/leaf-archived-patches/work/server/0110-Cache-ItemStack-max-stack-size.patch new file mode 100644 index 00000000..55d9d72f --- /dev/null +++ b/leaf-archived-patches/work/server/0110-Cache-ItemStack-max-stack-size.patch @@ -0,0 +1,66 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Fri, 7 Feb 2025 21:41:51 +0100 +Subject: [PATCH] Cache ItemStack max stack size + +TODO: check this, fix get max stack size + +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index fd7c1e800cbd4919a1a47f6c468c8776535bd028..fdd7e89bacc98e23f067ba17d0bd93ee84a388cb 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -194,6 +194,7 @@ public final class ItemStack implements DataComponentHolder, net.caffeinemc.mods + private static final Logger LOGGER = LogUtils.getLogger(); + public static final ItemStack EMPTY = new ItemStack((Void)null); + private static final Component DISABLED_ITEM_TOOLTIP = Component.translatable("item.disabled").withStyle(ChatFormatting.RED); ++ private int maxStackSize; + private int count; + private int popTime; + @Deprecated +@@ -289,11 +290,13 @@ public final class ItemStack implements DataComponentHolder, net.caffeinemc.mods + this.count = count; + this.components = components; + this.getItem().verifyComponentsAfterLoad(this); ++ this.maxStackSize = getMaxStackSizeInternal(); // Leaf - Cache ItemStack max stack size + } + + private ItemStack(@Nullable Void unused) { + this.item = null; + this.components = new PatchedDataComponentMap(DataComponentMap.EMPTY); ++ this.maxStackSize = 1; // Leaf - Cache ItemStack max stack size - taken from ItemStack#isEmpty + } + + public static DataResult validateComponents(DataComponentMap components) { +@@ -619,9 +622,15 @@ public final class ItemStack implements DataComponentHolder, net.caffeinemc.mods + } + + public int getMaxStackSize() { +- return this.getOrDefault(DataComponents.MAX_STACK_SIZE, Integer.valueOf(1)); ++ return maxStackSize; // Leaf - Cache ItemStack max stack size + } + ++ // Leaf start - Cache ItemStack max stack size ++ private int getMaxStackSizeInternal() { ++ return this.getOrDefault(DataComponents.MAX_STACK_SIZE, 1); ++ } ++ // Leaf end - Cache ItemStack max stack size ++ + public boolean isStackable() { + return this.getMaxStackSize() > 1 && (!this.isDamageableItem() || !this.isDamaged()); + } +@@ -1339,6 +1348,7 @@ public final class ItemStack implements DataComponentHolder, net.caffeinemc.mods + this.components = new PatchedDataComponentMap(this.item.components()); + this.applyComponents(patch); + // Paper end - change base component prototype ++ this.maxStackSize = getMaxStackSizeInternal(); // Leaf - Cache ItemStack max stack size + } + // CraftBukkit end + +@@ -1396,6 +1406,7 @@ public final class ItemStack implements DataComponentHolder, net.caffeinemc.mods + } + // Leaf end - Lithium - equipment tracking + this.count = count; ++ maxStackSize = count <= 0 ? 1 : getMaxStackSizeInternal(); // Leaf - Cache ItemStack max stack size - taken from ItemStack#isEmpty + } + + public void limitSize(int maxSize) { diff --git a/leaf-server/minecraft-patches/features/0089-Smart-sort-entities-in-NearestLivingEntitySensor.patch b/leaf-server/minecraft-patches/features/0089-Smart-sort-entities-in-NearestLivingEntitySensor.patch index 657eff5b..88628c47 100644 --- a/leaf-server/minecraft-patches/features/0089-Smart-sort-entities-in-NearestLivingEntitySensor.patch +++ b/leaf-server/minecraft-patches/features/0089-Smart-sort-entities-in-NearestLivingEntitySensor.patch @@ -10,7 +10,7 @@ on entity count, if entity count doesn't reach the Bucket Sort threshold, FastBitRadix Sort will be used. (see https://ieeexplore.ieee.org/document/7822019 for more) When entity count reached the threshold, Bucket Sort will be used. -In non-strict testing, this can give ~20-40% improvement (54MSPT -> 44MSPT), +In non-strict test, this can give ~20-40% improvement (54MSPT -> 44MSPT), under 625 villagers situation. diff --git a/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java diff --git a/leaf-server/minecraft-patches/features/0109-PaperPR-Fix-MC-117075-Block-Entities-Unload-Lag-Spik.patch b/leaf-server/minecraft-patches/features/0109-PaperPR-Fix-MC-117075-Block-Entities-Unload-Lag-Spik.patch new file mode 100644 index 00000000..5538756e --- /dev/null +++ b/leaf-server/minecraft-patches/features/0109-PaperPR-Fix-MC-117075-Block-Entities-Unload-Lag-Spik.patch @@ -0,0 +1,51 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrPowerGamerBR +Date: Sun, 26 Nov 2023 13:02:16 -0300 +Subject: [PATCH] PaperPR: Fix MC-117075: Block Entities Unload Lag Spike + +Original license: GPLv3 +Original project: https://github.com/SparklyPower/SparklyPaper +Paper pull request: https://github.com/PaperMC/Paper/pull/9970 + +We replaced the `blockEntityTickers` list with a custom list based on fastutil's `ObjectArrayList` with a small yet huge change for us: A method that allows us to remove a list of indexes from the list. + +This is WAY FASTER than using `removeAll` with a list of entries to be removed, because we don't need to calculate the identity of each block entity to be removed, and we can jump directly to where the search should begin, giving a performance boost for small removals (because we don't need to loop thru the entire list to find what element should be removed) and a performance boost for big removals (no need to calculate the identity of each block entity). + +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 53cabe7dabc83618c8941c95e95c5b7e23ee694e..7d3163802640449b6bdaa93595518d7d0f62488b 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -113,7 +113,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public static final int TICKS_PER_DAY = 24000; + public static final int MAX_ENTITY_SPAWN_Y = 20000000; + public static final int MIN_ENTITY_SPAWN_Y = -20000000; +- public final List blockEntityTickers = Lists.newArrayList(); // Paper - public ++ public final org.dreeam.leaf.util.list.BlockEntityTickersList blockEntityTickers = new org.dreeam.leaf.util.list.BlockEntityTickersList(); // Paper - public // SparklyPaper - optimize block entity removals + protected final NeighborUpdater neighborUpdater; + private final List pendingBlockEntityTickers = Lists.newArrayList(); + private boolean tickingBlockEntities; +@@ -1523,14 +1523,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + boolean runsNormally = this.tickRateManager().runsNormally(); + + int tickedEntities = 0; // Paper - rewrite chunk system +- var toRemove = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet(); // Paper - Fix MC-117075; use removeAll +- toRemove.add(null); // Paper - Fix MC-117075 + for (tileTickPosition = 0; tileTickPosition < this.blockEntityTickers.size(); tileTickPosition++) { // Paper - Disable tick limiters + this.tileTickPosition = (this.tileTickPosition < this.blockEntityTickers.size()) ? this.tileTickPosition : 0; + TickingBlockEntity tickingBlockEntity = this.blockEntityTickers.get(this.tileTickPosition); + // Spigot end + if (tickingBlockEntity.isRemoved()) { +- toRemove.add(tickingBlockEntity); // Paper - Fix MC-117075; use removeAll ++ this.blockEntityTickers.markAsRemoved(this.tileTickPosition); // toRemove.add(tickingBlockEntity); // SparklyPaper - optimize block entity removals // Paper - Fix MC-117075; use removeAll + } else if (runsNormally && this.shouldTickBlocksAt(tickingBlockEntity.getPos())) { + tickingBlockEntity.tick(); + // Paper start - rewrite chunk system +@@ -1540,7 +1538,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + // Paper end - rewrite chunk system + } + } +- this.blockEntityTickers.removeAll(toRemove); // Paper - Fix MC-117075 ++ this.blockEntityTickers.removeMarkedEntries(); // SparklyPaper - optimize block entity removals + + this.tickingBlockEntities = false; + this.spigotConfig.currentPrimedTnt = 0; // Spigot diff --git a/leaf-server/minecraft-patches/features/0110-Sepals-Rearrange-the-attackable-conditions.patch b/leaf-server/minecraft-patches/features/0110-Sepals-Rearrange-the-attackable-conditions.patch new file mode 100644 index 00000000..6da07367 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0110-Sepals-Rearrange-the-attackable-conditions.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: cao-awa +Date: Fri, 23 Aug 2024 00:23:32 +0800 +Subject: [PATCH] Sepals: Rearrange the attackable conditions + +Original license: GPLv3 +Original project: https://github.com/cao-awa/Sepals + +Rearrange the attackable conditions +let less costs predicate running first +reduce the probability of high-costs calculating + +-- The complaints -- +Mojang's attackable predicate is: + +!entity.getBrain().hasMemoryModule(MemoryModuleType.HAS_HUNTING_COOLDOWN) + && Sensor.testAttackableTargetPredicate(entity, target) + && FrogEntity.isValidFrogFood(target) + && !this.isTargetUnreachable(entity, target) + && target.isInRange(entity, 10.0) + +in this case, 'Sensor#testAttackableTargetPredicate' has calls 'TargetPredicate#test' +that cause a very lots raycast calculate when entities too much in the area +but... minecraft's raycast is absolutely bad, very slow + +the 'TargetPredicate#test' in this case (800 frogs) has make 9.8ms costs in once game tick +among them, 'BlockView.raycast' contributed 7.3ms + +then i make it be: + +FrogEntity.isValidFrogFood(target) && + entity.getBrain().hasMemoryModule(MemoryModuleType.HAS_HUNTING_COOLDOWN) && + target.isInRange(entity, 10.0) && + Sensor.testAttackableTargetPredicate(entity, target) && + isTargetUnreachable(entity, target); + +the 'isValidFrogFood' is simple conditions, check the entity's tag has in 'frog_food' +and a extra check when entity is slime then skip it when it size not 1 + +the 'isInRange' and 'hasMemoryModule' also simple, it only a few math calculates + +Test Result: +800 frogs cramming in a 7x7 space: + +| Environment | time | Percent(Avg.) | +|-------------------------------------------------:|:------:|:-------------:| +| Vanilla (FrogAttackablesSensor#matches) | 10 ms | 100 % | +| With Lithium (FrogAttackablesSensor#matches) | 5.7 ms | 57 % | +| With Sepals (SepalsFrogBrain#attackable) | 0.1 ms | 1 % | +| With Sepals+Lithium (SepalsFrogBrain#attackable) | 0.1 ms | 1 % | + +diff --git a/net/minecraft/world/entity/ai/sensing/FrogAttackablesSensor.java b/net/minecraft/world/entity/ai/sensing/FrogAttackablesSensor.java +index d163a363273b52b3b3f0b5a74ac4d4ab37d24bb7..e88479ba1ebeff67a93baad4f6f8c83119ff5ff7 100644 +--- a/net/minecraft/world/entity/ai/sensing/FrogAttackablesSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/FrogAttackablesSensor.java +@@ -13,11 +13,11 @@ public class FrogAttackablesSensor extends NearestVisibleLivingEntitySensor { + + @Override + protected boolean isMatchingEntity(ServerLevel level, LivingEntity entity, LivingEntity target) { +- return !entity.getBrain().hasMemoryValue(MemoryModuleType.HAS_HUNTING_COOLDOWN) ++ return Frog.canEat(target) ++ && !entity.getBrain().hasMemoryValue(MemoryModuleType.HAS_HUNTING_COOLDOWN) ++ && target.closerThan(entity, 10.0) + && Sensor.isEntityAttackable(level, entity, target) +- && Frog.canEat(target) +- && !this.isUnreachableAttackTarget(entity, target) +- && target.closerThan(entity, 10.0); ++ && !this.isUnreachableAttackTarget(entity, target); // Sepals - Rearrange the attackable conditions + } + + private boolean isUnreachableAttackTarget(LivingEntity attacker, LivingEntity target) { diff --git a/leaf-server/minecraft-patches/features/0111-SparklyPaper-Skip-dirty-stats-copy-when-requesting-p.patch b/leaf-server/minecraft-patches/features/0111-SparklyPaper-Skip-dirty-stats-copy-when-requesting-p.patch new file mode 100644 index 00000000..ea9f51d9 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0111-SparklyPaper-Skip-dirty-stats-copy-when-requesting-p.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrPowerGamerBR +Date: Mon, 13 Jan 2025 14:32:08 -0300 +Subject: [PATCH] SparklyPaper: Skip dirty stats copy when requesting player + stats + + +diff --git a/net/minecraft/stats/ServerStatsCounter.java b/net/minecraft/stats/ServerStatsCounter.java +index b26dbe807e5cb0a42f6c06b933397902310e5616..ce89060bd01b253af7577fd0e6c03fc95f046b91 100644 +--- a/net/minecraft/stats/ServerStatsCounter.java ++++ b/net/minecraft/stats/ServerStatsCounter.java +@@ -81,11 +81,15 @@ public class ServerStatsCounter extends StatsCounter { + this.dirty.add(stat); + } + ++ // SparklyPaper start - Skip dirty stats copy when requesting player stats ++ /* + private Set> getDirty() { + Set> set = Sets.newHashSet(this.dirty); + this.dirty.clear(); + return set; + } ++ */ ++ // SparklyPaper end + + public void parseLocal(DataFixer fixerUpper, String json) { + try { +@@ -194,10 +198,12 @@ public class ServerStatsCounter extends StatsCounter { + public void sendStats(ServerPlayer player) { + Object2IntMap> map = new Object2IntOpenHashMap<>(); + +- for (Stat stat : this.getDirty()) { ++ for (Stat stat : this.dirty) { // SparklyPaper - Skip dirty stats copy when requesting player stats + map.put(stat, this.getValue(stat)); + } + ++ this.dirty.clear(); // SparklyPaper - Skip dirty stats copy when requesting player stats ++ + player.connection.send(new ClientboundAwardStatsPacket(map)); + } + } diff --git a/leaf-server/minecraft-patches/features/0112-SparklyPaper-Reset-dirty-flag-when-loading-maps-from.patch b/leaf-server/minecraft-patches/features/0112-SparklyPaper-Reset-dirty-flag-when-loading-maps-from.patch new file mode 100644 index 00000000..c9472249 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0112-SparklyPaper-Reset-dirty-flag-when-loading-maps-from.patch @@ -0,0 +1,22 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MrPowerGamerBR +Date: Wed, 5 Jun 2024 15:20:00 -0300 +Subject: [PATCH] SparklyPaper: Reset dirty flag when loading maps from the + disk + +By default, the server will start rewriting all map datas to the disk after loading it, even if the map didn't have any changes + +This also slows down world saving a lot if you have a lot of maps + +diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 681dec447486138088fe5f705ef4fadab531139f..07f9287ff1f1dbd1795582c74102c072ea59b29f 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -198,6 +198,7 @@ public class MapItemSavedData extends SavedData { + } + } + ++ mapItemSavedData.setDirty(false); // SparklyPaper - reset dirty flag when loading maps from the disk (context for updates: this modification is at the end of the map "load" function) + return mapItemSavedData; + } + diff --git a/leaf-server/minecraft-patches/features/0113-Optimize-checking-nearby-players-for-spawning.patch b/leaf-server/minecraft-patches/features/0113-Optimize-checking-nearby-players-for-spawning.patch new file mode 100644 index 00000000..6fde26d4 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0113-Optimize-checking-nearby-players-for-spawning.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 8 Feb 2025 05:32:30 +0100 +Subject: [PATCH] Optimize checking nearby players for spawning + + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index 8986c059e7aadb58ae8d9ab7b848de10f9faa6b2..546b20f8998c71ca1a701de7efcedd8d821105e4 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -719,7 +719,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + } + + private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos, boolean reducedRange) { +- double blockRange; // Paper - use from event ++ //double blockRange; // Paper - use from event // Leaf - Optimize checking nearby players for spawning - move down + // Spigot end + // Paper start - chunk tick iteration optimisation + final ca.spottedleaf.moonrise.common.list.ReferenceList players = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers( +@@ -731,23 +731,39 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + + final ServerPlayer[] raw = players.getRawDataUnchecked(); + final int len = players.size(); ++ // Leaf start - Optimize checking nearby players for spawning ++ // Precompute chunk center once ++ // inline, copy from SectionPos#sectionToBlockCoord ++ final double centerX = (chunkPos.x << 4) + 8; ++ final double centerZ = (chunkPos.z << 4) + 8; + +- Objects.checkFromIndexSize(0, len, raw.length); + for (int i = 0; i < len; ++i) { + final ServerPlayer serverPlayer = raw[i]; +- // Paper start - PlayerNaturallySpawnCreaturesEvent +- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; +- blockRange = 16384.0D; ++ ++ if (serverPlayer.isSpectator()) continue; // Skip spectators early ++ ++ final double blockRangeSquared; ++ + if (reducedRange) { +- event = serverPlayer.playerNaturallySpawnedEvent; ++ // Handle reduced range from PlayerNaturallySpawnCreaturesEvent ++ // Paper start - PlayerNaturallySpawnCreaturesEvent ++ final com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = serverPlayer.playerNaturallySpawnedEvent; + if (event == null || event.isCancelled()) continue; +- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ final int spawnRadius = event.getSpawnRadius(); ++ blockRangeSquared = (double) (spawnRadius * spawnRadius) * 256.0; // (radius << 4)^2 ++ // Paper end - PlayerNaturallySpawnCreaturesEvent ++ } else { ++ blockRangeSquared = 16384.0D; // Default 128^2 + } +- // Paper end - PlayerNaturallySpawnCreaturesEvent +- if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, blockRange)) { ++ ++ // Calculate squared distance using precomputed center ++ final double dx = serverPlayer.getX() - centerX; ++ final double dz = serverPlayer.getZ() - centerZ; ++ if (dx * dx + dz * dz < blockRangeSquared) { + return true; + } + } ++ // Leaf end - Optimize checking nearby players for spawning + + return false; + // Paper end - chunk tick iteration optimisation diff --git a/leaf-server/minecraft-patches/features/0114-Cache-supporting-block-check.patch b/leaf-server/minecraft-patches/features/0114-Cache-supporting-block-check.patch new file mode 100644 index 00000000..133644d2 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0114-Cache-supporting-block-check.patch @@ -0,0 +1,63 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 8 Feb 2025 05:32:30 +0100 +Subject: [PATCH] Cache supporting block check + + +diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java +index 4544dd876d3cbcdb9b774b4a1f0c4737f3124bc5..6ca446fd9ab38329ba505526a56f8e4f64a9a639 100644 +--- a/net/minecraft/world/entity/Entity.java ++++ b/net/minecraft/world/entity/Entity.java +@@ -1083,12 +1083,36 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + return this.mainSupportingBlockPos.isPresent() && this.mainSupportingBlockPos.get().equals(pos); + } + ++ // Leaf start - Cache supporting block check ++ private boolean canSkipSupportingBlockSearch = false; ++ private BlockState cachedSupportingBlockState = null; ++ // Leaf end - Cache supporting block check ++ + protected void checkSupportingBlock(boolean onGround, @Nullable Vec3 movement) { ++ // Leaf start - Cache supporting block check ++ // Skip full check if no movement and cache is valid ++ if (movement == null || (movement.x == 0 && movement.z == 0 && movement.y == 0)) { ++ if (canSkipSupportingBlockSearch) { ++ return; ++ } ++ } else { ++ // Invalidate cache on movement ++ canSkipSupportingBlockSearch = false; ++ cachedSupportingBlockState = null; ++ } ++ // Leaf end - Cache supporting block check + if (onGround) { + AABB boundingBox = this.getBoundingBox(); + AABB aabb = new AABB(boundingBox.minX, boundingBox.minY - 1.0E-6, boundingBox.minZ, boundingBox.maxX, boundingBox.minY, boundingBox.maxZ); + Optional optional = this.level.findSupportingBlock(this, aabb); + if (optional.isPresent() || this.onGroundNoBlocks) { ++ // Leaf start - Cache supporting block check ++ if (optional.isPresent()) { // Cache the block state if found ++ BlockPos pos = optional.get(); ++ cachedSupportingBlockState = this.level.getBlockState(pos); ++ canSkipSupportingBlockSearch = true; ++ } ++ // Leaf end - Cache supporting block check + this.mainSupportingBlockPos = optional; + } else if (movement != null) { + AABB aabb1 = aabb.move(-movement.x, 0.0, -movement.z); +@@ -1105,6 +1129,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + } + ++ ++ // Leaf start - Cache supporting block check ++ // Helper method to get cached supporting block state ++ @Nullable ++ public BlockState getCachedSupportingBlock() { ++ return canSkipSupportingBlockSearch ? cachedSupportingBlockState : null; ++ } ++ // Leaf end - Cache supporting block check ++ + public boolean onGround() { + return this.onGround; + } diff --git a/leaf-server/minecraft-patches/features/0115-Avoid-useless-deque-clear-on-LevelTicks-cleanupAfter.patch b/leaf-server/minecraft-patches/features/0115-Avoid-useless-deque-clear-on-LevelTicks-cleanupAfter.patch new file mode 100644 index 00000000..f9feba13 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0115-Avoid-useless-deque-clear-on-LevelTicks-cleanupAfter.patch @@ -0,0 +1,19 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MachineBreaker +Date: Wed, 16 Oct 2024 03:39:24 -0400 +Subject: [PATCH] Avoid useless deque clear on LevelTicks#cleanupAfterTick + + +diff --git a/net/minecraft/world/ticks/LevelTicks.java b/net/minecraft/world/ticks/LevelTicks.java +index fbf0d3b808c66e8971c747619f6acf7417af5ef7..d4542a86a2a9bfcfa7b6b7a213f233542ffed797 100644 +--- a/net/minecraft/world/ticks/LevelTicks.java ++++ b/net/minecraft/world/ticks/LevelTicks.java +@@ -182,7 +182,7 @@ public class LevelTicks implements LevelTickAccess { + } + + private void cleanupAfterTick() { +- this.toRunThisTick.clear(); ++ //this.toRunThisTick.clear(); // Leaf - Avoid useless deque clear on LevelTicks#cleanupAfterTick - This method runs after toRunThisTick is polled so this is always empty + this.containersToTick.clear(); + this.alreadyRunThisTick.clear(); + this.toRunThisTickSet.clear(); diff --git a/leaf-server/minecraft-patches/features/0116-Replace-brain-activity-maps-with-optimized-collectio.patch b/leaf-server/minecraft-patches/features/0116-Replace-brain-activity-maps-with-optimized-collectio.patch new file mode 100644 index 00000000..dbdcd2c0 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0116-Replace-brain-activity-maps-with-optimized-collectio.patch @@ -0,0 +1,21 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 8 Feb 2025 20:45:14 +0100 +Subject: [PATCH] Replace brain activity maps with optimized collection + + +diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java +index ea6c8e85ccff67b1c24109732f74f1e8199cad07..e27284f9897923f67985e3d60c3438bd00cc4a51 100644 +--- a/net/minecraft/world/entity/ai/Brain.java ++++ b/net/minecraft/world/entity/ai/Brain.java +@@ -390,8 +390,8 @@ public class Brain { + + for (Pair> pair : tasks) { + this.availableBehaviorsByPriority +- .computeIfAbsent(pair.getFirst(), integer -> Maps.newHashMap()) +- .computeIfAbsent(activity, activity1 -> Sets.newLinkedHashSet()) ++ .computeIfAbsent(pair.getFirst(), integer -> new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>()) // Leaf - Replace brain activity maps with optimized collection ++ .computeIfAbsent(activity, activity1 -> new it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet<>()) // Leaf - Replace brain activity maps with optimized collection + .add((BehaviorControl)pair.getSecond()); + } + } diff --git a/leaf-server/minecraft-patches/features/0117-Remove-stream-in-villagers.patch b/leaf-server/minecraft-patches/features/0117-Remove-stream-in-villagers.patch new file mode 100644 index 00000000..445dc443 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0117-Remove-stream-in-villagers.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 8 Feb 2025 22:11:16 +0100 +Subject: [PATCH] Remove stream in villagers + +TradeWithVillager#figureOutWhatIAmWillingToTrade +In the test, this can give ~40% improvement (~20ms -> ~12ms), +under 2048 villagers situation. +And ~100% improvement (~36ms -> ~0ms), under 512 villagers situation. + +Villager#countFoodPointsInInventory +In the test, this can give ~82.14% improvement (~1456ms -> ~260ms), +under 2048 villagers situation. +And ~93.92% improvement (~1382ms -> ~84ms), under 512 villagers situation. + +diff --git a/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java +index 4d8523a43d60cd6b4fd5546ffb3a61417b2c475b..8921faa7b893aae9e91a6f8e36dcd751308f9bab 100644 +--- a/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java ++++ b/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java +@@ -77,9 +77,19 @@ public class TradeWithVillager extends Behavior { + } + + private static Set figureOutWhatIAmWillingToTrade(Villager villager, Villager other) { +- ImmutableSet set = other.getVillagerData().getProfession().requestedItems(); +- ImmutableSet set1 = villager.getVillagerData().getProfession().requestedItems(); +- return set.stream().filter(item -> !set1.contains(item)).collect(Collectors.toSet()); ++ // Leaf start - Remove stream in villagers ++ ImmutableSet otherItems = other.getVillagerData().getProfession().requestedItems(); ++ ImmutableSet villagerItems = villager.getVillagerData().getProfession().requestedItems(); ++ Set result = new java.util.HashSet<>(); ++ ++ for (Item item : otherItems) { ++ if (!villagerItems.contains(item)) { ++ result.add(item); ++ } ++ } ++ ++ return result; ++ // Leaf end - Remove stream in villagers + } + + private static void throwHalfStack(Villager villager, Set stack, LivingEntity entity) { +diff --git a/net/minecraft/world/entity/npc/Villager.java b/net/minecraft/world/entity/npc/Villager.java +index bee017f2c47a9f0876e2e05ce1c720332fb74566..0b4c4707139c9c72929799818ec1a1b25575d70e 100644 +--- a/net/minecraft/world/entity/npc/Villager.java ++++ b/net/minecraft/world/entity/npc/Villager.java +@@ -985,7 +985,17 @@ public class Villager extends AbstractVillager implements ReputationEventHandler + + private int countFoodPointsInInventory() { + SimpleContainer inventory = this.getInventory(); +- return FOOD_POINTS.entrySet().stream().mapToInt(entry -> inventory.countItem(entry.getKey()) * entry.getValue()).sum(); ++ // Leaf start - Remove stream in villagers ++ int sum = 0; ++ ++ for (Map.Entry entry : FOOD_POINTS.entrySet()) { ++ Item item = entry.getKey(); ++ int points = entry.getValue(); ++ sum += inventory.countItem(item) * points; ++ } ++ ++ return sum; ++ // Leaf end - Remove stream in villagers + } + + public boolean hasFarmSeeds() { diff --git a/leaf-server/minecraft-patches/features/0118-Optimize-baby-villager-sensor.patch b/leaf-server/minecraft-patches/features/0118-Optimize-baby-villager-sensor.patch new file mode 100644 index 00000000..9004445f --- /dev/null +++ b/leaf-server/minecraft-patches/features/0118-Optimize-baby-villager-sensor.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 8 Feb 2025 22:36:31 +0100 +Subject: [PATCH] Optimize baby villager sensor + +In the test, this can give ~16.58% improvement (~2316ms -> ~1932ms), +under 2048 villagers situation. +And ~42.93% improvement (~764ms -> ~436ms), under 512 villagers situation. + +diff --git a/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java b/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java +index 1c010e5b75506945e5281021a2ddad424044d28f..2b973a3ba7d65330fa4690e71e5321c28457ec61 100644 +--- a/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java ++++ b/net/minecraft/world/entity/ai/memory/NearestVisibleLivingEntities.java +@@ -42,7 +42,7 @@ public class NearestVisibleLivingEntities { + } + + public Iterable findAll(Predicate predicate) { +- return Iterables.filter(this.nearbyEntities, target -> predicate.test(target) && this.lineOfSightTest.test(target)); ++ return Iterables.filter(this.nearbyEntities, target -> predicate.test(target) && this.lineOfSightTest.test(target)); // Leaf - Optimize baby villager sensor - diff on change + } + + public Stream find(Predicate predicate) { +diff --git a/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java b/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java +index 24d1928445b5571e040a2b12d5c82e77a880d9bd..4b2964aeb4e21fe41f42c2902db63ce28322063a 100644 +--- a/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java ++++ b/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java +@@ -22,11 +22,25 @@ public class VillagerBabiesSensor extends Sensor { + } + + private List getNearestVillagerBabies(LivingEntity livingEntity) { +- return ImmutableList.copyOf(this.getVisibleEntities(livingEntity).findAll(this::isVillagerBaby)); ++ // Leaf start - Optimize baby villager sensor ++ NearestVisibleLivingEntities visibleEntities = this.getVisibleEntities(livingEntity); ++ ImmutableList.Builder babies = ImmutableList.builder(); ++ ++ // Inline and use single loop - copy from NearestVisibleLivingEntities#findAll and isVillagerBaby ++ for (LivingEntity target : visibleEntities.nearbyEntities) { ++ if (target.getType() == EntityType.VILLAGER ++ && target.isBaby() ++ && visibleEntities.lineOfSightTest.test(target)) { ++ babies.add(target); ++ } ++ } ++ ++ return babies.build(); ++ // Leaf end - Optimize baby villager sensor + } + + private boolean isVillagerBaby(LivingEntity livingEntity) { +- return livingEntity.getType() == EntityType.VILLAGER && livingEntity.isBaby(); ++ return livingEntity.getType() == EntityType.VILLAGER && livingEntity.isBaby(); // Leaf - Optimize baby villager sensor - diff on change + } + + private NearestVisibleLivingEntities getVisibleEntities(LivingEntity livingEntity) { diff --git a/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch b/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch new file mode 100644 index 00000000..e86dea63 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0119-Only-player-pushable.patch @@ -0,0 +1,94 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Dreeam <61569423+Dreeam-qwq@users.noreply.github.com> +Date: Wed, 27 Nov 2024 23:13:12 -0500 +Subject: [PATCH] Only player pushable + +Useful for extreme cases like massive entities collide together in a small area + +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index f4f978073fca1be8fe18bc13f64385d4c0cd4b3d..deb31fa0e0c9c8ccb21c5ae7e86bb1c5406e2177 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -3624,7 +3624,7 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf + this.checkAutoSpinAttack(boundingBox, this.getBoundingBox()); + } + +- this.pushEntities(); ++ if (!org.dreeam.leaf.config.modules.gameplay.OnlyPlayerPushable.enabled) this.pushEntities(); // Leaf - Only player pushable + // Paper start - Add EntityMoveEvent + // Purpur start - Ridables + if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { +@@ -3762,7 +3762,14 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf + return; + } + // Paper end - don't run getEntities if we're not going to use its result +- List entities = this.level().getEntities(this, this.getBoundingBox(), EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule)); // Paper - Climbing should not bypass cramming gamerule ++ // Leaf start - Only player pushable ++ final AABB box = this.getBoundingBox(); ++ final Predicate conditions = EntitySelector.pushable(this, this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule); ++ ++ List entities = org.dreeam.leaf.config.modules.gameplay.OnlyPlayerPushable.enabled ++ ? getNearbyPushablePlayers(this, box, conditions) ++ : this.level().getEntities(this, box, conditions); // Paper - Climbing should not bypass cramming gamerule ++ // Leaf end - Only player pushable + if (!entities.isEmpty()) { + // Paper - don't run getEntities if we're not going to use its result; moved up + if (_int > 0 && entities.size() > _int - 1 && this.random.nextInt(4) == 0) { +@@ -3795,6 +3802,44 @@ public abstract class LivingEntity extends Entity implements Attackable, net.caf + } + } + ++ // Leaf start - Only player pushable ++ public List getNearbyPushablePlayers(Entity entity, AABB box, Predicate conditions) { ++ final Vec3 vec = entity.position; ++ final net.minecraft.core.BlockPos.MutableBlockPos mutablePos = new net.minecraft.core.BlockPos.MutableBlockPos(); ++ ++ mutablePos.set(vec.x, vec.y, vec.z); ++ ++ final ca.spottedleaf.moonrise.common.list.ReferenceList players = ((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel) this.level()).moonrise$getNearbyPlayers().getPlayers( ++ mutablePos, ca.spottedleaf.moonrise.common.misc.NearbyPlayers.NearbyMapType.SPAWN_RANGE ++ ); ++ ++ if (players == null) { ++ return new ArrayList<>(); ++ } ++ ++ List ret = null; ++ ++ final ServerPlayer[] raw = players.getRawDataUnchecked(); ++ final int len = players.size(); ++ ++ java.util.Objects.checkFromIndexSize(0, len, raw.length); ++ ++ for (int i = 0; i < len; ++i) { ++ final ServerPlayer player = raw[i]; ++ if (player != entity && box.intersects(player.getBoundingBox()) && conditions.test(player)) { ++ if (ret == null) { ++ ret = new ArrayList<>(len - i); ++ ret.add(player); ++ } else { ++ ret.add(player); ++ } ++ } ++ } ++ ++ return ret == null ? new ArrayList<>() : ret; ++ } ++ // Leaf end - Only player pushable ++ + protected void checkAutoSpinAttack(AABB boundingBoxBeforeSpin, AABB boundingBoxAfterSpin) { + AABB aabb = boundingBoxBeforeSpin.minmax(boundingBoxAfterSpin); + List entities = this.level().getEntities(this, aabb); +diff --git a/net/minecraft/world/entity/decoration/ArmorStand.java b/net/minecraft/world/entity/decoration/ArmorStand.java +index 21153f37c169e987d7876d1b914105223ac10ee7..a8bd9f027b5ce360b9e720a7734451bcf9f701d4 100644 +--- a/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -326,7 +326,7 @@ public class ArmorStand extends LivingEntity implements net.caffeinemc.mods.lith + + @Override + protected void pushEntities() { +- if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups ++ if (org.dreeam.leaf.config.modules.gameplay.OnlyPlayerPushable.enabled || !this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups // Leaf - Only player pushable + for (Entity entity : this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), RIDABLE_MINECARTS)) { // Paper - optimise collisions + if (this.distanceToSqr(entity) <= 0.2) { + entity.push(this); diff --git a/leaf-server/minecraft-patches/features/0120-Remove-iterators-from-Inventory-contains.patch b/leaf-server/minecraft-patches/features/0120-Remove-iterators-from-Inventory-contains.patch new file mode 100644 index 00000000..38c4d642 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0120-Remove-iterators-from-Inventory-contains.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Thu, 13 Feb 2025 01:25:40 +0100 +Subject: [PATCH] Remove iterators from Inventory#contains + + +diff --git a/net/minecraft/world/entity/player/Inventory.java b/net/minecraft/world/entity/player/Inventory.java +index 839cbb67d3d38960d9114a4db5bab911b66a573c..e2237ffebadc8f010688c6e7336f4278193a1a20 100644 +--- a/net/minecraft/world/entity/player/Inventory.java ++++ b/net/minecraft/world/entity/player/Inventory.java +@@ -568,9 +568,13 @@ public class Inventory implements Container, Nameable { + } + + public boolean contains(ItemStack stack) { +- for (List list : this.compartments) { +- for (ItemStack itemStack : list) { +- if (!itemStack.isEmpty() && ItemStack.isSameItemSameComponents(itemStack, stack)) { ++ // Leaf start - Remove iterators from Inventory#contains ++ for (int i = 0; i < this.compartments.size(); i++) { ++ List list = this.compartments.get(i); ++ for (int j = 0; j < list.size(); j++) { ++ ItemStack itemstack1 = list.get(j); ++ if (!itemstack1.isEmpty() && ItemStack.isSameItemSameComponents(itemstack1, stack)) { ++ // Leaf end - Remove iterators from Inventory#contains + return true; + } + } +@@ -580,9 +584,13 @@ public class Inventory implements Container, Nameable { + } + + public boolean contains(TagKey tag) { +- for (List list : this.compartments) { +- for (ItemStack itemStack : list) { +- if (!itemStack.isEmpty() && itemStack.is(tag)) { ++ // Leaf start - Remove iterators from Inventory#contains ++ for (int i = 0; i < this.compartments.size(); i++) { ++ List list = this.compartments.get(i); ++ for (int j = 0; j < list.size(); j++) { ++ ItemStack itemstack = list.get(j); ++ if (!itemstack.isEmpty() && itemstack.is(tag)) { ++ // Leaf end - Remove iterators from Inventory#contains + return true; + } + } +@@ -592,9 +600,13 @@ public class Inventory implements Container, Nameable { + } + + public boolean contains(Predicate predicate) { +- for (List list : this.compartments) { +- for (ItemStack itemStack : list) { +- if (predicate.test(itemStack)) { ++ // Leaf start - Remove iterators from Inventory#contains ++ for (int i = 0; i < this.compartments.size(); i++) { ++ List list = this.compartments.get(i); ++ for (int j = 0; j < list.size(); j++) { ++ ItemStack itemstack = list.get(j); ++ if (predicate.test(itemstack)) { ++ // Leaf end - Remove iterators from Inventory#contains + return true; + } + } diff --git a/leaf-server/minecraft-patches/features/0121-Alternative-Brain-Behaviour.patch b/leaf-server/minecraft-patches/features/0121-Alternative-Brain-Behaviour.patch new file mode 100644 index 00000000..b6b27059 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0121-Alternative-Brain-Behaviour.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Fri, 14 Feb 2025 14:58:59 +0100 +Subject: [PATCH] Alternative Brain Behaviour + +In the test, this can give ~54.87% improvement (~25712ms -> ~11604ms), +under 1024 villagers situation. + +diff --git a/net/minecraft/world/entity/ai/Brain.java b/net/minecraft/world/entity/ai/Brain.java +index e27284f9897923f67985e3d60c3438bd00cc4a51..0ff7564e0e848bd38e82f9089bfd7249fa649dc5 100644 +--- a/net/minecraft/world/entity/ai/Brain.java ++++ b/net/minecraft/world/entity/ai/Brain.java +@@ -268,23 +268,52 @@ public class Brain { + return this.activeActivities; + } + ++ // Leaf start - Alternative Brain Behaviour ++ private ObjectArrayList> runningBehaviorsCache; ++ private long lastRunningBehaviorCheck = -1; ++ // Leaf end - Alternative Brain Behaviour ++ + @Deprecated + @VisibleForDebug + public List> getRunningBehaviors() { +- List> list = new ObjectArrayList<>(); ++ // Leaf start - Alternative Brain Behaviour ++ long currentTick = getCurrentTick(); ++ ++ // Use cached result if within update interval ++ if (runningBehaviorsCache != null && (currentTick - lastRunningBehaviorCheck) < org.dreeam.leaf.config.modules.opt.BrainRunningBehaviorCacheUpdate.interval) { ++ return runningBehaviorsCache; ++ } ++ ++ // Initialize or reuse cache list ++ if (runningBehaviorsCache == null) { ++ runningBehaviorsCache = new ObjectArrayList<>(32); ++ } else { ++ runningBehaviorsCache.clear(); ++ } ++ ++ for (Map>> activityMap : availableBehaviorsByPriority.values()) { ++ for (Set> behaviors : activityMap.values()) { ++ if (behaviors.isEmpty()) continue; + +- for (Map>> map : this.availableBehaviorsByPriority.values()) { +- for (Set> set : map.values()) { +- for (BehaviorControl behaviorControl : set) { +- if (behaviorControl.getStatus() == Behavior.Status.RUNNING) { +- list.add(behaviorControl); ++ for (BehaviorControl behavior : behaviors) { ++ if (behavior.getStatus() == Behavior.Status.RUNNING) { ++ runningBehaviorsCache.add(behavior); + } + } + } + } + +- return list; ++ lastRunningBehaviorCheck = currentTick; ++ ++ return runningBehaviorsCache; ++ } ++ ++ // Helper method to get current tick ++ private long getCurrentTick() { ++ // This should be implemented to return the current game tick ++ return System.nanoTime() / 50_000_000; // Approximate tick time of 50ms + } ++ // Leaf end - Alternative Brain Behaviour + + public void useDefaultActivity() { + this.setActiveActivity(this.defaultActivity); diff --git a/leaf-server/minecraft-patches/features/0122-Cache-eligible-players-for-despawn-checks.patch b/leaf-server/minecraft-patches/features/0122-Cache-eligible-players-for-despawn-checks.patch new file mode 100644 index 00000000..a7654586 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0122-Cache-eligible-players-for-despawn-checks.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Fri, 14 Feb 2025 20:08:14 +0100 +Subject: [PATCH] Cache eligible players for despawn checks + + +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index 26223a3c26691303a91b988b6d84373a303785dd..857da17532bbbdc02aa755c13d844795f5bf36d8 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -735,6 +735,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return this.structureManager; + } + ++ public Player[] eligibleDespawnCheckingPlayerCache = new Player[0]; // Leaf - Cache eligible players for despawn checks ++ + public void tick(BooleanSupplier hasTimeLeft) { + this.handlingTick = true; + TickRateManager tickRateManager = this.tickRateManager(); +@@ -802,6 +804,19 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + } + + io.papermc.paper.entity.activation.ActivationRange.activateEntities(this); // Paper - EAR ++ ++ // Leaf start - Cache eligible players for despawn checks ++ List serverPlayers = new ArrayList<>(players().size()); ++ for (int i = 0; i < players().size(); i++) { ++ ServerPlayer player = players().get(i); ++ if (net.minecraft.world.entity.EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player)) { ++ serverPlayers.add(player); ++ } ++ } ++ ++ eligibleDespawnCheckingPlayerCache = serverPlayers.toArray(new Player[0]); ++ // Leaf end - Cache eligible players for despawn checks ++ + this.entityTickList + .forEach( + entity -> { +diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java +index 01c30802a1d0127f2ed36efa7511c2ac6b2b5cfa..523d27ae8837bc4da2f993964aa99ab91617ec01 100644 +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -1569,6 +1569,13 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc + this.containerMenu.broadcastChanges(); + } + ++ // Leaf start - Cache eligible players for despawn checks ++ @Override ++ public boolean isAlive() { ++ return !this.isRemoved() && this.entityData.get(DATA_HEALTH_ID) > 0.0f && !this.dead; ++ } ++ // Leaf end - Cache eligible players for despawn checks ++ + // CraftBukkit start - moved bed result checks from below into separate method + private Either getBedResult(BlockPos at, Direction direction) { + if (this.isSleeping() || !this.isAlive()) { +diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java +index b541236c39e3f36bcc619fffe83e32987df20adf..67d9415a53813675d9b7dadf928756d59204208e 100644 +--- a/net/minecraft/world/entity/Mob.java ++++ b/net/minecraft/world/entity/Mob.java +@@ -854,7 +854,24 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab + if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { + this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause + } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { +- Entity nearestPlayer = this.level().findNearbyPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API ++ // Leaf start - Cache eligible players for despawn checks ++ Entity nearestPlayer = null; ++ ++ if (this.level() instanceof ServerLevel serverLevel) { ++ double minDist = Double.MAX_VALUE; ++ for (int i = 0; i < serverLevel.eligibleDespawnCheckingPlayerCache.length; i++) { ++ Player cachedPlayer = serverLevel.eligibleDespawnCheckingPlayerCache[i]; ++ double d1 = cachedPlayer.distanceToSqr(this); ++ if (d1 <= minDist) { ++ minDist = d1; ++ nearestPlayer = cachedPlayer; ++ } ++ } ++ } else { ++ nearestPlayer = this.level().findNearbyPlayer(this, -1.0, EntitySelector.PLAYER_AFFECTS_SPAWNING); // Paper - Affects Spawning API ++ } ++ // Leaf end - Cache eligible players for despawn checks ++ + if (nearestPlayer != null) { + // Paper start - Configurable despawn distances + final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory()); diff --git a/leaf-server/minecraft-patches/features/0123-Slightly-optimise-getNearestPlayer.patch b/leaf-server/minecraft-patches/features/0123-Slightly-optimise-getNearestPlayer.patch new file mode 100644 index 00000000..9fb38c3c --- /dev/null +++ b/leaf-server/minecraft-patches/features/0123-Slightly-optimise-getNearestPlayer.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sun, 16 Feb 2025 09:21:50 +0100 +Subject: [PATCH] Slightly optimise getNearestPlayer + + +diff --git a/net/minecraft/world/level/EntityGetter.java b/net/minecraft/world/level/EntityGetter.java +index 670860df81a3abfc1b8b53be505fce0ee32ee2c4..083a2b5da246113913bcd5d0b2b9be42cf0554d9 100644 +--- a/net/minecraft/world/level/EntityGetter.java ++++ b/net/minecraft/world/level/EntityGetter.java +@@ -201,23 +201,42 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst + } + // Paper end - Affects Spawning API + ++ // Leaf start - Slightly optimise getNearestPlayer + @Nullable + default Player getNearestPlayer(double x, double y, double z, double distance, @Nullable Predicate predicate) { +- double d = -1.0; ++ if (distance < 0.0) { ++ distance = Double.MAX_VALUE; ++ } else { ++ distance = distance * distance; ++ } ++ + Player player = null; + +- for (Player player1 : this.players()) { +- if (predicate == null || predicate.test(player1)) { ++ if (predicate == null) { ++ for (int i = 0; i < this.players().size(); i++) { ++ Player player1 = this.players().get(i); + double d1 = player1.distanceToSqr(x, y, z); +- if ((distance < 0.0 || d1 < distance * distance) && (d == -1.0 || d1 < d)) { +- d = d1; ++ if (d1 < distance) { ++ distance = d1; + player = player1; + } + } ++ } else { ++ for (int i = 0; i < this.players().size(); i++) { ++ Player player1 = this.players().get(i); ++ if (predicate.test(player1)) { ++ double d1 = player1.distanceToSqr(x, y, z); ++ if (d1 < distance) { ++ distance = d1; ++ player = player1; ++ } ++ } ++ } + } + + return player; + } ++ // Leaf end - Slightly optimise getNearestPlayer + + // Paper start + default List findNearbyBukkitPlayers(double x, double y, double z, double radius, boolean notSpectator) { diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/OnlyPlayerPushable.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/OnlyPlayerPushable.java new file mode 100644 index 00000000..341652be --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/gameplay/OnlyPlayerPushable.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.gameplay; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class OnlyPlayerPushable extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.GAMEPLAY.getBaseKeyName() + ".only-player-pushable"; + } + + public static boolean enabled = false; + + @Override + public void onLoaded() { + enabled = config.getBoolean(getBasePath(), enabled, """ + Enable to make only player pushable + This option override the armorstand doCollisionEntityLookups and + ...write in docs"""); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/BrainRunningBehaviorCacheUpdate.java b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/BrainRunningBehaviorCacheUpdate.java new file mode 100644 index 00000000..f1ae5bf9 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/config/modules/opt/BrainRunningBehaviorCacheUpdate.java @@ -0,0 +1,21 @@ +package org.dreeam.leaf.config.modules.opt; + +import org.dreeam.leaf.config.ConfigModules; +import org.dreeam.leaf.config.EnumConfigCategory; + +public class BrainRunningBehaviorCacheUpdate extends ConfigModules { + + public String getBasePath() { + return EnumConfigCategory.PERF.getBaseKeyName(); + } + + public static int interval = 5; + + @Override + public void onLoaded() { + interval = config.getInt(getBasePath() + ".entity-running-behavior-cache-update-interval", interval, + config.pickStringRegionBased( + "How often entity update current brain running behavior list.", + "生物更新现有 Brain Behavior 列表缓存的间隔.")); + } +} diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/list/BlockEntityTickersList.java b/leaf-server/src/main/java/org/dreeam/leaf/util/list/BlockEntityTickersList.java new file mode 100644 index 00000000..48201f62 --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/list/BlockEntityTickersList.java @@ -0,0 +1,103 @@ +package org.dreeam.leaf.util.list; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.world.level.block.entity.TickingBlockEntity; + +import java.util.Arrays; +import java.util.Collection; + +/** + * A list for ServerLevel's blockEntityTickers + *

+ * This list behaves identically to ObjectArrayList, but it has an additional method, `removeAllByIndex`, that allows a list of integers to be passed indicating what + * indexes should be deleted from the list + *

+ * This is faster than using removeAll, since we don't need to compare the identity of each block entity, and faster than looping thru each index manually and deleting with remove, + * since we don't need to resize the array every single remove. + */ +public final class BlockEntityTickersList extends ObjectArrayList { + + private final IntOpenHashSet toRemove = new IntOpenHashSet(); + private int startSearchFromIndex = -1; + + /** + * Creates a new array list with {@link #DEFAULT_INITIAL_CAPACITY} capacity. + */ + public BlockEntityTickersList() { + super(); + } + + /** + * Creates a new array list and fills it with a given collection. + * + * @param c a collection that will be used to fill the array list. + */ + public BlockEntityTickersList(final Collection c) { + super(c); + } + + /** + * Marks an entry as removed + * + * @param index the index of the item on the list to be marked as removed + */ + public void markAsRemoved(final int index) { + // The block entities list always loop starting from 0, so we only need to check if the startSearchFromIndex is -1 and that's it + if (this.startSearchFromIndex == -1) + this.startSearchFromIndex = index; + + this.toRemove.add(index); + } + + /** + * Removes elements that have been marked as removed. + */ + public void removeMarkedEntries() { + if (this.startSearchFromIndex == -1) // No entries in the list, skip + return; + + removeAllByIndex(startSearchFromIndex, toRemove); + toRemove.clear(); + this.startSearchFromIndex = -1; // Reset the start search index + } + + /** + * Removes elements by their index. + */ + private void removeAllByIndex(final int startSearchFromIndex, final IntOpenHashSet c) { // can't use Set because we want to avoid autoboxing when using contains + final int requiredMatches = c.size(); + if (requiredMatches == 0) + return; // exit early, we don't need to do anything + + final Object[] a = this.a; + int j = startSearchFromIndex; + int matches = 0; + for (int i = startSearchFromIndex; i < size; i++) { // If the user knows the first index to be removed, we can skip a lot of unnecessary comparsions + if (!c.contains(i)) { + // TODO: It can be possible to optimize this loop by tracking the start/finish and then using arraycopy to "skip" the elements, + // this would optimize cases where the index to be removed are far apart, HOWEVER it does have a big performance impact if you are doing + // "arraycopy" for each element + a[j++] = a[i]; + } else { + matches++; + } + + if (matches == requiredMatches) { // Exit the loop if we already removed everything, we don't need to check anything else + // We need to update the final size here, because we know that we already found everything! + // Because we know that the size must be currentSize - requiredMatches (because we have matched everything), let's update the value + // However, we need to copy the rest of the stuff over + if (i != (size - 1)) { // If it isn't the last index... + // i + 1 because we want to copy the *next* element over + // and the size - i - 1 is because we want to get the current size, minus the current index (which is i), and then - 1 because we want to copy -1 ahead (remember, we are adding +1 to copy the *next* element) + System.arraycopy(a, i + 1, a, j, size - i - 1); + } + j = size - requiredMatches; + break; + } + } + + Arrays.fill(a, j, size, null); + size = j; + } +}