From 7f4df7b783454b3f751e73b6878c31facf68247a Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Mon, 21 Jul 2025 22:42:44 +0200 Subject: [PATCH] initial commit --- .../features/0274-Optimise-getEntities.patch | 83 --------- .../features/0274-Optimize-getEntities.patch | 170 ++++++++++++++++++ .../features/0278-cache-BlockBehaviour.patch | 57 ++++++ .../features/0062-entity-to-uuid-set.patch | 20 +++ .../leaf/util/map/EntityIdToUuidSet.java | 102 +++++++++++ 5 files changed, 349 insertions(+), 83 deletions(-) delete mode 100644 leaf-server/minecraft-patches/features/0274-Optimise-getEntities.patch create mode 100644 leaf-server/minecraft-patches/features/0274-Optimize-getEntities.patch create mode 100644 leaf-server/minecraft-patches/features/0278-cache-BlockBehaviour.patch create mode 100644 leaf-server/paper-patches/features/0062-entity-to-uuid-set.patch create mode 100644 leaf-server/src/main/java/org/dreeam/leaf/util/map/EntityIdToUuidSet.java diff --git a/leaf-server/minecraft-patches/features/0274-Optimise-getEntities.patch b/leaf-server/minecraft-patches/features/0274-Optimise-getEntities.patch deleted file mode 100644 index babcd40c..00000000 --- a/leaf-server/minecraft-patches/features/0274-Optimise-getEntities.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Taiyou06 -Date: Sat, 12 Jul 2025 02:00:43 +0200 -Subject: [PATCH] Optimise-getEntities - -Co-authored by: Martijn Muijsers - -diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -index 5ebc1b2cf186b512da5b62fedba16b612f2fa6ed..14dc37f0ed08b4eb4c7740ec68d23df0060c0955 100644 ---- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -+++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java -@@ -579,6 +579,14 @@ public final class ChunkEntitySlices { - - final BasicEntityList[] entitiesBySection = this.entitiesBySection; - -+ // Cache AABB fields to local variables to reduce field accesses inside the hot loop. -+ final double boxMinX = box.minX; -+ final double boxMinY = box.minY; -+ final double boxMinZ = box.minZ; -+ final double boxMaxX = box.maxX; -+ final double boxMaxY = box.maxY; -+ final double boxMaxZ = box.maxZ; -+ - for (int section = min; section <= max; ++section) { - final BasicEntityList list = entitiesBySection[section - minSection]; - -@@ -587,11 +595,18 @@ public final class ChunkEntitySlices { - } - - final Entity[] storage = list.storage; -+ final int len = Math.min(storage.length, list.size()); - -- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ for (int i = 0; i < len; ++i) { - final Entity entity = storage[i]; - -- if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ if (entity == null || entity == except) { -+ continue; -+ } -+ -+ // Inline AABB#intersects to avoid a method call and use the cached AABB fields. -+ final AABB entityBB = entity.getBoundingBox(); -+ if (entityBB.maxX <= boxMinX || entityBB.minX >= boxMaxX || entityBB.maxY <= boxMinY || entityBB.minY >= boxMaxY || entityBB.maxZ <= boxMinZ || entityBB.minZ >= boxMaxZ) { - continue; - } - -@@ -618,19 +633,32 @@ public final class ChunkEntitySlices { - - final BasicEntityList[] entitiesBySection = this.entitiesBySection; - -+ // Cache AABB fields to local variables to reduce field accesses inside the hot loop. -+ final double boxMinX = box.minX; -+ final double boxMinY = box.minY; -+ final double boxMinZ = box.minZ; -+ final double boxMaxX = box.maxX; -+ final double boxMaxY = box.maxY; -+ final double boxMaxZ = box.maxZ; -+ - for (int section = min; section <= max; ++section) { - final BasicEntityList list = entitiesBySection[section - minSection]; -- - if (list == null) { - continue; - } - - final Entity[] storage = list.storage; -+ final int len = Math.min(storage.length, list.size()); - -- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ for (int i = 0; i < len; ++i) { - final Entity entity = storage[i]; -+ if (entity == null || entity == except) { -+ continue; -+ } - -- if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ // Inline AABB#intersects to avoid a method call and use the cached AABB fields. -+ final AABB entityBB = entity.getBoundingBox(); -+ if (entityBB.maxX <= boxMinX || entityBB.minX >= boxMaxX || entityBB.maxY <= boxMinY || entityBB.minY >= boxMaxY || entityBB.maxZ <= boxMinZ || entityBB.minZ >= boxMaxZ) { - continue; - } - diff --git a/leaf-server/minecraft-patches/features/0274-Optimize-getEntities.patch b/leaf-server/minecraft-patches/features/0274-Optimize-getEntities.patch new file mode 100644 index 00000000..2ae1b218 --- /dev/null +++ b/leaf-server/minecraft-patches/features/0274-Optimize-getEntities.patch @@ -0,0 +1,170 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Sat, 12 Jul 2025 02:00:43 +0200 +Subject: [PATCH] Optimize-getEntities + +Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) + +Co-authored by: Martijn Muijsers + +diff --git a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +index 5ebc1b2cf186b512da5b62fedba16b612f2fa6ed..14dc37f0ed08b4eb4c7740ec68d23df0060c0955 100644 +--- a/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java ++++ b/ca/spottedleaf/moonrise/patches/chunk_system/level/entity/ChunkEntitySlices.java +@@ -579,6 +579,14 @@ public final class ChunkEntitySlices { + + final BasicEntityList[] entitiesBySection = this.entitiesBySection; + ++ // Cache AABB fields to local variables to reduce field accesses inside the hot loop. ++ final double boxMinX = box.minX; ++ final double boxMinY = box.minY; ++ final double boxMinZ = box.minZ; ++ final double boxMaxX = box.maxX; ++ final double boxMaxY = box.maxY; ++ final double boxMaxZ = box.maxZ; ++ + for (int section = min; section <= max; ++section) { + final BasicEntityList list = entitiesBySection[section - minSection]; + +@@ -587,11 +595,18 @@ public final class ChunkEntitySlices { + } + + final Entity[] storage = list.storage; ++ final int len = Math.min(storage.length, list.size()); + +- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ for (int i = 0; i < len; ++i) { + final Entity entity = storage[i]; + +- if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { ++ if (entity == null || entity == except) { ++ continue; ++ } ++ ++ // Inline AABB#intersects to avoid a method call and use the cached AABB fields. ++ final AABB entityBB = entity.getBoundingBox(); ++ if (entityBB.maxX <= boxMinX || entityBB.minX >= boxMaxX || entityBB.maxY <= boxMinY || entityBB.minY >= boxMaxY || entityBB.maxZ <= boxMinZ || entityBB.minZ >= boxMaxZ) { + continue; + } + +@@ -618,19 +633,32 @@ public final class ChunkEntitySlices { + + final BasicEntityList[] entitiesBySection = this.entitiesBySection; + ++ // Cache AABB fields to local variables to reduce field accesses inside the hot loop. ++ final double boxMinX = box.minX; ++ final double boxMinY = box.minY; ++ final double boxMinZ = box.minZ; ++ final double boxMaxX = box.maxX; ++ final double boxMaxY = box.maxY; ++ final double boxMaxZ = box.maxZ; ++ + for (int section = min; section <= max; ++section) { + final BasicEntityList list = entitiesBySection[section - minSection]; +- + if (list == null) { + continue; + } + + final Entity[] storage = list.storage; ++ final int len = Math.min(storage.length, list.size()); + +- for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { ++ for (int i = 0; i < len; ++i) { + final Entity entity = storage[i]; ++ if (entity == null || entity == except) { ++ continue; ++ } + +- if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { ++ // Inline AABB#intersects to avoid a method call and use the cached AABB fields. ++ final AABB entityBB = entity.getBoundingBox(); ++ if (entityBB.maxX <= boxMinX || entityBB.minX >= boxMaxX || entityBB.maxY <= boxMinY || entityBB.minY >= boxMaxY || entityBB.maxZ <= boxMinZ || entityBB.minZ >= boxMaxZ) { + continue; + } + +diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java +index d6be86981219f0fe0db15d36d9c85e9a56020373..a5fb2e4e264ab14b867a0b084ce7a461d3b5eaae 100644 +--- a/net/minecraft/world/entity/LivingEntity.java ++++ b/net/minecraft/world/entity/LivingEntity.java +@@ -282,13 +282,17 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + public List drops = new java.util.ArrayList<>(); // Paper - Restore vanilla drops behavior + public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; + public boolean collides = true; +- public Set collidableExemptions = new java.util.HashSet<>(); ++ public it.unimi.dsi.fastutil.ints.IntOpenHashSet collidableExemptions = new it.unimi.dsi.fastutil.ints.IntOpenHashSet(); // Leaf - optimize getEntities ++ private boolean hasAnyExemptions = false; // Leaf - optimize getEntities ++ private SynchedEntityData.DataItem healthDataItem; // Leaf - Cached reference to avoid array lookup + public boolean bukkitPickUpLoot; + public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper + public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event + public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + public int shieldBlockingDelay = this.level().paperConfig().misc.shieldBlockingDelay; // Paper - Make shield blocking delay configurable + protected boolean shouldBurnInDay = false; public boolean shouldBurnInDay() { return this.shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Purpur - API for any mob to burn daylight ++ private Boolean cachedFixClimbingBypassingCrammingRule = null; ++ private Level cachedConfigLevel = null; + // CraftBukkit end + + protected LivingEntity(EntityType entityType, Level level) { +@@ -304,6 +308,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + this.setYRot((float)(Math.random() * (float) (Math.PI * 2))); + this.yHeadRot = this.getYRot(); + this.brain = this.makeBrain(EMPTY_BRAIN); ++ healthDataItem = null; + } + + @Contract( +@@ -1413,7 +1418,11 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + return (float) serverPlayer.getBukkitEntity().getHealth(); + } + // CraftBukkit end +- return this.entityData.get(DATA_HEALTH_ID); ++ ++ // Optimize entity data access by caching the DataItem reference ++ // This avoids the array lookup in entityData.get() for every call ++ if (this.healthDataItem == null) {this.healthDataItem = this.entityData.getItem(DATA_HEALTH_ID);} ++ return this.healthDataItem.getValue(); + } + + public void setHealth(float health) { +@@ -2282,7 +2291,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + + @Override + public boolean isAlive() { +- return !this.isRemoved() && this.getHealth() > 0.0F && !this.dead; // Paper - Check this.dead ++ return !this.dead && !this.isRemoved() && this.getHealth() > 0.0F; // Paper - Check this.dead // Leaf - check the cheapest first + } + + public boolean isLookingAtMe(LivingEntity entity, double tolerance, boolean scaleByDistance, boolean visual, double... yValues) { +@@ -4044,10 +4053,21 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + return !this.isRemoved() && this.collides; // CraftBukkit + } + ++ // Leaf start - optimize getEntities ++ private boolean getFixClimbingBypassingCrammingRule() { ++ Level currentLevel = this.level(); ++ if (this.cachedFixClimbingBypassingCrammingRule == null || this.cachedConfigLevel != currentLevel) { ++ this.cachedConfigLevel = currentLevel; ++ this.cachedFixClimbingBypassingCrammingRule = currentLevel.paperConfig().collisions.fixClimbingBypassingCrammingRule; ++ } ++ return this.cachedFixClimbingBypassingCrammingRule; ++ } ++ // Leaf end - optimize getEntities ++ + // Paper start - Climbing should not bypass cramming gamerule + @Override + public boolean isPushable() { +- return this.isCollidable(this.level().paperConfig().collisions.fixClimbingBypassingCrammingRule); ++ return this.isCollidable(this.getFixClimbingBypassingCrammingRule()); // Leaf - optimize getEntities + } + + @Override +@@ -4059,7 +4079,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin + // CraftBukkit start - collidable API + @Override + public boolean canCollideWithBukkit(Entity entity) { +- return this.isPushable() && this.collides != this.collidableExemptions.contains(entity.getUUID()); ++ return this.isPushable() && this.collides && (!hasAnyExemptions || !collidableExemptions.contains(entity.getId())); + } + // CraftBukkit end + diff --git a/leaf-server/minecraft-patches/features/0278-cache-BlockBehaviour.patch b/leaf-server/minecraft-patches/features/0278-cache-BlockBehaviour.patch new file mode 100644 index 00000000..710b86ce --- /dev/null +++ b/leaf-server/minecraft-patches/features/0278-cache-BlockBehaviour.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Mon, 21 Jul 2025 19:16:53 +0200 +Subject: [PATCH] cache BlockBehaviour + +Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) + +diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java +index 611887f5f8f218f5ec1ad19580f3123a60b20d46..3ea4157d7e57d7c7bc3af751593541f61fca7393 100644 +--- a/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -982,19 +982,25 @@ public abstract class BlockBehaviour implements FeatureElement { + } + + public boolean is(TagKey tag) { +- return this.getBlock().builtInRegistryHolder().is(tag); ++ // Cache the holder reference to avoid multiple calls ++ return this.owner.builtInRegistryHolder().is(tag); + } + + public boolean is(TagKey tag, Predicate predicate) { +- return this.is(tag) && predicate.test(this); ++ // Optimize by avoiding the redundant is() call and using cached holder ++ Holder holder = this.owner.builtInRegistryHolder(); ++ return holder.is(tag) && predicate.test(this); + } + + public boolean is(HolderSet holder) { +- return holder.contains(this.getBlock().builtInRegistryHolder()); ++ // Cache the block holder reference ++ return holder.contains(this.owner.builtInRegistryHolder()); + } + + public boolean is(Holder block) { +- return this.is(block.value()); ++ // Direct reference comparison first (fastest), then fallback to value comparison ++ Holder thisHolder = this.owner.builtInRegistryHolder(); ++ return thisHolder == block || this.owner == block.value(); + } + + public Stream> getTags() { +@@ -1011,11 +1017,13 @@ public abstract class BlockBehaviour implements FeatureElement { + } + + public boolean is(Block block) { +- return this.getBlock() == block; ++ // we use owner directly ++ return this.owner == block; + } + + public boolean is(ResourceKey block) { +- return this.getBlock().builtInRegistryHolder().is(block); ++ // Cache the holder reference ++ return this.owner.builtInRegistryHolder().is(block); + } + + public final FluidState getFluidState() { // Paper - Perf: Final for inlining diff --git a/leaf-server/paper-patches/features/0062-entity-to-uuid-set.patch b/leaf-server/paper-patches/features/0062-entity-to-uuid-set.patch new file mode 100644 index 00000000..ce6245c4 --- /dev/null +++ b/leaf-server/paper-patches/features/0062-entity-to-uuid-set.patch @@ -0,0 +1,20 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Taiyou06 +Date: Mon, 21 Jul 2025 19:39:45 +0200 +Subject: [PATCH] entity to uuid set + +Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +index fc4fa99a993a017676da2be3cb254399d421bce1..76a164df551dd5660fde09230d568ef7e8dcce54 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +@@ -901,7 +901,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { + + @Override + public Set getCollidableExemptions() { +- return this.getHandle().collidableExemptions; ++ return new org.dreeam.leaf.util.map.EntityIdToUuidSet(this.getHandle().collidableExemptions, this.getHandle().level()); + } + + @Override diff --git a/leaf-server/src/main/java/org/dreeam/leaf/util/map/EntityIdToUuidSet.java b/leaf-server/src/main/java/org/dreeam/leaf/util/map/EntityIdToUuidSet.java new file mode 100644 index 00000000..1896a6dd --- /dev/null +++ b/leaf-server/src/main/java/org/dreeam/leaf/util/map/EntityIdToUuidSet.java @@ -0,0 +1,102 @@ +/* + *Licensed under: LGPL-3.0 (https://www.gnu.org/licenses/lgpl-3.0.html) + */ + +package org.dreeam.leaf.util.map; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Level; + +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.UUID; + +public class EntityIdToUuidSet extends AbstractSet { + private final it.unimi.dsi.fastutil.ints.IntOpenHashSet backing; + private final Level level; + + public EntityIdToUuidSet(it.unimi.dsi.fastutil.ints.IntOpenHashSet backing, Level level) { + this.backing = backing; + this.level = level; + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof UUID uuid)) return false; + Entity entity = ((ServerLevel) level).getEntity(uuid); + return entity != null && backing.contains(entity.getId()); + } + + @Override + public boolean add(UUID uuid) { + Entity entity = ((ServerLevel) level).getEntity(uuid); + if (entity == null) return false; + return backing.add(entity.getId()); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof UUID uuid)) return false; + Entity entity = ((ServerLevel) level).getEntity(uuid); + return entity != null && backing.remove(entity.getId()); + } + + @Override + public Iterator iterator() { + return new Iterator() { + private final it.unimi.dsi.fastutil.ints.IntIterator intIterator = backing.iterator(); + private UUID nextUuid = null; + private boolean hasPrecomputed = false; + + @Override + public boolean hasNext() { + if (!hasPrecomputed) { + computeNext(); + } + return nextUuid != null; + } + + @Override + public UUID next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + UUID result = nextUuid; + hasPrecomputed = false; + nextUuid = null; + return result; + } + + @Override + public void remove() { + intIterator.remove(); + } + + private void computeNext() { + while (intIterator.hasNext()) { + int entityId = intIterator.nextInt(); + Entity entity = level.getEntity(entityId); + if (entity != null) { + nextUuid = entity.getUUID(); + hasPrecomputed = true; + return; + } + } + nextUuid = null; + hasPrecomputed = true; + } + }; + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public void clear() { + backing.clear(); + } +}